From 5e2ebe4d96d0b925a17007804815ed893916942f Mon Sep 17 00:00:00 2001 From: znetsixe Date: Tue, 14 Apr 2026 14:10:23 +0200 Subject: [PATCH] fix(safety): overfill must keep pumps running, not shut them down MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two hard rules for the safety controller, matching sewer PS design: 1. BELOW stopLevel (dry-run): pumps CANNOT start. All downstream equipment shut down. safetyControllerActive=true blocks _controlLogic so level control can't restart pumps. Only manual override or emergency can change this. 2. ABOVE overflow level (overfill): pumps CANNOT stop. Only UPSTREAM equipment is shut down (stop more water coming in). Machine groups (downstream pumps) are NOT shut down — they must keep draining. safetyControllerActive is NOT set, so _controlLogic continues commanding pumps at the demand dictated by the level curve (which is >100% near overflow = all pumps at maximum). Only manual override or emergency stop can shut pumps during an overfill event. Previously the overfill branch called turnOffAllMachines() on machine groups AND set safetyControllerActive=true, which shut down the pumps and blocked level control from restarting them — exactly backwards for a sewer pumping station where the sewage keeps coming. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/specificClass.js | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/specificClass.js b/src/specificClass.js index bacb65c..573dfa1 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -558,6 +558,24 @@ class PumpingStation { /* --------------------------- Safety --------------------------- */ + /** + * Safety controller — two hard rules: + * + * 1. BELOW stopLevel (dry-run): pumps CANNOT start. + * Shuts down all downstream machines + machine groups. + * Only a manual override or emergency can restart them. + * safetyControllerActive = true → blocks _controlLogic. + * + * 2. ABOVE overflow level (overfill): pumps CANNOT stop. + * Shuts down UPSTREAM equipment only (stop more water coming in). + * Does NOT shut down downstream pumps or machine groups — they + * must keep draining. Does NOT set safetyControllerActive — the + * level-based control keeps running so pumps stay at the demand + * dictated by the current level (which will be >100% near overflow, + * meaning all pumps at maximum via the normal demand curve). + * Only a manual override or emergency stop can shut pumps during + * an overfill event. + */ _safetyController(remainingTime, direction) { this.safetyControllerActive = false; @@ -584,10 +602,12 @@ class PumpingStation { const triggerHighVol = this.basin.maxVolOverflow * ((Number(overfillThresholdPercent) || 0) / 100); const triggerLowVol = this.basin.minVol * (1 + ((Number(dryRunThresholdPercent) || 0) / 100)); + // Rule 1: DRY-RUN — below stopLevel, pumps cannot run. if (direction === 'draining') { const timeTriggered = timeProtectionEnabled && remainingTime != null && remainingTime < timeleftToFullOrEmptyThresholdSeconds; const dryRunTriggered = dryRunEnabled && vol < triggerLowVol; if (timeTriggered || dryRunTriggered) { + // Shut down all downstream equipment — pumps must stop. Object.values(this.machines).forEach((machine) => { const pos = machine?.config?.functionality?.positionVsParent; if ((pos === 'downstream' || pos === 'atequipment') && machine._isOperationalState()) { @@ -597,28 +617,38 @@ class PumpingStation { Object.values(this.stations).forEach((station) => station.handleInput('parent', 'execSequence', 'shutdown')); Object.values(this.machineGroups).forEach((group) => group.turnOffAllMachines()); this.logger.warn( - `Safe guard triggered: vol=${vol.toFixed(2)} m3, remainingTime=${remainingTime ? remainingTime.toFixed(1) : 'N/A'} s; shutting down downstream equipment` + `Dry-run safety: vol=${vol.toFixed(2)} m3, remainingTime=${remainingTime ? remainingTime.toFixed(1) : 'N/A'} s; shutting down downstream equipment` ); + // Block _controlLogic so level-based control can't restart pumps. this.safetyControllerActive = true; } } + // Rule 2: OVERFILL — above overflow level, pumps cannot stop. + // Only shut down UPSTREAM equipment. Downstream pumps + machine + // groups keep running at whatever the level control demands + // (which will be >100% near overflow = all pumps at max). + // Do NOT set safetyControllerActive — _controlLogic must keep + // running to maintain pump demand. if (direction === 'filling') { const timeTriggered = timeProtectionEnabled && remainingTime != null && remainingTime < timeleftToFullOrEmptyThresholdSeconds; const overfillTriggered = overfillEnabled && vol > triggerHighVol; if (timeTriggered || overfillTriggered) { + // Shut down UPSTREAM only — stop more water coming in. Object.values(this.machines).forEach((machine) => { const pos = machine?.config?.functionality?.positionVsParent; if (pos === 'upstream' && machine._isOperationalState()) { machine.handleInput('parent', 'execSequence', 'shutdown'); } }); - Object.values(this.machineGroups).forEach((group) => group.turnOffAllMachines()); Object.values(this.stations).forEach((station) => station.handleInput('parent', 'execSequence', 'shutdown')); + // NOTE: machine groups (downstream pumps) are NOT shut down. + // They must keep draining to prevent overflow from worsening. this.logger.warn( - `Safe guard triggered: vol=${vol.toFixed(2)} m3, remainingTime=${remainingTime ? remainingTime.toFixed(1) : 'N/A'} s; shutting down upstream equipment` + `Overfill safety: vol=${vol.toFixed(2)} m3, remainingTime=${remainingTime ? remainingTime.toFixed(1) : 'N/A'} s; shutting down upstream equipment only — pumps keep running` ); - this.safetyControllerActive = true; + // NOTE: safetyControllerActive is NOT set — level control + // keeps commanding pumps at maximum demand. } } }