Files
EVOLV/wiki/Functional-Overview.md
znetsixe 123ef6fca3 wiki + submodules: Functional Overview page + bump pumpingStation / generalFunctions / monster
Submodule pointers
- pumpingStation: realistic basin defaults, ramp-foot visual fix, manual-mode
  observability, new 02-Dashboard.json (charts + raw-output table), wiki
  Home/Reference-Examples with screenshots + demo GIF.
- generalFunctions: pumpingStation config schema defaults aligned with the
  new editor drag-in values; startLevel description corrected (ramp foot is
  inflowLevel, not startLevel).
- monster: examples cleanup — drop pre-refactor flows, ship single
  02-integrated-e2e.json.

Wiki
- New wiki/Functional-Overview.md: companion to Architecture covering the
  process side — what each node physically represents and which control
  objective it serves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:52:36 +02:00

627 lines
25 KiB
Markdown

# Functional Overview
![code-ref](https://img.shields.io/badge/code--ref-5ae8788-blue)
![view](https://img.shields.io/badge/view-process_%2F_water_side-orange)
> [!NOTE]
> What each EVOLV node physically represents and what control objective it serves. Companion to [Architecture](Architecture), which describes the **code** shape; this page describes the **process** the code models. Read this when you want to know "what is actually happening in the water", not "what is happening in the JavaScript".
---
## Plant-level process flow
Real water flows left to right through real equipment. EVOLV models a subset of that equipment as Node-RED nodes. The coloured nodes below are modelled by EVOLV; the grey ones are upstream / downstream of the EVOLV-modelled section.
```mermaid
flowchart LR
inf["Influent"]:::extern
scr["Coarse screens"]:::extern
grit["Grit chamber"]:::extern
ps1["Inlet lift station"]:::pc
primary["Primary settler"]:::extern
reactor["Aerobic reactor"]:::unit
secondary["Secondary settler"]:::unit
disinf["Disinfection / UV"]:::extern
eff["Effluent"]:::extern
sludge["Sludge handling"]:::extern
inf --> scr --> grit --> ps1 --> primary --> reactor --> secondary --> disinf --> eff
secondary -. RAS .-> reactor
secondary -. WAS .-> sludge
classDef pc fill:#0c99d9,color:#fff,stroke:#075a82,stroke-width:2px
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef extern fill:#f0f0f0,color:#666,stroke:#bbb,stroke-width:1px,stroke-dasharray:4 4
```
### Plant step to EVOLV node mapping
| Plant step | What happens physically | EVOLV node(s) |
|:---|:---|:---|
| Inlet lift station | Wet-well buffer; pumps raise water to plant elevation | pumpingStation + machineGroupControl + rotatingMachine |
| Aerobic reactor | Bacteria consume NH4, COD under aeration; O2 supplied by diffusers | reactor + diffuser + measurement |
| Secondary settler | Biomass settles by gravity; clean water overflows; sludge returns or is wasted | settler + rotatingMachine (RAS pump) |
| Flow distribution | Multi-valve manifold for effluent split, airflow distribution, RAS proportioning | valveGroupControl + valve |
| Composite sampling | Flow-proportional sample buildup for lab analysis | monster + measurement |
| Telemetry to UI | Time-series storage + dashboard provisioning | dashboardAPI (Grafana) + Port 1 to InfluxDB |
---
## pumpingStation &mdash; wet-well lift station
### Physical view
A wet-well buffer at low elevation. Inflow arrives by gravity from the upstream sewer; pumps lift water to plant elevation. Level is the controlled variable.
```
inflow
|
v
+---------------------+
| | <-- overflow weir (safety, alarm)
| |
| ~ ~ ~ ~ ~ ~ ~ ~ ~ | <-- startLevel (e.g. 80% of basin)
| |
| | <-- band where 1 pump runs
| |
| ~ ~ ~ ~ ~ ~ ~ ~ ~ | <-- stopLevel (e.g. 30%)
| |
| | <-- band where pumps idle
| |
| ___________________| <-- dryRunLevel (cutoff, alarm)
| | | | | |
| v v v v |
| P1 P2 P3 P4 |
+--|----|----|----|---+
v v v v
outflow (to next stage)
```
### Process variables
| Variable | Typical range | Unit | Source |
|:---|:---|:---|:---|
| Inflow Q_in | 0 to 2000 | m3/h | gravity sewer; measured by inlet flow meter |
| Outflow Q_out | 0 to 2000 | m3/h | sum of running pump flows |
| Level | 0 to 5 | m | level sensor (radar, ultrasonic, hydrostatic) |
| Volume | 0 to V_max | m3 | basin geometry &times; level |
| Predicted ETA full / empty | seconds | s | (V_max &minus; V) / (Q_in &minus; Q_out) |
### Control objective
| Goal | Mechanism |
|:---|:---|
| Keep level in operating band | Schmitt-trigger hysteresis: start pumps at `startLevel`, stop at `stopLevel` |
| Avoid rapid pump cycling | `stopLevel` strictly below `startLevel` (deadband) |
| Avoid overflow | High-level alarm; safety controller can override |
| Avoid running dry | Low-level cutoff stops all pumps |
| Stay near pump BEP | Demand is shared by machineGroupControl using each pump's curve |
### EVOLV implementation
- `pumpingStation.configure()` builds a `BasinGeometry` from `config.basin` plus a `FlowAggregator` that integrates volume from inflow / outflow measurements per tick.
- Level + inflow + outflow arrive as `measurement` children with positions `inflow`, `outflow`, `atequipment` for the level.
- The `control` strategy module picks one of several modes (level-based, flow-based, time-based). The level-based strategy implements the Schmitt trigger and shifted-ramp behaviour.
- `SafetyController` guards against panic / dry-run / overfill conditions and can block dispatch.
---
## rotatingMachine &mdash; single pump or compressor
### Physical view
A centrifugal pump (or compressor) characterised by its supplier curves. Speed sets where on the curve the machine operates. The operating point is the intersection of pump curve and system curve.
```
Q-H curve (head vs flow) Q-P curve (power vs flow)
^ ^
H | * P | *
| * | *
| * <-- BEP | *
| * | * <-- BEP region
| * | *
|________________ Q |____________________ Q
flow [m3/h] flow [m3/h]
Operating point = (system curve) intersect (pump curve at running speed)
```
### Process variables
| Variable | Typical range | Unit |
|:---|:---|:---|
| Flow Q | 1 to 1000 | m3/h |
| Head H (or differential pressure) | 1 to 100 | m (or bar) |
| Power P | 1 to 500 | kW |
| Efficiency &eta; | 0.3 to 0.85 | &mdash; |
| Speed N | 0 to 100 | % of rated |
### Physical state machine (water-side)
```mermaid
stateDiagram-v2
[*] --> off
off --> warmingup: cmd.startup
warmingup --> operational: warmup time elapsed
operational --> accelerating: setpoint changes
accelerating --> operational: target reached
operational --> decelerating: setpoint reduced
decelerating --> operational: target reached
operational --> coolingdown: cmd.shutdown
coolingdown --> off: cooldown time elapsed
operational --> emergencystop: cmd.estop
warmingup --> emergencystop: cmd.estop
emergencystop --> off: cmd.reset
```
### Control objective
| Goal | Mechanism |
|:---|:---|
| Deliver commanded flow / speed | Internal FSM ramps speed up / down within configured rates |
| Predict outputs before sensors react | Q + P + &eta; predicted from speed + measured pressure differential via characteristic curves |
| Detect drift | `drift/` module compares predicted vs measured; fires HealthStatus levels 0..3 |
| Protect during warmup / cooldown | Some transitions are non-interruptible (configurable per machine) |
### EVOLV implementation
- `curves/` module loads supplier characteristic curves (Q-H, Q-P, Q-&eta;); supports multi-dataset assets.
- `prediction/` module interpolates curve values at current operating point.
- `state/` module owns the FSM with configurable warmup / cooldown / ramp times.
- `drift/` module assesses prediction quality and emits `HealthStatus`.
---
## machineGroupControl &mdash; multi-pump load sharing
### Physical view
Multiple pumps share a common discharge header. The group operating point is where the **sum of pump curves** intersects the system curve. The load-sharing problem is: which pumps run at what speed to deliver demand at the lowest combined power.
```
Pump A curve Pump B curve Pump C curve
^ ^ ^
| | |
+-> share Q_A -->+-> share Q_B -->+-> share Q_C
|
sum = Q_demand
v
total power = P_A + P_B + P_C
minimize subject to per-pump curve constraints
```
### Process variables
| Variable | Source |
|:---|:---|
| Group demand Q_d | Inbound from parent (pumpingStation, UI, scheduler) |
| Per-pump setpoint | Computed each tick |
| Pressure (downstream) | Measurement child, position `downstream` |
| Group totals (flow, power, efficiency) | Sum / weighted average of per-pump predictions |
### Control objective
| Goal | Mechanism |
|:---|:---|
| Deliver Q_d at lowest total power | `GroupOperatingPoint` solver picks per-pump shares |
| Avoid frequent pump start / stop | Hysteresis on pump count + `NCog` switching metric |
| Stay near per-pump BEP | Penalise operating points far from BEP in the solver |
| Settle latest demand if upstream fires faster than children absorb | `DemandDispatcher` (LatestWinsGate) keeps only the freshest dispatch in flight |
### EVOLV implementation
- `dispatch/DemandDispatcher` wraps a `LatestWinsGate` so a rapid stream of demands collapses to the most recent.
- `efficiency/groupEfficiency` computes mean group efficiency at the current shares.
- `totals/TotalsCalculator` aggregates flow / power across active machines.
---
## valveGroupControl &mdash; multi-valve flow distribution
### Physical view
Multiple valves on a distribution manifold. Each valve has a flow coefficient K_v that varies with position. The group must split a total available flow between branches.
```
upstream pressure P_up
|
v
+---+---+---+
| | | |
v v v v
Kv1 Kv2 Kv3 Kv4 <-- per-valve K_v
| | | |
Q1 Q2 Q3 Q4 <-- per-branch flow
Q_i = K_v_i * sqrt(P_up - P_branch_i)
sum(Q_i) = Q_available (from upstream flow source)
```
### Process variables
| Variable | Typical range | Unit |
|:---|:---|:---|
| Valve position | 0 to 100 | % |
| K_v at full open | 1 to 1000 | m3/h / sqrt(bar) |
| Differential pressure across valve | 0.1 to 5 | bar |
| Per-branch flow split | percentage of total | % |
### Control objective
| Goal | Mechanism |
|:---|:---|
| Achieve target per-branch flow split | Solve per-valve K_v from inverse characteristic |
| Respect upstream availability | Read total flow from registered flow source (pumpingStation, MGC, etc.) |
| Honour valve position limits | Clamp K_v to physical valve range |
### Note on softwareType registration
`valveGroupControl.configure()` registers five softwareTypes &mdash; `valve`, `machine`, `machinegroup`, `pumpingstation`, `valvegroupcontrol`. Only `valve` is a true S88 child. The other four are flow-source registrations: VGC reads their flow output to know how much it has to distribute.
---
## reactor &mdash; bioreactor (ASM kinetics)
### Physical view
A tank where bacteria consume substrate under aeration. Continuous-flow operation (CSTR or PFR). The state of the reactor is described by 13 ASM state variables (soluble + particulate fractions of organic matter, ammonia, nitrate, biomass, alkalinity, oxygen).
```
air from diffuser
|
v bubbles
Q_in +-------+-------+ Q_out
---->| ~ ~ ~ ~ ~ ~ |---->
| ~ ~ ~ ~ ~ ~ |
C_in | ~ ~ ~ ~ ~ | C_out
| ~ ~ ~ ~ ~ ~ |
| ~ ~ ~ ~ ~ ~ |
+----------------------+
Mass balance per ASM component i:
V * dC_i/dt = Q_in * C_in_i - Q_out * C_out_i + V * r_i(C, T, DO, ...)
inflow term outflow term reaction term
```
### Process variables
| Variable | Typical range | Unit |
|:---|:---|:---|
| Volume V | 100 to 10000 | m3 |
| Hydraulic retention time HRT | 4 to 24 | h |
| Sludge retention time SRT | 5 to 30 | d |
| MLSS | 2000 to 5000 | mg/L |
| Temperature | 5 to 30 | degC |
| DO setpoint | 1 to 3 | mg/L |
| NH4 effluent target | < 1 | mg/L |
| NO3 effluent | 0 to 15 | mg/L |
### Control objective
| Goal | Mechanism |
|:---|:---|
| Maintain DO setpoint | DO measurement &rarr; diffuser airflow loop (closed externally) |
| Achieve effluent quality | Manage HRT via reactor inflow, SRT via WAS rate |
| Operate stably across temperature | Kinetics are temperature-corrected via Arrhenius factors |
### Engine choice
| Engine | When to use |
|:---|:---|
| CSTR | Single well-mixed tank or short reactor |
| PFR | Long, narrow plug-flow reactor; discretised into grid cells along the flow path |
Set via `config.reactor_type`.
### EVOLV implementation
- `kinetics/baseEngine.js` &mdash; common state vector + boundary-condition handling.
- `kinetics/cstr.js` &mdash; single-zone integrator.
- `kinetics/pfr.js` &mdash; multi-zone PFR with a grid.
- Diffuser fires `data.otr` on its emitter; reactor subscribes and treats OTR as an O2 source term.
- Downstream `settler` subscribes to reactor `stateChange`; the settler reads effluent composition each tick.
---
## settler &mdash; secondary clarifier
### Physical view
A wide, shallow tank where biomass settles by gravity. A sludge blanket forms at the bottom; clean water overflows the rim at the top. A return-pump sucks settled sludge back to the reactor; a smaller waste stream removes excess (WAS).
```
Q_in (from reactor)
+ biomass C_in
|
v
+------------------------------+
| clean water |---> overflow Q_out
| | low TSS
| - - - - - - - - - - - - - | <-- top of sludge blanket
| (settling zone) |
| = = = = = = = = = = = = = |
| compacting biomass |
| ########################### | <-- sludge blanket
+-----------+---+--------------+
| |
v v
underflow to reactor (RAS)
high TSS small bleed (WAS)
```
### Process variables
| Variable | Typical range | Unit |
|:---|:---|:---|
| Surface area | 50 to 2000 | m2 |
| Depth | 3 to 5 | m |
| Surface loading rate (SLR) | 0.5 to 2 | m/h |
| RAS flow | 50 to 150 | % of inflow |
| WAS flow | 1 to 5 | % of inflow |
| Effluent TSS | < 30 | mg/L |
| Underflow TSS | 6000 to 12000 | mg/L |
### Control objective
| Goal | Mechanism |
|:---|:---|
| Keep sludge blanket below overflow weir | RAS pump rate adjusted to inflow |
| Maintain reactor MLSS target | WAS rate set as fraction of inflow |
| Avoid blanket carryover | Limit SLR; alarm on rising blanket level |
### EVOLV implementation
- `settler._connectReactor` attaches `emitter.on('stateChange', ...)` to the upstream reactor, pulling effluent composition each tick.
- `settler._connectMachine` registers the RAS pump (a `rotatingMachine`) as a child.
- Settling-velocity model (Tak&aacute;cs or Vesilind) is in the settler's `src/`; see [Settling Models](Concept-Settling-Models).
---
## diffuser &mdash; aeration device
### Physical view
A perforated panel or membrane at the bottom of the reactor that releases fine bubbles. Bubbles rise through the water column; oxygen dissolves into the water across the gas-liquid interface.
```
reactor side
~ ~ ~ ~ ~ dissolved O2 enters water
~ ~ ~ ~ ~
o o o o o o <-- bubbles rising
o o o o o o
o o o o o
ooo ooo
ooo oo
+----[========]----+ <-- diffuser membrane / panel
| | | | | | | | | compressed air manifold
+-+--+-+--+-+--+-+-+
^
air inflow
(from blower upstream)
```
### Process variables
| Variable | Typical range | Unit |
|:---|:---|:---|
| Airflow per diffuser | 1 to 10 | Nm3/h |
| Header pressure | 0.3 to 0.7 | bar |
| Water depth (above diffuser) | 4 to 6 | m |
| K_La (volumetric mass-transfer coefficient) | 1 to 20 | 1/h |
| Alpha factor (wastewater vs clean water) | 0.5 to 0.9 | &mdash; |
| OTR (oxygen transfer rate) | 1 to 5 | kg-O2/h per diffuser |
### Control objective
| Goal | Mechanism |
|:---|:---|
| Deliver enough OTR to meet reactor DO setpoint | Modulate airflow via upstream blower / valve |
| Distribute air evenly across panels | Manifold sizing; sometimes a valveGroupControl on the air side |
| Avoid over-aeration (energy waste) | DO feedback loop |
### EVOLV implementation
- `diffuser` reads `headerPressure`, water depth, airflow as inputs.
- Computes OTR from K_La (configurable, supplier-specific), alpha factor, water properties.
- Emits `data.otr` on its emitter. Reactor subscribes via `emitter.on('otr', ...)` &mdash; this is **not** a child-register handshake.
---
## valve &mdash; single valve actuator
### Physical view
A control valve in a pipe. Position 0..100% maps to K_v via the valve's characteristic curve (linear, equal-percentage, or quick-opening). Flow through the valve obeys `Q = K_v * sqrt(dP)`.
```
flow +---+ flow
----------| |----------->
| ===== <-- closure element (plug, ball, disc, gate)
| |
+---+
^
position 0..100%
position --(characteristic curve)--> K_v
Q = K_v * sqrt(P_up - P_down)
```
### Process variables
| Variable | Typical range | Unit |
|:---|:---|:---|
| Position | 0 to 100 | % |
| K_v at full open | 1 to 1000 | m3/h / sqrt(bar) |
| Differential pressure | 0.1 to 5 | bar |
| Stroke time (close to open) | 10 to 60 | s |
### Physical state machine
valve shares the rotatingMachine state model. States: `off`, `idle`, `warmingup`, `operational`, `accelerating` (opening / closing), `decelerating`, `coolingdown`, `emergencystop`, `maintenance`. `warmingup` and `coolingdown` are protected (cannot be aborted).
### Control objective
| Goal | Mechanism |
|:---|:---|
| Reach commanded position | Move at the configured `reactionSpeed` rate |
| Avoid water hammer | Limit how fast position changes |
| Provide flow feedback to upstream | Computed Q from current K_v and measured dP |
### EVOLV implementation
- `valve` registers `measurement` children for position / pressure feedback.
- Inherits the shared FSM (`generalFunctions` state config) with rotatingMachine.
- Characteristic curve is supplier-specific and loaded similarly to pump curves.
---
## measurement &mdash; sensor signal conditioning
### Physical view
A field sensor (level meter, flow meter, pressure transducer, DO probe, ...) outputs a raw signal. EVOLV's `measurement` node wraps that signal: scales it from instrument units (mA, mV, raw counts) to engineering units, smooths it, rejects outliers, and publishes the result to a parent process node.
```
Sensor 4-20 mA (or digital, or MQTT)
in the pipe -----------------+
|
v
+--------------------------------+
| measurement node |
| |
| raw input |
| | |
| v scaling (mA -> EU) |
| v smoothing |
| v outlier rejection |
| v calibration offset |
| |
+-------+------------------------+
|
v data.<type>
parent process node
(pumpingStation, reactor, ...)
```
### Three input modes
| Mode | Source | When to use |
|:---|:---|:---|
| analog | 4-20 mA, 0-10 V, raw scaled value | Direct PLC / IO-card analog input |
| digital | Boolean (on / off, ok / fault) | Limit switches, status contacts |
| MQTT | External MQTT broker topic | Field bus, sensor with its own gateway |
### Process variables (examples)
| Type | Typical range | Unit | Example sensor |
|:---|:---|:---|:---|
| level | 0 to 5 | m | radar level meter |
| flow | 0 to 2000 | m3/h | electromagnetic flowmeter |
| pressure | 0 to 10 | bar | piezo pressure transmitter |
| temperature | 5 to 40 | degC | PT100 RTD |
| DO | 0 to 10 | mg/L | optical dissolved-O2 probe |
| NH4 | 0 to 50 | mg/L | ion-selective electrode |
| TSS | 0 to 5000 | mg/L | optical turbidity sensor |
### Control objective
| Goal | Mechanism |
|:---|:---|
| Deliver trustworthy values to parent | Pipeline: scaling &rarr; smoothing &rarr; outlier &rarr; publish |
| Survive sensor faults | Outlier rejection + sticky-last-good behaviour |
| Calibrate against reference | `cmd.calibrate` triggers a calibration cycle |
---
## monster &mdash; composite sampling
### Physical view
A virtual composite sampler: imagine a small bucket beside the pipe. Every time a unit volume of water flows past, a unit dose of that water is added to the bucket. After a sampling period (e.g. 24 h) the bucket contains a flow-proportional composite of every concentration over that period.
```
____________________
| sampling bucket | <-- accumulated sample
| ~~~~~~ |
| ~~~~~ |
| ~ |
|_____________________|
|
| sampling dose dV at every flow increment
v
-----++------------------------+-----> main pipe
| (flow Q, conc C) flow
v
dV = (Q * dt / total_flow_target) * sample_volume
composite_C(t) = integral( C(s) * dV(s) ) / total_dV
```
### Process variables
| Variable | Typical range | Unit |
|:---|:---|:---|
| Sampling period | 1 to 24 | h |
| Bucket volume target | 2 to 10 | L |
| Sample doses per period | 24 to 96 | &mdash; |
| Output composite concentration | as configured per parameter | mg/L, NTU, &hellip; |
### Control objective
| Goal | Mechanism |
|:---|:---|
| Build a representative composite sample over the period | Flow-proportional dosing |
| Produce reportable averages | Each tick, accumulate flow-weighted concentration |
| Reset for next period | At end of period, emit composite and reset bucket |
### Gotchas
| Gotcha | Detail |
|:---|:---|
| `assetType` must be `"flow"` exactly | Sub-types like `"flow-electromagnetic"` are silently ignored by monster's child router |
| `constraints.flowmeter` not forwarded | Toggling proportional-vs-time mode has no runtime effect in current code. Tracked in `.claude/refactor/OPEN_QUESTIONS.md` |
---
## dashboardAPI &mdash; Grafana provisioning
### Physical view
There is none. `dashboardAPI` does not model any piece of water-treatment equipment. It is a utility that auto-generates Grafana dashboards from a node's softwareType + measurements, so operators see the right panels per equipment without hand-building dashboard JSON.
### Operational role
| Trigger | Effect |
|:---|:---|
| Any EVOLV node sends `child.register` to dashboardAPI | dashboardAPI composes a dashboard JSON from the template for that softwareType and POSTs an upsert to Grafana |
| Telemetry arrives in InfluxDB (Port 1 of process nodes) | Grafana panels query InfluxDB and render the trends |
dashboardAPI is the one node in the platform that does not extend `BaseDomain` (it is a passive HTTP bridge). See `.claude/refactor/OPEN_QUESTIONS.md`.
---
## Where it all fits
If you imagine a wastewater plant from inlet to outlet, every EVOLV node is a piece of equipment you would see on a P&ID. The software's job is to model that equipment well enough that:
1. Operators can run the plant without watching the water directly (predictions + telemetry).
2. New plants can be commissioned by composing standard nodes (no bespoke control code).
3. Anomalies surface as `HealthStatus` flags before they become operator problems.
See [Topology Patterns](Topology-Patterns) for how to assemble these nodes into a working plant.
---
## Related pages
| Page | Why |
|:---|:---|
| [Home](Home) | Top-level node map and S88 hierarchy |
| [Topology Patterns](Topology-Patterns) | Standard assemblies of these nodes |
| [Architecture](Architecture) | The **code** counterpart of this page |
| [Topic Conventions](Topic-Conventions) | How process variables travel between nodes |
| [Telemetry](Telemetry) | How process variables land in InfluxDB and Grafana |
| [ASM Models](Concept-ASM-Models) | The reactor's biological kinetics in detail |
| [Pump Affinity Laws](Concept-Pump-Affinity-Laws) | Pump curve physics |
| [Settling Models](Concept-Settling-Models) | Settler physics |
| [Signal Processing &mdash; Sensors](Concept-Signal-Processing-Sensors) | Measurement node pipeline |