Files
settler/wiki/Home.md
znetsixe 6953d6473e P9.3: wiki/Home.md following 14-section visual-first template + wiki:* scripts
Auto-generated topic-contract + data-model sections via shared wikiGen
script. Hand-written Mermaid diagrams for position-in-platform, code
map, child registration, lifecycle, configuration, state chart (where
applicable).

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

242 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# settler
> **Reflects code as of `7bf464b` · regenerated `2026-05-11` via `npm run wiki:all`**
> If this banner is stale, the page may be out of date. Treat as informative, not authoritative.
## 1. What this node is
**settler** is an S88 Unit that models a secondary clarifier. It takes the upstream reactor's effluent stream, performs a 13-species TSS mass balance, and splits it into three Fluent envelopes: clarified effluent, surplus sludge, and return sludge. A downstream return pump (rotatingMachine child) draws the return-sludge flow.
## 2. Position in the platform
```mermaid
flowchart LR
upstream[reactor<br/>upstream<br/>Unit]:::unit
settler[settler<br/>Unit]:::unit
downstream[reactor<br/>downstream<br/>Unit]:::unit
return[rotatingMachine<br/>return pump<br/>Equipment]:::equip
tss[measurement<br/>type=quantity (tss)<br/>position=atequipment]:::ctrl
upstream -.stateChange.-> settler
settler -->|Fluent inlet=0,1,2| downstream
return -->|child.register downstream| settler
settler -.F_sr.-> return
tss -->|quantity (tss).measured.atequipment| settler
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
```
S88 colours: Unit `#50a8d9`, Equipment `#86bbdd`, Control Module `#a9daee`. Source of truth: `.claude/rules/node-red-flow-layout.md`.
## 3. Capability matrix
| Capability | Status | Notes |
|---|---|---|
| TSS mass-balance split (3 streams) | ✅ | Effluent / surplus / return derived from `F_in * Cs[12] / C_TS`. |
| Particulate zeroing in effluent | ✅ | Species 712 set to 0 in effluent when `F_s > 0`. |
| Particulate concentration in sludge | ✅ | Species 712 scaled by `F_in / F_s` in surplus + return. |
| Return-pump flow draw | ✅ | `F_sr` = min(pump flow, F_s). Surplus = F_s F_sr. |
| F_s clamp to F_in | ✅ | Prevents negative effluent when X_TS_in > C_TS. |
| Manual influent override | ✅ | `data.influent` lets ops supply `{ F, C }` directly. |
| Multiple reactor upstreams | ❌ | Only one `upstreamReactor` slot; last registration wins. |
| Stateful FSM | ❌ | Stateless transform — recomputes on every push. |
## 4. Code map
```mermaid
flowchart TB
subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"]
nc["buildDomainConfig()<br/>static DomainClass = Settler<br/>static commands"]
end
subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
sc["Settler.configure()<br/>ChildRouter rules<br/>getEffluent — TSS split<br/>_connectReactor (manual listener)"]
end
subgraph commands["src/commands/"]
cmds["index.js + handlers.js<br/>data.influent + aliases"]
end
nc --> sc
nc --> cmds
```
| Module | Owns | Read first if you're changing… |
|---|---|---|
| `specificClass.js` | All domain logic: getEffluent split, reactor + machine + measurement wiring, getOutput, getStatusBadge. | Mass-balance math, child wiring, telemetry shape. |
| `commands/` | Single command (`data.influent`) + aliases + payload validation. | Manual-influent topic, new aliases. |
Settler is small enough (~140 LOC) that no concern-split was needed (per P6.6).
## 5. Topic contract
> **Auto-generated** from `src/commands/index.js`. Do NOT hand-edit between the markers. Re-run `npm run wiki:contract`.
<!-- BEGIN AUTOGEN: topic-contract -->
| Canonical topic | Aliases | Payload | Effect |
|---|---|---|---|
| `data.influent` | `influent`, `setInfluent` | `any` | Pushes a value into the node's measurement stream. |
<!-- END AUTOGEN: topic-contract -->
## 6. Child registration
```mermaid
flowchart LR
subgraph kids["accepted children (softwareType)"]
m["measurement"]:::ctrl
r["reactor<br/>upstream"]:::unit
mach["machine<br/>downstream"]:::equip
end
m -->|"&lt;type&gt;.measured.&lt;position&gt;"| h_m[_connectMeasurement]
r -.stateChange.-> h_r[_connectReactor<br/>manual listener]
mach -->|registered| h_mach[_connectMachine<br/>sets returnPump]
h_r --> pull[upstreamReactor.getEffluent]
pull --> emit[notifyOutputChanged]
classDef ctrl fill:#a9daee,color:#000
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
```
| softwareType | filter | wired to | side-effect |
|---|---|---|---|
| `measurement` | any | `_connectMeasurement` | Re-emits on settler's measurements; `quantity (tss)` updates `C_TS`. |
| `reactor` | `positionVsParent=upstream` (warns otherwise) | `_connectReactor` | Stores as `upstreamReactor`; subscribes to its **own** `emitter` (NOT `measurements.emitter`) for `'stateChange'`. |
| `machine` | `positionVsParent=downstream` | `_connectMachine` | Stores as `returnPump`; sets `machine.upstreamSource = settler`. |
### 6.1 Reactor ↔ settler wiring (the load-bearing bit)
The reactor pushes its `stateChange` event on `reactor.emitter`, not `reactor.measurements.emitter`. The standard `router.onMeasurement` path can't subscribe — so settler attaches the listener manually inside `_connectReactor`. On each fire, settler **pulls** the upstream effluent via `reactor.getEffluent` and copies it into `this.F_in` + `this.Cs_in`.
`reactor.getEffluent` historically returned either an array (3-stream) or a single envelope — the 2026-03-02 `_connectReactor` fix preserves both shapes:
```js
const raw = this.upstreamReactor.getEffluent;
const effluent = Array.isArray(raw) ? raw[0] : raw;
this.F_in = effluent.payload.F;
this.Cs_in = effluent.payload.C;
this.notifyOutputChanged();
```
If you change the reactor's effluent shape, this is the line to update.
## 7. Lifecycle — what one stateChange does
```mermaid
sequenceDiagram
participant reactor as upstream reactor
participant settler as settler
participant pump as return pump child
participant downstream as downstream consumer
participant out as Port-0 output
reactor->>settler: emitter.emit('stateChange')
settler->>reactor: pull getEffluent
reactor-->>settler: { F, C[13] }
settler->>settler: F_in = F, Cs_in = C
settler->>pump: read measurements.flow.measured.atequipment
pump-->>settler: returnFlow
settler->>settler: getEffluent — split into 3 inlets
settler->>out: [Fluent inlet=0, Fluent inlet=1, Fluent inlet=2]
out->>downstream: 3 msgs on Port 0
```
The split runs lazily inside `getEffluent`: each call recomputes from current `F_in`, `Cs_in`, `C_TS`, and the pump's reported `flow.measured.atequipment`.
## 8. Data model — `getOutput()`
Port 0 carries the 3-envelope Fluent stream directly; Port 1 (this snapshot) is the scalar dashboard view.
<!-- BEGIN AUTOGEN: data-model -->
| Key | Type | Unit | Sample |
|---|---|---|---|
| `C_TS` | number | — | `2500` |
| `F_eff` | number | — | `0` |
| `F_in` | number | — | `0` |
| `F_return` | number | — | `0` |
| `F_surplus` | number | — | `0` |
<!-- END AUTOGEN: data-model -->
**Concrete sample** (typical operating point):
```json
{
"F_in": 1000,
"C_TS": 2500,
"F_eff": 850.0,
"F_surplus": 50.0,
"F_return": 100.0
}
```
`F_eff + F_surplus + F_return = F_in` always holds (modulo float). Particulates concentrate by `F_in / F_s` in the surplus + return streams.
## 9. Configuration — editor form ↔ config keys
```mermaid
flowchart TB
subgraph editor["Node-RED editor form"]
f1[Name]
f2[Position vs parent]
f3[Logging level]
end
subgraph config["Domain config slice"]
c1[general.name]
c2[functionality.positionVsParent]
c3[general.logging.logLevel]
end
f1 --> c1
f2 --> c2
f3 --> c3
```
| Form field | Config key | Default | Range | Where used |
|---|---|---|---|---|
| Name | `general.name` | `Settler` | string | display + Port-1 topic |
| ID | `general.id` | `null` | nullable string | child registration key |
| Software type | `functionality.softwareType` | `settler` | string | parent-side router filter |
| Position vs parent | `functionality.positionVsParent` | `downstream` | enum: `upstream` / `atEquipment` / `downstream` | parent-side routing |
| Logging level | `general.logging.logLevel` | `info` | enum | logger threshold |
Settler has no operational config of its own — all behaviour is driven by the runtime state (`F_in`, `Cs_in`, `C_TS`). Tune behaviour by feeding it different reactor effluents or `C_TS` measurements.
## 10. State chart
Skipped — settler is stateless. Each `stateChange` (or `data.influent`, or `quantity (tss)` update) recomputes the 3 Fluent streams from scratch.
## 11. Examples
| Tier | File | What it shows | Status |
|---|---|---|---|
| Basic | `examples/basic.flow.json` | Inject `data.influent`, watch 3-stream split | ✅ in repo |
| Integration | `examples/integration.flow.json` | reactor (upstream) + settler + return pump | ✅ in repo |
| Edge | `examples/edge.flow.json` | F_s clamp + zero-influent fallback | ✅ in repo |
One screenshot per tier where helpful. PNG ≤ 200 KB under `wiki/_partial-screenshots/settler/`.
## 12. Debug recipes
| Symptom | First thing to check | Where to look |
|---|---|---|
| `F_eff` negative or NaN | `C_TS` zero or `Cs_in[12]` huge. F_s clamp should prevent — confirm clamp present. | `specificClass.js → getEffluent` |
| Settler never updates after reactor changes | Reactor child not on `'upstream'` position, or listener attached to wrong emitter. | `_connectReactor` — listens on `reactor.emitter`, NOT `measurements.emitter`. |
| Return-sludge flow = 0 | `returnPump.measurements.type('flow').variant('measured').position('atEquipment')` empty. Wire a flow measurement on the pump. | `_connectMachine`, pump measurement chain. |
| 3 Fluent envelopes not arriving downstream | `payload.inlet` selector on the downstream reactor mismatches (0=eff, 1=surplus, 2=return). | downstream reactor's `data.fluent` handler. |
| `quantity (tss)` updates don't change `C_TS` | Measurement child's `asset.type` not `quantity (tss)` exactly. | `_updateMeasurement` switch. |
## 13. When you would NOT use this node
- Use settler for **secondary clarification** downstream of a biological reactor. For primary sedimentation (raw sewage), the species-7-12 zeroing is wrong — model that as a separate process.
- Don't use settler as a generic mass-balance node — the 13-species ASM3 vector is hard-coded.
- Skip settler when the downstream reactor doesn't need a 3-stream split (e.g. single-tank SBR). A direct reactor → reactor wire is lighter.
## 14. Known limitations / current issues
| # | Issue | Tracked in |
|---|---|---|
| 1 | Only one `upstreamReactor` slot — multi-reactor settlers not supported (last registration wins). | `_connectReactor` |
| 2 | TSS mass balance uses index 12 (`X_TS`) hard-coded — coupled tightly to ASM3 species ordering. | `getEffluent`, `_updateMeasurement` |
| 3 | Settler depends on `mathjs` (~14 MB install) but only uses it transitively via reactor; no direct mathjs call in settler code. | `package.json` |
| 4 | No flow-balance check at runtime — if particulate concentration drives F_s above F_in, the clamp masks an upstream bug rather than warning. | `getEffluent` |