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>
340 lines
12 KiB
Markdown
340 lines
12 KiB
Markdown
# Topology Patterns
|
|
|
|

|
|
-brightgreen)
|
|
|
|
> [!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](#1-pumping-station-with-grouped-pumps) | Lift station, single basin, N pumps load-shared |
|
|
| [2. Reactor + diffuser + settler train](#2-reactor--diffuser--settler-train) | Biological treatment line |
|
|
| [3. Valve group on a distribution manifold](#3-valve-group-on-a-distribution-manifold) | Multi-valve flow split with upstream flow context |
|
|
| [4. Composite sampling](#4-composite-sampling) | Flow-proportional grab samples for lab analysis |
|
|
| [5. Dashboard provisioning](#5-dashboard-provisioning) | Auto-generated Grafana dashboards |
|
|
| [Worked example — small WWTP](#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.
|
|
|
|
```mermaid
|
|
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.
|
|
|
|
```mermaid
|
|
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` → `reactor` is data-only. Diffuser fires `data.otr` on its emitter; reactor subscribes via `emitter.on('otr', ...)`. There is no `child.register` handshake between them. See `nodes/reactor/src/specificClass.js` `configure()`.
|
|
|
|
> [!IMPORTANT]
|
|
> `reactor` → `settler` is a `stateChange` subscription, not a parent / child edge. Settler's `_connectReactor` attaches `emitter.on('stateChange', ...)` to pull effluent composition from the upstream reactor. The `reactor` softwareType 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 `valveGroupControl` upstream 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.
|
|
|
|
```mermaid
|
|
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.js` lines 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.
|
|
|
|
```mermaid
|
|
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:
|
|
> 1. `measurement.config.asset.type` must be exactly `"flow"`. A value like `"flow-electromagnetic"` is silently ignored by monster's child router.
|
|
> 2. `monster.config.constraints.flowmeter` exists in the schema but is not forwarded by `buildDomainConfig`. 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.
|
|
|
|
```mermaid
|
|
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.
|
|
|
|
```mermaid
|
|
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` → `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.
|
|
|
|
> [!CAUTION]
|
|
> `diffuser` → `reactor` as a child registration. Diffuser emits OTR via its emitter; reactor subscribes via `emitter.on`. No `child.register` handshake.
|
|
|
|
> [!CAUTION]
|
|
> `measurement` parented under `dashboardAPI`. dashboardAPI accepts any node for Grafana provisioning, but `measurement` should register with the process node it is monitoring, not with dashboardAPI.
|
|
|
|
---
|
|
|
|
## Related pages
|
|
|
|
| Page | Why |
|
|
|:---|:---|
|
|
| [Home](Home) | Top-level node map |
|
|
| [Architecture](Architecture) | Three-tier code + generalFunctions API |
|
|
| [Topic Conventions](Topic-Conventions) | What topics flow on each edge |
|
|
| [Telemetry](Telemetry) | Port 0 / 1 / 2 InfluxDB layout |
|