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>
6.3 KiB
rotatingMachine — Contract
Hand-maintained for Phase 5; the ## Inputs table is generated from
src/commands/index.js (see Phase 9 generator). Keep ≤ 100 lines.
Inputs (msg.topic on Port 0)
| Canonical | Aliases (deprecated) | Payload | Effect |
|---|---|---|---|
set.mode |
setMode |
string — one of the allowed mode names |
Calls source.setMode(payload). |
cmd.startup |
— | { source?: string } |
Calls source.handleInput(payload.source ?? 'parent', 'execSequence', 'startup'). |
cmd.shutdown |
— | { source?: string } |
Calls source.handleInput(payload.source ?? 'parent', 'execSequence', 'shutdown'). |
cmd.estop |
emergencystop |
{ source?: string, action?: string } |
Calls source.handleInput(payload.source ?? 'parent', payload.action ?? 'emergencystop'). |
execSequence |
— (legacy umbrella) | { source, action, parameter } with action ∈ {'startup','shutdown'} |
Content-based router: forwards to cmd.startup / cmd.shutdown handler based on payload.action. Unknown action logs warn and is dropped. Whole topic is legacy — prefer the canonical cmd.* topics. |
set.setpoint |
execMovement |
{ source, action, setpoint } — setpoint coerced to Number |
Calls source.handleInput(payload.source ?? 'parent', payload.action ?? 'execMovement', Number(payload.setpoint)). |
set.flow-setpoint |
flowMovement |
{ source, action, setpoint } |
Calls source.handleInput(payload.source ?? 'parent', payload.action ?? 'flowMovement', Number(payload.setpoint)). |
data.simulate-measurement |
simulateMeasurement |
{ type, position?, value, unit, timestamp? } — type ∈ {pressure, flow, temperature, power}; position defaults to 'atEquipment' |
Validated dispatch: rejects non-finite value, unsupported type, missing unit, or unit that fails isUnitValidForType. Pressure routes via updateSimulatedMeasurement(type, position, value, ctx); flow/temperature/power route via updateMeasured<Type>(value, position, ctx). The injected childId/childName = 'dashboard-sim' marks the source. |
query.curves |
showWorkingCurves |
none | Calls source.showWorkingCurves() and replies on Port 0 with { topic: 'showWorkingCurves', payload: <result> } via ctx.send. |
query.cog |
CoG |
none | Calls source.showCoG() and replies on Port 0 with { topic: 'showCoG', payload: <result> }. |
child.register |
registerChild |
string — child Node-RED id; msg.positionVsParent carries position |
Resolves child via RED.nodes.getNode(payload) and registers it through childRegistrationUtils.registerChild(child.source, msg.positionVsParent). Unknown ids log warn. |
Aliases log a one-time deprecation warning the first time they fire.
execSequence demux
The pre-refactor topic execSequence carried { source, action, parameter }
where action selected the verb (startup or shutdown). The command
registry does not natively dispatch by payload content, so execSequence
keeps its own descriptor whose handler forwards directly to the
canonical cmd.startup / cmd.shutdown handler based on
payload.action. The deprecation warning fires once. Future-Phase-7
removal of execSequence is a behavioural change — callers must migrate
to cmd.startup / cmd.shutdown.
Outputs (msg.topic on Port 0/1/2)
- Port 0 (process):
msg.topic = config.general.name. Payload built byoutputUtils.formatMsg(..., 'process')fromgetOutput()— delta-compressed (only changed fields are emitted). Onquery.curves/query.cogthe node additionally emits{ topic: 'showWorkingCurves' | 'showCoG', payload: <result> }as a synchronous reply on Port 0. - Port 1 (InfluxDB telemetry): same shape as Port 0, formatted with the
'influxdb'formatter. - Port 2 (registration): at startup the node sends one
{ topic: 'registerChild', payload: <node.id>, positionVsParent }to the upstream parent (typically amachineGroupControlorpumpingStation).positionVsParentdefaults to'atEquipment'.
Events emitted by source.measurements.emitter
The MeasurementContainer fires <type>.<variant>.<position> whenever
the corresponding series receives a new value. Parents subscribe via the
generic child.measurements.emitter.on(eventName, ...) handshake.
rotatingMachine publishes:
flow.predicted.atequipment,flow.predicted.downstream,flow.predicted.max,flow.predicted.min— predicted operating point.power.predicted.atequipment— predicted shaft power.temperature.measured.atequipment— ambient/process temperature.atmPressure.measured.atequipment— barometric reference.pressure.measured.upstream,pressure.measured.downstream,pressure.measured.differential— when pressure children register ordata.simulate-measurement type=pressureruns.flow.measured.<position>,power.measured.atequipment,temperature.measured.<position>— when sensor children register or thedata.simulate-measurementtopic supplies values.
Position labels are normalised to lowercase in the event name. The exact set is data-driven by which children register and what they publish.
Events emitted by source.state.emitter
positionChange— fires when the position percentage changes (per movement tick). Data:{ position, state, mode, timestamp }.stateChange— fires on transitions of the operating state machine (idle → starting → warmingup → operational → accelerating → decelerating → stopping → coolingdown → idle, plusoff,maintenance). Data: the new state string.
Children registered by this node
rotatingMachine accepts measurement children through the
childRegistrationUtils handshake. Children typically have
asset.type ∈ {pressure, flow, power, temperature}. The machine
subscribes to the matching <asset.type>.measured.<positionVsParent>
event and mirrors the value into its own MeasurementContainer.
Two virtual children are reserved by the data.simulate-measurement
topic: incoming simulated values are tagged with
childId/childName = 'dashboard-sim' so dashboard-driven inputs are
distinguishable from real sensor children in downstream telemetry.
Position labels accepted from children are upstream, downstream,
atEquipment (and case variants — normalised internally).