- wiki/functional-description.md: rename Overfill Protection → High-volume
Safety; tighten basin-ordering chain; relocate level-based mode
diagrams under wiki/diagrams/modes/level-based/; document the new
flow.predicted.overflow.default position (replaces the previous
child='overflow' under position 'out'); add underflowVolume +
predictedUnderflowVolume entries.
- wiki/modes/{levelbased,powerbased}.md: paragraph cleanups.
- wiki/diagrams: move level-linear basin diagram under modes/level-based/
alongside a new level-log variant.
- simulations/run.js: add max_demand_gt expectation.
- simulations/scenarios/*: minor fixture updates.
- test/basic/nodeClass-config.test.js: new config-shape coverage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
87 lines
5.7 KiB
Markdown
87 lines
5.7 KiB
Markdown
---
|
||
title: Level-based mode
|
||
mode: levelbased
|
||
status: implemented
|
||
updated: 2026-04-22
|
||
---
|
||
|
||
# Level-based mode
|
||
|
||
The simplest and most widely deployed control strategy. Demand is a direct, static function of basin level — no feedback loop, no predictions beyond the level measurement itself. This page uses the [shared basin model](../functional-description.md#basin-model); see [`modes/README.md`](README.md) for the template other mode pages follow.
|
||
|
||
## At a glance
|
||
|
||
| Item | Value |
|
||
|---|---|
|
||
| Signal driving demand | basin level (measured, predicted fallback) |
|
||
| Output | demand 0–100 % forwarded to every MGC child |
|
||
| Thresholds adjusted at runtime? | No — static from editor config |
|
||
| Use when | Inflow is sewer-gravity (no smart metering) and operator wants a predictable, inspectable response |
|
||
|
||
## Diagram
|
||
|
||

|
||
|
||
*Editable sources: [`../diagrams/modes/level-based/basin-mode-level-linear.drawio.svg`](../diagrams/modes/level-based/basin-mode-level-linear.drawio.svg) and [`../diagrams/modes/level-based/basin-mode-level-log.drawio.svg`](../diagrams/modes/level-based/basin-mode-level-log.drawio.svg) (drag into [draw.io](https://app.diagrams.net/) — they round-trip).*
|
||
|
||
## Inputs
|
||
|
||
| Signal | Where from | Role |
|
||
|---|---|---|
|
||
| current level | `measurement` child (`measured`) → predicted from volume integrator (fallback) | X-axis of the transfer function |
|
||
| `config.control.levelbased.minLevel` | editor, static | below → pumps hard OFF |
|
||
| `config.control.levelbased.startLevel` | editor, static | falling ramp reaches 0 % here; rising demand holds 0 % until the inlet level |
|
||
| `config.control.levelbased.maxLevel` | editor, static | where demand saturates at 100 % |
|
||
| `config.control.levelbased.curveType` | editor, static | `linear` or `log`; log is fast early response |
|
||
|
||
The three control thresholds plus curve type are the mode-specific configuration. Nothing here is recomputed at runtime.
|
||
|
||
## Threshold policy
|
||
|
||
| Threshold | Source | Adjustable at runtime? |
|
||
|---|---|---|
|
||
| `minLevel` | `config.control.levelbased.minLevel` | No |
|
||
| `startLevel` | `config.control.levelbased.startLevel` | No |
|
||
| `maxLevel` | `config.control.levelbased.maxLevel` | No |
|
||
| `curveType` | `config.control.levelbased.curveType` | No |
|
||
|
||
That this policy is trivial (all static) is **the defining simplicity of this mode**. Modes like `powerBased` or future `weather-aware` variants will recompute these thresholds on the fly.
|
||
|
||
## Demand formula
|
||
|
||
```text
|
||
if level < minLevel:
|
||
demand = 0
|
||
MGC → turnOffAllMachines() # explicit shutdown, not just "0 %"
|
||
elif direction == filling:
|
||
demand = curve(level, [inflowLevel, maxLevel], [0 %, 100 %])
|
||
elif direction == draining:
|
||
demand = curve(level, [startLevel, maxLevel], [0 %, 100 %])
|
||
else:
|
||
demand = previous demand
|
||
```
|
||
|
||
Below the active lower ramp point, demand is 0 %. Above `maxLevel`, demand is 100 %. `curve` is either linear or logarithmic; the log variant rises faster early in the ramp. The MGC is free to distribute the demand across its pumps however its own policy dictates (equal split, lead-lag, staging — that's the MGC's business).
|
||
|
||
## Edge cases
|
||
|
||
- **Cold start with level in the dead zone.** `demand` has no prior value; it defaults to `0`. Pumps stay OFF until the level first crosses `startLevel` upward. Once it does, normal ramp-and-hold behaviour engages.
|
||
- **Level sensor drops out mid-run.** `_selectBestNetFlow` falls back to predicted level (computed from the volume integrator) — the mode doesn't care which variant wins, it just reads the chosen level.
|
||
- **Both sensor and predictor unavailable.** The mode's preconditions fail; `_controlLogic` logs a warning and exits without issuing a command. The last-known demand is held, which is safe.
|
||
- **Level crosses `maxLevel` upward.** Demand saturates at 100 %. Level may still continue rising if inflow > station capacity — this is the scenario that trips the overflow-safety layer (see below).
|
||
- **Level crosses `dryRunLevel` downward.** The **safety layer** (not this mode) force-shuts all downstream pumps regardless of what demand the mode is commanding. The mode's demand is effectively overridden until level climbs back above `dryRunLevel + hysteresis_margin`.
|
||
- **Level crosses `overflowLevel` upward.** The safety layer logs the spill event and raises an alarm. The mode continues commanding at 100 % — which is what you want, because the pumps should keep draining as fast as physically possible. (See [functional description § Safety controller](../functional-description.md#safety-controller) for the gravity-sewer caveat.)
|
||
|
||
## Why this is worth migrating off of
|
||
|
||
Level-based is fine for steady-state sewer inflows. It has two known weaknesses:
|
||
|
||
1. **Predictable, not proactive.** It can't *pre-empty* the basin ahead of a forecasted storm or a power-price peak. Modes like `weather-aware` or `powerBased` can — by moving `startLevel` down or up at runtime.
|
||
2. **Thresholds assume pump capacity is fixed.** If you add or remove pumps, the `startLevel ↔ maxLevel` band that gave smooth 0-100 % coverage no longer matches the new capacity. Flow-based and percentage-based modes are less brittle to capacity changes because they close the loop on *what you actually measure* (outflow or fill %) rather than *what you assume the level→capacity map is*.
|
||
|
||
## Related
|
||
|
||
- [Functional description](../functional-description.md) — basin model, net-flow selection, safety layer (shared across all modes)
|
||
- [modes/README.md](README.md) — mode index + template
|
||
- Other mode pages: *to be written* (`flowbased.md`, `pressurebased.md`, `percentagebased.md`, `powerbased.md`, `hybrid.md`, `manual.md`)
|