P6: convert reactor to platform infrastructure
Refactor of reactor to use BaseNodeAdapter + commandRegistry + statusBadge. reactor follows the platform refactor plan in .claude/refactor/MODULE_SPLIT.md. 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:
51
CONTRACT.md
Normal file
51
CONTRACT.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# reactor — 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 |
|
||||
|---|---|---|---|
|
||||
| `data.clock` | `clock` | `msg.timestamp` (ms since epoch) | Calls `source.updateState(timestamp)` — advances the ASM kinetics integrator by `n_iter` time steps that fit between `currentTime` and the supplied timestamp (scaled by `speedUpFactor`). |
|
||||
| `data.fluent` | `Fluent` | `{ inlet: number, F: number, C: number[13] }` | Writes the per-inlet flow rate (`F`, m³/d) and concentration vector (`C`) into `engine.Fs[inlet]` / `engine.Cs_in[inlet]`. |
|
||||
| `data.otr` | `OTR` | numeric | Sets the externally-supplied oxygen transfer rate (used when `kla` is NaN). |
|
||||
| `data.temperature` | `Temperature` | numeric or `{ value: number }` | Sets `engine.temperature` (°C). Non-numeric payloads are warned and ignored. |
|
||||
| `data.dispersion` | `Dispersion` | numeric | PFR only — sets the axial dispersion coefficient `D` (m²/d). |
|
||||
| `child.register` | `registerChild` | child node id (string) | Looks up the sibling node via `RED.nodes.getNode(id)` and delegates to `source.childRegistrationUtils.registerChild` with `msg.positionVsParent`. |
|
||||
|
||||
Aliases log a one-time deprecation warning the first time they fire.
|
||||
|
||||
## Outputs (msg.topic on Port 0/1/2)
|
||||
|
||||
- **Port 0 (process):** every tick emits the engine's effluent:
|
||||
`{ topic: 'Fluent', payload: { inlet: 0, F, C: number[13] }, timestamp }`.
|
||||
For a PFR an additional `{ topic: 'GridProfile', payload: { grid, n_x, d_x, length, species, timestamp } }`
|
||||
message goes out on the same port before the effluent.
|
||||
- **Port 1 (InfluxDB telemetry):** formatted via `outputUtils.formatMsg(..., 'influxdb')`
|
||||
from `getOutput()` — carries `flow_total`, `temperature`, and one field per ASM3
|
||||
species (`S_O`, `S_I`, `S_S`, `S_NH`, `S_N2`, `S_NO`, `S_HCO`, `X_I`, `X_S`, `X_H`,
|
||||
`X_STO`, `X_A`, `X_TS`).
|
||||
- **Port 2 (registration):** at startup the node sends one
|
||||
`{ topic: 'child.register', payload: <node.id>, positionVsParent, distance }`
|
||||
to its parent.
|
||||
|
||||
## Events emitted by `source.emitter`
|
||||
|
||||
- `stateChange` — fires after every `updateState()` that advances the integrator.
|
||||
Payload is the new `currentTime` (ms since epoch). Downstream reactors register
|
||||
via `child.register` and subscribe to this event to pull the upstream
|
||||
effluent on each advance.
|
||||
- `output-changed` — base notification fired by `updateState()` so the
|
||||
BaseNodeAdapter pipeline pushes outputs (currently used only as a heartbeat;
|
||||
effluent is emitted directly from the periodic tick).
|
||||
|
||||
## Children accepted
|
||||
|
||||
- `measurement` — subscribes to `<type>.measured.<position>` on the child's
|
||||
`measurements.emitter`. Recognised reconciliations: `temperature.measured.atEquipment`
|
||||
writes `engine.temperature`; PFR additionally honours
|
||||
`quantity (oxygen).measured.<distance>` to reconcile dissolved-oxygen
|
||||
concentration into the nearest grid cell.
|
||||
- `reactor` — registers as the upstream reactor; the downstream `updateState`
|
||||
pulls the upstream effluent into `Fs[0]` / `Cs_in[0]` before integrating.
|
||||
Reference in New Issue
Block a user