A pre-build design & sizing simulator for a data center's energy system. Configure the supply and demand for a specific site, simulate it over a representative year, and get the economics plus a grid-fit screen — to de-risk decisions before FID and to open the conversation with the grid operator.
Not a digital twin: there is no physical asset to mirror and no live sensor feed. It is a design simulator.
A pure core depends on nothing outward; one service layer is the single callable API;
every interface (CLI, REST, MCP, dashboard) is a thin adapter over it; everything
configurable lives in a typed ScenarioConfig; the core knows no file formats and no
providers.
Interface adapters interfaces/{cli,api,mcp} · frontend/ <- thin, no logic
| calls
Service layer services/ (run / compare / validate / optimize / list)
| uses
Domain core core/ (components · dispatch · engine · analysis · models)
| depends on (interfaces only)
Ports core/components/base · core/dispatch/base · data/base
^ implemented by
Adapters components/* · data/formats/* · registry/
| Path | What |
|---|---|
core/ |
pure domain — models/, components/base.ts (the Asset seam), dispatch/, engine/, analysis/ |
components/ |
concrete asset models (plugins) behind Asset |
data/ |
the DataSource/FileFormat ports, schemas/ (canonical shapes), formats/ (by file format), file_source.ts, datasets/ |
config/ |
ScenarioConfig (the copilot contract) + the GridReady builder |
services/ |
the single callable API |
interfaces/ |
cli/, api/ (REST), mcp/ (copilot) |
frontend/ |
the dashboard — a thin client over the API |
registry/ |
plugin registration: components + dispatch + formats |
ingestion/ |
provider-specific normalization, OUTSIDE the core |
tests/ |
engine parity vs the original engine, plus data/config tests |
Nothing provider-shaped lives in core/ or data/. data/formats/ is organized by
format, so adding Parquet is one file; adding a provider touches nothing in the core.
npm install
npm run convert-data # legacy JS exports -> canonical CSVs in data/datasets/
# vertical slice from the CLI
npm run cli -- demo frankfurt --it 90 --pue 1.35 --cap 60 --solar 120 --batt 200 --firm 62
npm run cli -- list-assets
npm run cli -- run config/examples/frankfurt_grid_battery.json --full-year --out /tmp/r.json
# the dashboard + REST API (then open http://localhost:8000)
npm run dev
# the copilot interface (stdio MCP server)
npm run mcp
npm test # engine parity + data/config tests
npm run typecheckAsset—core/components/base.ts:offer(t, state) -> Offer,apply(t, setpoint, state) -> State.DispatchStrategy—core/dispatch/base.ts:decide(t, offers, netLoad).DataSource/FileFormat—data/base.ts.- Canonical schemas —
data/schemas/index.ts:TimeSeries,CapacityInfo. ScenarioConfig—config/scenario.ts: the LLM ↔ engine boundary.Result—core/models/index.ts: the contract the dashboard renders.
Real nodes from real public data (5 EU bidding zones), crude swappable models of every component, rule-based merit-order dispatch, hourly, one representative year. Outputs: annual cost, LCOE, peak draw, renewable fraction, firm hours, battery cycles, payback, plus the grid-fit screen (hours over capacity, curtailment/firm needed).
Deferred behind the seams already built: more format readers, optimization dispatch,
the live operator-model backend, third-party engines, and the full per-asset dashboard
(the AssetView seam — see frontend/components/).
This refactor preserves the original engine's numbers exactly — see the parity test in
tests/parity.test.tsagainsttests/legacy_reference.ts.