Files
valve/CONTRACT.md
znetsixe e27135bdc4 P6: convert valve to BaseDomain + BaseNodeAdapter + concern split
Refactor of valve to use the platform infrastructure (BaseDomain, BaseNodeAdapter,
ChildRouter, commandRegistry, statusBadge). Extracts concerns into
focused modules per .claude/refactor/MODULE_SPLIT.md generic template.
Tests stay green; CONTRACT.md generated; legacy aliases preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:09:22 +02:00

5.9 KiB

valve — Contract

Generated from src/commands/index.js (canonical topic + alias list) plus the hand-written events section. 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). Invalid mode logs warn and is dropped.
cmd.startup { source?: string } Calls source.handleInput(payload.source ?? 'parent', 'execSequence', 'startup').
cmd.shutdown { source?: string } Calls source.handleInput(payload.source ?? 'parent', 'execSequence', 'shutdown'). Pre-shutdown the valve ramps to position 0 if currently operational.
cmd.estop emergencystop, emergencyStop { source?: string, action?: string } Calls source.handleInput(payload.source ?? 'parent', payload.action ?? 'emergencystop').
execSequence — (legacy umbrella) { source, action, parameter } with action ∈ {'startup','shutdown','emergencyStop','emergencystop'} Content-based router: forwards to canonical cmd.startup / cmd.shutdown / cmd.estop based on payload.action. Unknown action logs warn. Prefer the canonical cmd.* topics.
set.position execMovement { source, action, setpoint } — setpoint coerced to Number; valve position percent in [0, 100] Calls source.handleInput(payload.source ?? 'parent', payload.action ?? 'execMovement', Number(payload.setpoint)).
data.flow updateFlow { variant, value, position, unit? }variant ∈ {'measured','predicted'} Pushes a flow value into the measurement container at <position> and triggers a deltaP recompute through the hydraulic model.
query.curve showcurve none Calls source.showCurve() and replies on Port 0 with { topic: 'Showing curve', payload: <result> } via ctx.send.
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). The valve's registerChild records the child for fluid-contract tracking.

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. 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 / cmd.estop handler based on payload.action. The deprecation warning fires once. Future-Phase-7 removal of execSequence is a behavioural change — callers must migrate to the canonical topics.

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.curve the node additionally emits { topic: 'Showing curve', 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: 'child.register', payload: <node.id>, positionVsParent, distance } to its upstream parent (typically a valveGroupControl). positionVsParent defaults to 'atEquipment'.

getOutput() keys per tick include: <position>_<variant>_<type> slots from the measurement container (e.g. delta_predicted_pressure, downstream_measured_flow), plus state, percentageOpen, moveTimeleft, mode.

Events emitted by source.emitter

  • deltaPChange — fires whenever the hydraulic model recomputes a finite deltaP. Data: the deltaP value in unitPolicy.output.pressure (default mbar). Consumed by valveGroupControl to update group totals.
  • fluidCompatibilityChange — fires when the upstream fluid-contract status changes (status / expected / received / sourceCount / message). Data: FluidCompatibility.getCompatibility().
  • fluidContractChange — fires whenever the fluid contract that this valve advertises downstream changes. Data: FluidCompatibility.getContract().

Events emitted by source.state.emitter

  • positionChange — fires when the position percentage changes (per movement tick). Data: { position, state, mode, timestamp }. The valve itself listens and triggers a Kv lookup + deltaP recompute.
  • stateChange — fires on transitions of the operating state machine (idle → starting → warmingup → operational → accelerating → decelerating → stopping → coolingdown → idle, plus off).

Events emitted by source.measurements.emitter

The MeasurementContainer fires <type>.<variant>.<position> whenever a series receives a new value. Parents subscribe via the generic child.measurements.emitter.on(eventName, ...) handshake. valve publishes:

  • pressure.predicted.delta — predicted pressure drop across the valve.
  • pressure.measured.<position>, pressure.predicted.<position> — when upstream pressure data arrives via data.flow-driven recompute or direct measurement pushes.
  • flow.measured.<position>, flow.predicted.<position> — mirrored from upstream sources via data.flow.

Position labels are normalised to lowercase in the event name.

Children registered by this node

valve accepts upstream sources (machine, rotatingmachine, machinegroup, machinegroupcontrol, pumpingstation, valvegroupcontrol, …) via child.register. The handler records each child for fluid-contract tracking: the valve reads either the child's getFluidContract() result, its asset.serviceType field, or a default per software type (liquid for the rotating-equipment family). It then subscribes to the child's fluidContractChange so re-keyed contracts propagate.