Fix basin + control-zone diagram accuracy

Three corrections after review:

1. Overfill behaviour — replaced "overfill trip (upstream stops)" with
   "spill over weir (measure & log)" and added a Known Limitation box
   in the Safety section. The code's execSequence:shutdown on upstream
   children only makes sense in a cascaded-station layout; for the
   gravity-sewer case the inflow can't be stopped (toilets back up).
   Correct response is spill measurement + alarm.

2. Demand-ramp annotations — removed "100% / 0% demand" and the
   RUN arrow from the basin cross-section. Those are levelbased-mode
   specifics; the basin model should describe physical geometry only.
   Demand annotations remain in the Control logic thermometer.

3. heightInlet placement — moved below startLevel in both diagrams.
   Matches physical reality: pumps start before water rises to the
   gravity-inlet pipe. Flags a contradictory comment in
   specificClass.js for cleanup next time that file is touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
znetsixe
2026-04-22 12:08:27 +02:00
parent e654a04d86
commit 7bd5e617bc

@@ -173,27 +173,31 @@ The basin is modelled as a rectangular prism with constant cross-section. Everyt
┌─────────────────┐ ◄─ heightBasin (rim) ┌─────────────────┐ ◄─ heightBasin (rim)
│ │ │ │
│ freeboard │ │ freeboard │
├─ ─ ─ ─ ─ ─ ─ ─ ─┤ ◄─ heightOverflow ═══► overfill trip ├─ ─ ─ ─ ─ ─ ─ ─ ─┤ ◄─ heightOverflow ═══► spill over weir
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ (upstream stops) │≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ (measure & log —
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ see Safety note)
├╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌┤ ◄─ maxFlowLevel
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ │≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│
├╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌┤ ◄─ maxFlowLevel ═══► 100 % demand │≈≈≈ SCALING ≈≈≈≈≈│ (levelbased: demand
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ │≈≈≈ RANGE ≈≈≈≈≈│ ramps 0 → 100 %
│≈≈≈≈≈ RUN ≈≈≈≈≈≈≈│ │ ramp │≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ across this band)
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ │ linearly ├╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌┤ ◄─ startLevel
INFLOW ═══════►╣≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ ◄─ heightInlet │ ≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ │ │≈≈≈ DEAD ZONE ≈≈≈│ (hysteresis —
├╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌┤ ◄─ startLevel ═══► 0 % demand │≈≈ (keep cmd) ≈≈│ keep last command)
│≈≈ DEAD ZONE ≈≈≈≈│ ─── hysteresis INFLOW ═══════►╣≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ ◄─ heightInlet
│≈≈ (keep cmd) ≈≈≈│ │≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│
├╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌┤ ◄─ stopLevel ═══► unconditional STOP ├╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌┤ ◄─ stopLevel ═══► unconditional STOP
│≈≈≈≈≈ BUFFER ≈≈≈≈│ │≈≈≈≈≈ BUFFER ≈≈≈≈│
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ │≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│
OUTFLOW ◄══════╣≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ ◄─ heightOutlet ═══► dry-run trip OUTFLOW ◄══════╣≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ ◄─ heightOutlet ═══► dry-run trip
│░░░ DEAD VOLUME ░│ (downstream stops) │░░░ DEAD VOLUME ░│ (downstream stops)
└─────────────────┘ ◄─ 0 (floor) └─────────────────┘ ◄─ 0 (floor)
``` ```
**Typical ordering** (bottom → top): `stopLevel < startLevel = minFlowLevel ≤ heightInlet < maxFlowLevel heightOverflow`. **Typical ordering** (bottom → top): `stopLevel < heightInlet < startLevel = minFlowLevel < maxFlowLevel heightOverflow`.
> ⚠️ The comment block in `specificClass.js` currently says `startLevel ≤ heightInlet` (inlet above startLevel). The physical convention is the opposite: pumps start *before* the water reaches the gravity inlet, so `heightInlet < startLevel`. Worth fixing in the code comment next time that file is touched.
**minHeightBasedOn** — which pipe defines `minVol`, the operational floor used for the initial seed, the dry-run trigger, and the 0 % point of the fill percentage: **minHeightBasedOn** — which pipe defines `minVol`, the operational floor used for the initial seed, the dry-run trigger, and the 0 % point of the fill percentage:
@@ -246,7 +250,7 @@ flowPositions = { inflow: ['in', 'upstream'], outflow: ['out', 'downstream'] }
level level
┼── heightOverflow ═══ overfill trip ─► upstream STOP ┼── heightOverflow ─── weir crest (spill → measure)
│ ┐ │ ┐
│ │ RUN │ │ RUN
@@ -254,17 +258,17 @@ flowPositions = { inflow: ['in', 'upstream'], outflow: ['out', 'downstream'] }
│ │ │ │
┼── maxFlowLevel ═══ ┴ 100 % demand ┼── maxFlowLevel ═══ ┴ 100 % demand
┼── heightInlet ─── inflow pipe │ ┐
│ (scaling range)
┼── startLevel ═══ 0 % demand (ramp starts) │ │
┼── startLevel ═══ 0 % demand ── ramp starts
│ ┐ │ ┐
│ │ DEAD ZONE │ │ DEAD ZONE
│ hysteresis — keep last cmd ┼── heightInlet ─── │ hysteresis — keep last cmd
│ │ │ │
┼── stopLevel ═══ ┴ unconditional STOP ┼── stopLevel ═══ ┴ unconditional STOP
┼── heightOutlet ─── outflow pipe ┼── heightOutlet ─── outflow pipe (dry-run trip here)
│ ═══ dry-run trip ─► downstream STOP
┴── 0 (floor) ┴── 0 (floor)
``` ```
@@ -310,6 +314,8 @@ flowPositions = { inflow: ['in', 'upstream'], outflow: ['out', 'downstream'] }
During overfill, level-based control naturally commands ≥100 % on the downstream MGC because the level is above `maxFlowLevel`. During overfill, level-based control naturally commands ≥100 % on the downstream MGC because the level is above `maxFlowLevel`.
> ⚠️ **Known limitation — gravity-sewer context.** The "upstream STOP" action only makes sense in a **cascaded** station layout where the upstream equipment is an EVOLV-controllable pump or station. In a conventional wastewater wet-well the inflow is gravity-fed from the municipal sewer and **cannot be stopped** — attempting to would back up toilets. For that case the correct response to an overfill event is to **measure and log the spill over the weir** (for compliance reporting) and raise an alarm, while keeping downstream pumps at maximum demand. The current code fires `execSequence: shutdown` on upstream children regardless of what they are; that should be gated on "is the upstream actually controllable?" and supplemented with overflow-rate tracking. Tracked as follow-up work.
A missing volume reading is treated as a hard fault: every direct machine is sent `execSequence: shutdown` and `safetyControllerActive` latches. Calibrate predicted volume (`calibratePredictedVolume`) or wire a level measurement to recover. A missing volume reading is treated as a hard fault: every direct machine is sent `execSequence: shutdown` and `safetyControllerActive` latches. Calibrate predicted volume (`calibratePredictedVolume`) or wire a level measurement to recover.
## Registration — which children count as flow? ## Registration — which children count as flow?