All method pages

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. Entry frontend/src/main.tsx.
  • Backend API: backend/api/app.py (FastAPI, the web layer) wrapping backend/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 /health and 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 to localhost; 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

  1. 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.
  2. 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, the api.ts type, and every client.ts/room consumer. Never let the UI invent fields the backend doesn’t send.
  • New engine behaviour belongs in core/; controllers.py orchestrates and app.py only exposes — keep that separation.

From the app’s documentation, docs/01-architecture/system-architecture.md · Hyprisma 1.0.5