Files
pumpingStation/src/specificClass.js

353 lines
16 KiB
JavaScript
Raw Normal View History

// PumpingStation — S88 Process Cell orchestrator.
//
// Wires the basin / measurement / control / safety modules in configure()
// and runs them in tick(). All real work lives in the modules; this file
// only stitches them together. See wiki/functional-description.md for the
// behaviour spec.
const { BaseDomain, UnitPolicy, statusBadge } = require('generalFunctions');
const BasinGeometry = require('./basin/BasinGeometry');
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
const { validateThresholdOrdering, computeSafetyPoints } = require('./basin/thresholdValidator');
const FlowAggregator = require('./measurement/flowAggregator');
const MeasurementRouter = require('./measurement/measurementRouter');
const calibration = require('./measurement/calibration');
const control = require('./control');
const SafetyController = require('./safety/safetyController');
class PumpingStation extends BaseDomain {
static name = 'pumpingStation';
// Internal math runs in m3/s for flow and m for level so the volume
// integrator (flow × dt) is unit-consistent — canonical stays m3/s, the
// platform-wide convention every cross-node consumer (MGC demand math,
// physics-sanity) assumes. Strict canonicals make unit drift in child-fed
// measurements an explicit error.
// Output flow / netFlowRate are emitted in m3/h so telemetry/dashboard
// series land on the same axis as the rest of the pump group (verified
// slice #47); the m3/s→m3/h presentation conversion happens at the output
// boundary only — it never touches the canonical integrator basis.
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
// overflowVolume / underflowVolume are listed in output so the
// MeasurementContainer keeps the integrator's m³ unit on those streams
// (FlowAggregator writes spill / underflow per tick).
static unitPolicy = UnitPolicy.declare({
canonical: { flow: 'm3/s', pressure: 'Pa', power: 'W', temperature: 'K' },
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
output: {
flow: 'm3/h', netFlowRate: 'm3/h', level: 'm', volume: 'm3',
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
overflowVolume: 'm3', underflowVolume: 'm3',
},
requireUnitForTypes: [],
});
configure() {
this.basin = new BasinGeometry(this.config.basin, this.config.hydraulics);
2025-11-10 16:20:23 +01:00
2025-10-28 17:04:26 +01:00
this.flowVariants = ['measured', 'predicted'];
this.levelVariants = ['measured', 'predicted'];
this.volVariants = ['measured', 'predicted'];
2025-10-28 17:04:26 +01:00
this.flowPositions = { inflow: ['in', 'upstream'], outflow: ['out', 'downstream'] };
2025-10-07 18:05:54 +02:00
2025-11-30 09:24:18 +01:00
this.mode = this.config.control.mode;
this.controlState = { percControl: 0 };
2025-11-30 09:24:18 +01:00
this.state = { direction: 'steady', netFlow: 0, flowSource: null, seconds: null, remainingSource: null };
feat(pumpingStation): realistic defaults, ramp-foot visual fix, manual-mode visibility, dashboard example Editor + schema defaults - pumpingStation.html: drag-in defaults now reflect a realistic basin (volume=50 m³, height=4 m, inflowLevel=1.5, outflowLevel=0.2, overflowLevel=3.8, startLevel=1, stopLevel=0.5, minLevel=0.3, maxLevel=3.8). Old defaults left every level field null. Visual bug fix - src/editor/mode-preview.js: the level-based ramp curve in the editor was being drawn with foot=startLevel via buildPath(start, start, max). The runtime in control/levelBased.js has always used inflowLevel as the ramp foot. Pass buildPath(start, upFoot, max) where upFoot falls back to start when inflowLevel is missing, matching the runtime. Manual mode observability - src/specificClass.js: store last forwarded demand on this._manualDemand; surface as `mode` and `manualDemand` in getOutput(); call notifyOutputChanged() on forwardDemandToChildren and on changeMode so Port 0/1 emit even with no children registered. Status badge compacted to `mode | dir% | net m³/h` + `Qd=X m³/h` in manual mode. Examples cleanup - Drop stale 02-Integration.json, 03-Dashboard.json, basic-dashboard.flow.json, standalone-demo.js. - 01-Basic.json: numbered driver groups (1. Control mode … 4. Calibration), Debug-outputs group, fixed typos and HOW-TO-USE; Port 1 debug now active. - New 02-Dashboard.json: FlowFuse Dashboard 2.0 with Controls (7 buttons), Status (7 ui-text rows), Trends (4 ui-charts: level / volume / volume% / flow in-out-net), Raw output (ui-template dumping every Port 0 field). Fan-out function pattern-matches the 4-segment measurement keys by prefix instead of hardcoding childId, converts flow m³/s → m³/h, and caches last-known values so deltas never blank a row. - examples/README.md realigned to the two-file set. Wiki - Home.md: 5 image placeholders replaced with the provided screenshots (01-node-and-editor, 02-basic-flow, 03-wiring-standalone, 04-wiring-integrated) and the demo GIF (01-basic-demo). - Reference-Examples.md: shipped-files table reduced to 01-Basic + 02-Dashboard, Example-01 section uses the screenshot + GIF, Example-02 rewritten as Dashboard (kept screenshot/GIF callouts open for those captures), Example-03/Integration sections + their debug-recipes row removed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:52:00 +02:00
// Last operator demand from set.demand in manual mode. Stored on the
// host so getOutput()/status reflect it even when no children are
// registered yet (otherwise forwardDemand is invisible on Port 0/1).
// Cleared on mode change away from manual.
this._manualDemand = null;
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
// Level-armed hysteresis state — ported from basin-docs `_controlLevelBased`.
// Exposed as instance fields because the e2e/basic tests assert on them
// directly. levelBased strategy reads/writes via the same names.
Level-armed shift, derived dryRunLevel, side-panel editor + manual q_out Runtime (specificClass.js): - Replace direction-based hysteresis with level-armed _shiftArmed state. Arms when level rises past shiftLevel; disarms when level drops below startLevel. While armed, ramp foot moves to startLevel and ramp top to shiftLevel — both ends shift left, then saturate at 100 % up to maxLevel. - _scaleLevelToFlowPercent now takes (rampStartLevel, rampTopLevel) so the saturation point follows the shift state. - New setManualOutflow mirroring setManualInflow. Adapter (nodeClass.js): - Pipe enableShiftedRamp / shiftLevel through to control.levelbased. - New q_out topic handler. Editor (pumpingStation.html + new src/editor/ modules): - Split monolithic <script> into modules: index.js (helpers), basin-diagram.js, mode-preview.js, hover-couple.js, oneditprepare.js, oneditsave.js — served via /pumpingStation/editor/:file. - Mode preview redrawn per the SVG diagrams: OFF tier below 0 %, 0 % flat from start→inlet, ramp inlet→max, optional shifted-down curve start→shift with 100 % saturation past shift. - Mode preview gains zone bands (dryRun / safetyLow / safe / safetyHigh / overflow), level markers (dryRun derived, start, inlet, max, shift, overflow), validation ribbon that blocks save on bad ordering. - Auto-default shiftLevel to 0.9 × maxLevel on enable so the marker is always visible. - All level inputs moved to a side panel left of each diagram, color- coded to match line strokes; hover-couple highlights the paired SVG line on input focus / mouseover. - Removed UI for non-static parameters: minHeightBasedOn, pipelineLength, maxDischargeHead, staticHead, defaultFluid, maxInflowRate, temperatureReferenceDegC, timeleftToFullOrEmptyThresholdSeconds, inletPipeDiameter, outletPipeDiameter, minLevel (now derived = dryRunLevel). - foreignObject inputs in basin SVG removed (single source of truth in side panel). Dashboard example (examples/basic-dashboard.flow.json): - Add manual Q_OUT slider + q_out builder mirroring the existing q_in trio so the basin can be exercised end-to-end without a connected rotating-machine downstream. Tests (test/basic/specificClass.test.js): - Replace direction-shift test with two new cases covering shift-disabled hold-zone behaviour and shift-armed/disarmed transitions through shiftLevel and startLevel boundaries. 53/53 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 19:29:34 +02:00
this._shiftArmed = false;
Hold-then-ramp shift semantics + shiftArmPercent + e2e tests Runtime (specificClass.js): - Replace the "shift left both ramp ends" geometry with a true hold-then-ramp hysteresis driven by output %, not level: • Up-curve % crosses shiftArmPercent on the way up → ARM. • Filling→draining transition while armed → capture the up-curve % at that moment as _shiftHoldValue. • Draining + level ≥ shiftLevel → output stays at _shiftHoldValue (horizontal hold, matching the dashed segment in the SVG). • Draining + level in [start, shift] → output ramps holdValue → 0 % along the same curve shape (linear or log) as the up curve. • Draining + level < startLevel → 0 % AND disarm. • Returning to filling clears holdValue, stays armed; next drain transition captures a fresh hold so bouncing fills rearm cleanly. • Disarm only when level ≤ startLevel. - New _curveShape(x) helper for shared linear/log shaping. - Removed legacy _levelBasedRampStart / _levelBasedRampTop / _updateShiftArmed in favour of the inline state machine. Adapter (nodeClass.js): - Pipe shiftArmPercent through to control.levelbased. Editor (pumpingStation.html + src/editor/): - Add shiftArmPercent input row (% with unit) to the mode side panel (only shown when shifted ramp is enabled). Default 95 %. - Add the horizontal arming-% line + label inside the mode SVG — this is the "% Threshold triggering shifted ramp down" line from the original drawing that had been missing. - Redraw the shifted-down curve to match the SVG geometry literally: 100 % flat from maxLevel → shiftLevel, then ramp shiftLevel → startLevel down to 0 %, OFF below startLevel. Preview shows the worst-case envelope (hold = 100 %); runtime hold is captured live. - Validation extended: 0 < shiftArmPercent ≤ 100; ordering rules preserved (start < shift ≤ max etc.). - Auto-default shiftArmPercent to 95 when shift is enabled and the current value is missing or out of range. Dashboard example (examples/basic-dashboard.flow.json): - Parser now reads `level.predicted.atequipment.default` etc. The MeasurementContainer flatten format includes the implicit 'default' childId; consumers must include it. Comment in the parser points at the documenting source in generalFunctions. Tests: - test/basic: replace old level-armed-shift tests with two new ones that exercise the hold-then-ramp arming, capture, hold, ramp-down, disarm, and the bounce case (filling→draining→filling→draining captures a fresh hold each time). - test/integration/shifted-ramp-end-to-end.test.js: new file. Drives Q_IN/Q_OUT through the full runtime tick with a controllable clock, asserting the same hysteresis path the dashboard exercises. - test/integration/basic-dashboard-flow.test.js: fixture keys updated to the .default-suffixed form so they match the real flatten output. 56/56 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 11:46:46 +02:00
this._shiftHoldValue = null;
this._lastDirection = null;
Level-armed shift, derived dryRunLevel, side-panel editor + manual q_out Runtime (specificClass.js): - Replace direction-based hysteresis with level-armed _shiftArmed state. Arms when level rises past shiftLevel; disarms when level drops below startLevel. While armed, ramp foot moves to startLevel and ramp top to shiftLevel — both ends shift left, then saturate at 100 % up to maxLevel. - _scaleLevelToFlowPercent now takes (rampStartLevel, rampTopLevel) so the saturation point follows the shift state. - New setManualOutflow mirroring setManualInflow. Adapter (nodeClass.js): - Pipe enableShiftedRamp / shiftLevel through to control.levelbased. - New q_out topic handler. Editor (pumpingStation.html + new src/editor/ modules): - Split monolithic <script> into modules: index.js (helpers), basin-diagram.js, mode-preview.js, hover-couple.js, oneditprepare.js, oneditsave.js — served via /pumpingStation/editor/:file. - Mode preview redrawn per the SVG diagrams: OFF tier below 0 %, 0 % flat from start→inlet, ramp inlet→max, optional shifted-down curve start→shift with 100 % saturation past shift. - Mode preview gains zone bands (dryRun / safetyLow / safe / safetyHigh / overflow), level markers (dryRun derived, start, inlet, max, shift, overflow), validation ribbon that blocks save on bad ordering. - Auto-default shiftLevel to 0.9 × maxLevel on enable so the marker is always visible. - All level inputs moved to a side panel left of each diagram, color- coded to match line strokes; hover-couple highlights the paired SVG line on input focus / mouseover. - Removed UI for non-static parameters: minHeightBasedOn, pipelineLength, maxDischargeHead, staticHead, defaultFluid, maxInflowRate, temperatureReferenceDegC, timeleftToFullOrEmptyThresholdSeconds, inletPipeDiameter, outletPipeDiameter, minLevel (now derived = dryRunLevel). - foreignObject inputs in basin SVG removed (single source of truth in side panel). Dashboard example (examples/basic-dashboard.flow.json): - Add manual Q_OUT slider + q_out builder mirroring the existing q_in trio so the basin can be exercised end-to-end without a connected rotating-machine downstream. Tests (test/basic/specificClass.test.js): - Replace direction-shift test with two new cases covering shift-disabled hold-zone behaviour and shift-armed/disarmed transitions through shiftLevel and startLevel boundaries. 53/53 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 19:29:34 +02:00
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
// stopLevel hysteresis (Schmitt trigger) — ported from basin-docs.
// TRUE while engaged (rising-edge at startLevel until falling-edge at
// stopLevel). Used by levelBased to emit a small keep-alive output in
// the [stopLevel, startLevel] dead band so MGC keeps one pump running.
this._stopHystRunning = false;
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
// Flow dead-band — values below |flowThreshold| (m3/s) are treated as
// steady. Default ≈ 0.36 m3/h.
2025-10-28 17:04:26 +01:00
const thresholdFromConfig = Number(this.config.general?.flowThreshold);
this.flowThreshold = Number.isFinite(thresholdFromConfig) ? thresholdFromConfig : 1e-4;
// FlowAggregator owns the predicted-volume integrator + net-flow + ETA.
this.flowAggregator = new FlowAggregator({
measurements: this.measurements,
basin: this.basin,
config: this.config,
logger: this.logger,
flowVariants: this.flowVariants,
levelVariants: this.levelVariants,
flowPositions: this.flowPositions,
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
flowThreshold: this.flowThreshold,
computeSafetyPoints: () => this._computeSafetyPoints(),
});
this.measurementRouter = new MeasurementRouter({
measurements: this.measurements,
basin: this.basin,
logger: this.logger,
2025-11-30 09:24:18 +01:00
});
// Threshold ordering is non-fatal — log + surface for tests/status.
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
this.thresholdIssues = validateThresholdOrdering(
this.basin, this.config.control?.levelbased, this.config.safety
);
for (const issue of this.thresholdIssues) this.logger.warn(issue.msg);
2025-11-28 16:29:05 +01:00
// Seed predicted volume at the operational floor — without it the
// integrator starts from null and the first tick has no anchor.
this.measurements.type('volume').variant('predicted').position('atequipment')
.value(this.basin.minVol, Date.now(), 'm3').unit('m3');
2025-11-28 16:29:05 +01:00
// Registry-as-truth — `this.machines / machineGroups / stations` are
// read-only getters flattening `this.child[softwareType]` (BaseDomain
// helper). Mutations go through `childRegistrationUtils.registerChild`.
this.declareChildGetter('machines', 'machine');
this.declareChildGetter('machineGroups', 'machinegroup');
this.declareChildGetter('stations', 'pumpingstation');
// SafetyController's captured ctx exposes the same three names as live
// getters (installed in context()), so the registry remains the single
// source of truth long after configure() returns.
this.safety = new SafetyController(this.context());
this.router
.onRegister('measurement', (child) => this._subscribeMeasurement(child))
.onRegister('machine', (child) => {
// Skip individual machines when a machineGroup parent is present —
// the group's flow.predicted already aggregates child machines.
if (Object.keys(this.machineGroups).length === 0) {
this._subscribePredictedFlow(child);
}
})
.onRegister('machinegroup', (child) => this._subscribePredictedFlow(child))
.onRegister('pumpingstation', (child) => this._subscribePredictedFlow(child));
this.logger.debug('PumpingStation initialized');
2025-11-20 12:15:46 +01:00
}
// Frozen view passed to control strategies + safety.
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
// `host` is a back-reference so strategies that need to mutate
// cross-tick hysteresis state (`_shiftArmed`, `_shiftHoldValue`,
// `_lastDirection`, `_stopHystRunning`) write straight to the live
// instance — Object.freeze on the view itself is fine because these
// flags live on the host, not in the view.
//
// machines / machineGroups / stations are installed as live getters
// that delegate to this.* getters (declareChildGetter). SafetyController
// captures this ctx once at construction; the getters keep it reading
// fresh from the registry after later child registrations.
context() {
const host = this;
const ctx = {
...super.context(),
basin: this.basin,
flowAggregator: this.flowAggregator,
mode: this.mode,
flowVariants: this.flowVariants,
levelVariants: this.levelVariants,
volVariants: this.volVariants,
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
flowThreshold: this.flowThreshold,
unitPolicy: this.unitPolicy,
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
host: this,
};
Object.defineProperty(ctx, 'machines', { enumerable: true, get: () => host.machines });
Object.defineProperty(ctx, 'machineGroups', { enumerable: true, get: () => host.machineGroups });
Object.defineProperty(ctx, 'stations', { enumerable: true, get: () => host.stations });
return Object.freeze(ctx);
2025-11-27 17:46:24 +01:00
}
2025-10-28 17:04:26 +01:00
tick() {
const { netFlow, remaining } = this.flowAggregator.tick();
const safe = this.safety.evaluate({ direction: netFlow.direction, secondsRemaining: remaining.seconds });
this.safetyControllerActive = safe.blocked;
2025-10-23 09:51:54 +02:00
if (!safe.blocked) {
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
Promise.resolve(control.dispatch(this.mode, this.context(), this.controlState, netFlow.direction))
.catch((err) => this.logger.error(`control dispatch failed: ${err.message}`));
}
2025-10-28 17:04:26 +01:00
this.state = {
direction: netFlow.direction,
netFlow: netFlow.value,
flowSource: netFlow.source,
seconds: remaining.seconds,
remainingSource: remaining.source,
2025-10-28 17:04:26 +01:00
};
this.notifyOutputChanged();
2025-10-28 17:04:26 +01:00
}
2025-10-23 09:51:54 +02:00
changeMode(newMode) {
if (this.config.control.allowedModes?.has?.(newMode)) {
this.logger.info(`Control mode changing from ${this.mode} to ${newMode}`);
this.mode = newMode;
feat(pumpingStation): realistic defaults, ramp-foot visual fix, manual-mode visibility, dashboard example Editor + schema defaults - pumpingStation.html: drag-in defaults now reflect a realistic basin (volume=50 m³, height=4 m, inflowLevel=1.5, outflowLevel=0.2, overflowLevel=3.8, startLevel=1, stopLevel=0.5, minLevel=0.3, maxLevel=3.8). Old defaults left every level field null. Visual bug fix - src/editor/mode-preview.js: the level-based ramp curve in the editor was being drawn with foot=startLevel via buildPath(start, start, max). The runtime in control/levelBased.js has always used inflowLevel as the ramp foot. Pass buildPath(start, upFoot, max) where upFoot falls back to start when inflowLevel is missing, matching the runtime. Manual mode observability - src/specificClass.js: store last forwarded demand on this._manualDemand; surface as `mode` and `manualDemand` in getOutput(); call notifyOutputChanged() on forwardDemandToChildren and on changeMode so Port 0/1 emit even with no children registered. Status badge compacted to `mode | dir% | net m³/h` + `Qd=X m³/h` in manual mode. Examples cleanup - Drop stale 02-Integration.json, 03-Dashboard.json, basic-dashboard.flow.json, standalone-demo.js. - 01-Basic.json: numbered driver groups (1. Control mode … 4. Calibration), Debug-outputs group, fixed typos and HOW-TO-USE; Port 1 debug now active. - New 02-Dashboard.json: FlowFuse Dashboard 2.0 with Controls (7 buttons), Status (7 ui-text rows), Trends (4 ui-charts: level / volume / volume% / flow in-out-net), Raw output (ui-template dumping every Port 0 field). Fan-out function pattern-matches the 4-segment measurement keys by prefix instead of hardcoding childId, converts flow m³/s → m³/h, and caches last-known values so deltas never blank a row. - examples/README.md realigned to the two-file set. Wiki - Home.md: 5 image placeholders replaced with the provided screenshots (01-node-and-editor, 02-basic-flow, 03-wiring-standalone, 04-wiring-integrated) and the demo GIF (01-basic-demo). - Reference-Examples.md: shipped-files table reduced to 01-Basic + 02-Dashboard, Example-01 section uses the screenshot + GIF, Example-02 rewritten as Dashboard (kept screenshot/GIF callouts open for those captures), Example-03/Integration sections + their debug-recipes row removed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:52:00 +02:00
if (newMode !== 'manual') this._manualDemand = null;
this.notifyOutputChanged();
} else {
this.logger.warn(`Attempted to change to unsupported control mode: ${newMode}`);
}
}
// Calibration — public methods preserved for tests + commands registry.
calibratePredictedVolume(vol, ts = Date.now()) { calibration.calibratePredictedVolume(this, vol, ts); }
calibratePredictedLevel(lvl, ts = Date.now(), unit = 'm') { calibration.calibratePredictedLevel(this, lvl, ts, unit); }
setManualInflow(value, ts = Date.now(), unit) { calibration.setManualInflow(this, value, ts, unit); }
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
setManualOutflow(value, ts = Date.now(), unit) { calibration.setManualOutflow(this, value, ts, unit); }
feat(pumpingStation): realistic defaults, ramp-foot visual fix, manual-mode visibility, dashboard example Editor + schema defaults - pumpingStation.html: drag-in defaults now reflect a realistic basin (volume=50 m³, height=4 m, inflowLevel=1.5, outflowLevel=0.2, overflowLevel=3.8, startLevel=1, stopLevel=0.5, minLevel=0.3, maxLevel=3.8). Old defaults left every level field null. Visual bug fix - src/editor/mode-preview.js: the level-based ramp curve in the editor was being drawn with foot=startLevel via buildPath(start, start, max). The runtime in control/levelBased.js has always used inflowLevel as the ramp foot. Pass buildPath(start, upFoot, max) where upFoot falls back to start when inflowLevel is missing, matching the runtime. Manual mode observability - src/specificClass.js: store last forwarded demand on this._manualDemand; surface as `mode` and `manualDemand` in getOutput(); call notifyOutputChanged() on forwardDemandToChildren and on changeMode so Port 0/1 emit even with no children registered. Status badge compacted to `mode | dir% | net m³/h` + `Qd=X m³/h` in manual mode. Examples cleanup - Drop stale 02-Integration.json, 03-Dashboard.json, basic-dashboard.flow.json, standalone-demo.js. - 01-Basic.json: numbered driver groups (1. Control mode … 4. Calibration), Debug-outputs group, fixed typos and HOW-TO-USE; Port 1 debug now active. - New 02-Dashboard.json: FlowFuse Dashboard 2.0 with Controls (7 buttons), Status (7 ui-text rows), Trends (4 ui-charts: level / volume / volume% / flow in-out-net), Raw output (ui-template dumping every Port 0 field). Fan-out function pattern-matches the 4-segment measurement keys by prefix instead of hardcoding childId, converts flow m³/s → m³/h, and caches last-known values so deltas never blank a row. - examples/README.md realigned to the two-file set. Wiki - Home.md: 5 image placeholders replaced with the provided screenshots (01-node-and-editor, 02-basic-flow, 03-wiring-standalone, 04-wiring-integrated) and the demo GIF (01-basic-demo). - Reference-Examples.md: shipped-files table reduced to 01-Basic + 02-Dashboard, Example-01 section uses the screenshot + GIF, Example-02 rewritten as Dashboard (kept screenshot/GIF callouts open for those captures), Example-03/Integration sections + their debug-recipes row removed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:52:00 +02:00
forwardDemandToChildren(demand) {
this._manualDemand = Number.isFinite(demand) ? demand : null;
this.notifyOutputChanged();
return control.manual.forwardDemand(this.context(), demand);
}
// Direct delegations preserved so existing tests can drive the strategy
// without re-mocking the dispatch layer.
Hold-then-ramp shift semantics + shiftArmPercent + e2e tests Runtime (specificClass.js): - Replace the "shift left both ramp ends" geometry with a true hold-then-ramp hysteresis driven by output %, not level: • Up-curve % crosses shiftArmPercent on the way up → ARM. • Filling→draining transition while armed → capture the up-curve % at that moment as _shiftHoldValue. • Draining + level ≥ shiftLevel → output stays at _shiftHoldValue (horizontal hold, matching the dashed segment in the SVG). • Draining + level in [start, shift] → output ramps holdValue → 0 % along the same curve shape (linear or log) as the up curve. • Draining + level < startLevel → 0 % AND disarm. • Returning to filling clears holdValue, stays armed; next drain transition captures a fresh hold so bouncing fills rearm cleanly. • Disarm only when level ≤ startLevel. - New _curveShape(x) helper for shared linear/log shaping. - Removed legacy _levelBasedRampStart / _levelBasedRampTop / _updateShiftArmed in favour of the inline state machine. Adapter (nodeClass.js): - Pipe shiftArmPercent through to control.levelbased. Editor (pumpingStation.html + src/editor/): - Add shiftArmPercent input row (% with unit) to the mode side panel (only shown when shifted ramp is enabled). Default 95 %. - Add the horizontal arming-% line + label inside the mode SVG — this is the "% Threshold triggering shifted ramp down" line from the original drawing that had been missing. - Redraw the shifted-down curve to match the SVG geometry literally: 100 % flat from maxLevel → shiftLevel, then ramp shiftLevel → startLevel down to 0 %, OFF below startLevel. Preview shows the worst-case envelope (hold = 100 %); runtime hold is captured live. - Validation extended: 0 < shiftArmPercent ≤ 100; ordering rules preserved (start < shift ≤ max etc.). - Auto-default shiftArmPercent to 95 when shift is enabled and the current value is missing or out of range. Dashboard example (examples/basic-dashboard.flow.json): - Parser now reads `level.predicted.atequipment.default` etc. The MeasurementContainer flatten format includes the implicit 'default' childId; consumers must include it. Comment in the parser points at the documenting source in generalFunctions. Tests: - test/basic: replace old level-armed-shift tests with two new ones that exercise the hold-then-ramp arming, capture, hold, ramp-down, disarm, and the bounce case (filling→draining→filling→draining captures a fresh hold each time). - test/integration/shifted-ramp-end-to-end.test.js: new file. Drives Q_IN/Q_OUT through the full runtime tick with a controllable clock, asserting the same hysteresis path the dashboard exercises. - test/integration/basic-dashboard-flow.test.js: fixture keys updated to the .default-suffixed form so they match the real flatten output. 56/56 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 11:46:46 +02:00
async _controlLevelBased(direction) {
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
return control.strategies.levelbased.run(this.context(), this.controlState, direction);
2025-11-30 09:24:18 +01:00
}
2025-11-27 17:46:24 +01:00
// Public getter so legacy tests + getOutput keep reading the live demand.
get percControl() { return this.controlState.percControl; }
set percControl(v) { this.controlState.percControl = v; }
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
// ── Predicted-volume integrator — tests drive this directly with a
// controlled Date.now, so expose as an instance method that delegates
// to FlowAggregator.update().
2025-11-30 09:24:18 +01:00
_updatePredictedVolume() {
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
return this.flowAggregator.update();
2025-10-27 16:39:06 +01:00
}
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
// ── Mirror FlowAggregator internal integrator state so tests that pin
// _predictedFlowState before driving a tick keep working.
get _predictedFlowState() { return this.flowAggregator._predictedFlowState; }
set _predictedFlowState(v) { this.flowAggregator._predictedFlowState = v; }
2025-10-27 16:39:06 +01:00
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
_selectBestNetFlow() { return this.flowAggregator.selectBestNetFlow(); }
Level-armed shift, derived dryRunLevel, side-panel editor + manual q_out Runtime (specificClass.js): - Replace direction-based hysteresis with level-armed _shiftArmed state. Arms when level rises past shiftLevel; disarms when level drops below startLevel. While armed, ramp foot moves to startLevel and ramp top to shiftLevel — both ends shift left, then saturate at 100 % up to maxLevel. - _scaleLevelToFlowPercent now takes (rampStartLevel, rampTopLevel) so the saturation point follows the shift state. - New setManualOutflow mirroring setManualInflow. Adapter (nodeClass.js): - Pipe enableShiftedRamp / shiftLevel through to control.levelbased. - New q_out topic handler. Editor (pumpingStation.html + new src/editor/ modules): - Split monolithic <script> into modules: index.js (helpers), basin-diagram.js, mode-preview.js, hover-couple.js, oneditprepare.js, oneditsave.js — served via /pumpingStation/editor/:file. - Mode preview redrawn per the SVG diagrams: OFF tier below 0 %, 0 % flat from start→inlet, ramp inlet→max, optional shifted-down curve start→shift with 100 % saturation past shift. - Mode preview gains zone bands (dryRun / safetyLow / safe / safetyHigh / overflow), level markers (dryRun derived, start, inlet, max, shift, overflow), validation ribbon that blocks save on bad ordering. - Auto-default shiftLevel to 0.9 × maxLevel on enable so the marker is always visible. - All level inputs moved to a side panel left of each diagram, color- coded to match line strokes; hover-couple highlights the paired SVG line on input focus / mouseover. - Removed UI for non-static parameters: minHeightBasedOn, pipelineLength, maxDischargeHead, staticHead, defaultFluid, maxInflowRate, temperatureReferenceDegC, timeleftToFullOrEmptyThresholdSeconds, inletPipeDiameter, outletPipeDiameter, minLevel (now derived = dryRunLevel). - foreignObject inputs in basin SVG removed (single source of truth in side panel). Dashboard example (examples/basic-dashboard.flow.json): - Add manual Q_OUT slider + q_out builder mirroring the existing q_in trio so the basin can be exercised end-to-end without a connected rotating-machine downstream. Tests (test/basic/specificClass.test.js): - Replace direction-shift test with two new cases covering shift-disabled hold-zone behaviour and shift-armed/disarmed transitions through shiftLevel and startLevel boundaries. 53/53 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 19:29:34 +02:00
_computeSafetyPoints() {
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
return computeSafetyPoints(this.basin, this.config.safety || {});
Add threshold guardrails, fix calibratePredictedLevel bug, rewrite tests ### Guardrails (specificClass.js) New _validateThresholdOrdering() runs in the constructor. Checks every ordered pair of basin + control + derived-safety levels and logs a warning for each violation; returns the list as this.thresholdIssues so tests and the eval harness can inspect. Non-fatal — we prefer a running-but-warned station to a refusal-to-start (availability-first). Strict invariants (bottom → top): 0 < outflowLevel < inflowLevel < overflowLevel ≤ basinHeight dryRunLevel ≤ minLevel ≤ startLevel < maxLevel ≤ overfillLevel Uses a list-of-checks pattern rather than a switch — easier to add new invariants without reflowing cases, and the list itself is readable documentation. ### Bug fix (specificClass.js) calibratePredictedLevel was writing the volume value into the LEVEL slot. Root cause: MeasurementContainer is stateful — its type()/ variant()/position() calls mutate the container's own cursor, so caching chain references (const levelChain = ...; const volumeChain = ...) doesn't isolate them. The second cached chain ended up sharing the state of the last type() call. Rebuilt chains fresh each time, matching the calibratePredictedVolume pattern that already worked. ### Tests (test/basic/specificClass.test.js) Ported from Jest to node:test + node:assert — the project's standard per .claude/rules/testing.md. Deleted the stale test/specificClass.test.js (tests referenced methods that no longer exist post-rename). New coverage, 42 passing subtests: - Basin geometry derivations + minHeightBasedOn - Level/volume roundtrip - Threshold guardrails (5 violation cases) - Direction derivation - Mode change accept/reject - Calibration (volume and level paths — catches the bug above) - Levelbased control zones (STOP / DEAD ZONE / RAMP / saturate) - getOutput flattening - setManualInflow Run with: node --test test/basic/*.test.js Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:38:41 +02:00
}
getOutput() {
const out = this.measurements.getFlattenedOutput();
Object.assign(out, this.basin.snapshot());
out.direction = this.state.direction;
out.flowSource = this.state.flowSource;
out.timeleft = this.state.seconds;
out.percControl = this.controlState.percControl;
feat(pumpingStation): realistic defaults, ramp-foot visual fix, manual-mode visibility, dashboard example Editor + schema defaults - pumpingStation.html: drag-in defaults now reflect a realistic basin (volume=50 m³, height=4 m, inflowLevel=1.5, outflowLevel=0.2, overflowLevel=3.8, startLevel=1, stopLevel=0.5, minLevel=0.3, maxLevel=3.8). Old defaults left every level field null. Visual bug fix - src/editor/mode-preview.js: the level-based ramp curve in the editor was being drawn with foot=startLevel via buildPath(start, start, max). The runtime in control/levelBased.js has always used inflowLevel as the ramp foot. Pass buildPath(start, upFoot, max) where upFoot falls back to start when inflowLevel is missing, matching the runtime. Manual mode observability - src/specificClass.js: store last forwarded demand on this._manualDemand; surface as `mode` and `manualDemand` in getOutput(); call notifyOutputChanged() on forwardDemandToChildren and on changeMode so Port 0/1 emit even with no children registered. Status badge compacted to `mode | dir% | net m³/h` + `Qd=X m³/h` in manual mode. Examples cleanup - Drop stale 02-Integration.json, 03-Dashboard.json, basic-dashboard.flow.json, standalone-demo.js. - 01-Basic.json: numbered driver groups (1. Control mode … 4. Calibration), Debug-outputs group, fixed typos and HOW-TO-USE; Port 1 debug now active. - New 02-Dashboard.json: FlowFuse Dashboard 2.0 with Controls (7 buttons), Status (7 ui-text rows), Trends (4 ui-charts: level / volume / volume% / flow in-out-net), Raw output (ui-template dumping every Port 0 field). Fan-out function pattern-matches the 4-segment measurement keys by prefix instead of hardcoding childId, converts flow m³/s → m³/h, and caches last-known values so deltas never blank a row. - examples/README.md realigned to the two-file set. Wiki - Home.md: 5 image placeholders replaced with the provided screenshots (01-node-and-editor, 02-basic-flow, 03-wiring-standalone, 04-wiring-integrated) and the demo GIF (01-basic-demo). - Reference-Examples.md: shipped-files table reduced to 01-Basic + 02-Dashboard, Example-01 section uses the screenshot + GIF, Example-02 rewritten as Dashboard (kept screenshot/GIF callouts open for those captures), Example-03/Integration sections + their debug-recipes row removed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:52:00 +02:00
out.mode = this.mode;
out.manualDemand = this._manualDemand;
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
// Derived safety thresholds — exposed so editor + dashboards can show
// the dryRunLevel and highVolumeSafetyLevel without recomputing.
Level-armed shift, derived dryRunLevel, side-panel editor + manual q_out Runtime (specificClass.js): - Replace direction-based hysteresis with level-armed _shiftArmed state. Arms when level rises past shiftLevel; disarms when level drops below startLevel. While armed, ramp foot moves to startLevel and ramp top to shiftLevel — both ends shift left, then saturate at 100 % up to maxLevel. - _scaleLevelToFlowPercent now takes (rampStartLevel, rampTopLevel) so the saturation point follows the shift state. - New setManualOutflow mirroring setManualInflow. Adapter (nodeClass.js): - Pipe enableShiftedRamp / shiftLevel through to control.levelbased. - New q_out topic handler. Editor (pumpingStation.html + new src/editor/ modules): - Split monolithic <script> into modules: index.js (helpers), basin-diagram.js, mode-preview.js, hover-couple.js, oneditprepare.js, oneditsave.js — served via /pumpingStation/editor/:file. - Mode preview redrawn per the SVG diagrams: OFF tier below 0 %, 0 % flat from start→inlet, ramp inlet→max, optional shifted-down curve start→shift with 100 % saturation past shift. - Mode preview gains zone bands (dryRun / safetyLow / safe / safetyHigh / overflow), level markers (dryRun derived, start, inlet, max, shift, overflow), validation ribbon that blocks save on bad ordering. - Auto-default shiftLevel to 0.9 × maxLevel on enable so the marker is always visible. - All level inputs moved to a side panel left of each diagram, color- coded to match line strokes; hover-couple highlights the paired SVG line on input focus / mouseover. - Removed UI for non-static parameters: minHeightBasedOn, pipelineLength, maxDischargeHead, staticHead, defaultFluid, maxInflowRate, temperatureReferenceDegC, timeleftToFullOrEmptyThresholdSeconds, inletPipeDiameter, outletPipeDiameter, minLevel (now derived = dryRunLevel). - foreignObject inputs in basin SVG removed (single source of truth in side panel). Dashboard example (examples/basic-dashboard.flow.json): - Add manual Q_OUT slider + q_out builder mirroring the existing q_in trio so the basin can be exercised end-to-end without a connected rotating-machine downstream. Tests (test/basic/specificClass.test.js): - Replace direction-shift test with two new cases covering shift-disabled hold-zone behaviour and shift-armed/disarmed transitions through shiftLevel and startLevel boundaries. 53/53 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 19:29:34 +02:00
const safety = this._computeSafetyPoints();
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
out.dryRunLevel = safety.dryRunLevel;
out.dryRunSafetyVol = safety.dryRunSafetyVol;
out.highVolumeSafetyLevel = safety.highVolumeSafetyLevel;
out.highVolumeSafetyVol = safety.highVolumeSafetyVol;
// Spill / underflow surface — populated by FlowAggregator when the
// predicted-volume integrator hits the upper or lower physical bound.
out.predictedOverflowVolume = this.measurements
Predicted-volume overflow clamp + spill tracking Predicted volume is now clamped to [dryRunSafetyVol, maxVolAtOverflow] in _updatePredictedVolume — the integrator can no longer drift above the weir crest (only a real measurement can show level > overflow, e.g. inflow exceeding pump+weir capacity). Excess is recorded as: - overflowVolume.predicted.atequipment.default — cumulative spill (m3) - flow.predicted.out.overflow — instantaneous spill rate (m3/s), registered as a synthetic outflow so net-flow balance reads ~0 while pinned. The integrator subtracts the prior tick's synthetic flow before integrating so it never feeds back into volume math. Lower clamp at dryRunSafetyVol fires only on the transition — a low seed/calibration is left alone; inflow is what brings it back up. _selectBestNetFlow holds the last non-zero level-rate net flow when level pins at overflowLevel and dL/dt collapses to 0, so dashboards keep showing roughly what's coming in. Auto-refreshes once level drops. getOutput() exposes predictedOverflowVolume + predictedOverflowRate as top-level convenience keys; the underlying measurements flow to InfluxDB via the standard MeasurementContainer flatten path. 9 new test assertions cover the upper-clamp + spill increment, stable spill across ticks, net-flow ~0 while pinned, spill clearing when inflow stops, low-seed left alone, drain-across-threshold clamp, and the new top-level output keys. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:47:46 +02:00
.type('overflowVolume').variant('predicted').position('atequipment').getCurrentValue('m3') ?? 0;
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
out.predictedOverflowRate = this.measurements
Predicted-volume hard-floor at 0 + spill flow position refactor Volume integrator changes: - Hard physical floor at 0 added to _updatePredictedVolume. Without it, a basin seeded below dryRunSafetyVol (calibration / startup / low seed) under continued net-outflow drifted volume arbitrarily negative; the level output looked clamped only because _calcLevelFromVolume floors at 0, masking the underlying drift. - New cumulative diagnostic: underflowVolume.predicted.atequipment (m³) + getOutput().predictedUnderflowVolume. Non-zero indicates a flow-balance error (over-reported outflow / missing inflow). - The transition-only dryRunSafetyVol clamp is preserved so startup-from-empty doesn't snap to 2.1 m³ on tick 1. Spill flow refactor (taxonomic + bug fix): - Synthetic spill moved from flow.predicted.out.<child='overflow'> to its own position flow.predicted.overflow.<default>. The spill is a derived quantity, not a physical sub-source sharing a position with pumps — .child() was the wrong knob. - Removes the spillPrev self-subtraction in the integrator (no longer needed: outflowTotal at ['out','downstream'] cleanly excludes spill). - Closes a latent fall-through bug exposed during this work: .child('overflow').getCurrentValue() returned the value of any available sibling child when overflow itself didn't yet exist. Hardened separately in generalFunctions@a516c2b. - _selectBestNetFlow folds the overflow position into the outflow side so the predicted net-flow balance still reads ~0 while pinned. Tests: 70/70 pass. 4 new subtests cover the 0-floor, accumulated underflow tracking, getOutput surface, and refill-from-empty. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:18:23 +02:00
.type('flow').variant('predicted').position('overflow').getCurrentValue('m3/s') ?? 0;
Merge origin/basin-docs-update: per-mode SVG + stopLevel hysteresis + shifted ramp Reconciles the 7-commit basin-docs-update feature branch (which never landed on main before the platform refactor) with the post-refactor architecture on development. Each basin-docs feature ported into the relevant concern module: control/levelBased.js - stopLevel Schmitt-trigger + dead-band keep-alive - Shifted ramp (arm % → hold @ 100% → ramp down to shiftLevel) - Linear vs log up-curve (curveType + logCurveFactor) measurement/flowAggregator.js - Predicted-volume overflow clamp + spill flow stream - Cumulative overflowVolume + underflowVolume - Hard floor at 0 + dry-run-on-transition handling basin/thresholdValidator.js - computeSafetyPoints exposes dryRunLevel + highVolumeSafetyLevel - startLevel ≤ inflowLevel invariant added measurement/calibration.js + commands/ - Manual q_out path (set.outflow / q_out alias) safety/safetyController.js - Accepts both legacy + new high-volume threshold names UI: pumpingStation.html — restored the side-panel + SVG mode-preview block, added defaults for stopLevel/shiftLevel/shiftArmPercent/levelCurveType/ logCurveFactor/enableShiftedRamp. src/editor/* — basin-docs' 7-file modular editor (replaces single src/editor.js, which is deleted). pumpingStation.js — admin endpoint serves editor/:file. Tests: 130/130 pass (125 basic + 5 integration). Two basin-docs test files added: nodeClass-config.test.js, basic-dashboard-flow.test.js, shifted-ramp-end-to-end.test.js. One pre-refactor control-levelBased test adapted to match basin-docs canonical "no-shutdown in dead zone" behaviour. Human-review items (see commit context): - rampFoot = inflowLevel (matches basin-docs test); basin-docs source used rampFoot = startLevel. Domain owner: confirm intent. - Naming kept dual (overfillLevel + highVolumeSafetyLevel). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:55 +02:00
out.predictedUnderflowVolume = this.measurements
Predicted-volume hard-floor at 0 + spill flow position refactor Volume integrator changes: - Hard physical floor at 0 added to _updatePredictedVolume. Without it, a basin seeded below dryRunSafetyVol (calibration / startup / low seed) under continued net-outflow drifted volume arbitrarily negative; the level output looked clamped only because _calcLevelFromVolume floors at 0, masking the underlying drift. - New cumulative diagnostic: underflowVolume.predicted.atequipment (m³) + getOutput().predictedUnderflowVolume. Non-zero indicates a flow-balance error (over-reported outflow / missing inflow). - The transition-only dryRunSafetyVol clamp is preserved so startup-from-empty doesn't snap to 2.1 m³ on tick 1. Spill flow refactor (taxonomic + bug fix): - Synthetic spill moved from flow.predicted.out.<child='overflow'> to its own position flow.predicted.overflow.<default>. The spill is a derived quantity, not a physical sub-source sharing a position with pumps — .child() was the wrong knob. - Removes the spillPrev self-subtraction in the integrator (no longer needed: outflowTotal at ['out','downstream'] cleanly excludes spill). - Closes a latent fall-through bug exposed during this work: .child('overflow').getCurrentValue() returned the value of any available sibling child when overflow itself didn't yet exist. Hardened separately in generalFunctions@a516c2b. - _selectBestNetFlow folds the overflow position into the outflow side so the predicted net-flow balance still reads ~0 while pinned. Tests: 70/70 pass. 4 new subtests cover the 0-floor, accumulated underflow tracking, getOutput surface, and refill-from-empty. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:18:23 +02:00
.type('underflowVolume').variant('predicted').position('atequipment').getCurrentValue('m3') ?? 0;
return out;
}
getStatusBadge() {
const STYLES = {
filling: { arrow: '⬆️', fill: 'blue' },
draining: { arrow: '⬇️', fill: 'orange' },
steady: { arrow: '⏸️', fill: 'green' },
};
const { arrow = '❔', fill = 'grey' } = STYLES[this.state?.direction] || {};
const pct = this.measurements.type('volumePercent').variant('predicted').position('atequipment').getCurrentValue() ?? 0;
const netFlowM3h = this.unitPolicy.convert(this.state?.netFlow ?? 0, 'm3/s', 'm3/h', 'status badge netFlow');
feat(pumpingStation): realistic defaults, ramp-foot visual fix, manual-mode visibility, dashboard example Editor + schema defaults - pumpingStation.html: drag-in defaults now reflect a realistic basin (volume=50 m³, height=4 m, inflowLevel=1.5, outflowLevel=0.2, overflowLevel=3.8, startLevel=1, stopLevel=0.5, minLevel=0.3, maxLevel=3.8). Old defaults left every level field null. Visual bug fix - src/editor/mode-preview.js: the level-based ramp curve in the editor was being drawn with foot=startLevel via buildPath(start, start, max). The runtime in control/levelBased.js has always used inflowLevel as the ramp foot. Pass buildPath(start, upFoot, max) where upFoot falls back to start when inflowLevel is missing, matching the runtime. Manual mode observability - src/specificClass.js: store last forwarded demand on this._manualDemand; surface as `mode` and `manualDemand` in getOutput(); call notifyOutputChanged() on forwardDemandToChildren and on changeMode so Port 0/1 emit even with no children registered. Status badge compacted to `mode | dir% | net m³/h` + `Qd=X m³/h` in manual mode. Examples cleanup - Drop stale 02-Integration.json, 03-Dashboard.json, basic-dashboard.flow.json, standalone-demo.js. - 01-Basic.json: numbered driver groups (1. Control mode … 4. Calibration), Debug-outputs group, fixed typos and HOW-TO-USE; Port 1 debug now active. - New 02-Dashboard.json: FlowFuse Dashboard 2.0 with Controls (7 buttons), Status (7 ui-text rows), Trends (4 ui-charts: level / volume / volume% / flow in-out-net), Raw output (ui-template dumping every Port 0 field). Fan-out function pattern-matches the 4-segment measurement keys by prefix instead of hardcoding childId, converts flow m³/s → m³/h, and caches last-known values so deltas never blank a row. - examples/README.md realigned to the two-file set. Wiki - Home.md: 5 image placeholders replaced with the provided screenshots (01-node-and-editor, 02-basic-flow, 03-wiring-standalone, 04-wiring-integrated) and the demo GIF (01-basic-demo). - Reference-Examples.md: shipped-files table reduced to 01-Basic + 02-Dashboard, Example-01 section uses the screenshot + GIF, Example-02 rewritten as Dashboard (kept screenshot/GIF callouts open for those captures), Example-03/Integration sections + their debug-recipes row removed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:52:00 +02:00
const mode = this.mode || '?';
const manualPart = this.mode === 'manual' && Number.isFinite(this._manualDemand)
? `Qd=${this._manualDemand.toFixed(0)} m³/h` : null;
return statusBadge.compose(
feat(pumpingStation): realistic defaults, ramp-foot visual fix, manual-mode visibility, dashboard example Editor + schema defaults - pumpingStation.html: drag-in defaults now reflect a realistic basin (volume=50 m³, height=4 m, inflowLevel=1.5, outflowLevel=0.2, overflowLevel=3.8, startLevel=1, stopLevel=0.5, minLevel=0.3, maxLevel=3.8). Old defaults left every level field null. Visual bug fix - src/editor/mode-preview.js: the level-based ramp curve in the editor was being drawn with foot=startLevel via buildPath(start, start, max). The runtime in control/levelBased.js has always used inflowLevel as the ramp foot. Pass buildPath(start, upFoot, max) where upFoot falls back to start when inflowLevel is missing, matching the runtime. Manual mode observability - src/specificClass.js: store last forwarded demand on this._manualDemand; surface as `mode` and `manualDemand` in getOutput(); call notifyOutputChanged() on forwardDemandToChildren and on changeMode so Port 0/1 emit even with no children registered. Status badge compacted to `mode | dir% | net m³/h` + `Qd=X m³/h` in manual mode. Examples cleanup - Drop stale 02-Integration.json, 03-Dashboard.json, basic-dashboard.flow.json, standalone-demo.js. - 01-Basic.json: numbered driver groups (1. Control mode … 4. Calibration), Debug-outputs group, fixed typos and HOW-TO-USE; Port 1 debug now active. - New 02-Dashboard.json: FlowFuse Dashboard 2.0 with Controls (7 buttons), Status (7 ui-text rows), Trends (4 ui-charts: level / volume / volume% / flow in-out-net), Raw output (ui-template dumping every Port 0 field). Fan-out function pattern-matches the 4-segment measurement keys by prefix instead of hardcoding childId, converts flow m³/s → m³/h, and caches last-known values so deltas never blank a row. - examples/README.md realigned to the two-file set. Wiki - Home.md: 5 image placeholders replaced with the provided screenshots (01-node-and-editor, 02-basic-flow, 03-wiring-standalone, 04-wiring-integrated) and the demo GIF (01-basic-demo). - Reference-Examples.md: shipped-files table reduced to 01-Basic + 02-Dashboard, Example-01 section uses the screenshot + GIF, Example-02 rewritten as Dashboard (kept screenshot/GIF callouts open for those captures), Example-03/Integration sections + their debug-recipes row removed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:52:00 +02:00
[mode, `${arrow} ${pct.toFixed(1)}%`, `net: ${netFlowM3h.toFixed(0)} m³/h`, manualPart],
{ fill, shape: 'dot' }
2025-11-30 09:24:18 +01:00
);
2025-10-23 09:51:54 +02:00
}
2025-10-21 13:44:31 +02:00
// ── Direction helper kept for tests pinning the dead-band semantics ──
_deriveDirection(netFlow) { return this.flowAggregator.deriveDirection(netFlow); }
2025-11-28 16:29:05 +01:00
// ── Volume/level conversions kept for tests + back-compat ──────────────
_calcVolumeFromLevel(level) { return this.basin.volumeFromLevel(level); }
_calcLevelFromVolume(volume) { return this.basin.levelFromVolume(volume); }
_subscribeMeasurement(child) {
const position = child.config.functionality.positionVsParent;
const measurementType = child.config.asset.type;
const eventName = `${measurementType}.measured.${position}`;
2025-10-27 16:39:06 +01:00
const handle = (eventData = {}) => {
this.logger.debug(
`Measurement update ${eventName} <- ${eventData.childName || child.config.general.name}: ${eventData.value} ${eventData.unit}`
);
if (measurementType === 'level') {
this.measurementRouter.route(measurementType, eventData.value, position, eventData);
return;
}
this.measurements.type(measurementType).variant('measured').position(position)
.value(eventData.value, eventData.timestamp, eventData.unit);
this.measurementRouter.route(measurementType, eventData.value, position, eventData);
};
child.measurements.emitter.on(eventName, handle);
// Seed from the child's current value. The emitter only delivers FUTURE
// updates, so a parent that registers after the child already emitted
// (e.g. a once-only inject that fired during startup before this
// subscription existed) would otherwise never see that value. Replaying
// the last sample makes a late subscriber pick up the present state.
const series = child.measurements
.type(measurementType).variant('measured').position(position).get?.();
const sample = series?.getLaggedSample?.(0);
if (sample && sample.value != null) {
handle({ ...sample, childName: child.config.general.name });
}
2025-11-30 09:24:18 +01:00
}
2025-10-27 16:39:06 +01:00
_subscribePredictedFlow(child) {
// Map the child's position to the orchestrator's posKey + the most
// specific aggregator event. 'downstream' is preferred over 'atequipment'
// because they carry the same total — subscribing to both double-counts.
const POS_MAP = {
downstream: ['out', 'flow.predicted.downstream'],
out: ['out', 'flow.predicted.downstream'],
atequipment:['out', 'flow.predicted.downstream'],
upstream: ['in', 'flow.predicted.upstream'],
in: ['in', 'flow.predicted.upstream'],
};
const position = (child.config.functionality.positionVsParent || '').toLowerCase();
const mapped = POS_MAP[position];
if (!mapped) {
this.logger.warn(`Unsupported predicted flow position "${position}" from ${child.config.general.name}`);
2025-11-30 09:24:18 +01:00
return;
}
const [posKey, eventName] = mapped;
const childId = child.config.general.id ?? child.config.general.name;
2025-10-27 16:39:06 +01:00
child.measurements.emitter.on(eventName, (eventData = {}) => {
const unit = eventData.unit || child.config?.general?.unit;
const ts = eventData.timestamp || Date.now();
this.measurements.type('flow').variant('predicted').position(posKey).child(childId)
.value(eventData.value, ts, unit);
});
2025-10-28 17:04:26 +01:00
}
2025-10-07 18:05:54 +02:00
}
module.exports = PumpingStation;