For every meaningful piece of the system, which side it lives on — and why it lives there.
The short version: the live product is a static, zero-backend site. Nothing Python runs when you use it. The math your browser executes is JavaScript generated from the Python; Python is the single source, the build-time data source, and the thing the tests hold the JavaScript honest against. Tap any row to see why it sits where it does.
Living document — regenerated as the system changes.
engine.py · agents.py · provenance.py · scenarios.py · validate.py · feature layer (waterfall / envelope / sensitivity)build_site.pyreference/engine.js), and emits the static files below.engine.js · full-engine.js · data.js · explore.html · full.html · model.htmlengine.js · /full + /model → full-engine.js + data.js. All math is client-side; nothing is uploaded.engine.pyPython · single sourcencue/engine.py is canonical; site/engine.js / full-engine.js are generated from it at build time by tools/py2js_engine.py (a small, readable transpiler), so the browser still runs plain JS with no backend, but nobody hand-maintains a second copy. The generator is proven faithful by test_generator_parity.py (generated JS vs Python over the scenarios + thousands of random inputs). The old hand-written JS is frozen in oracle/engine.js as an independent witness. (Decision C closed edge 1.)validate.pyPython + JS twinncue-runtime.js and inline checks in the tool UI. Guarded by test_site_parity.py.scenarios.pyPython + JS twindata.js additionally exports the scenario names for labels. The scenario bar you click on /full and /model is reading the JS engine's copy.agents.pyPython → data.jsdata.js at build. The browser reads the move-sets from data.js; the toggles on /model apply exactly those values. So the agent moves trace to Python, they just aren't computed live — they're static facts today (heuristics), not yet reasoned.provenance.pyPython → data.jsdata.js. The citation chips you see when an Agent is toggled on, and the "Sources & confidence" panel, render straight from that exported data.waterfall · envelope · sensitivityInline JS + Python twinwaterfall.py / envelope.py / sensitivity.py, and test_features_parity.py runs that JavaScript through Node and asserts it equals the Python — number for number.orchestrator.pyPython + JS twin · live on /modelorchestrator.py) and as a JavaScript twin in ncue-runtime.js, both parity-tested. As of decision B, "Hand it to the team" on /model runs the JS twin (window.NCUE.optimize) — not the old inline composer. It's validator-aware: a move that breaches the DSCR floor or the facility cap is penalised, and "penciled" requires zero flags. So the guards now actually bind on the live tool. (This closed edges 2 & 3.)grounding.pyPython only · dormantllm_agent.pyPython only · dormant/agents is just a redirect to /how-it-works./exploreBrowser · engine.jsncue/viewer.py but rewired to compute entirely in the browser via engine.js (the /api/run server path in viewer.py is for local dev only — it's never deployed). Zero backend./fullBrowser · full-engine.js + data.jsfull.template.html; the engine is supplied by full-engine.js and the provenance/agent data by data.js. Every dashboard, the sensitivity grid, the waterfall, export — all inline JavaScript, all client-side./modelBrowser · full-engine.js + data.jsP / run / recalc), so /model and /full can never disagree. The agent move-sets and citations come from data.js; the engine still does all the math.build_site.py and committed to the repo. These are the seam between Python truth and the browser.engine.js & full-engine.jsGenerated from engine.pyncue/engine.py (decision C) and packaged two ways: engine.js as a namespaced module (window.NCUE_ENGINE, used by /explore + /model) and full-engine.js as bare globals (run, BASE, SCN… that the full tool's inline UI references by name). Same math, two wrappers, one source.data.jsGenerated from Pythontest_site_parity.py loads it and checks it against Python.ncue-runtime.jsHand-written JS twin · live on /modelserver/mcp_server.pyPython · live for MCP consumersserver/api.pyPython · scaffold, unhosted/optimize, /run, /health) that runs the orchestrator with the optional LLM agents. It's the seam for taking the Agents online — built and tested, but not hosted. Needs a container host + an API key.cli.py · viewer.pyPython onlycli.py runs scenarios from the terminal. viewer.py is a local Python dev server and the template that build_site.py turns into the static /explore page. On the live site only the static output exists.test_parity.py — the engine (Python) vs the frozen JS oracle. test_features_parity.py — the waterfall/envelope/sensitivity inline JS vs Python, run through Node. test_site_parity.py — the browser twins (engine.js + ncue-runtime.js) vs Python, same KPIs and same orchestrator path. test_agents.py + test_provenance.py — Python units. CI runs all of them before every deploy; a red test blocks the ship.The engine used to live twice: a hand-written reference/engine.js (what shipped) and ncue/engine.py (what the tests checked), kept in sync by hand. Now engine.py is the only source and the JavaScript is generated from it (tools/py2js_engine.py), so there is one place to change the math. Rounding-convention note: the generated engine adopts Python's banker's rounding (round half to even), where the old hand-written JS used half-up. The only field affected is the displayed affordable-unit count, and only on the rare input where it lands exactly on a half — every scenario and 3,000 random inputs are otherwise bit-identical, and nothing downstream uses the rounded integer.
There used to be three: orchestrator.py, ncue-runtime.js, and a small inline composer on /model — and it was the inline one that ran, so the tested logic and the DSCR/facility guards weren't actually binding on the live tool. Decision B routed /model through the tested twin (window.NCUE.optimize): the validators now bind live (a breaching move is penalised; "go" needs zero flags), and there's one composition logic, parity-guarded against Python.
ncue-runtime.js no longer orphaned — closed (decision B)It now powers /model's "Hand it to the team" and gap bar, so it's a live, tested part of the site — not a file carried only for the test suite.
The waterfall / envelope / sensitivity math is still hand-written twice — inline JS in the tool UI and Python in the feature layer — kept equal only by the test. The engine itself is now single-source (edge 1); extending the same generator to the feature layer is the planned next step, after which this closes too.
engine.js, full-engine.js, data.js, and the generated HTML are checked into the repo. The parity test catches stale numbers, but someone editing the site without rebuilding could still commit a drifted file. CI rebuilds on deploy, which mostly contains this.
llm_agent.py, grounding.py, and server/api.py are built and tested but unhosted — the Agents are heuristic everywhere until a host + key turn them on. (Decision D.)
Prices and example figures on the content pages are written into the HTML as prose. They don't recalculate, and they aren't covered by any test.
github-token.txt and cloudflare-token.txt are gitignored and re-supplied per session, but they persist in the mounted folder between sessions. Safe (never committed), worth knowing they're there.