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)
│ │
│ freeboard │
├─ ─ ─ ─ ─ ─ ─ ─ ─┤ ◄─ heightOverflow ═══► overfill trip
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ (upstream stops)
├─ ─ ─ ─ ─ ─ ─ ─ ─┤ ◄─ heightOverflow ═══► spill over weir
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ (measure & log —
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ see Safety note)
├╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌┤ ◄─ maxFlowLevel
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│
├╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌┤ ◄─ maxFlowLevel ═══► 100 % demand
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│
│≈≈≈≈≈ RUN ≈≈≈≈≈≈≈│ │ ramp
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ │ linearly
INFLOW ═══════►╣≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ ◄─ heightInlet │
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ │
├╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌┤ ◄─ startLevel ═══► 0 % demand
│≈≈ DEAD ZONE ≈≈≈≈│ ─── hysteresis
│≈≈ (keep cmd) ≈≈≈│
├╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌┤ ◄─ stopLevel ═══► unconditional STOP
│≈≈≈ SCALING ≈≈≈≈≈│ (levelbased: demand
│≈≈≈ RANGE ≈≈≈≈≈│ ramps 0 → 100 %
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ across this band)
├╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌┤ ◄─ startLevel
≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│
│≈≈≈ DEAD ZONE ≈≈≈│ (hysteresis —
│≈≈ (keep cmd) ≈≈│ keep last command)
INFLOW ═══════►╣≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ ◄─ heightInlet
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│
├╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌ ╌┤ ◄─ stopLevel ═══► unconditional STOP
│≈≈≈≈≈ BUFFER ≈≈≈≈│
│≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│
OUTFLOW ◄══════╣≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ ◄─ heightOutlet ═══► dry-run trip
│░░░ DEAD VOLUME ░│ (downstream stops)
OUTFLOW ◄══════╣≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈│ ◄─ heightOutlet ═══► dry-run trip
│░░░ DEAD VOLUME ░│ (downstream stops)
└─────────────────┘ ◄─ 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:
@@ -246,7 +250,7 @@ flowPositions = { inflow: ['in', 'upstream'], outflow: ['out', 'downstream'] }
level
┼── heightOverflow ═══ overfill trip ─► upstream STOP
┼── heightOverflow ─── weir crest (spill → measure)
│ ┐
│ │ RUN
@@ -254,17 +258,17 @@ flowPositions = { inflow: ['in', 'upstream'], outflow: ['out', 'downstream'] }
│ │
┼── maxFlowLevel ═══ ┴ 100 % demand
┼── heightInlet ─── inflow pipe
┼── startLevel ═══ 0 % demand (ramp starts)
│ ┐
│ (scaling range)
│ │
┼── startLevel ═══ 0 % demand ── ramp starts
│ ┐
│ │ DEAD ZONE
│ hysteresis — keep last cmd
┼── heightInlet ─── │ hysteresis — keep last cmd
│ │
┼── stopLevel ═══ ┴ unconditional STOP
┼── heightOutlet ─── outflow pipe
│ ═══ dry-run trip ─► downstream STOP
┼── heightOutlet ─── outflow pipe (dry-run trip here)
┴── 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`.
> ⚠️ **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.
## Registration — which children count as flow?