// PumpingStation editor — dynamic input bounds. // Sets HTML5 min/max attributes on every level and percent input based on // the current values of related inputs, so the up/down arrows stop at // values that respect the basin hierarchy: // // 0 < outflowLevel < dryRunLevel < startLevel < maxLevel ≤ overflowLevel ≤ basinHeight // 0 < outflowLevel < inflowLevel < overflowLevel ≤ basinHeight // // startLevel is intentionally NOT clamped against inflowLevel: pushing // startLevel above the gravity-feed inlet is the "buffer in the sewer" // configuration where upstream pipe storage absorbs flow before pumping // engages. The level-based ramp foot is max(startLevel, inflowLevel) so // either ordering is valid. // // The user can still type out-of-range values via the keyboard (HTML5 // min/max only constrain the spinner). The validation ribbons in // basin-diagram.js and mode-preview.js catch typed violations and the // oneditsave handler blocks Deploy until they're resolved. (function () { const ns = window.PSEditor = window.PSEditor || {}; const fNum = (id) => ns.fNum(id); const EPS = 0.001; // smallest meaningful step (mm-precision) const setBounds = (id, min, max) => { const el = document.getElementById(`node-input-${id}`); if (!el) return; if (Number.isFinite(min)) el.setAttribute('min', String(min)); else el.removeAttribute('min'); if (Number.isFinite(max)) el.setAttribute('max', String(max)); else el.removeAttribute('max'); }; ns.bounds = { apply() { const basinHeight = fNum('basinHeight'); const outflow = fNum('outflowLevel'); const dryPct = fNum('dryRunThresholdPercent'); const start = fNum('startLevel'); const inlet = fNum('inflowLevel'); const max = fNum('maxLevel'); const overflow = fNum('overflowLevel'); const shiftEnabled = !!document.getElementById('node-input-enableShiftedRamp')?.checked; // Derived dryRunLevel (lower bound for startLevel). const dryRun = (Number.isFinite(outflow) && Number.isFinite(dryPct)) ? outflow * (1 + dryPct / 100) : null; // Geometry — basin envelope. setBounds('basinHeight', EPS, undefined); setBounds('basinVolume', EPS, undefined); // Levels (each capped by the next-higher level if defined). setBounds('outflowLevel', EPS, Number.isFinite(start) && Number.isFinite(dryPct) ? start / (1 + dryPct / 100) - EPS // keep dryRun < start : (start ?? inlet ?? max ?? overflow ?? basinHeight)); setBounds('startLevel', Number.isFinite(dryRun) ? dryRun + EPS : EPS, max ?? overflow ?? basinHeight); setBounds('inflowLevel', EPS, max ?? overflow ?? basinHeight); setBounds('maxLevel', inlet ?? start ?? EPS, overflow ?? basinHeight); setBounds('overflowLevel', max ?? inlet ?? start ?? EPS, basinHeight); // stopLevel — explicit pump-off threshold. Must sit between // dryRunLevel and startLevel (so it can be reached during draining // before pumps re-engage). setBounds('stopLevel', Number.isFinite(dryRun) ? dryRun + EPS : EPS, start ?? inlet ?? max ?? overflow ?? basinHeight); // holdLevel — 0 % ramp foot. Defaults to startLevel (no hold band); // when raised above startLevel, pumps engage at startLevel but emit // 0 % across [startLevel, holdLevel] before the ramp begins. Bounds: // startLevel ≤ holdLevel < maxLevel. setBounds('holdLevel', Number.isFinite(start) ? start : EPS, max ?? overflow ?? basinHeight); // Shift inputs (only relevant when shifted ramp enabled). if (shiftEnabled) { setBounds('shiftLevel', Number.isFinite(start) ? start : EPS, max ?? overflow ?? basinHeight); setBounds('shiftArmPercent', 1, 100); } // Percentages. // dryRun% capped so dryRunLevel ≤ startLevel. let dryMax = 99; if (Number.isFinite(start) && Number.isFinite(outflow) && outflow > 0) { dryMax = Math.max(0, Math.min(99, ((start / outflow) - 1) * 100)); } setBounds('dryRunThresholdPercent', 0, dryMax); // highVol% bounded (1, 100). Equal to 100 means no margin to overflow. setBounds('highVolumeSafetyThresholdPercent', 1, 100); }, }; })();