### eval/ (scenario-based evaluation)
Complements the unit tests under test/basic. Scenarios fluctuate inputs
over simulated time, record every tick to JSONL, print a summary
table + event log, and check expectations. Complementary to unit
tests — these answer "how does the system respond to this input
profile" rather than "is this function correct".
- eval/run.js — driver; monkey-patches Date.now so the
volume integrator ticks at 1 s/iter
regardless of wall-clock
- eval/scenarios/ — one file per scenario
- levelbased-steady.js — constant inflow, demand converges
- levelbased-storm.js — inflow surge, demand saturates
- safety-dry-run-trip.js — manual mode, empty basin, safety trips
- eval/formatters/table.js — ASCII summary of sampled ticks
- eval/logs/ — per-scenario JSONL output (one line per tick)
- eval/README.md — usage + scenario file shape + how to pipe
into InfluxDB/Grafana
All three starter scenarios PASS with their expectations.
### wiki/modes/ (tier template pages)
The levelbased page templated Tier-1 modes (static transfer function).
Added worked examples for the other two tiers so all mode pages share
a common skeleton and new modes have something concrete to imitate:
- flowbased.md — Tier 2 (PID on measured outflow)
- powerbased.md — Tier 2 (levelbased curve clipped by grid power budget)
- mpc.md — Tier 3 (optimisation + forecast; block diagram +
scenario time-series instead of a fixed curve)
- modes/README.md — updated with the three-tier classification table
and diagram-type-per-tier guidance
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.8 KiB
title, mode, tier, status, updated
| title | mode | tier | status | updated |
|---|---|---|---|---|
| Power-based mode | powerBased | 2 | placeholder | 2026-04-22 |
Power-based mode — Tier 2 template
Status — not yet implemented. Placeholder. This page documents the intended shape of a grid-aware / netcongestion-aware station.
At a glance
| Item | Value |
|---|---|
| Tier | 2 — parameterised transfer function |
| Signal driving demand | basin level (primary), max-power budget (clip) |
| Secondary inputs | measured pump power, live grid-price / peak-hours signal |
| Output | demand 0–100 % clipped so Σ pump power ≤ maxPowerKW(t) |
| Thresholds adjusted at runtime? | maxPowerKW(t) yes — level thresholds no |
| Use when | Grid has peak-hour tariffs or net-congestion caps |
Diagram — the levelbased curve with a moving clip ceiling
demand % ← dashed line: levelbased curve
100 ┤ ╱ ─────── ← solid: clip at powerBudget(t)
│ ╱ clip lowers
│ ╱ during grid peak
│ ╱ ─────────
│ ╱ ╱
│ ╱ ╱
│ ╱ ╱
0 ┼────────●───────●─────────────────────► level
startLevel maxLevel
↑ the family of curves:
clip=100% (grid idle),
clip=70% (shoulder),
clip=40% (peak).
The shape stays levelbased; the ceiling drops when the grid is strained. That's the Tier-2 signature: same input axis, parameter shifts the curve.
Inputs
| Signal | Where from | Role |
|---|---|---|
| current level | as in levelbased | primary input |
config.control.powerBased.maxPowerKW |
editor, static | hard cap on station power |
config.control.powerBased.powerControlMode |
limit / optimize |
whether to just clip or to schedule |
| live grid signal (future) | external topic or forecast | modulates the cap over time |
| measured pump power | power.measured.* from children |
real-time feedback against the cap |
Threshold policy
Level thresholds (minLevel, startLevel, maxLevel) are identical to levelbased — they define the shape of the underlying curve. What's new is a runtime-varying ceiling demandCap(t) derived from the power budget.
demandCap(t) = 100 × (maxPowerKW(t) / nominalStationPowerAtFull) — where maxPowerKW(t) may come from config (static limit mode) or an external grid-price feed (dynamic).
Demand formula
rawDemand = levelbasedDemand(level) # the underlying Tier-1 curve
demandCap = min(100, 100 × maxPowerKW(t) / nominalStationPower)
demand = min(rawDemand, demandCap)
When demandCap < rawDemand, the mode sacrifices drainage rate to stay within power budget. Level may rise — the overfill safety layer still applies as the last line of defence.
Edge cases
- Peak hour with rising level. demandCap drops faster than level rises → demand gets clipped; level approaches
overflowLevel. If overfill safety trips, it overrides the clip (safety wins). - Power signal dropout. Fall back to static
maxPowerKWfrom config; log warning. - Grid exit from peak while basin is nearly full. demandCap jumps back to 100; PID is memoryless so demand rises in one tick to match rawDemand.
- Measured vs predicted pump power. Cap is enforced on predicted (decisions are made before the pump responds). Reconcile against measured for logging/diagnostics.
Related
- Functional description
- modes/levelbased.md — Tier 1 reference (the curve that powerBased clips)
- modes/flowbased.md — other Tier-2 example with different control variable