Files
pumpingStation/wiki/modes/levelbased.md
Rene De Ren 6ab585bcc2 Docs + simulations refresh; align spill-flow keys with new position
- 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>
2026-05-06 17:23:20 +02:00

5.7 KiB
Raw Blame History

title, mode, status, updated
title mode status updated
Level-based mode levelbased implemented 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; see modes/README.md for the template other mode pages follow.

At a glance

Item Value
Signal driving demand basin level (measured, predicted fallback)
Output demand 0100 % 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

Level-linear basin mode — demand vs level transfer function

Editable sources: ../diagrams/modes/level-based/basin-mode-level-linear.drawio.svg and ../diagrams/modes/level-based/basin-mode-level-log.drawio.svg (drag into draw.io — 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

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 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.
  • Functional description — basin model, net-flow selection, safety layer (shared across all modes)
  • modes/README.md — mode index + template
  • Other mode pages: to be written (flowbased.md, pressurebased.md, percentagebased.md, powerbased.md, hybrid.md, manual.md)