P9.3 + examples: fresh 3-tier flows + pilot wiki Home.md
examples/ (new — was empty except standalone-demo.js):
01-Basic.json 14 nodes, inject + dashboard, no parent
02-Integration.json 32 nodes, 2 tabs, measurement + MGC + 2 pumps,
link-out/link-in channels per node-red-flow-layout.md
03-Dashboard.json 63 nodes, 3 tabs (process + UI + setup),
FlowFuse charts + sliders, trend-split pattern
README.md load instructions
tools/build-examples.js regenerator
All canonical topic names only (set.*, cmd.*, data.*, child.*). No
legacy aliases. Every ui-* widget has x/y. Every chart has the full
mandatory key set from node-red-flow-layout.md §4.
wiki/Home.md (new) — pilot page for the 14-section visual-first template.
Sections 5 (topic-contract) + 9 (data-model) are auto-generated via the
new npm run wiki:* scripts; everything else hand-written following
.claude/refactor/WIKI_TEMPLATE.md.
package.json — added wiki:contract / wiki:datamodel / wiki:all scripts
wired to ../generalFunctions/scripts/wikiGen.js.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
285
wiki/Home.md
Normal file
285
wiki/Home.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# pumpingStation
|
||||
|
||||
> **Reflects code as of `d2384b1` · regenerated `<YYYY-MM-DD>` via `npm run wiki:all`**
|
||||
> If this banner is stale, the page may be out of date. Treat as informative, not authoritative.
|
||||
|
||||
## 1. What this node is
|
||||
|
||||
**pumpingStation** is an S88 Process Cell that owns a wet-well basin and orchestrates the pumps that drain it. It tracks measured + predicted volume, evaluates safety interlocks (dry-run, overfill), and dispatches a control strategy that hands a demand setpoint to one or more downstream machine groups or individual pumps.
|
||||
|
||||
## 2. Position in the platform
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
ps[pumpingStation<br/>Process Cell]:::pc
|
||||
meas_lvl[measurement<br/>type=level<br/>position=atequipment]:::ctrl
|
||||
meas_in[measurement<br/>type=flow<br/>position=upstream]:::ctrl
|
||||
mgc[machineGroupControl<br/>Unit]:::unit
|
||||
|
||||
meas_lvl -.data.-> ps
|
||||
meas_in -.data.-> ps
|
||||
ps -->|set.demand| mgc
|
||||
mgc -.evt.flow-predicted.-> ps
|
||||
mgc -->|child.register| ps
|
||||
classDef pc fill:#0c99d9,color:#fff
|
||||
classDef unit fill:#50a8d9,color:#000
|
||||
classDef ctrl fill:#a9daee,color:#000
|
||||
```
|
||||
|
||||
S88 colours: Process Cell `#0c99d9`, Unit `#50a8d9`, Control Module `#a9daee`. Source of truth: `.claude/rules/node-red-flow-layout.md`.
|
||||
|
||||
## 3. Capability matrix
|
||||
|
||||
| Capability | Status | Notes |
|
||||
|---|---|---|
|
||||
| Predicts basin volume from net flow | ✅ | Integrator seeded from `basin.minVol`; recomputes level. |
|
||||
| Accepts measured level / volume / pressure | ✅ | Routed via `measurementRouter` on child registration. |
|
||||
| Level-based control strategy | ✅ | Linear or log ramp between `minLevel` and `maxLevel`. |
|
||||
| Flow-based control strategy | ✅ | PID against `flowSetpoint`. |
|
||||
| Manual demand passthrough | ✅ | `set.demand` only honoured in `manual` mode. |
|
||||
| Dry-run safety interlock | ✅ | Stops downstream pumps when volume < `minVol` while draining. |
|
||||
| Overfill safety interlock | ✅ | Stops upstream equipment when volume crosses overfill threshold. |
|
||||
| Cascaded children (sub-stations) | ⚠️ | Accepted via `pumpingstation` softwareType but not exercised in production. |
|
||||
|
||||
## 4. Code map
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"]
|
||||
nc["buildDomainConfig()<br/>static DomainClass, commands<br/>static tickInterval = 1000ms"]
|
||||
end
|
||||
subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
|
||||
sc["PumpingStation.configure()<br/>declares ChildRouter rules<br/>tick() → safety → control"]
|
||||
end
|
||||
subgraph concerns["src/ concern modules"]
|
||||
basin["basin/<br/>BasinGeometry + thresholdValidator"]
|
||||
measurement["measurement/<br/>flowAggregator + router + calibration"]
|
||||
control["control/<br/>levelbased / flowbased / manual"]
|
||||
safety["safety/<br/>SafetyController"]
|
||||
commands["commands/<br/>topic registry + handlers"]
|
||||
end
|
||||
nc --> sc
|
||||
sc --> basin
|
||||
sc --> measurement
|
||||
sc --> control
|
||||
sc --> safety
|
||||
nc --> commands
|
||||
```
|
||||
|
||||
| Module | Owns | Read first if you're changing… |
|
||||
|---|---|---|
|
||||
| `basin/` | Geometry, volume↔level conversion, threshold ordering | Capacity, level↔volume math, fill %. |
|
||||
| `measurement/` | Net-flow aggregation, predicted-volume integrator, calibration | Predicted volume / time-to-full. |
|
||||
| `control/` | Control strategy dispatch (`levelbased`, `flowbased`, `manual`) | Demand calculation, mode behaviour. |
|
||||
| `safety/` | Dry-run + overfill rules, pump-shutdown side-effects | Safety envelope, alarm reactions. |
|
||||
| `commands/` | Input-topic registry and handlers | New input topics, payload validation. |
|
||||
|
||||
## 5. Topic contract
|
||||
|
||||
> **Auto-generated** from `src/commands/index.js`. Do NOT hand-edit between the markers. Re-run `npm run wiki:contract`.
|
||||
|
||||
<!-- BEGIN AUTOGEN: topic-contract -->
|
||||
|
||||
| Canonical topic | Aliases | Payload | Effect |
|
||||
|---|---|---|---|
|
||||
| `set.mode` | `changemode` | `string` | Replaces the named state value with the supplied payload. |
|
||||
| `child.register` | `registerChild` | `string` | Parent/child plumbing — registers or unregisters a child node. |
|
||||
| `cmd.calibrate.volume` | `calibratePredictedVolume` | `any` | Triggers an action / sequence — not idempotent. |
|
||||
| `cmd.calibrate.level` | `calibratePredictedLevel` | `any` | Triggers an action / sequence — not idempotent. |
|
||||
| `set.inflow` | `q_in` | `any` | Replaces the named state value with the supplied payload. |
|
||||
| `set.demand` | `Qd` | `any` | Replaces the named state value with the supplied payload. |
|
||||
|
||||
<!-- END AUTOGEN: topic-contract -->
|
||||
|
||||
## 6. Child registration
|
||||
|
||||
Mirrors the `ChildRouter` declarations in `specificClass.js → configure()`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph kids["accepted children (softwareType)"]
|
||||
m["measurement"]:::ctrl
|
||||
mach["machine<br/>(rotatingMachine)"]:::equip
|
||||
mgc["machinegroup"]:::unit
|
||||
sub["pumpingstation<br/>(sub-station)"]:::pc
|
||||
end
|
||||
m -->|"<type>.measured.<position>"| route1[_subscribeMeasurement<br/>routes to measurementRouter]
|
||||
mach -->|flow.predicted.<in or out>| route2[_subscribePredictedFlow<br/>+ flowAggregator]
|
||||
mgc -->|flow.predicted.<in or out>| route2
|
||||
sub -->|flow.predicted.<in or out>| route2
|
||||
route1 --> tick[tick]
|
||||
route2 --> tick
|
||||
classDef ctrl fill:#a9daee,color:#000
|
||||
classDef equip fill:#86bbdd,color:#000
|
||||
classDef unit fill:#50a8d9,color:#000
|
||||
classDef pc fill:#0c99d9,color:#fff
|
||||
```
|
||||
|
||||
| softwareType | onRegister side-effect | Subscribed events |
|
||||
|---|---|---|
|
||||
| `measurement` | `_subscribeMeasurement(child)` — registers in MeasurementContainer. | `<type>.measured.<position>` for any type (pressure, level, flow, …). |
|
||||
| `machine` | Stored in `this.machines[id]`. **Skipped when a machineGroup parent is present** to avoid double-counting. | `flow.predicted.<in|out>` per the child's `positionVsParent`. |
|
||||
| `machinegroup` | Stored in `this.machineGroups[id]`. | `flow.predicted.<in|out>`. |
|
||||
| `pumpingstation` | Stored in `this.stations[id]`. | `flow.predicted.<in|out>`. |
|
||||
|
||||
## 7. Lifecycle — what one tick does
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant child as measurement / pump child
|
||||
participant ps as pumpingStation
|
||||
participant fa as flowAggregator
|
||||
participant sf as safetyController
|
||||
participant ctl as control strategy
|
||||
participant out as Port-0 output
|
||||
|
||||
child->>ps: data event (measured.level / flow.predicted.out)
|
||||
ps->>ps: ChildRouter dispatches to handler
|
||||
Note over ps: every 1000 ms (static tickInterval)
|
||||
ps->>fa: tick() — net flow, ETA, predicted volume
|
||||
ps->>sf: evaluate({direction, secondsRemaining})
|
||||
alt safety blocked
|
||||
sf-->>ps: blocked=true, reason
|
||||
Note over ctl: skipped this tick
|
||||
else safety clear
|
||||
ps->>ctl: dispatch(mode, ctx, controlState)
|
||||
ctl-->>ps: percControl updated
|
||||
end
|
||||
ps->>ps: notifyOutputChanged()
|
||||
ps->>out: msg{topic, payload (delta-compressed)}
|
||||
```
|
||||
|
||||
## 8. Data model — `getOutput()`
|
||||
|
||||
What lands on Port 0. Built in `getOutput()`, then delta-compressed by `outputUtils.formatMsg`.
|
||||
|
||||
<!-- BEGIN AUTOGEN: data-model -->
|
||||
|
||||
| Key | Type | Unit | Sample |
|
||||
|---|---|---|---|
|
||||
| `direction` | string | — | `"steady"` |
|
||||
| `flowSource` | null | — | `null` |
|
||||
| `heightBasin` | number | m | `1` |
|
||||
| `inflowLevel` | number | m | `2` |
|
||||
| `maxVol` | number | m3 | `1` |
|
||||
| `maxVolAtOverflow` | number | m3 | `2.5` |
|
||||
| `minHeightBasedOn` | string | — | `"outlet"` |
|
||||
| `minVol` | number | m3 | `0.2` |
|
||||
| `minVolAtInflow` | number | m3 | `2` |
|
||||
| `minVolAtOutflow` | number | m3 | `0.2` |
|
||||
| `outflowLevel` | number | m | `0.2` |
|
||||
| `overflowLevel` | number | m | `2.5` |
|
||||
| `percControl` | number | % | `0` |
|
||||
| `surfaceArea` | number | m2 | `1` |
|
||||
| `timeleft` | null | s | `null` |
|
||||
| `volEmptyBasin` | number | m3 | `1` |
|
||||
| `volume.predicted.atequipment.wikigen-pumpingstation-id` | number | m3 | `0.2` |
|
||||
|
||||
<!-- END AUTOGEN: data-model -->
|
||||
|
||||
The `<nodeId>` segment of the MeasurementContainer key is the Node-RED node id assigned at deploy time; auto-gen substitutes a placeholder stub.
|
||||
|
||||
## 9. Configuration — editor form ↔ config keys
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph editor["Node-RED editor form"]
|
||||
f1[Basin: volume / height]
|
||||
f2[Levels: inflow / outflow / overflow]
|
||||
f3[Control mode]
|
||||
f4[Level setpoints: min / start / max]
|
||||
f5[Safety: dry-run % / overfill %]
|
||||
end
|
||||
subgraph config["Domain config slice"]
|
||||
c1[basin.volume<br/>basin.height]
|
||||
c2[basin.inflowLevel<br/>basin.outflowLevel<br/>basin.overflowLevel]
|
||||
c3[control.mode]
|
||||
c4[control.levelbased.minLevel<br/>control.levelbased.startLevel<br/>control.levelbased.maxLevel]
|
||||
c5[safety.dryRunThresholdPercent<br/>safety.overfillThresholdPercent]
|
||||
end
|
||||
f1 --> c1
|
||||
f2 --> c2
|
||||
f3 --> c3
|
||||
f4 --> c4
|
||||
f5 --> c5
|
||||
```
|
||||
|
||||
| Form field | Config key | Default | Range | Where used |
|
||||
|---|---|---|---|---|
|
||||
| `basinVolume` | `basin.volume` | `1` | > 0 (m³) | `BasinGeometry` |
|
||||
| `basinHeight` | `basin.height` | `1` | > 0 (m) | `BasinGeometry` |
|
||||
| `inflowLevel` | `basin.inflowLevel` | `2` | ≥ 0 (m) | threshold validator, control |
|
||||
| `outflowLevel` | `basin.outflowLevel` | `0.2` | ≥ 0 (m) | dead-volume floor |
|
||||
| `overflowLevel` | `basin.overflowLevel` | `2.5` | > 0 (m) | overfill safety |
|
||||
| `controlMode` | `control.mode` | `levelbased` | enum | `control/dispatch` |
|
||||
| `minLevel` | `control.levelbased.minLevel` | `1` | ≥ 0 (m) | `levelBased.run` |
|
||||
| `startLevel` | `control.levelbased.startLevel` | `1` | ≥ minLevel | ramp foot |
|
||||
| `maxLevel` | `control.levelbased.maxLevel` | `4` | ≤ overflowLevel | ramp top |
|
||||
| `enableDryRunProtection` | `safety.enableDryRunProtection` | `true` | bool | `SafetyController` |
|
||||
| `dryRunThresholdPercent` | `safety.dryRunThresholdPercent` | `2` | 0–100 % | dry-run trip |
|
||||
| `enableOverfillProtection` | `safety.enableOverfillProtection` | `true` | bool | overfill safety |
|
||||
| `overfillThresholdPercent` | `safety.overfillThresholdPercent` | `98` | 0–100 % | overfill trip |
|
||||
|
||||
## 10. State chart
|
||||
|
||||
Two orthogonal state vectors: **control mode** (operator-driven) and **safety state** (data-driven). The diagram shows them together — most transitions are independent.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state ControlMode {
|
||||
[*] --> none
|
||||
none --> levelbased: set.mode
|
||||
levelbased --> flowbased: set.mode
|
||||
flowbased --> manual: set.mode
|
||||
manual --> levelbased: set.mode
|
||||
levelbased --> none: set.mode
|
||||
}
|
||||
state SafetyState {
|
||||
[*] --> nominal
|
||||
nominal --> dryRun: vol < minVol AND draining
|
||||
nominal --> overfill: vol > overfillThreshold AND filling
|
||||
dryRun --> nominal: vol ≥ minVol
|
||||
overfill --> nominal: vol ≤ overfillThreshold
|
||||
}
|
||||
```
|
||||
|
||||
While the safety state is `dryRun`, control dispatch is **skipped** entirely. While `overfill`, control still runs (pumps must keep draining) but upstream equipment is shut down.
|
||||
|
||||
## 11. Examples
|
||||
|
||||
Example flows live under `examples/` in the repo. The structured tier-1/2/3 flows for this node are still in progress; until they land, the standalone simulator demo is the only runnable artefact.
|
||||
|
||||
| Tier | File | What it shows | Status |
|
||||
|---|---|---|---|
|
||||
| Basic | `examples/01-Basic.flow.json` | Inject + dashboard, single basin, no parent | ⏳ TBD |
|
||||
| Integration | `examples/02-Integration.flow.json` | pumpingStation + MGC + 2 pumps + measurement children | ⏳ TBD |
|
||||
| Dashboard | `examples/03-Dashboard.flow.json` | Live FlowFuse charts (level, net flow, ETA) | ⏳ TBD |
|
||||
| Headless | `examples/standalone-demo.js` | Node.js-only simulator, no Node-RED | ✅ in repo |
|
||||
|
||||
## 12. Debug recipes
|
||||
|
||||
| Symptom | First thing to check | Where to look |
|
||||
|---|---|---|
|
||||
| Status badge stuck on `❔ 0.0%` | Did any volume / level measurement register? Watch Port 2 + first-child event. | Editor debug tap on Port 2 + `_subscribeMeasurement` log line. |
|
||||
| `direction` always `steady` | Net flow inside `general.flowThreshold` dead-band (default 0.0001 m³/s). | `flowAggregator.deriveDirection`. |
|
||||
| `set.demand` ignored | Mode isn't `manual`. Check `set.mode` history. | `handlers.setDemand` debug log. |
|
||||
| Predicted volume drifts off measured | Calibration needed — fire `cmd.calibrate.volume` with a known reading. | `measurement/calibration.js`. |
|
||||
| Pumps don't stop on dry-run | `safety.enableDryRunProtection` must be `true` AND the orchestrator must see `direction='draining'`. | `SafetyController.evaluate`. |
|
||||
| Threshold-ordering warnings on startup | `validateThresholdOrdering` printed `inflowLevel < overflowLevel` style violations. | `basin/thresholdValidator.js`. |
|
||||
|
||||
> Never ship `enableLog: 'debug'` in a demo — fills the container log within seconds and obscures real errors. Use only for live debugging.
|
||||
|
||||
## 13. When you would NOT use this node
|
||||
|
||||
- Use pumpingStation for a **wet-well basin** that needs orchestrated drainage. For a single pump with no basin model, use `rotatingMachine` directly.
|
||||
- Don't use pumpingStation to schedule a fixed pump rota — its modes are reactive (level / flow / manual). Use an external scheduler if you need a calendar-driven schedule.
|
||||
- Skip pumpingStation if you don't need predicted volume / time-to-full. A bare `machineGroupControl` is lighter when the upstream basin is modelled elsewhere.
|
||||
|
||||
## 14. Known limitations / current issues
|
||||
|
||||
| # | Issue | Tracked in |
|
||||
|---|---|---|
|
||||
| 1 | Cascaded `pumpingstation` children accepted but not exercised in production — semantics of nested stations are not test-covered. | TBD |
|
||||
| 2 | `pressureBased`, `percentageBased`, `powerBased`, and `hybrid` are in the config enum but not implemented as control strategies. | `control/index.js` — only `levelbased` / `flowbased` / `manual` dispatched. |
|
||||
| 3 | Predicted-volume integrator can drift over long horizons without a measured-level calibration source. | `cmd.calibrate.volume` is operator-triggered, not automatic. |
|
||||
| 4 | Tier 1/2/3 example flows not yet written — current `examples/` only contains the standalone simulator. | P2.14 (Docker E2E) + P9 wiki cleanup. |
|
||||
Reference in New Issue
Block a user