Files
reactor/wiki/Reference-Contracts.md
znetsixe 6b8ae5cfc3 docs(wiki): regenerate topic-contract AUTOGEN block via wiki-gen
Replaces the agent-written placeholder inside Reference-Contracts.md with
the authoritative table generated from src/commands/index.js. Both the
BEGIN and END markers are normalized to the canonical form used by
`@evolv/wiki-gen`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:11:49 +02:00

228 lines
12 KiB
Markdown

# Reference &mdash; Contracts
![code-ref](https://img.shields.io/badge/code--ref-0e34403-blue)
> [!NOTE]
> Full topic contract, configuration schema, and child-registration filters for `reactor`. Source of truth: `src/commands/index.js`, `src/specificClass.js` `configure()`, and the schema at `generalFunctions/src/configs/reactor.json`.
>
> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only.
>
> For an intuitive overview, return to the [Home](Home).
---
## Topic contract
The registry lives in `src/commands/index.js`. Each descriptor maps a canonical `msg.topic` to its handler; aliases emit a one-time deprecation warning the first time they fire.
<!-- BEGIN AUTOGEN: topic-contract -->
| Canonical topic | Aliases | Payload | Unit | Effect |
|---|---|---|---|---|
| `data.clock` | `clock` | any | — | Push the simulation clock tick (timestamp / dt) to the ASM solver. |
| `data.fluent` | `Fluent` | `object` | — | Push the influent stream (payload: {F: flow m3/h, C: [concentrations mg/L]}). |
| `data.otr` | `OTR` | any | — | Push the current oxygen-transfer rate into the reactor. |
| `data.temperature` | `Temperature` | any | — | Push the current reactor temperature. |
| `data.dispersion` | `Dispersion` | any | — | Push a dispersion/mixing parameter update. |
| `child.register` | `registerChild` | any | — | Register a child node (settler / measurement) with this reactor. |
<!-- END AUTOGEN: topic-contract -->
### Modes / sources / actions
reactor has **no mode, no action allow-lists, no source gating**. All topics are accepted as long as the payload shape is valid. (Contrast with `rotatingMachine`, which gates every input through a mode &times; source matrix.)
---
## Data model &mdash; `getOutput()` shape
Composed each tick by `src/specificClass.js` `getOutput()`. Used to build the Port-1 InfluxDB payload; Port 0 carries the engine's `getEffluent` envelope directly.
### Port-0 process payload
The engine's effluent envelope, emitted on every successful `updateState` advance:
```json
{
"topic": "Fluent",
"payload": { "inlet": 0, "F": <m³/d>, "C": [<13 species, mg/L>] },
"timestamp": <ms since epoch>
}
```
For a PFR an additional message is sent **before** the `Fluent` on the same port each advance:
```json
{
"topic": "GridProfile",
"payload": {
"grid": [[<13 cells of n_x>]],
"n_x": <int>,
"d_x": <m>,
"length": <m>,
"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"],
"timestamp": <ms since epoch>
}
}
```
### Port-1 telemetry &mdash; scalar keys
| Key | Type | Unit | Source |
|:---|:---|:---|:---|
| `flow_total` | number | m³/d | `sum(Fs)` from effluent envelope |
| `temperature` | number | &deg;C | `engine.temperature` |
| `S_O` | number | mg/L | effluent `C[0]` &mdash; capped to saturation by `_capDissolvedOxygen` |
| `S_I` | number | mg/L | effluent `C[1]` |
| `S_S` | number | mg/L | effluent `C[2]` |
| `S_NH` | number | mg/L | effluent `C[3]` |
| `S_N2` | number | mg/L | effluent `C[4]` |
| `S_NO` | number | mg/L | effluent `C[5]` |
| `S_HCO` | number | mmol/L | effluent `C[6]` &mdash; alkalinity |
| `X_I` | number | mg/L | effluent `C[7]` |
| `X_S` | number | mg/L | effluent `C[8]` |
| `X_H` | number | mg/L | effluent `C[9]` |
| `X_STO` | number | mg/L | effluent `C[10]` |
| `X_A` | number | mg/L | effluent `C[11]` |
| `X_TS` | number | mg/L | effluent `C[12]` |
Non-finite species values are **omitted** from the output (the `Number.isFinite` guard in `getOutput`); they are not emitted as `null`. Pick one convention per consumer (absent vs null) and document it &mdash; see `.claude/rules/output-coverage.md`.
### Species ordering
The 13-species vector is **fixed**:
| Index | Key | Group |
|:---:|:---|:---|
| 0 | `S_O` | soluble |
| 1 | `S_I` | soluble |
| 2 | `S_S` | soluble |
| 3 | `S_NH` | soluble |
| 4 | `S_N2` | soluble |
| 5 | `S_NO` | soluble |
| 6 | `S_HCO` | soluble |
| 7 | `X_I` | particulate |
| 8 | `X_S` | particulate |
| 9 | `X_H` | particulate |
| 10 | `X_STO` | particulate |
| 11 | `X_A` | particulate |
| 12 | `X_TS` | particulate |
Don't reshuffle &mdash; `getOutput()` and `_flattenEngineConfig()` both depend on this exact order, as does `additional_nodes/settling-basin` and the downstream `settler` node.
### Status badge
`getStatusBadge()` in `src/specificClass.js`:
```
<EngineType> T=<°C>.X C F=<m³/d>.XX m³/d S_O=<mg/L>.XX mg/L
```
Engine type is the constructor name with `Reactor_` stripped (so `CSTR` or `PFR`). Badge is always green-dot (no FSM-driven state).
---
## Configuration schema &mdash; editor form to config keys
Source of truth: `generalFunctions/src/configs/reactor.json` plus `nodeClass.buildDomainConfig` (`src/nodeClass.js`).
### General (`config.general`)
| Form field | Config key | Default | Notes |
|:---|:---|:---|:---|
| Name | `general.name` | `Reactor` | Human-readable. |
| (auto-assigned) | `general.id` | `null` | Node-RED node id. |
| Default unit | `general.unit` | `null` | Unused by the reactor's own logic (the engines pick up units from the schema's `rules.unit` strings); kept for parent compatibility. |
| Log enabled | `general.logging.enabled` | `true` | Master switch. |
| Log level | `general.logging.logLevel` | `info` | `debug` / `info` / `warn` / `error`. |
### Functionality (`config.functionality`)
| Form field | Config key | Default | Notes |
|:---|:---|:---|:---|
| Position vs parent | `functionality.positionVsParent` | `atEquipment` | Used in the child-register payload that goes UP to whatever parent registers this reactor. Enum: `upstream` / `atEquipment` / `downstream`. |
| (hidden) | `functionality.softwareType` | `reactor` | Constant. |
| (hidden) | `functionality.role` | `Biological reactor for wastewater treatment` | Constant. |
### Reactor (`config.reactor`)
| Form field | Config key | Schema default | Range / unit | Notes |
|:---|:---|:---|:---|:---|
| Reactor type | `reactor.reactor_type` | `CSTR` | enum: `CSTR` / `PFR` | Selected once at `configure()`. `_buildEngine` calls `.toUpperCase()` so `pfr` and `PFR` both resolve. |
| Volume | `reactor.volume` | `1000` | m³, `> 0` | Used by mass balance and (PFR) surface-area derivation. |
| Length | `reactor.length` | `10` | m, `> 0` | **PFR only.** Sets axial extent and grid pitch (`d_x = length / n_x`). |
| Resolution | `reactor.resolution_L` | `10` | integer `&ge; 1` | **PFR only.** Grid cell count `n_x`. |
| Alpha | `reactor.alpha` | `0.5` | `0..1` | **PFR only.** Inlet boundary blend: `0` = pure Danckwerts, `1` = fully mixed inlet. |
| Inlets | `reactor.n_inlets` | `1` | integer `&ge; 1` | `Fs[]` / `Cs_in[]` array size. |
| kLa | `reactor.kla` | `0` | 1/h, `&ge; 0`; set `NaN` to disable | Enables internal aeration `OTR = kla &middot; (sat(T) &minus; S_O)`. When `NaN`, `data.otr` is honoured instead. |
| Time step | `reactor.timeStep` | `0.001` | `&ge; 0.0001` | Schema declares unit `h`; `baseEngine.js` converts by `&divide; 86400` (treating it as seconds). See [Limitations &mdash; timeStep unit mismatch](Reference-Limitations#timestep-unit-mismatch). |
| Speed-up factor | `reactor.speedUpFactor` | `1` | `&ge; 1` | Multiplies wall-clock &Delta;t when computing `n_iter`. `2` means twice as many internal steps per second. |
### Initial state (`config.initialState`)
13 starting concentrations, all written into the engine's `state` (CSTR: single row; PFR: replicated across all `n_x` grid cells at construction).
| Form field | Config key | Schema default | HTML default | Unit | Notes |
|:---|:---|:---|:---|:---|:---|
| Initial S_O | `initialState.S_O` | `0` | check editor | mg/L | Capped to saturation on the first tick. |
| Initial S_I | `initialState.S_I` | `30` | check editor | mg/L | Inert soluble COD. |
| Initial S_S | `initialState.S_S` | `70` | check editor | mg/L | Readily biodegradable substrate. |
| Initial S_NH | `initialState.S_NH` | `25` | check editor | mg/L | Ammonium &mdash; declines with nitrification. |
| Initial S_N2 | `initialState.S_N2` | `0` | check editor | mg/L | Dinitrogen. |
| Initial S_NO | `initialState.S_NO` | `0` | check editor | mg/L | Nitrate / nitrite. |
| Initial S_HCO | `initialState.S_HCO` | `5` | check editor | mmol/L | Alkalinity. |
| Initial X_I | `initialState.X_I` | `1000` | check editor | mg/L | Inert particulate COD. |
| Initial X_S | `initialState.X_S` | `100` | check editor | mg/L | Slowly biodegradable substrate. |
| Initial X_H | `initialState.X_H` | `2000` | check editor | mg/L | Heterotrophic biomass. |
| Initial X_STO | `initialState.X_STO` | `0` | check editor | mg/L | Stored COD in biomass. |
| Initial X_A | `initialState.X_A` | `200` | **`0.001`** | mg/L | **Footgun.** HTML default in `reactor.html` (per `CONTRACT.md`) is effectively zero, disabling nitrification. Always verify the deployed form value. |
| Initial X_TS | `initialState.X_TS` | `3500` | check editor | mg/L | Total suspended solids &mdash; drives downstream settler split. |
> [!WARNING]
> The HTML form supplies its own defaults; for fields where they differ from the schema (notably `X_A`), the HTML wins at deploy time. Either match the schema in the HTML or audit every deployed flow.
### Unit policy
reactor does **not** declare a UnitPolicy in `specificClass`. Units are carried in the schema's `rules.unit` strings (m³, m, 1/h, mg/L, mmol/L) and consumed by the engines without normalisation through MeasurementContainer's canonical-unit rule. Notable internal conversions:
| Quantity | What the engine uses internally | Where converted |
|:---|:---|:---|
| `timeStep` | days | `baseEngine.js` line ~40: `timeStep = config.timeStep / 86400` |
| `Fs` | m³/d (assumed by mass-balance formulas) | not converted &mdash; the caller is expected to push m³/d on `data.fluent` |
| `temperature` | &deg;C | stored as supplied (Celsius); `_calcOxygenSaturation(T)` expects &deg;C |
This is a known divergence from the platform-wide canonical-unit rule (`Pa` / `m³/s` / `W` / `K`). Tracked.
---
## Child registration
Source: `src/specificClass.js` `configure()` (ChildRouter wiring) + `BaseReactorEngine._connectMeasurement` / `_connectReactor`.
| Software type | Filter | Wired to | Side-effect |
|:---|:---|:---|:---|
| `measurement` | `asset.type = 'temperature'`, `positionVsParent = atEquipment` | `engine._connectMeasurement` &rarr; `_updateMeasurement` | Writes `engine.temperature`. CSTR only honours this. |
| `measurement` | `asset.type = 'quantity (oxygen)'`, `positionVsParent = <numeric distance>` | `engine._connectMeasurement` &rarr; `Reactor_PFR._updateMeasurement` | **PFR only.** Maps measurement to nearest grid cell by `clamp(round(pos / length &times; n_x), 0, n_x &minus; 1)`. Writes into `state[cell][S_O_INDEX]`. |
| `reactor` | `positionVsParent = 'upstream'` | `engine._connectReactor` | Subscribes to upstream reactor's `stateChange`. Each event triggers downstream `updateState`, which pulls upstream `getEffluent` into `Fs[0]` / `Cs_in[0]` before integrating. |
### Not a child: `diffuser`
`diffuser` (Equipment Module) is **not** registered as a reactor child. It feeds aeration via the `data.otr` topic on Port 0. No child-registration handshake is involved. If you want the diffuser's OTR to drive the reactor, wire the diffuser's process output to the reactor's input directly.
### Unrecognised softwareType
`BaseReactorEngine.registerChild` logs `Unrecognized softwareType: <x>` and drops the registration. There is no `valve`, `rotatingMachine`, etc. acceptance path.
---
## Related pages
| Page | Why |
|:---|:---|
| [Home](Home) | Intuitive overview |
| [Reference &mdash; Architecture](Reference-Architecture) | Code map, integration sequence, kinetics |
| [Reference &mdash; Examples](Reference-Examples) | Shipped flows + debug recipes |
| [Reference &mdash; Limitations](Reference-Limitations) | Known issues and open questions |
| [EVOLV &mdash; Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions) | Platform-wide topic rules |
| [EVOLV &mdash; Telemetry](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Telemetry) | Port 0 / 1 / 2 InfluxDB layout |