107 lines
3.6 KiB
JavaScript
107 lines
3.6 KiB
JavaScript
|
|
// Basic tests for the calibration helpers.
|
||
|
|
|
||
|
|
const test = require('node:test');
|
||
|
|
const assert = require('node:assert/strict');
|
||
|
|
|
||
|
|
const { MeasurementContainer } = require('generalFunctions');
|
||
|
|
const {
|
||
|
|
calibratePredictedVolume,
|
||
|
|
calibratePredictedLevel,
|
||
|
|
setManualInflow,
|
||
|
|
} = require('../../src/measurement/calibration');
|
||
|
|
|
||
|
|
function makeBasin() {
|
||
|
|
return {
|
||
|
|
surfaceArea: 10,
|
||
|
|
minVol: 2,
|
||
|
|
maxVol: 50,
|
||
|
|
maxVolAtOverflow: 45,
|
||
|
|
overflowLevel: 4.5,
|
||
|
|
outflowLevel: 0.2,
|
||
|
|
inflowLevel: 3,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function makeCtx(seedVolume = null) {
|
||
|
|
const measurements = new MeasurementContainer({
|
||
|
|
autoConvert: true,
|
||
|
|
preferredUnits: { flow: 'm3/s', level: 'm', volume: 'm3' },
|
||
|
|
});
|
||
|
|
const basin = makeBasin();
|
||
|
|
if (seedVolume != null) {
|
||
|
|
measurements.type('volume').variant('predicted').position('atequipment')
|
||
|
|
.value(seedVolume, Date.now() - 5_000, 'm3').unit('m3');
|
||
|
|
}
|
||
|
|
const ctx = { measurements, basin };
|
||
|
|
return ctx;
|
||
|
|
}
|
||
|
|
|
||
|
|
test('calibratePredictedVolume clears prior series and writes new value', async () => {
|
||
|
|
const ctx = makeCtx(12);
|
||
|
|
const before = ctx.measurements.type('volume').variant('predicted').position('atequipment')
|
||
|
|
.getCurrentValue('m3');
|
||
|
|
assert.ok(Math.abs(before - 12) < 1e-9);
|
||
|
|
|
||
|
|
const ts = Date.now();
|
||
|
|
calibratePredictedVolume(ctx, 30, ts);
|
||
|
|
|
||
|
|
const m = ctx.measurements.type('volume').variant('predicted').position('atequipment').get();
|
||
|
|
assert.equal(m.values.length, 1, 'series should hold exactly the calibration point');
|
||
|
|
assert.ok(Math.abs(m.getCurrentValue() - 30) < 1e-9);
|
||
|
|
|
||
|
|
// Level was derived: 30 / 10 = 3 m.
|
||
|
|
const lvl = ctx.measurements.type('level').variant('predicted').position('atequipment')
|
||
|
|
.getCurrentValue('m');
|
||
|
|
assert.ok(Math.abs(lvl - 3) < 1e-9, `derived level was ${lvl}`);
|
||
|
|
|
||
|
|
assert.equal(ctx._predictedFlowState.lastTimestamp, ts);
|
||
|
|
assert.equal(ctx._predictedFlowState.inflow, 0);
|
||
|
|
assert.equal(ctx._predictedFlowState.outflow, 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('calibratePredictedLevel writes both level and derived volume', async () => {
|
||
|
|
const ctx = makeCtx(2);
|
||
|
|
calibratePredictedLevel(ctx, 4.0, Date.now(), 'm');
|
||
|
|
|
||
|
|
const lvl = ctx.measurements.type('level').variant('predicted').position('atequipment')
|
||
|
|
.getCurrentValue('m');
|
||
|
|
assert.ok(Math.abs(lvl - 4.0) < 1e-9);
|
||
|
|
|
||
|
|
const vol = ctx.measurements.type('volume').variant('predicted').position('atequipment')
|
||
|
|
.getCurrentValue('m3');
|
||
|
|
assert.ok(Math.abs(vol - 40) < 1e-9, `derived volume was ${vol}`);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('setManualInflow writes to flow.predicted.in.manual-qin', async () => {
|
||
|
|
const ctx = makeCtx();
|
||
|
|
const ts = Date.now();
|
||
|
|
setManualInflow(ctx, 0.025, ts, 'm3/s');
|
||
|
|
|
||
|
|
const series = ctx.measurements.type('flow').variant('predicted').position('in').child('manual-qin');
|
||
|
|
const val = series.getCurrentValue('m3/s');
|
||
|
|
assert.ok(Math.abs(val - 0.025) < 1e-9, `manual-qin value was ${val}`);
|
||
|
|
|
||
|
|
// It must NOT collide with the default child bucket.
|
||
|
|
const defaultBucket = ctx.measurements.measurements?.flow?.predicted?.in?.default;
|
||
|
|
assert.equal(defaultBucket, undefined);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('calibration uses ctx.flowAggregator.resetState when present', async () => {
|
||
|
|
const ctx = makeCtx(5);
|
||
|
|
let resetCalled = null;
|
||
|
|
ctx.flowAggregator = { resetState: (ts) => { resetCalled = ts; } };
|
||
|
|
|
||
|
|
const ts = 1234567890;
|
||
|
|
calibratePredictedVolume(ctx, 20, ts);
|
||
|
|
|
||
|
|
assert.equal(resetCalled, ts);
|
||
|
|
// The plain bag should NOT be touched when the aggregator hook is present.
|
||
|
|
assert.equal(ctx._predictedFlowState, undefined);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('calibratePredictedVolume rejects bad context', async () => {
|
||
|
|
assert.throws(() => calibratePredictedVolume({}, 10));
|
||
|
|
assert.throws(() => calibratePredictedLevel({}, 1.0));
|
||
|
|
assert.throws(() => setManualInflow({}, 0.01));
|
||
|
|
});
|