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>
52 lines
3.1 KiB
Markdown
52 lines
3.1 KiB
Markdown
# 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.
|