MethodArchitecture
partial some parts checked against the code, others not yet · what the statuses mean
System architecture
In simple terms
The app has two halves that run on your own computer: a web page you click around in (the frontend), and a calculation engine with a small web API behind it (the backend). When you change something on screen, the page sends a small request to the engine, the engine does the physics and economics, and sends the numbers back for the page to draw.
There is no cloud and no shared server: it is a desktop tool that happens to use a browser as its interface. The people who use it sign in to it on that computer — a local password, no outside provider — and every project belongs to a workspace whose members alone can see it.
Why it’s built this way
Splitting the heavy Python computation (which needs scientific libraries and a
solver) from the interactive UI (which needs a modern browser stack) lets each use
the best tools for its job. Keeping everything on localhost keeps the tool simple
and private while preserving the seams to become a multi-user product later
(every record already carries an owner id; the database is behind an interface).
Where it lives
- Frontend:
frontend/— Vite + React + TypeScript. Entryfrontend/src/main.tsx. - Backend API:
backend/api/app.py(FastAPI, the web layer) wrappingbackend/api/controllers.py(PtxApi, the framework-agnostic core) — 163 routes, all listed in the [API reference](api-overview). - Engines & domain logic:
backend/core/. - Storage:
backend/db/(SQLite) +backend/data/(Parquet result files).
The pieces and how they talk
flowchart LR
subgraph Browser["Browser (frontend/)"]
UI[React rooms & components]
Client[client.ts]
Api[api.ts — typed fetch]
UI --> Client --> Api
end
subgraph Server["localhost:8000 (backend/)"]
App[app.py — FastAPI routes]
Ctrl[controllers.py — PtxApi]
Core[core/ — engines, financials, documents]
DB[(SQLite + Parquet)]
App --> Ctrl --> Core --> DB
end
Api -- HTTP JSON --> App
Reading the diagram: UI components never call the network directly. They call
client.ts (a thin, intention-revealing wrapper), which calls api.ts (typed
fetch against VITE_API_BASE, default http://localhost:8000). That HTTP
request hits a route in app.py, which delegates to a PtxApi method in
controllers.py, which runs the domain logic in core/ and reads/writes SQLite +
Parquet. The response is JSON, shaped by TypeScript types that mirror the backend.
This client.ts → api.ts → FastAPI boundary is the one seam to respect: the UI
depends only on client.ts, and both sides agree on the response shapes.
Runtime & deployment model
Verified: this is a local tool, used by people who sign in to it. Concretely:
- Run the backend:
venv312/bin/python backend/scripts/serve.py --reload. - Run the frontend dev server:
cd frontend && npm run dev(serves on:5173). - Sign-in with a local password (since 2026-11-03): every route except
/healthand the three sign-in routes needs a session; what a user may do follows their role in the workspace (viewer · editor · admin,backend/core/access.py), and an id from another workspace answers 404. The server binds tolocalhost; opening it to a network is the office Wi-Fi sharing (serve.py --share, since 2026-11-04). - No cloud deployment, container, or CI in the repository today.
- Configuration is a local
.env(paths, solver choice, host/port) — no secrets, tokens, or credentials.
This matters for reading the rest of the docs: wherever other tools would discuss
login, tenancy, or production hardening, here the honest answer is usually “not
applicable yet — this is a localhost tool.” The multi-user seams exist (owner ids,
a repository interface) but are not activated. See the security review under
docs/06-operations/.
The staged-engine spine
The domain logic is organised as a ladder of fidelity — each stage adds exactly one physics layer:
| Stage | Adds | Dispatch | Engine entry |
|---|---|---|---|
| Prefeasibility · Mode A | annual averages | none | core/run.py (closed-form) |
| Prefeasibility · Mode B | + real weather (hourly) | greedy rules | core/greedy.py |
| Feasibility · rule-based | + operating constraints | myopic rules, no foresight | core/feasibility_sim.py |
| Feasibility · solver | + operating constraints + optimality | LP (optimal) | core/feasibility.py |
A run with no look-ahead (Mode B, the rule-based engine) and the LP with perfect foresight bracket how the plant really runs; the annual screen rests on an assumed curtailment floor and can land on either side — the core mental model of the product. See How prefeasibility works, the Application overview and the Backend section for detail.
Two data contracts worth knowing
- SOLVE vs POST-PROCESS. A solve is expensive (re-run the engine); a
post-process is cheap (re-derive financials/views from an existing solve
without re-running). The API reflects this in the
{ result, financials }bundle and in how feasibility persists an hourly frame once, then derives every chart on read. - Book-canonical parameters. Every technical/financial number lives in a versioned assumption book, threaded through the engines and financials — so editing any parameter changes results, and nothing is hard-coded in the engine.
How to modify this safely
- Changing an API response shape means changing three places in step: the
core/producer, theapi.tstype, and everyclient.ts/room consumer. Never let the UI invent fields the backend doesn’t send. - New engine behaviour belongs in
core/;controllers.pyorchestrates andapp.pyonly exposes — keep that separation.
Related topics
From the app’s documentation, docs/01-architecture/system-architecture.md · Hyprisma 1.0.5