# 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. | Canonical topic | Aliases | Payload | Unit | Effect | |---|---|---|---|---| | `data.influent` | `influent`, `setInfluent` | any | — | Push the influent stream (payload: {F: flow m3/h, C: [concentrations mg/L]}). | | `child.register` | `registerChild` | `string` | — | Register a child node (typically a measurement) with this settler. | > [!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 — the downstream `getEffluent` math will produce nonsense but the node will not throw. --- ## Data model — `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. ### 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: ``` ... ``` Position labels are normalised to lowercase. The trailing `` is the registering measurement child's id. Settler does not write its own measurements directly — every key in this group came from a registered child (the `_connectMeasurement` re-emit). ### Status badge `getStatusBadge()` in `src/specificClass.js`: | Condition | Badge | |:---|:---| | `F_in <= 0` | `statusBadge.idle('no influent')` (grey ring) | | else | green dot, label `F_in= eff= surplus=` (m³/h, 2 dp) | No state-symbol enumeration — settler has no FSM. --- ## Configuration schema — 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 — 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 `{}` — 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 — 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 `.measured.` 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** — `this.upstreamReactor` is a single slot; the last `child.register` call wins. --- ## Related pages | Page | Why | |:---|:---| | [Home](Home) | Intuitive overview | | [Reference — Architecture](Reference-Architecture) | Code map, reactor ↔ settler wiring, mass-balance math | | [Reference — Examples](Reference-Examples) | Shipped flows + debug recipes | | [Reference — Limitations](Reference-Limitations) | Known issues and open questions | | [EVOLV — Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions) | Platform-wide topic rules | | [EVOLV — Telemetry](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Telemetry) | Port 0 / 1 / 2 InfluxDB layout |