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>
This commit is contained in:
248
wiki/Topology-Patterns.md
Normal file
248
wiki/Topology-Patterns.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user