Files
rotatingMachine/CONTRACT.md
znetsixe c5bb375dd0 P5 wave 1: extract rotatingMachine concerns into focused modules
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>
2026-05-10 21:38:45 +02:00

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 by outputUtils.formatMsg(..., 'process') from getOutput() — delta-compressed (only changed fields are emitted). On query.curves / query.cog the 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 a machineGroupControl or pumpingStation). positionVsParent defaults 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 or data.simulate-measurement type=pressure runs.
  • flow.measured.<position>, power.measured.atequipment, temperature.measured.<position> — when sensor children register or the data.simulate-measurement topic 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, plus off, 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).