P6: convert valveGroupControl to BaseDomain + BaseNodeAdapter + concern split

Refactor of valveGroupControl 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>
This commit is contained in:
znetsixe
2026-05-10 22:09:24 +02:00
parent 0aa538c2c1
commit e02cd1a7a7
8 changed files with 704 additions and 895 deletions

67
CONTRACT.md Normal file
View File

@@ -0,0 +1,67 @@
# valveGroupControl — Contract
Hand-maintained for Phase 6; the `## Inputs` table is generated from
`src/commands/index.js` (see Phase 9 generator). Keep ≤ 80 lines.
## Inputs (msg.topic on Port 0)
| Canonical | Aliases (deprecated) | Payload | Effect |
|---|---|---|---|
| `set.mode` | `setMode` | `string` — one of `auto`, `virtualControl`, `fysicalControl`, `maintenance` | Switches the control strategy via `source.setMode(payload)`. |
| `set.position` | `setpoint` | `any` | Reserved for future per-valve positional override; currently a debug-logged no-op pending Phase 7. |
| `child.register` | `registerChild` | `string` — the child node's Node-RED id | Resolves the child via `RED.nodes.getNode` and registers it through `childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent)`. |
| `cmd.execSequence` | `execSequence` | `{ source, action, parameter }` | Forwards to `source.handleInput(source, action, parameter)`. |
| `data.totalFlow` | `totalFlowChange` | numeric, `{ value, position?, variant?, unit? }`, or `{ source, action, ... }` | Updates total measured/predicted flow at the configured position; drives `calcValveFlows` to re-distribute across valves. |
| `cmd.emergencyStop` | `emergencyStop`, `emergencystop` | optional `{ source }` | Runs the `emergencystop` sequence via `handleInput`. |
| `set.reconcileInterval` | `setReconcileInterval` | numeric — seconds (> 0) | Re-tunes the periodic flow-reconciliation interval. Min clamp 100 ms. |
Aliases log a one-time deprecation warning the first time they fire.
## 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). Output keys follow
`<position>_<variant>_<type>` plus `mode` and `maxDeltaP`.
- **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 }`
to the upstream parent.
## Events emitted by `source.emitter` / `source.measurements.emitter`
- `output-changed` (`source.emitter`) — public output state shifted; the
adapter listens and pushes Ports 0/1.
- `fluidContractChange` (`source.emitter`) — group-level fluid contract
(status / serviceType / sourceCount) changed. Parents (e.g. an upstream
valve registering this VGC as its parent) subscribe to react.
- `reconcileIntervalChange` (`source.emitter`) — emitted by
`setReconcileIntervalSeconds`; the adapter restarts the tick loop.
- `flow.predicted.atequipment` (`source.measurements.emitter`) — total
predicted group flow (sum of per-valve assigned flows).
- `pressure.predicted.deltaMax` (`source.measurements.emitter`) — max
delta-P across registered valves.
The exact set is data-driven by which sources/valves register and what
they publish; downstream consumers subscribe by event name.
## Children registered by this node
valveGroupControl accepts two child classes through the
`childRegistrationUtils` handshake:
- `valve` — an individual valve. Stored in `source.valves[id]`. VGC binds
to the child's `positionChange` (via `child.state.emitter`) and
`deltaPChange` (via `child.emitter`) events to re-distribute flow and
re-compute group max delta-P.
- `machine` / `rotatingmachine` / `machinegroup` / `machinegroupcontrol` /
`pumpingstation` / `valvegroupcontrol` — an upstream **source**. Stored
in `source.sources[id]`. VGC subscribes to the source's
`flow.predicted.*` / `flow.measured.*` events to drive `updateFlow`,
and reads the child's `getFluidContract()` (if present) plus
`fluidContractChange` events to aggregate the group's upstream service
type (`getFluidContract()` exposes the resolved view).
Position labels accepted from children are `upstream`, `downstream`,
`atEquipment` (and case variants — normalised internally).