P2 wave 1: extract concerns from pumpingStation specificClass
Splits pumpingStation/src/ into focused concern modules. specificClass.js
will be slimmed to an orchestrator in P2.9 (integration); for now both
the inlined logic AND the new modules coexist so tests stay green
throughout.
src/basin/ BasinGeometry + thresholdValidator (pure)
src/measurement/ flowAggregator + measurementRouter + calibration
src/control/ levelBased + flowBased(stub) + manual + index dispatcher
src/safety/ safetyController split into dryRun + overfill rules
src/commands/ registry array + handlers (canonical names from start)
src/editor.js 260 lines of SVG basin-diagram redraw, was inline in .html
examples/standalone-demo.js was if(require.main===module) at bottom of specificClass.js
CONTRACT.md canonical inputs + outputs + emitted events
Modified:
src/specificClass.js removed the 170-line standalone demo block
pumpingStation.html oneditprepare/oneditsave delegate to editor.{init,save}
pumpingStation.js added admin endpoint serving src/editor.js
102 basic tests pass (60 new + 42 existing).
specificClass.js itself is unchanged in behaviour — integration is P2.9.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
80
src/measurement/calibration.js
Normal file
80
src/measurement/calibration.js
Normal file
@@ -0,0 +1,80 @@
|
||||
// Calibration helpers for the pumping-station predicted volume / level
|
||||
// streams. Pure functions over a context bag holding the live
|
||||
// MeasurementContainer + basin geometry. After every calibration the
|
||||
// integrator state is reset so the next tick starts from the new anchor.
|
||||
|
||||
function _resetFlowState(ctx, timestamp) {
|
||||
if (ctx.flowAggregator?.resetState) {
|
||||
ctx.flowAggregator.resetState(timestamp);
|
||||
return;
|
||||
}
|
||||
ctx._predictedFlowState = { inflow: 0, outflow: 0, lastTimestamp: timestamp };
|
||||
}
|
||||
|
||||
function _clearSeries(measurements, type) {
|
||||
const series = measurements.type(type).variant('predicted').position('atequipment');
|
||||
if (series.exists()) {
|
||||
const m = series.get();
|
||||
if (m) {
|
||||
m.values = [];
|
||||
m.timestamps = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _levelFromVolume(basin, volume) {
|
||||
const area = basin.surfaceArea;
|
||||
return area > 0 ? Math.max(volume, 0) / area : 0;
|
||||
}
|
||||
|
||||
function _volumeFromLevel(basin, level) {
|
||||
const area = basin.surfaceArea;
|
||||
return area > 0 ? Math.max(level, 0) * area : 0;
|
||||
}
|
||||
|
||||
function calibratePredictedVolume(ctx, calibratedVol, timestamp = Date.now()) {
|
||||
if (!ctx?.measurements || !ctx.basin) {
|
||||
throw new Error('calibratePredictedVolume: ctx.measurements and ctx.basin required');
|
||||
}
|
||||
const { measurements, basin } = ctx;
|
||||
|
||||
_clearSeries(measurements, 'volume');
|
||||
_clearSeries(measurements, 'level');
|
||||
|
||||
measurements.type('volume').variant('predicted').position('atequipment')
|
||||
.value(calibratedVol, timestamp, 'm3').unit('m3');
|
||||
measurements.type('level').variant('predicted').position('atequipment')
|
||||
.value(_levelFromVolume(basin, calibratedVol), timestamp, 'm');
|
||||
|
||||
_resetFlowState(ctx, timestamp);
|
||||
}
|
||||
|
||||
function calibratePredictedLevel(ctx, level, timestamp = Date.now(), unit = 'm') {
|
||||
if (!ctx?.measurements || !ctx.basin) {
|
||||
throw new Error('calibratePredictedLevel: ctx.measurements and ctx.basin required');
|
||||
}
|
||||
const { measurements, basin } = ctx;
|
||||
|
||||
_clearSeries(measurements, 'volume');
|
||||
_clearSeries(measurements, 'level');
|
||||
|
||||
measurements.type('level').variant('predicted').position('atequipment')
|
||||
.value(level, timestamp, unit);
|
||||
measurements.type('volume').variant('predicted').position('atequipment')
|
||||
.value(_volumeFromLevel(basin, level), timestamp, 'm3');
|
||||
|
||||
_resetFlowState(ctx, timestamp);
|
||||
}
|
||||
|
||||
function setManualInflow(ctx, value, timestamp = Date.now(), unit = 'm3/s') {
|
||||
if (!ctx?.measurements) throw new Error('setManualInflow: ctx.measurements required');
|
||||
const num = Number(value);
|
||||
ctx.measurements.type('flow').variant('predicted').position('in').child('manual-qin')
|
||||
.value(num, timestamp, unit);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
calibratePredictedVolume,
|
||||
calibratePredictedLevel,
|
||||
setManualInflow,
|
||||
};
|
||||
Reference in New Issue
Block a user