src/curves/ loader + normalizer (with cross-pressure anomaly
detection) + reverseCurve helper
src/prediction/ predictors (predictFlow/Power/Ctrl) +
groupPredictors (lazy group-scope views) +
OperatingPoint (pressure-driven prediction setpoints)
src/drift/ DriftAssessor (per-metric drift) + PredictionHealth
(composes flow/power/pressure into HealthStatus +
confidence sibling — see OPEN_QUESTIONS 2026-05-10)
src/pressure/ VirtualPressureChildren (dashboard-sim) +
PressureInitialization (real-vs-virtual tracking) +
PressureRouter (dispatches by position)
src/state/ stateBindings (state.emitter listener helper) +
isOperationalState
src/measurement/ measurementHandlers (dispatcher for flow/power/temp/pressure)
src/flow/ flowController (handleInput body — execSequence,
execMovement, flowMovement, emergencystop)
src/display/ workingCurves (showWorkingCurves + showCoG admin)
src/commands/ canonical names: set.mode, cmd.startup/shutdown/estop,
set.setpoint, set.flow-setpoint,
data.simulate-measurement, query.curves, query.cog,
child.register. execSequence demuxes by payload.action
to canonical cmd.* handlers.
CONTRACT.md inputs/outputs/events/children surface
110 basic tests pass (100 new + 10 pre-existing).
specificClass.js / nodeClass.js untouched — integration in P5 wave 2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
94 lines
3.2 KiB
JavaScript
94 lines
3.2 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
|
|
const PredictionHealth = require('../../src/drift/predictionHealth');
|
|
const DriftAssessor = require('../../src/drift/driftAssessor');
|
|
|
|
function makeHealth(overrides = {}) {
|
|
return new PredictionHealth({
|
|
getPressureInitializationStatus: () => ({
|
|
initialized: true, hasDifferential: true, source: 'differential',
|
|
}),
|
|
isOperational: () => true,
|
|
applyDriftPenalty: new DriftAssessor({}).applyDriftPenalty.bind(new DriftAssessor({})),
|
|
...overrides,
|
|
});
|
|
}
|
|
|
|
test('empty snapshots + differential pressure → nominal health, confidence=0.9', () => {
|
|
const ph = makeHealth();
|
|
const { health, confidence } = ph.evaluate({
|
|
flow: null,
|
|
power: null,
|
|
pressure: { level: 0, flags: [], source: 'differential' },
|
|
});
|
|
assert.equal(health.level, 0);
|
|
assert.ok(Math.abs(confidence - 0.9) < 1e-9);
|
|
assert.equal(typeof health.message, 'string');
|
|
});
|
|
|
|
test('pressure not initialized + flow drift level 2 → composite level >= 2 and multiple flags', () => {
|
|
const ph = makeHealth({
|
|
getPressureInitializationStatus: () => ({
|
|
initialized: false, hasDifferential: false, source: null,
|
|
}),
|
|
});
|
|
const { health, confidence } = ph.evaluate({
|
|
flow: { valid: true, nrmse: 0.3, immediateLevel: 2, longTermLevel: 0 },
|
|
power: null,
|
|
pressure: { level: 2, flags: ['no_pressure_input'], source: null },
|
|
});
|
|
assert.ok(health.level >= 2);
|
|
assert.ok(health.flags.includes('no_pressure_input'));
|
|
assert.ok(health.flags.includes('flow_medium_immediate_drift'));
|
|
assert.ok(confidence < 0.5);
|
|
});
|
|
|
|
test('returned object has both health and confidence', () => {
|
|
const ph = makeHealth();
|
|
const out = ph.evaluate({ flow: null, power: null, pressure: { level: 0, flags: [], source: 'differential' } });
|
|
assert.ok('health' in out);
|
|
assert.ok('confidence' in out);
|
|
assert.equal(typeof out.confidence, 'number');
|
|
assert.equal(typeof out.health.level, 'number');
|
|
});
|
|
|
|
test('non-operational forces confidence=0 and bumps level >=2', () => {
|
|
const ph = makeHealth({ isOperational: () => false });
|
|
const { health, confidence } = ph.evaluate({
|
|
flow: null, power: null,
|
|
pressure: { level: 0, flags: [], source: 'differential' },
|
|
});
|
|
assert.equal(confidence, 0);
|
|
assert.ok(health.flags.includes('not_operational'));
|
|
assert.ok(health.level >= 2);
|
|
});
|
|
|
|
test('curve-edge penalty applies when current position is near min/max', () => {
|
|
const ph = makeHealth({
|
|
getCurrentPosition: () => 0.01,
|
|
resolveSetpointBounds: () => ({ min: 0, max: 1 }),
|
|
});
|
|
const { health, confidence } = ph.evaluate({
|
|
flow: null, power: null,
|
|
pressure: { level: 0, flags: [], source: 'differential' },
|
|
});
|
|
assert.ok(health.flags.includes('near_curve_edge'));
|
|
assert.ok(confidence < 0.9);
|
|
});
|
|
|
|
test('HealthStatus shape — has the standardised five fields', () => {
|
|
const ph = makeHealth();
|
|
const { health } = ph.evaluate({
|
|
flow: null, power: null,
|
|
pressure: { level: 0, flags: [], source: 'differential' },
|
|
});
|
|
assert.ok('level' in health);
|
|
assert.ok('flags' in health);
|
|
assert.ok('message' in health);
|
|
assert.ok('source' in health);
|
|
assert.ok(Array.isArray(health.flags));
|
|
});
|