// 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)); });