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

96 lines
5.9 KiB
Markdown

# 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.