P6: convert diffuser to BaseDomain + BaseNodeAdapter + concern split

Refactor of diffuser 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:26 +02:00
parent 7fbd207985
commit 0ec9dd15a7
6 changed files with 295 additions and 373 deletions

71
CONTRACT.md Normal file
View File

@@ -0,0 +1,71 @@
# diffuser — Contract
Hand-maintained for Phase 6; 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 |
|---|---|---|---|
| `data.flow` | `air_flow` | `number` — airflow in Nm³/h | Calls `source.setFlow(payload)`; clamps to ≥ 0 and recomputes OTR. |
| `set.density` | `density` | `number` — diffuser density (per m²) | Calls `source.setDensity(payload)` and recomputes. |
| `set.water-height` | `height_water` | `number` — water column height in m | Calls `source.setWaterHeight(payload)`; clamps to ≥ 0 and recomputes head + total pressure. |
| `set.header-pressure` | `header_pressure` | `number` — header gauge pressure in mbar | Calls `source.setHeaderPressure(payload)` and recomputes. |
| `set.elements` | `elements` | `number` — element count (rounded; must be > 0) | Calls `source.setElementCount(payload)` and recomputes per-element flow. |
| `set.alfa-factor` | `alfaFactor` | `number` — alpha correction (≥ 0) | Calls `source.setAlfaFactor(payload)` and recomputes oxygen output. |
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). Fields:
- `iPressure`, `iMWater`, `iFlow` — echoed inputs.
- `nFlow` — normalised airflow (Nm³/h).
- `oOtr` — interpolated oxygen transfer rate (g O₂ / Nm³).
- `oPLoss` — total head loss (mbar) = static head + diffuser ΔP.
- `oKgo2H` — kg O₂ per hour at current operating point.
- `oFlowElement` — flow per element (Nm³/h/element).
- `efficiency` — combined OTR/ΔP efficiency (0100).
- `slope` — local OTR-vs-flow slope.
- `oZoneOtr` — reactor zone OTR (kg O₂ / m³ / day) computed against
`diffuser.zoneVolume`; `0` when zone volume is unset.
- `idle` — true when `data.flow ≤ 0`.
- `warning`, `alarm` — string arrays describing flow-per-element band
excursions.
- **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 the upstream parent (typically a reactor).
`positionVsParent` defaults to `'atEquipment'`.
## Port-count change (Phase 6)
Pre-refactor the diffuser exposed 4 outputs (process, dbase, reactor
control with `topic: 'OTR'`, parent registration). The reactor control
message merged into Port 0 as `oZoneOtr`; consumers that previously
listened to the dedicated control port should switch to reading
`payload.oZoneOtr` from the process output. The legacy `OTR` topic is
removed in this refactor — there is no alias, since the data shape
differs (single value vs full process payload).
## Events emitted by `source.measurements.emitter`
None today. The diffuser does not currently publish typed measurements
through `MeasurementContainer`; all output flows via `getOutput()`.
A future phase may promote `oOtr` and `oZoneOtr` to typed series so
parent reactors can subscribe through the standard `ChildRouter`
handshake.
## Events emitted by `source.emitter`
- `output-changed` — fires whenever an input setter recomputes the
oxygen-transfer state. `BaseNodeAdapter` listens and pushes the
delta-compressed Port 0 / Port 1 messages.
## Children registered by this node
None. The diffuser is a leaf Equipment Module; it registers itself with
its parent (reactor / process cell) via the Port 2 handshake.