Files
EVOLV/wiki/Topology-Patterns.md
znetsixe 2ccc8aea9e wiki: master EVOLV wiki refactor — 7 new pages + corrected Home
Complete redesign of the platform-level wiki. Previous Home.md had a
broken Mermaid diagram (showed pumpingStation → valveGroupControl as a
parent/child edge, which isn't in any configure() declaration). Audit
of all 12 specificClass.js configure() calls drives the new ground-truth
hierarchy.

New pages:
- Home.md (rewritten — accurate mermaid, full node + concept index)
- Architecture.md (3-tier code structure, generalFunctions API surface,
  child-registration sequence)
- Topology-Patterns.md (5 verified plant configurations + worked example)
- Topic-Conventions.md (set./cmd./evt./data./child. + unit policy + S88
  palette + measurement key shape + status badge + HealthStatus)
- Telemetry.md (Port 0/1/2 contracts + InfluxDB line-protocol layout +
  FlowFuse charts + Grafana provisioning)
- Getting-Started.md (clone, install, Docker vs local, first example)
- Glossary.md (S88, EVOLV runtime, WWTP, pumps, control, project terms)
- _Sidebar.md (gitea wiki navigation)

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

249 lines
9.3 KiB
Markdown

# Topology Patterns
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`**
Typical plant configurations and how nodes wire together. Each pattern is **verified** against the corresponding nodes' `configure()` declarations.
## Pattern 1 — Pumping station with grouped pumps
The canonical wet-well lift station. One basin, one demand controller (`pumpingStation`), one load-sharing coordinator (`machineGroupControl`), N pumps. Level + flow measurements feed the basin model.
```mermaid
flowchart TB
subgraph PC["Process Cell"]
ps[pumpingStation]:::pc
end
subgraph UN["Unit"]
mgc[machineGroupControl]:::unit
end
subgraph EM["Equipment"]
rmA[rotatingMachine A]:::equip
rmB[rotatingMachine B]:::equip
rmC[rotatingMachine C]:::equip
end
subgraph CM["Control Module"]
ml[measurement: level]:::ctrl
mfin[measurement: inflow]:::ctrl
mpA[measurement: pressure A]:::ctrl
mpB[measurement: pressure B]:::ctrl
mpC[measurement: pressure C]:::ctrl
end
ps --> mgc
mgc --> rmA
mgc --> rmB
mgc --> rmC
ml -. data .-> ps
mfin -. data .-> ps
mpA -. data .-> rmA
mpB -. data .-> rmB
mpC -. data .-> rmC
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
```
**Data flow:**
- `pumpingStation` computes basin volume + level dynamics from inflow/outflow measurements.
- `pumpingStation` emits a demand setpoint downstream to `machineGroupControl` on its Port 0 or via `set.demand`.
- `machineGroupControl` solves a per-pump operating point using each pump's characteristic curve + measured pressure, sends `set.setpoint` to each `rotatingMachine`.
- Each `rotatingMachine` runs its own FSM (idle/warmingup/operational/coolingdown/emergencystop) and predicts flow/power from pressure + speed.
**Notes:**
- For a single-pump station, `pumpingStation` can register `rotatingMachine` directly (skip the MGC) — `pumpingStation`'s `configure()` accepts `machine` as a child softwareType.
- For two stations in series, the downstream PS can register the upstream PS as a `pumpingstation` softwareType source.
## Pattern 2 — Reactor / settler train with aeration
Biological treatment line. Reactor runs ASM kinetics, diffuser drives O₂ transfer, settler clarifies effluent and returns sludge via a return pump.
```mermaid
flowchart TB
subgraph UN["Unit"]
reactor[reactor]:::unit
settler[settler]:::unit
end
subgraph EM["Equipment"]
diff[diffuser]:::equip
rp[rotatingMachine<br/>return pump]:::equip
end
subgraph CM["Control Module"]
mt[measurement: temperature]:::ctrl
mdo[measurement: dissolved O₂]:::ctrl
mts[measurement: TSS]:::ctrl
end
reactor ==stateChange==> settler
diff -. OTR data .-> reactor
settler -->|return pump child| rp
mt -. data .-> reactor
mdo -. data .-> reactor
mts -. data .-> settler
mdo -. data .-> diff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
```
**Data flow:**
- `reactor.configure()` registers `measurement` (temperature, DO) and upstream `reactor` (for chained tanks).
- `diffuser` emits `data.otr` on its emitter; reactor subscribes via `emitter.on('otr', …)`**not** a child registration, just a data subscription.
- `reactor` emits `stateChange` after every kinetics step. `settler._connectReactor` subscribes via `emitter.on('stateChange', …)` and pulls effluent composition.
- `settler.configure()` accepts `reactor` (the upstream), `machine` (return pump), and `measurement` children.
**Notes:**
- Reactor supports two kinetics engines: CSTR (continuous-stirred tank) and PFR (plug-flow). Set via `config.reactor_type`.
- DO setpoint feedback (DO measurement → diffuser airflow) is not wired automatically — connect via a small control function or use a `valveGroupControl` upstream of an airflow valve.
## Pattern 3 — Valve group on a distribution manifold
Multi-valve flow distribution. VGC computes per-valve K_v shares to satisfy a target distribution while respecting upstream flow availability.
```mermaid
flowchart TB
subgraph PC["Process Cell"]
ps[pumpingStation<br/>upstream flow source]:::pc
end
subgraph UN["Unit"]
vgc[valveGroupControl]:::unit
end
subgraph EM["Equipment"]
vA[valve A]:::equip
vB[valve B]:::equip
vC[valve C]:::equip
end
ps -. flow source .-> vgc
vgc --> vA
vgc --> vB
vgc --> vC
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
```
**Important detail:** `valveGroupControl.configure()` registers four extra softwareTypes — `machine`, `machinegroup`, `pumpingstation`, `valvegroupcontrol`**not as S88 children** but as **flow sources**. VGC uses them to read upstream flow availability when computing per-valve splits. The arrow above is `child.register` from pumpingStation to vgc; the semantic relationship is "VGC knows about this upstream flow producer", not "VGC controls pumpingStation".
## Pattern 4 — Composite sampling
`monster` runs a proportional sampling program — accumulates samples in a bucket based on integrated flow. Used as a virtual sensor for downstream lab analysis.
```mermaid
flowchart TB
subgraph UN["Unit"]
monster[monster]:::unit
end
subgraph CM["Control Module"]
mflow[measurement: flow<br/>assetType MUST be 'flow']:::ctrl
mq[measurement: any quality<br/>e.g. NH₄, COD]:::ctrl
end
mflow -. data .-> monster
mq -. data .-> monster
classDef unit fill:#50a8d9,color:#000
classDef ctrl fill:#a9daee,color:#000
```
**Gotchas:**
- `measurement.config.asset.type` MUST be `"flow"` exactly — `"flow-electromagnetic"` or any sub-type is silently ignored by monster's child router.
- `monster.config.constraints.flowmeter` exists in the schema but is **not forwarded** by `buildDomainConfig` — toggling proportional-vs-time mode has no effect at runtime. (Tracked in OPEN_QUESTIONS.md.)
## Pattern 5 — Dashboard provisioning
`dashboardAPI` doesn't operate on data — it generates Grafana dashboards. Any node can register with `dashboardAPI` via `child.register`; dashboardAPI then composes a dashboard JSON from the node's softwareType + measurements and POSTs to Grafana's HTTP API.
```mermaid
flowchart LR
subgraph EVOLV["EVOLV process nodes"]
ps[pumpingStation]:::pc
mgc[machineGroupControl]:::unit
rm[rotatingMachine]:::equip
end
subgraph UT["Utility"]
dash[dashboardAPI]:::util
end
grafana[(Grafana<br/>HTTP API)]:::ext
ps -. child.register .-> dash
mgc -. child.register .-> dash
rm -. child.register .-> dash
dash -->|POST /api/dashboards/db| grafana
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef util fill:#dddddd,color:#000
classDef ext fill:#fff2cc,color:#000
```
**Notes:**
- `dashboardAPI` is the one node in the platform that doesn't extend `BaseDomain` (it's a passive HTTP bridge — see OPEN_QUESTIONS.md for the deferral decision).
- The `meta` field of dashboardAPI's outbound msg carries `{nodeId, softwareType, uid, title}` for correlating responses.
## Putting it all together — example plant
A small WWTP combining all patterns:
```mermaid
flowchart TB
subgraph PC["Process Cell"]
ps1[pumpingStation<br/>inlet lift]:::pc
ps2[pumpingStation<br/>RAS pumping]:::pc
end
subgraph UN["Unit"]
mgc1[MGC inlet]:::unit
mgc2[MGC RAS]:::unit
vgc[VGC effluent split]:::unit
r1[reactor aerobic]:::unit
s1[settler]:::unit
mon[monster<br/>composite sampler]:::unit
end
subgraph EM["Equipment"]
rm1[pump A]:::equip
rm2[pump B]:::equip
rm3[RAS pump]:::equip
d1[diffuser]:::equip
v1[valve 1]:::equip
v2[valve 2]:::equip
end
ps1 --> mgc1
mgc1 --> rm1
mgc1 --> rm2
ps2 --> mgc2
mgc2 --> rm3
r1 ==stateChange==> s1
s1 -->|return pump| rm3
d1 -. OTR .-> r1
ps2 -. flow source .-> vgc
vgc --> v1
vgc --> v2
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
```
This is the kind of diagram each `wiki/Home.md` per node should be able to fit into — every edge is reproducible from a `configure()` declaration.
## Anti-patterns
-`pumpingStation → valveGroupControl` as a parent/child edge. PS does not register VGC. VGC registers PS as a *flow source*; the edge goes the other way semantically.
- ❌ A `diffuser → reactor` child registration. Diffuser emits OTR via its emitter; reactor subscribes. No `child.register` handshake.
-`measurement` parented under `dashboardAPI`. dashboardAPI accepts any node for Grafana provisioning, but `measurement` registers with the **process** node it's monitoring, not with dashboardAPI.
## Related pages
- [Home](Home) — top-level node map
- [Architecture](Architecture) — 3-tier code structure + generalFunctions API
- [Topic-Conventions](Topic-Conventions) — what topics flow between nodes