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>
59 lines
1.8 KiB
JavaScript
59 lines
1.8 KiB
JavaScript
/**
|
|
* Thin adapter over the generalFunctions state machine emitter.
|
|
* Holds no state of its own — exposes bind/unbind and the
|
|
* shared definition of which states count as "operational" for
|
|
* downstream measurement processing.
|
|
*/
|
|
|
|
const OPERATIONAL_STATES = [
|
|
'operational',
|
|
'accelerating',
|
|
'decelerating',
|
|
'warmingup',
|
|
];
|
|
|
|
/**
|
|
* Attaches positionChange / stateChange listeners to a state machine.
|
|
* Returns an idempotent teardown function. Both handlers are required —
|
|
* the bindings encode the lifecycle contract between the FSM and the
|
|
* specificClass orchestrator, so leaving one half wired is a bug.
|
|
*/
|
|
function bindStateEvents(ctx) {
|
|
if (!ctx || !ctx.state || !ctx.state.emitter) {
|
|
throw new Error('bindStateEvents: ctx.state.emitter is required');
|
|
}
|
|
const { state, onPositionChange, onStateChange } = ctx;
|
|
if (typeof onPositionChange !== 'function' || typeof onStateChange !== 'function') {
|
|
throw new Error('bindStateEvents: onPositionChange and onStateChange handlers are required');
|
|
}
|
|
|
|
state.emitter.on('positionChange', onPositionChange);
|
|
state.emitter.on('stateChange', onStateChange);
|
|
|
|
let removed = false;
|
|
return function teardown() {
|
|
if (removed) return;
|
|
removed = true;
|
|
state.emitter.off('positionChange', onPositionChange);
|
|
state.emitter.off('stateChange', onStateChange);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* True when the FSM is in a state that should accept measurement
|
|
* updates and recompute predictions. Pure helper, accepts the state
|
|
* machine instance so callers can pass a fake in tests.
|
|
*/
|
|
function isOperationalState(stateInstance) {
|
|
if (!stateInstance || typeof stateInstance.getCurrentState !== 'function') {
|
|
return false;
|
|
}
|
|
return OPERATIONAL_STATES.includes(stateInstance.getCurrentState());
|
|
}
|
|
|
|
module.exports = {
|
|
bindStateEvents,
|
|
isOperationalState,
|
|
OPERATIONAL_STATES,
|
|
};
|