Files
settler/wiki/Reference-Contracts.md

177 lines
9.8 KiB
Markdown
Raw Normal View History

# Reference — Contracts
![code-ref](https://img.shields.io/badge/code--ref-a3583a3-blue)
> [!NOTE]
> Pending full node review (2026-05). Content reflects `CONTRACT.md`, `src/commands/index.js`, and `generalFunctions/src/configs/settler.json` only.
Full topic contract, configuration schema, and child-registration filters for `settler`. Source of truth: `src/commands/index.js`, `src/specificClass.js` `configure()`, and the schema at `generalFunctions/src/configs/settler.json`. 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 — populate via wiki-gen tool (TODO) -->
| Canonical topic | Aliases | Payload | Unit | Effect |
|:---|:---|:---|:---|:---|
| `data.influent` | `influent`, `setInfluent` | `{F: number, C: number[13]}` &mdash; either field optional | `F` in m³/h, `C[*]` in mg/L | Replaces influent flow and/or the 13-species concentration vector on the domain (`source.F_in`, `source.Cs_in`). Triggers `notifyOutputChanged`, which re-emits the 3-stream Fluent envelope on Port 0. |
| `child.register` | `registerChild` | `string` (child node id) | &mdash; | Register a child node (measurement / reactor / machine) with this settler. Port 2 wiring does this automatically in normal flows; the explicit handler exists because `BaseNodeAdapter` does not have an implicit registration path. |
<!-- END AUTOGEN: topic-contract -->
> [!NOTE]
> Pending full node review (2026-05). The autogen markers above will be populated by a future `npm run wiki:contract` tool. Until then the table is hand-maintained against `src/commands/index.js`.
### Mode / source / action allow-lists
**Not applicable.** Settler has no operational mode, no source allow-list, no action allow-list. Both topics are accepted unconditionally; payload-shape validation lives in the handler itself.
The `data.influent` handler validates:
```js
if (!p || typeof p !== 'object' || Array.isArray(p)) {
log?.warn?.(`data.influent expects an object {F, C}; got ${typeof p}`);
return;
}
if (typeof p.F === 'number' && Number.isFinite(p.F)) source.F_in = p.F;
if (Array.isArray(p.C)) source.Cs_in = [...p.C];
```
Non-finite or non-numeric `F` is silently ignored. Non-array `C` is silently ignored. Either field may be omitted to update only the other. Negative `F` is **not** rejected &mdash; the downstream `getEffluent` math will produce nonsense but the node will not throw.
---
## Data model &mdash; `getOutput()` shape
Composed each tick by `src/specificClass.js` `getOutput()`. Port 0 carries the 3-message Fluent stream **directly** (not via `getOutput`); Port 1 (this snapshot) is the scalar dashboard view.
<!-- BEGIN AUTOGEN: data-model — populate via wiki-gen tool (TODO) -->
### Scalar keys
| Key | Type | Unit | Source | Notes |
|:---|:---|:---|:---|:---|
| `F_in` | number | m³/h | `host.F_in` | Influent flow. Default 0. |
| `C_TS` | number | mg/L | `host.C_TS` | Target return-sludge concentration. Default 2500. Updated by `quantity (tss)` measurement child. |
| `F_eff` | number | m³/h | `streams[0].payload.F` | Clarified effluent flow. |
| `F_surplus` | number | m³/h | `streams[1].payload.F` | Surplus sludge flow (`F_s - F_sr`). |
| `F_return` | number | m³/h | `streams[2].payload.F` | Return sludge flow (`min(pumpFlow, F_s)`). |
### Per-measurement keys
For every `(type, variant, position)` stored in `this.measurements` MeasurementContainer, the flattened output emits:
```
<type>.<variant>.<position>.<childId>
```
Position labels are normalised to lowercase. The trailing `<childId>` is the registering measurement child's id. Settler does not write its own measurements directly &mdash; every key in this group came from a registered child (the `_connectMeasurement` re-emit).
<!-- END AUTOGEN: data-model -->
### Status badge
`getStatusBadge()` in `src/specificClass.js`:
| Condition | Badge |
|:---|:---|
| `F_in <= 0` | `statusBadge.idle('no influent')` (grey ring) |
| else | green dot, label `F_in=<n.nn> eff=<n.nn> surplus=<n.nn>` (m³/h, 2 dp) |
No state-symbol enumeration &mdash; settler has no FSM.
---
## Configuration schema &mdash; editor form to config keys
Source of truth: `generalFunctions/src/configs/settler.json` plus `settler.html`.
### General (`config.general`)
| Form field | Config key | Default | Notes |
|:---|:---|:---|:---|
| Name | `general.name` | `"Settler"` | Human-readable name. |
| (auto-assigned) | `general.id` | `null` | Node-RED node id. |
| Default unit | `general.unit` | `null` | Default measurement unit. Currently unused by settler &mdash; child measurements carry their own units. |
| Enable logging | `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 |
|:---|:---|:---|:---|
| (hidden) | `functionality.softwareType` | `settler` | Constant. Used by the parent's router as a registration filter. |
| (hidden) | `functionality.role` | `Secondary settler for sludge separation` | Documentation string. |
| Position vs parent | `functionality.positionVsParent` | `downstream` | One of `upstream` / `atEquipment` / `downstream`. Settler typically registers as `downstream` against an upstream reactor. |
### Node-RED-side (editor form, not domain config)
| Form field | Stored on | Default | Notes |
|:---|:---|:---|:---|
| Process Output Format | `nodeClass.processOutputFormat` | `process` | `process` / `json` / `csv`. Port-0 serialisation. |
| Database Output Format | `nodeClass.dbaseOutputFormat` | `influxdb` | `influxdb` / `json` / `csv`. Port-1 serialisation. |
`buildDomainConfig()` returns `{}` &mdash; settler does not push any editor-derived values into the domain at start-up. All operational state is runtime (`F_in`, `Cs_in`, `C_TS`).
> [!NOTE]
> Pending full node review (2026-05). The editor form (`settler.html`) is currently colour-drifted to `#e4a363` (orange); should be `#50a8d9` (Unit blue) per `.claude/rules/node-red-flow-layout.md` §16.
### Unit policy
| Quantity | Canonical (internal) | Output (Port 1) | Notes |
|:---|:---|:---|:---|
| Flow (`F_in`, `F_eff`, `F_surplus`, `F_return`) | m³/h | m³/h | No conversion &mdash; settler is unit-agnostic above the storage layer. |
| Concentration (`C_TS`, `Cs_in[*]`) | mg/L | mg/L | Same. |
> [!NOTE]
> Pending full node review (2026-05). Settler does **not** declare a `requireUnitForTypes` policy via MeasurementContainer; verify against the general unit policy before relying on internal canonicalisation.
---
## Child registration
Source: `src/specificClass.js` `configure()` (the `this.router.onRegister(...)` chain) and the three `_connect*` methods.
| Software type | Filter | Wired to | Side-effect |
|:---|:---|:---|:---|
| `measurement` | any (no asset-type / position filter at register time) | `_connectMeasurement(child)` | Subscribes to `<type>.measured.<position>` on the child's `measurements.emitter`. Re-emits on settler's own MeasurementContainer (lets settler's parent see the value). `quantity (tss)` updates `C_TS`; anything else logs an `error` from `_updateMeasurement` but the re-emit still happened. |
| `reactor` | `positionVsParent === 'upstream'` (warns otherwise but still registers) | `_connectReactor(child)` | Stored as `this.upstreamReactor`. Listener attached **manually** to `reactor.emitter` (NOT `measurements.emitter`) for `'stateChange'`; on fire, settler pulls `reactor.getEffluent` and copies `F_in` + `Cs_in`. Handles both array and single-envelope `getEffluent` shapes. |
| `machine` | `positionVsParent === 'downstream'` (warns + skips otherwise) | `_connectMachine(child)` | Stored as `this.returnPump`. Settler reads `returnPump.measurements.type('flow').variant('measured').position('atEquipment').getCurrentValue()` to determine `F_sr`. Sets `machineChild.upstreamSource = this` so the pump can use settler's `inlet=2` Fluent as its suction-side context. |
> [!NOTE]
> Pending full node review (2026-05). The `measurement` filter accepts any asset-type at register time; only the runtime `_updateMeasurement` switch acts on `quantity (tss)`. Other measurement types are silently re-emitted but not consumed. TODO: decide whether this is desired (pass-through telemetry) or a contract gap.
### No virtual children
Unlike `rotatingMachine`, settler does **not** auto-register any virtual measurement children. Every measurement must come from an explicitly wired child node.
---
## Parent relationship
Settler typically registers as `softwareType: 'settler'` with `positionVsParent: 'downstream'` against a reactor (the reactor's downstream stage). The downstream reactor consumes the three Fluent streams via `payload.inlet`:
| `inlet` | Consumer expectation |
|:---|:---|
| `0` (clarified effluent) | Routed onward to the next process unit. |
| `1` (surplus sludge) | Typically routed to a sludge-handling process (digestion, thickening, dewatering). |
| `2` (return sludge) | Drawn back to a reactor inlet or the head of the biological train. |
Multi-reactor settlers are **not supported** &mdash; `this.upstreamReactor` is a single slot; the last `child.register` call wins.
---
## Related pages
| Page | Why |
|:---|:---|
| [Home](Home) | Intuitive overview |
| [Reference &mdash; Architecture](Reference-Architecture) | Code map, reactor &harr; settler wiring, mass-balance math |
| [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 |