updates
This commit is contained in:
@@ -19,6 +19,21 @@ test('calcEfficiency runs through coolprop path without mocks', () => {
|
||||
const eff = machine.measurements.type('efficiency').variant('predicted').position('atEquipment').getCurrentValue();
|
||||
assert.equal(typeof eff, 'number');
|
||||
assert.ok(eff > 0);
|
||||
|
||||
const pressureDiffPa = (1200 - 800) * 100; // mbar -> Pa
|
||||
const flowM3s = 120 / 3600; // m3/h -> m3/s
|
||||
const expectedHydraulicPower = pressureDiffPa * flowM3s;
|
||||
const expectedHydraulicEfficiency = expectedHydraulicPower / 12000; // 12kW -> W
|
||||
|
||||
const hydraulicPower = machine.measurements.type('hydraulicPower').variant('predicted').position('atEquipment').getCurrentValue('W');
|
||||
const hydraulicEfficiency = machine.measurements.type('nHydraulicEfficiency').variant('predicted').position('atEquipment').getCurrentValue();
|
||||
const head = machine.measurements.type('pumpHead').variant('predicted').position('atEquipment').getCurrentValue('m');
|
||||
|
||||
assert.ok(Number.isFinite(hydraulicPower));
|
||||
assert.ok(Number.isFinite(hydraulicEfficiency));
|
||||
assert.ok(Number.isFinite(head));
|
||||
assert.ok(Math.abs(hydraulicPower - expectedHydraulicPower) < 1);
|
||||
assert.ok(Math.abs(hydraulicEfficiency - expectedHydraulicEfficiency) < 0.01);
|
||||
});
|
||||
|
||||
test('predictions use initialized medium pressure and not the minimum-pressure fallback', () => {
|
||||
@@ -33,7 +48,7 @@ test('predictions use initialized medium pressure and not the minimum-pressure f
|
||||
assert.equal(pressureStatus.initialized, true);
|
||||
assert.equal(pressureStatus.hasDifferential, true);
|
||||
|
||||
const expectedDiff = mediumDownstreamMbar - mediumUpstreamMbar;
|
||||
const expectedDiff = (mediumDownstreamMbar - mediumUpstreamMbar) * 100; // mbar -> Pa canonical
|
||||
assert.equal(Math.round(machine.predictFlow.fDimension), expectedDiff);
|
||||
assert.ok(machine.predictFlow.fDimension > 0);
|
||||
});
|
||||
|
||||
75
test/integration/prediction-health.integration.test.js
Normal file
75
test/integration/prediction-health.integration.test.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const Machine = require('../../src/specificClass');
|
||||
const { makeMachineConfig, makeStateConfig } = require('../helpers/factories');
|
||||
|
||||
test('flow drift is assessed with NRMSE and exposed in output', () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
|
||||
|
||||
machine.updateMeasuredPressure(700, 'upstream', { timestamp: Date.now(), unit: 'mbar', childName: 'pt-up' });
|
||||
machine.updateMeasuredPressure(1100, 'downstream', { timestamp: Date.now(), unit: 'mbar', childName: 'pt-down' });
|
||||
machine.updatePosition();
|
||||
|
||||
const predictedFlow = machine.measurements
|
||||
.type('flow')
|
||||
.variant('predicted')
|
||||
.position('downstream')
|
||||
.getCurrentValue('m3/h');
|
||||
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
machine.updateMeasuredFlow(predictedFlow * 0.92, 'downstream', {
|
||||
timestamp: Date.now() + i,
|
||||
unit: 'm3/h',
|
||||
childName: 'ft-down',
|
||||
});
|
||||
}
|
||||
|
||||
const output = machine.getOutput();
|
||||
assert.ok(Number.isFinite(output.flowNrmse));
|
||||
assert.equal(typeof output.flowImmediateLevel, 'number');
|
||||
assert.equal(typeof output.flowLongTermLevel, 'number');
|
||||
assert.ok(['high', 'medium', 'low', 'invalid'].includes(output.predictionQuality));
|
||||
assert.ok(Number.isFinite(output.predictionConfidence));
|
||||
assert.equal(output.predictionPressureSource, 'differential');
|
||||
assert.ok(Array.isArray(output.predictionFlags));
|
||||
});
|
||||
|
||||
test('power drift is assessed when measured power is provided', () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
|
||||
|
||||
machine.updateMeasuredPressure(700, 'upstream', { timestamp: Date.now(), unit: 'mbar', childName: 'pt-up' });
|
||||
machine.updateMeasuredPressure(1100, 'downstream', { timestamp: Date.now(), unit: 'mbar', childName: 'pt-down' });
|
||||
machine.updatePosition();
|
||||
|
||||
const predictedPower = machine.measurements
|
||||
.type('power')
|
||||
.variant('predicted')
|
||||
.position('atEquipment')
|
||||
.getCurrentValue('kW');
|
||||
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
machine.updateMeasuredPower(predictedPower * 1.08, 'atEquipment', {
|
||||
timestamp: Date.now() + i,
|
||||
unit: 'kW',
|
||||
childName: 'power-meter',
|
||||
});
|
||||
}
|
||||
|
||||
const output = machine.getOutput();
|
||||
assert.ok(Number.isFinite(output.powerNrmse));
|
||||
assert.equal(typeof output.powerImmediateLevel, 'number');
|
||||
assert.equal(typeof output.powerLongTermLevel, 'number');
|
||||
});
|
||||
|
||||
test('single-side pressure lowers prediction confidence category', () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
|
||||
machine.updateMeasuredPressure(950, 'downstream', { timestamp: Date.now(), unit: 'mbar', childName: 'pt-down' });
|
||||
|
||||
const output = machine.getOutput();
|
||||
assert.equal(output.predictionPressureSource, 'downstream');
|
||||
assert.ok(output.predictionConfidence < 0.9);
|
||||
assert.equal(output.pressureDriftLevel, 1);
|
||||
assert.ok(Array.isArray(output.predictionFlags));
|
||||
assert.ok(output.predictionFlags.includes('single_side_pressure'));
|
||||
});
|
||||
@@ -27,8 +27,8 @@ test('pressure initialization combinations are handled explicitly', () => {
|
||||
assert.equal(status.hasDifferential, false);
|
||||
assert.equal(status.source, 'upstream');
|
||||
const upstreamValue = machine.getMeasuredPressure();
|
||||
assert.equal(Math.round(upstreamValue), upstreamOnly);
|
||||
assert.equal(Math.round(machine.predictFlow.fDimension), upstreamOnly);
|
||||
assert.equal(Math.round(upstreamValue), upstreamOnly * 100);
|
||||
assert.equal(Math.round(machine.predictFlow.fDimension), upstreamOnly * 100);
|
||||
|
||||
// downstream only
|
||||
machine = createMachine();
|
||||
@@ -41,8 +41,8 @@ test('pressure initialization combinations are handled explicitly', () => {
|
||||
assert.equal(status.hasDifferential, false);
|
||||
assert.equal(status.source, 'downstream');
|
||||
const downstreamValue = machine.getMeasuredPressure();
|
||||
assert.equal(Math.round(downstreamValue), downstreamOnly);
|
||||
assert.equal(Math.round(machine.predictFlow.fDimension), downstreamOnly);
|
||||
assert.equal(Math.round(downstreamValue), downstreamOnly * 100);
|
||||
assert.equal(Math.round(machine.predictFlow.fDimension), downstreamOnly * 100);
|
||||
|
||||
// downstream and upstream
|
||||
machine = createMachine();
|
||||
@@ -57,8 +57,8 @@ test('pressure initialization combinations are handled explicitly', () => {
|
||||
assert.equal(status.hasDifferential, true);
|
||||
assert.equal(status.source, 'differential');
|
||||
const differentialValue = machine.getMeasuredPressure();
|
||||
assert.equal(Math.round(differentialValue), downstream - upstream);
|
||||
assert.equal(Math.round(machine.predictFlow.fDimension), downstream - upstream);
|
||||
assert.equal(Math.round(differentialValue), (downstream - upstream) * 100);
|
||||
assert.equal(Math.round(machine.predictFlow.fDimension), (downstream - upstream) * 100);
|
||||
});
|
||||
|
||||
test('real pressure child data has priority over simulated dashboard pressure', async () => {
|
||||
@@ -66,7 +66,7 @@ test('real pressure child data has priority over simulated dashboard pressure',
|
||||
|
||||
machine.updateSimulatedMeasurement('pressure', 'upstream', 900, { unit: 'mbar', timestamp: Date.now() });
|
||||
machine.updateSimulatedMeasurement('pressure', 'downstream', 1200, { unit: 'mbar', timestamp: Date.now() });
|
||||
assert.equal(Math.round(machine.getMeasuredPressure()), 300);
|
||||
assert.equal(Math.round(machine.getMeasuredPressure()), 30000);
|
||||
|
||||
const upstreamChild = makeChildMeasurement({ id: 'pt-up-real', name: 'PT Up', positionVsParent: 'upstream', type: 'pressure', unit: 'mbar' });
|
||||
const downstreamChild = makeChildMeasurement({ id: 'pt-down-real', name: 'PT Down', positionVsParent: 'downstream', type: 'pressure', unit: 'mbar' });
|
||||
@@ -77,7 +77,7 @@ test('real pressure child data has priority over simulated dashboard pressure',
|
||||
upstreamChild.measurements.type('pressure').variant('measured').position('upstream').value(700, Date.now(), 'mbar');
|
||||
downstreamChild.measurements.type('pressure').variant('measured').position('downstream').value(1300, Date.now(), 'mbar');
|
||||
|
||||
assert.equal(Math.round(machine.getMeasuredPressure()), 600);
|
||||
assert.equal(Math.round(machine.getMeasuredPressure()), 60000);
|
||||
const status = machine.getPressureInitializationStatus();
|
||||
assert.equal(status.source, 'differential');
|
||||
assert.equal(status.initialized, true);
|
||||
|
||||
@@ -25,3 +25,29 @@ test('registerChild listens to measurement events and stores measured pressure',
|
||||
assert.equal(typeof stored, 'number');
|
||||
assert.equal(Math.round(stored), 123);
|
||||
});
|
||||
|
||||
test('registerChild deduplicates listeners on re-registration', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig());
|
||||
const child = makeChildMeasurement({ id: 'pt-dup', positionVsParent: 'downstream', type: 'pressure', unit: 'mbar' });
|
||||
const eventName = 'pressure.measured.downstream';
|
||||
|
||||
let handlerCalls = 0;
|
||||
const originalUpdatePressure = machine.updateMeasuredPressure.bind(machine);
|
||||
machine.updateMeasuredPressure = (...args) => {
|
||||
handlerCalls += 1;
|
||||
return originalUpdatePressure(...args);
|
||||
};
|
||||
|
||||
machine.registerChild(child, 'measurement');
|
||||
machine.registerChild(child, 'measurement');
|
||||
|
||||
assert.equal(child.measurements.emitter.listenerCount(eventName), 1);
|
||||
|
||||
child.measurements
|
||||
.type('pressure')
|
||||
.variant('measured')
|
||||
.position('downstream')
|
||||
.value(321, Date.now(), 'mbar');
|
||||
|
||||
assert.equal(handlerCalls, 1);
|
||||
});
|
||||
|
||||
@@ -12,11 +12,13 @@ test('execSequence startup reaches operational with zero transition times', asyn
|
||||
assert.equal(machine.state.getCurrentState(), 'operational');
|
||||
});
|
||||
|
||||
test('execMovement updates controller position in operational state', async () => {
|
||||
test('execMovement constrains controller position to safe bounds in operational state', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
|
||||
const { max } = machine._resolveSetpointBounds();
|
||||
|
||||
await machine.handleInput('parent', 'execMovement', 10);
|
||||
|
||||
const pos = machine.state.getCurrentPosition();
|
||||
assert.ok(pos >= 9.9 && pos <= 10);
|
||||
assert.ok(pos <= max);
|
||||
assert.equal(pos, max);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user