All method pages

MethodDevelopment

verified checked against the code · what the statuses mean

Environment variables

In simple terms

All of the app’s data lives under one data root — by default backend/data/ inside the repository. Every path the backend uses (the database, stored results, the Parquet cache, the default assumption books, the confidential folder) is derived from that root by one module, backend/core/paths.py, and each can be moved on its own.

Nothing moves unless you set a variable. With no .env and no export, every location is exactly what it was before this scheme existed.

Precedence

  1. a shell export (export PTX_DATA_ROOT=…, or a variable set by the process that launches the server);
  2. the repo-root .env file, read once at start by python-dotenv with override=False — it fills only what the shell left unset;
  3. the default, listed below.

A relative value resolves against the repository root, never the working directory: PTX_DB_PATH=backend/data/ptx_projects.db means the same file whichever folder you start uvicorn from. ~ is expanded.

What the code reads (verified against paths.VARIABLES by a test)

Variable Default What it moves
PTX_DATA_ROOT backend/data The one data root; every path below defaults under it.
PTX_DB_PATH <root>/ptx_projects.db The SQLite database (WAL side files sit beside it).
PTX_AUDIT_DB_PATH audit.db beside the database (<root>/audit.db) The action log — who did what, when — in a SQLite file of its own (its own schema and version table; the main database’s migrations never touch it). Created by the first recorded event. Gitignored; back it up with the database.
PTX_DATA_RESULTS <root>/results Stored feasibility solves, saved runs, finder studies, the every-combination log — one folder per project, plus the machine-level _grid_machine.json at the root. The test fixtures set it to a temp dir.
PTX_DATA_PROCESSED <root>/processed The Parquet cache of open site series (site_datasets/<id>.parquet).
PTX_DATA_RAW <root>/raw Raw timeseries workbooks. (Stored dataset paths are repo-relative and resolve against the series root — PTX_SERIES_ROOT, by default the repo root; this is the folder new ingests write to.)
PTX_DAB_DIR <root>/dab The default assumption books shipped with the app and the committed ACTIVE marker (read-only to every workspace). A registry row’s relative file_path names a file in this folder by base name.
PTX_WORKSPACE_DAB_DIR <root>/dab_workspaces Each workspace’s own default books, one folder per workspace (<dir>/<workspace_id>/dab_<version>.xlsx, owner-only files). The app writes a default book only here: a version that still points at a shipped file is copied in the first time it is changed. Stored in the registry as dab_workspaces/<workspace_id>/<file>, relative to this folder. Gitignored; back it up with the database.
PTX_PRIVATE_DIR <root>/private Everything confidential that is not in the database: the encrypted source map, raw third-party inputs, and the Parquet cache of datasets flagged confidential (processed/<id>.parquet). Gitignored, refused by the pre-commit hook, never copied anywhere.
PTX_EXPORTS_TMP the system temp dir Where export temp folders (ptx-export-*) are created; they are deleted after the response is sent.
PTX_BACKUP_DIR ~/Desktop/PtX_backups Where the app writes the database backups it takes itself — today, before a workspace is deleted (ptx_projects-<stamp>-before-delete-workspace-<id>.db, made with the SQLite backup API, checked, mode 0600). Outside the data root, beside the hand-made backups. It must be a folder on this Mac — never a synced or shared one: the backup holds every project and every account’s password hash. The test suite and copy servers point it at a temp folder.
PTX_SERIES_ROOT the repository root What a relative dataset series_path resolves against (every stored series path is repo-relative). Set it to another checkout to read that checkout’s series in place — how a worktree runs the identical-solve gate on the measured series without copying or linking them (verify_db_copy.py --series-root). Read only by the series resolution (paths.resolve_data_file).
PTX_AUDIT_KEEP_MONTHS 12 How many months of the action log are kept row by row. At start-up and then once a day, older rows are folded into monthly counts per workspace and kind, and deleted. A value that is not a whole number of months (1 or more) is ignored with a warning.
PTX_SOLVE_WORKERS 2 How many engine jobs run at once on this server (core/solve_queue.py): a feasibility solve, a sweep, a Design lab study or search, “Try every combination”, the cost landscape, the check of which assumptions move the cost — and a request waiting on the LP (Least cost; Scale the plant and Grid on against off on the solver). The others wait their turn — first come, first served, except that a workspace with a job already running does not get a second worker while another workspace’s job waits — and the app shows them as Queued · N ahead (Feasibility suite, “When the server is busy”). A whole number, 1 or more; any other value is ignored with a warning in the server’s log and 2 holds. Read when the server starts. Set it to the cores you can spare for solving: an LP year holds one core and 1–2 GB for a minute or two.
PTX_GRID_WORKERS min(13, cores − 2) Worker processes for “Try every combination” and the book-impact pool (core/grid_runner.py) — inside ONE job of the solve queue.
PTX_DEV_PRINCIPAL unset Development only. A user id: every request that carries no session cookie acts as that user, in their oldest workspace with their role there (api/auth.py). The test suite sets it to local so TestClient requests need no sign-in; a developer’s server may too. Unset, every request without a session is refused (401). An unknown or disabled user id is ignored (still 401), never replaced by a default. The server logs a warning at start when it is set — never set it on a server other people can reach. Scripts that drive the API directly (the identical-solve gate, the Excel engine) do not read it: they act as an explicit principal.
PTX_DEV_ORIGINS http://localhost:5173,http://127.0.0.1:5173 The pages (origins) the API on 127.0.0.1 accepts requests from — the development frontend. Comma-separated; exact matches only. Only http://localhost:<port>, http://127.0.0.1:<port> or http://[::1]:<port> is accepted; any other entry is ignored with a warning in the server’s log (an entry list with nothing valid accepts no page at all). Read on every request (api/auth.py), and used by both the cross-site check and CORS. Before 2026-11-08 any localhost port was accepted. A copy frontend on another port (verification) names its own origin here, e.g. PTX_DEV_ORIGINS=http://localhost:5200. The shared HTTPS socket never uses it: there only this Mac’s sharing addresses count.
PTX_PUBLIC_HOST unset (the container image: localhost) The container only (backend/scripts/serve.py --behind-proxy, what the image runs). The ONE host name people open: the app answers to that name only (with localhost: to this machine’s own names), and a change must come from that page. A host name or address alone — no scheme, port or path, never * or 0.0.0.0. Without a proxy (below) only localhost is accepted: plain HTTP to any other name would carry passwords in clear. Unset, --behind-proxy refuses to start; without that flag it is not read. See Run with Docker.
PTX_TRUSTED_PROXIES unset The container only. The proxy in front of the app (Caddy on a server): its addresses or networks, comma-separated (172.29.66.2, 172.29.66.0/24). The server believes X-Forwarded-Proto and X-Forwarded-For from those addresses only — the scheme decides the cookie, the client’s address is what the sign-in throttle counts — and refuses every request that did not come through it over HTTPS, the health route excepted. *, an address range covering everything, or a malformed entry is refused at start. Unset: no proxy, nobody’s forwarded headers are believed (the image on your own computer).
VITE_API_BASE (frontend) http://localhost:8000 under the dev server; /api when the backend served the page The backend base URL, read by frontend/src/data/api.ts at build/dev time. Unset: the dev page (Vite) calls port 8000 on its own host; the built app served by the backend — backend/scripts/serve.py --share, or the container (--behind-proxy) — carries a <meta name="ptx-api-base" content="/api"> marker and calls /api on its own address (one origin, one cookie).

Sharing on the office Wi-Fi (since 2026-11-04) adds no variable: it is switched on by how the server is started (backend/scripts/serve.py --share, --share-port, --port), and its certificate authority lives in PTX_PRIVATE_DIR (<private>/tls/, keys mode 0600).

In the container image every path variable above is set to an absolute folder — the data under /data, the default books and resource workbooks the app ships under /app (read-only) — and the entrypoint refuses one that points elsewhere; no .env file is read there. The values are listed in Run with Docker.

Script-only switches, documented in their scripts and not in .env.example: PTX_VERIFY (set by verify_db_copy.py), PTX_WIND_SRC / PTX_SOLAR_SRC / PTX_RESOURCE_SRC (the private ingest scripts’ source-file overrides).

Not read, and no longer declared: PTX_SOLVER, PTX_SOLVER_TIMEOUT (the solver is HiGHS, chosen in core/feasibility.py) and PTX_API_HOST / PTX_API_PORT / PTX_API_RELOAD (host, port and reload come from the uvicorn command line). An older .env that still sets them is harmless; they are ignored.

Where the cache of a confidential series lands

A dataset flagged confidential caches its Parquet under <private>/processed/, never in the shared processed/site_datasets/. A cache file written to the shared location before this split is still read from there (left where it is, never copied); a fresh one is written private.

Practical implications

  • To move all the data: set PTX_DATA_ROOT and copy the tree there (the DAB folder included — the shipped books are tracked in git under backend/data/dab, the root’s dab/ must hold them or the default book cannot be read — and dab_workspaces/, each workspace’s own books, which exist nowhere else).
  • A copy of the app on other ports (verification): the API refuses pages it was not told about, so start it with PTX_DEV_ORIGINS=http://localhost:<the copy page's port>.
  • To keep the database elsewhere: set PTX_DB_PATH alone; results and caches stay under the root.
  • To change host/port: pass them to uvicorn (--host, --port, --reload).
  • Importing backend.api.app no longer creates the database: the API object is built on the first request, so a tool that imports the module leaves no file behind.
  • Deleting a project removes its whole results/<pid>/ folder — every store’s files, the book_impact_*.json documents, and finally the empty folder. A file no store claims keeps the folder (never a blind delete).

How to modify this safely

  • A new path goes into paths.py as a function that reads the environment when called — never a module constant; test_paths.py fails if a builder keeps an import-time copy, and if .env.example or this page misses a variable the code reads (or declares one it does not).
  • Never put secrets in .env for this tool: passwords are stored hashed in the database and a session lives in an HttpOnly cookie; nothing secret is configured.

From the app’s documentation, docs/05-development/environment-variables.md · Hyprisma 1.0.5