Source-tree mirror of EVOLV.wiki.git refactor (27a42ee on wiki.git): - 7 master pages rewritten with clean design (Home, Architecture, Topology-Patterns, Topic-Conventions, Telemetry, Getting-Started, Glossary). Tables and Mermaid for visuals, gitea alert callouts for warnings, shields badges for metadata only. No emoji as decoration. - Archive.md becomes a removal-changelog pointing readers to git history and to the successor pages. - _Sidebar.md updated to navigate the new flat-name layout. - Concept / finding / manual pages: uniform mini-header (badges + "reference page" callout) added without rewriting domain content. - Every internal link now uses the flat naming that resolves on the live gitea wiki (Concept-ASM-Models, Finding-BEP-..., etc.). On wiki.git: 29 Archive-* pages hard-deleted (the git history preserves them; Archive.md documents the removal). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
Topology Patterns
Note
Five canonical plant configurations and one worked example that combines them. Every edge in every diagram was checked against the parent's
configure()declaration in source. Use these as templates when wiring your own plant.
Pattern index
| Pattern | When to use it |
|---|---|
| 1. Pumping station with grouped pumps | Lift station, single basin, N pumps load-shared |
| 2. Reactor + diffuser + settler train | Biological treatment line |
| 3. Valve group on a distribution manifold | Multi-valve flow split with upstream flow context |
| 4. Composite sampling | Flow-proportional grab samples for lab analysis |
| 5. Dashboard provisioning | Auto-generated Grafana dashboards |
| Worked example — small WWTP | All five patterns combined |
1. Pumping station with grouped pumps
The canonical wet-well lift station: one basin model, one demand controller (pumpingStation), one load-sharing coordinator (machineGroupControl), N pumps (rotatingMachine × N), measurements for level + flow + per-pump pressure.
flowchart TB
subgraph PC["Process Cell"]
ps[pumpingStation]
end
subgraph UN["Unit"]
mgc[machineGroupControl]
end
subgraph EM["Equipment Module"]
rmA[rotatingMachine A]
rmB[rotatingMachine B]
rmC[rotatingMachine C]
end
subgraph CM["Control Module"]
ml["measurement — level"]
mfin["measurement — inflow"]
mpA["measurement — pressure A"]
mpB["measurement — pressure B"]
mpC["measurement — pressure C"]
end
ps --> mgc
mgc --> rmA
mgc --> rmB
mgc --> rmC
ml -. data .-> ps
mfin -. data .-> ps
mpA -. data .-> rmA
mpB -. data .-> rmB
mpC -. data .-> rmC
class ps pc
class mgc unit
class rmA,rmB,rmC equip
class ml,mfin,mpA,mpB,mpC ctrl
classDef pc fill:#0c99d9,color:#fff,stroke:#075a82,stroke-width:2px
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef equip fill:#86bbdd,color:#000,stroke:#5a90b2,stroke-width:2px
classDef ctrl fill:#a9daee,color:#000,stroke:#76b7d4,stroke-width:2px
Data flow
| Stage | What happens |
|---|---|
| Basin integration | pumpingStation integrates basin volume from inflow / outflow rates |
| Demand computation | pumpingStation computes a demand setpoint and dispatches it to machineGroupControl |
| Per-pump operating point | machineGroupControl solves a per-pump operating point using each pump's characteristic curve plus measured upstream pressure |
| Pump dispatch | Each rotatingMachine runs its own FSM (idle → warmingup → operational → coolingdown → emergencystop, plus accelerating / decelerating) and predicts flow + power from speed + pressure |
Variants
| Variant | How to wire |
|---|---|
| Single pump (no MGC) | pumpingStation.configure() accepts machine directly — skip the MGC and parent the rotatingMachine under pumpingStation |
| Cascaded stations | pumpingStation.configure() accepts pumpingstation as a child — downstream PS registers upstream PS to read its predicted outflow |
2. Reactor + diffuser + settler train
Biological treatment line. reactor runs ASM kinetics (CSTR or PFR engine, set via config.reactor_type). diffuser injects OTR. settler clarifies the effluent and drives a return pump.
flowchart TB
subgraph UN["Unit"]
reactor[reactor]
settler[settler]
end
subgraph EM["Equipment Module"]
diff[diffuser]
rp["rotatingMachine — return pump"]
end
subgraph CM["Control Module"]
mt["measurement — temperature"]
mdo["measurement — dissolved O2"]
mts["measurement — TSS"]
end
reactor ==stateChange==> settler
diff -. OTR data .-> reactor
settler -->|return pump| rp
mt -. data .-> reactor
mdo -. data .-> reactor
mts -. data .-> settler
mdo -. data .-> diff
class reactor,settler unit
class diff,rp equip
class mt,mdo,mts ctrl
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef equip fill:#86bbdd,color:#000,stroke:#5a90b2,stroke-width:2px
classDef ctrl fill:#a9daee,color:#000,stroke:#76b7d4,stroke-width:2px
Two non-standard wirings
Important
diffuser→reactoris data-only. Diffuser firesdata.otron its emitter; reactor subscribes viaemitter.on('otr', ...). There is nochild.registerhandshake between them. Seenodes/reactor/src/specificClass.jsconfigure().
Important
reactor→settleris astateChangesubscription, not a parent / child edge. Settler's_connectReactorattachesemitter.on('stateChange', ...)to pull effluent composition from the upstream reactor. ThereactorsoftwareType is registered as a child of settler even though the reactor is semantically upstream.
Caution
DO setpoint feedback is not automatic. A measured-DO → diffuser-airflow loop must be closed externally (a function node) or via a
valveGroupControlupstream of an airflow valve.
3. Valve group on a distribution manifold
Multi-valve flow distribution. valveGroupControl computes per-valve K_v shares to satisfy a target split while respecting upstream flow availability.
flowchart TB
subgraph PC["Process Cell"]
ps["pumpingStation — upstream flow source"]
end
subgraph UN["Unit"]
vgc[valveGroupControl]
end
subgraph EM["Equipment Module"]
vA[valve A]
vB[valve B]
vC[valve C]
end
ps -. flow source .-> vgc
vgc --> vA
vgc --> vB
vgc --> vC
class ps pc
class vgc unit
class vA,vB,vC equip
classDef pc fill:#0c99d9,color:#fff,stroke:#075a82,stroke-width:2px
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef equip fill:#86bbdd,color:#000,stroke:#5a90b2,stroke-width:2px
Important
VGC's child types are unusual.
valveGroupControl.configure()registers five softwareTypes:
valve— actual S88 child relationship (VGC controls these)machine,machinegroup,pumpingstation,valvegroupcontrol— flow sources. VGC reads upstream flow availability when computing splits. Semantic is "VGC knows about this flow producer", not "VGC controls it".See
nodes/valveGroupControl/src/specificClass.jslines 13–49.
4. Composite sampling
Virtual sensor for downstream lab analysis. monster accumulates samples in a bucket based on integrated flow — a flow-proportional grab sample.
flowchart TB
subgraph UN["Unit"]
monster[monster]
end
subgraph CM["Control Module"]
mflow["measurement — flow (assetType MUST be 'flow')"]
mq["measurement — any quality (e.g. NH4, COD)"]
end
mflow -. data .-> monster
mq -. data .-> monster
class monster unit
class mflow,mq ctrl
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef ctrl fill:#a9daee,color:#000,stroke:#76b7d4,stroke-width:2px
Warning
Two gotchas:
measurement.config.asset.typemust be exactly"flow". A value like"flow-electromagnetic"is silently ignored by monster's child router.monster.config.constraints.flowmeterexists in the schema but is not forwarded bybuildDomainConfig. Toggling proportional-vs-time mode has no runtime effect. Tracked in.claude/refactor/OPEN_QUESTIONS.md.
5. Dashboard provisioning
dashboardAPI doesn't operate on data — it generates Grafana dashboards. Any node registers via child.register; dashboardAPI composes a dashboard JSON from softwareType plus measurements and POSTs to Grafana's HTTP API.
flowchart LR
subgraph EVOLV["EVOLV process nodes (any softwareType)"]
direction TB
ps[pumpingStation]
mgc[machineGroupControl]
rm[rotatingMachine]
end
subgraph UT["Utility"]
dash[dashboardAPI]
end
grafana[("Grafana HTTP API")]
ps -. child.register .-> dash
mgc -. child.register .-> dash
rm -. child.register .-> dash
dash ==>|POST /api/dashboards/db| grafana
class ps pc
class mgc unit
class rm equip
class dash util
class grafana ext
classDef pc fill:#0c99d9,color:#fff,stroke:#075a82,stroke-width:2px
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef equip fill:#86bbdd,color:#000,stroke:#5a90b2,stroke-width:2px
classDef util fill:#dddddd,color:#000,stroke:#a8a8a8,stroke-width:2px
classDef ext fill:#fff2cc,color:#000,stroke:#aa8400,stroke-width:2px
| Behaviour | Detail |
|---|---|
| What it accepts | Any softwareType on child.register |
| What it emits | One HTTP POST per registered child, payload from nodes/dashboardAPI/src/config/templates/<softwareType>.json |
| Auth | Bearer token in config.grafanaConnector.bearerToken (when set) |
meta envelope |
{nodeId, softwareType, uid, title} for correlating responses |
| Architecture variance | The one node in the platform that does not extend BaseDomain. Documented in .claude/refactor/OPEN_QUESTIONS.md |
Worked example — small WWTP
All five patterns combined.
flowchart TB
subgraph PC["Process Cell"]
ps1["pumpingStation — inlet lift"]
ps2["pumpingStation — RAS pumping"]
end
subgraph UN["Unit"]
mgc1["MGC inlet"]
mgc2["MGC RAS"]
vgc["VGC effluent split"]
r1["reactor aerobic"]
s1["settler"]
mon["monster — composite sampler"]
end
subgraph EM["Equipment Module"]
rm1["pump A"]
rm2["pump B"]
rm3["RAS pump"]
d1["diffuser"]
v1["valve 1"]
v2["valve 2"]
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
class ps1,ps2 pc
class mgc1,mgc2,vgc,r1,s1,mon unit
class rm1,rm2,rm3,d1,v1,v2 equip
classDef pc fill:#0c99d9,color:#fff,stroke:#075a82,stroke-width:2px
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef equip fill:#86bbdd,color:#000,stroke:#5a90b2,stroke-width:2px
| Sub-pattern | Recognise it |
|---|---|
| Pumping station with grouped pumps (×2) | ps1 -> mgc1 -> {rm1, rm2} and ps2 -> mgc2 -> rm3 |
| Reactor + settler train | r1 ==stateChange==> s1 plus d1 -. OTR .-> r1 |
| Valve group on flow source | ps2 -. flow source .-> vgc -> {v1, v2} |
| Settler return pump | s1 -> rm3 |
| Composite sampling | mon (would be wired to inflow + quality measurements not drawn) |
Every edge here is reproducible from one of the patterns above.
Anti-patterns
Caution
pumpingStation→valveGroupControlas a parent / child edge. PS does not register VGC. VGC registers PS as a flow source — the edge goes the other way semantically.
Caution
diffuser→reactoras a child registration. Diffuser emits OTR via its emitter; reactor subscribes viaemitter.on. Nochild.registerhandshake.
Caution
measurementparented underdashboardAPI. dashboardAPI accepts any node for Grafana provisioning, butmeasurementshould register with the process node it is monitoring, not with dashboardAPI.
Related pages
| Page | Why |
|---|---|
| Home | Top-level node map |
| Architecture | Three-tier code + generalFunctions API |
| Topic Conventions | What topics flow on each edge |
| Telemetry | Port 0 / 1 / 2 InfluxDB layout |