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>
This commit is contained in:
Rene De Ren
2026-05-05 19:29:34 +02:00
parent da50403c76
commit 8a6ca1baeb
12 changed files with 1877 additions and 487 deletions

View File

@@ -47,26 +47,44 @@ class nodeClass {
inflowLevel: uiConfig.inflowLevel,
outflowLevel: uiConfig.outflowLevel,
overflowLevel: uiConfig.overflowLevel,
inletPipeDiameter: uiConfig.inletPipeDiameter,
outletPipeDiameter: uiConfig.outletPipeDiameter,
},
hydraulics: {
refHeight: uiConfig.refHeight,
minHeightBasedOn: uiConfig.minHeightBasedOn,
basinBottomRef: uiConfig.basinBottomRef,
maxInflowRate: uiConfig.maxInflowRate,
staticHead: uiConfig.staticHead,
maxDischargeHead: uiConfig.maxDischargeHead,
pipelineLength: uiConfig.pipelineLength,
defaultFluid: uiConfig.defaultFluid,
temperatureReferenceDegC: uiConfig.temperatureReferenceDegC,
},
control:{
mode: uiConfig.controlMode,
levelbased:{
minLevel:uiConfig.minLevel,
startLevel:uiConfig.startLevel,
maxLevel:uiConfig.maxLevel
maxLevel:uiConfig.maxLevel,
curveType: uiConfig.levelCurveType || uiConfig.curveType,
logCurveFactor: uiConfig.logCurveFactor,
enableShiftedRamp: uiConfig.enableShiftedRamp,
shiftLevel: uiConfig.shiftLevel
}
},
safety:{
enableDryRunProtection: uiConfig.enableDryRunProtection,
dryRunThresholdPercent: uiConfig.dryRunThresholdPercent,
enableHighVolumeSafety: uiConfig.enableHighVolumeSafety ?? uiConfig.enableOverfillProtection,
highVolumeSafetyThresholdPercent: uiConfig.highVolumeSafetyThresholdPercent ?? uiConfig.overfillThresholdPercent,
enableOverfillProtection: uiConfig.enableOverfillProtection,
overfillThresholdPercent: uiConfig.overfillThresholdPercent,
timeleftToFullOrEmptyThresholdSeconds: uiConfig.timeleftToFullOrEmptyThresholdSeconds
},
output: {
process: uiConfig.processOutputFormat,
dbase: uiConfig.dbaseOutputFormat
}
});
@@ -220,6 +238,13 @@ class nodeClass {
this.source.setManualInflow(val, ts, unit);
break;
}
case 'q_out': {
const val = Number(msg.payload);
const unit = msg?.unit;
const ts = msg?.timestamp || Date.now();
this.source.setManualOutflow(val, ts, unit);
break;
}
case 'Qd': {
// Manual demand: operator sets the target output via a
// dashboard slider. Only accepted when PS is in 'manual'