MethodFeatures
partial some parts checked against the code, others not yet · what the statuses mean
Feasibility suite
In simple terms
Feasibility answers a harder question than prefeasibility: how does this plant actually run, hour by hour, across a whole year? It replays the plant under the real operating constraints prefeasibility ignores (minimum load, ramps, storage dynamics, electrolyser standby) — in one of two dispatch modes (below). The results are explored across several linked sub-rooms.
Two dispatch modes (same year, two ways to run it)
The two modes, why their results bracket how a plant really runs, and the electrolyser’s part-load curve and modules (both modes) are explained once, in How feasibility works — the page the stage’s How it works tab shows. What each choice under How it runs does (engine, battery, H₂ buffer, promised output, horizon): How it runs — the feasibility run settings.
In the code: the rule-based engine is core/feasibility_sim.py, the solver (LP)
core/feasibility.py; the choice is saved as feas_mode. The battery strategy
(storage_strategy, one of reactive · proactive · stable · accumulate)
shapes the rule-based engine only — the LP never reads it. The electrolyser’s module count
is ely_module_count; the solver models a curved part-load profile as parallel segments
(still a pure LP, no integers).
Why it exists
Prefeasibility screens; feasibility de-risks. Lenders read these numbers, so the stage models real dispatch — coded rules that decide each hour without seeing ahead (the base case, the number you defend), or an optimiser with perfect foresight (the ideal ceiling) — and reports where the plant falls short (unserved minimum-load hours). It is the reference the prefeasibility screen is checked against.
Where it lives
- Frontend: the cockpit
frontend/src/shell/FeasibilityShell.tsxhosts the rooms infrontend/src/rooms/feasibility/as the tabs listed below. - Engines:
backend/core/feasibility_sim.py(rule-based) andbackend/core/feasibility.py(PyPSA network + HiGHS); persistence:backend/core/dispatch_store.py; orchestration:PtxApi.solve_feasibility/start_feasibility_solve.
The tabs
The feasibility stage is one cockpit; these are its tabs and Design lab views,
generated from frontend/src/shell/shellNav.ts and shell/FeasibilityShell.tsx:
Feasibility — /projects/:id/feasibility/<tab>, opens on overview.
| Group | Tab | Address | What it answers |
|---|---|---|---|
| Site | Resources | feasibility/resources |
What the site’s wind and sun give you, before any design. |
| Design | Design lab | feasibility/lab |
Vary the design: find a configuration, or size it at least cost. |
| Check the design | Overview | feasibility/overview |
The verdict and the cost of each product. |
| Check the design | Operations | feasibility/operations |
How the plant runs, hour by hour. |
| Check the design | Reliability | feasibility/reliability |
Delivered against promised; where it falls short, by cause, and what cures it. |
| Check the design | Storage | feasibility/storage |
The battery and hydrogen store: state of charge and value. |
| Check the design | Grid | feasibility/grid |
Grid draw, its cost, and the RFNBO screen. |
| Reference | How it works | feasibility/method |
How the feasibility engines work, and what this run consumed. |
| Tools | Assumptions | feasibility/assumptions |
This stage’s assumption book: every value, its source and its range. |
| Tools | Snapshots | feasibility/snapshots |
Save this stage’s design and results, and restore one. |
| Tools | Derivatives | feasibility/derivatives |
The value chain this stage solves: the products and the units each needs. |
| Tools | Emissions | feasibility/emissions |
Carbon intensity per product against the RFNBO limit — a screen, not a certificate. |
| Tools | Scenarios (partial) | feasibility/scenarios |
Compare two to four snapshots, designs or versions side by side. Partial here: Two snapshots compare inside Snapshots today; two to four side by side, and the four engines on one design, come with this tool. |
| Tools | Settings (partial) | feasibility/settings |
This project’s name, location, units and bundle. Partial here: Rename, duplicate, export and delete are in the project’s ⋯ menu in the sidebar; this page — place, units, the default book — is not built yet. |
Design lab views — feasibility/lab?mode=<view>; the first view opens when no mode is given (old modes kept as aliases: explore → one, storage → one).
| View | mode= |
What it does |
|---|---|---|
| One component | one |
Move one size — or two together — across a range; cost, output and reliability at each. |
| Try every combination | every |
A range and a step per component; every combination solved; the cheapest all-green design. |
| Scale the plant | scale |
Grow or shrink everything but storage together; cost and output per product. |
| Find a design | search |
Sample whole designs on the rule-based engine and explain the best one. |
| Least cost (LP) | optimizer |
The solver sizes the free components at least annual cost to make what you ask of each product. |
The classic room addresses are redirects since the cockpit became the only layout; the query string is kept.
| Old address | Opens |
|---|---|
/projects/:id/prefeasibility |
/projects/:id/prefeasibility/cockpit |
/projects/:id/methods |
/projects/:id/prefeasibility/method |
/projects/:id/feasibility |
/projects/:id/feasibility/operations |
/projects/:id/resources |
/projects/:id/feasibility/resources |
/projects/:id/feasibility-results |
/projects/:id/feasibility/overview |
/projects/:id/feasibility-inputs |
/projects/:id/feasibility/method?view=inputs |
/projects/:id/feasibility-methods |
/projects/:id/feasibility/method |
/projects/:id/reliability |
/projects/:id/feasibility/reliability |
/projects/:id/storage-lab |
/projects/:id/feasibility/storage |
/projects/:id/grid |
/projects/:id/feasibility/grid |
/projects/:id/finder |
/projects/:id/feasibility/lab?mode=search |
/projects/:id/optimizer |
/projects/:id/feasibility/lab?mode=optimizer |
/projects/:id/assumptions |
the Assumptions tab of the stage you were last in |
/projects/:id/snapshots |
the Snapshots tab of the stage you were last in |
/projects/:id/derivatives |
the Derivatives tab of the stage you were last in |
/projects/:id/emissions |
the Emissions tab of the stage you were last in |
/projects/:id/scenarios |
the Scenarios tab of the stage you were last in |
/projects/:id/settings |
the Settings tab of the stage you were last in |
“One place to edit, everywhere to read”: editing happens in the cockpit / Assumptions / version bars; the feasibility rooms read and explain.
How feasibility economics are computed (the Results room)
The dispatch engine returns physics only. The Results room shows economics by
taking the design annualized cost (the same CAPEX + OPEX as the plant you sized
— identical to the prefeasibility financials) and re-dividing it by the LP’s
realized production. So the feasibility LCO is higher than prefeasibility’s by
exactly the production shortfall (curtailment + operating limits) — the bankable
number. It preserves the prefeasibility shape (multi per-product per_derivative,
or NH₃ single-chain indicators), so the same cockpit charts render it.
(core/run.py::feasibility_financials.)
How it works, step by step
- Start the solve. Solve at the foot of the design panel — it first writes the changes the panel holds (“Solve · 3 changes”) — kicks off an async job; the panel polls until done. Full detail: the feasibility solve flow.
- SOLVE once. The engine builds a PyPSA network (renewables on CF curves, grid as a capped third supply, BESS, electrolyser Link, H₂ buffer Store, per-derivative always-on-≥-min-load Links with ramps, penalty slacks) and solves it with HiGHS. The hourly frame + duals + metadata are persisted once.
- POST-PROCESS on read. Every chart across the suite is derived from that one frame — dispatch envelopes, duration curve, calendar heatmap, reliability episodes, monthly buckets, curtailment-by-cause, storage-value duals.
When the server is busy: “Queued · 2 ahead”
The server runs only a few heavy jobs at once — two, unless whoever started it set
PTX_SOLVE_WORKERS (Environment variables). A job is a
feasibility solve on the solver (LP) or over several plant years, a sweep of one size in the Design lab,
Find a design, “Try every combination”, the cost landscape, the check of which
assumptions move the cost — and Least cost, or Scale the plant and Grid on against off
when the project runs the solver (LP). When every place is taken, a new job waits its
turn: the design panel, the Operations and Emissions tabs and the tool that started it
read Queued · 2 ahead — two jobs run or wait before it, from any workspace, never
whose — then Solving… once it starts; pressing Solve also raises a short notice
saying so. Nothing is lost while it waits, and you can leave the page.
Jobs start in the order they were asked, with one rule of fairness: a workspace that
already has a job running does not get a second place while another workspace’s job
waits. So with two places or more, the count can grow when the first job of a
workspace with nothing running arrives — it takes its turn first; with one place it
never does. A waiting job can be stopped like a running one (it then never starts),
and pressing Solve again while it waits keeps its place in line. A rule-based solve of
one year never waits: it takes about a second and starts at once, beside the places, and
is counted in nobody’s line — over several plant years it takes a place like the others.
The prefeasibility solve and the rule-based tools that answer at once (the plant-size
curve, the live Scale the plant, the grid size sweep) never wait either. In the code:
core/solve_queue.py.
Reading the results
- Delivered against promised (Reliability) — each product’s output against its promise
(
demand_t_yr, saved there; Find a design and Least cost (LP) start from it), the shortfall split into hours without enough hydrogen and hours without enough power; with no promise, production and utilization only. What the promise is and the three places that use it: How feasibility works, “The promise”. In the code:core/promise.py::promise_check, returned aspromisebyPtxApi.reliability_data. - Unserved min-load hours is the verdict metric: 0 means the design holds; a positive number (shown in amber) means the plant can’t meet a unit’s minimum load in some hours → size more storage / electrolyser, or accept it.
- Binding constraint per hour tells you why energy was curtailed (energy-limited, electrolyser-cap, buffer-full, synthesis-cap) → each has a specific remedy.
- Storage-value duals report the marginal tH₂/yr from +1 t buffer or +1 MWh BESS — whether more storage would pay.
- vs-Prefeasibility shows all three engines side by side (annual · timeseries · feasibility) so you can see where each lands.
Chart by chart, with what to do about each: How to read the Operations tab.
The storage sweep
It now runs in the Design lab’s One component view (the old storage lab mode opens
it). What it does and how to read its curve: How feasibility works,
“The storage sweep”. In the code: one solve per point on a copy of the project
(PtxApi.start_storage_sweep, POST /projects/{project_id}/feasibility/sweep); the best
pick is the cheapest storage within tolerance of the lowest unserved achieved; the
BESS-energy axis holds the 4-hour (C/4) power convention.
Failure & edge cases
- Missing hourly inputs for a sized carrier → 422 naming the carrier.
- Stale result after a sizing change → a staleness banner (the solve stamps the sizing it consumed; the room compares it to the live fingerprint).
- Solver error → surfaced via the job status, not a hung spinner.
- Every place on the server taken → the job waits,
queuedwith its place, and starts on its own (above); Stop takes it out of the line.
How to modify this safely
- New derived view → a post-process read route +
api.tstype; don’t widen the solve response with raw hours. - Keep the SOLVE/POST-PROCESS line and the “don’t read the store while running” poller rule intact.
Related topics
From the app’s documentation, docs/02-features/feasibility-suite.md · Hyprisma 1.0.5