Files
valveGroupControl/wiki/Reference-Examples.md
znetsixe 9552e4fba9 docs(wiki): full 5-page wiki matching the rotatingMachine reference format
Replaces the prior stub/partial wiki with a Home + Reference-{Architecture,
Contracts,Examples,Limitations} + _Sidebar structure. Topic-contract and
data-model sections wrapped in AUTOGEN markers for the future wiki-gen tool.
Source-vs-spec contradictions surfaced and flagged inline (not silently
fixed). Pending-review notes mark sections that need a full node review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:42:12 +02:00

158 lines
9.7 KiB
Markdown

# Reference &mdash; Examples
![code-ref](https://img.shields.io/badge/code--ref-b20a573-blue)
> [!NOTE]
> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only.
>
> Every example flow shipped under `nodes/valveGroupControl/examples/`, plus how to load them, what they show, and the debug recipes that go with them. Live source: `nodes/valveGroupControl/examples/`.
---
## Shipped examples
| File | Tier | Dependencies | What it shows |
|:---|:---:|:---|:---|
| `basic.flow.json` | 1 | EVOLV only | Inject `data.totalFlow` + 2 `valve` children registered as the group. No parent. Verifies Kv-share split and residual reconciliation. |
| `integration.flow.json` | 2 | EVOLV only | VGC + 2 valves + one upstream `rotatingMachine` source. Source's `flow.predicted.*` events drive the group; fluid-contract aggregation resolves to `liquid`. |
| `edge.flow.json` | 3 | EVOLV only | Edge cases: no valves available, residual non-convergence, cascaded VGC as upstream source. Currently a structural placeholder &mdash; TODO consult `examples/edge.flow.json` for actual scenarios. |
> [!IMPORTANT]
> The Tier-1 / Tier-2 / Tier-3 naming convention (`01 - <name>.json`) used by `rotatingMachine` has **not yet been applied** to VGC. Filenames are still the legacy `basic`/`integration`/`edge` triad. Migration tracked in `.agents/improvements/IMPROVEMENTS_BACKLOG.md`. When renaming, update `examples/README.md` in the same commit.
---
## Loading a flow
### Via the editor
1. Open the Node-RED editor at `http://localhost:1880`.
2. Menu &rarr; Import &rarr; drag the JSON file.
3. Click Deploy.
### Via the Admin API
```bash
curl -X POST -H 'Content-Type: application/json' \
--data @"nodes/valveGroupControl/examples/basic.flow.json" \
http://localhost:1880/flows
```
---
## Example: basic.flow.json
> [!IMPORTANT]
> **TODO: not yet validated against live Node-RED.** Steps below are inferred from `CONTRACT.md` + the topic registry. Consult `examples/basic.flow.json` directly for exact node ids and inject payloads. Screenshot needed once validated &mdash; save under `wiki/_partial-screenshots/valveGroupControl/01-basic-editor.png`.
Single VGC with 2 `valve` children. Demonstrates:
1. Wire each valve's Port 2 to VGC's input so `child.register` arrives automatically on deploy.
2. Inject `data.totalFlow` with payload `{ value: 80, unit: "m3/h", position: "atEquipment", variant: "measured" }`. VGC runs `calcValveFlows`:
- Splits 80 m³/h by Kv share across the 2 valves.
- Each valve's `updateFlow('predicted', share, 'downstream')` push.
- Re-reads each valve's accepted `flow.predicted.downstream`.
- Residual loop runs up to `maxPasses: 2` until `|residual| < 0.001`.
3. Watch Port 0 debug: `atEquipment_predicted_flow` settles at the assigned total; `deltaMax_predicted_pressure` updates as each valve emits `deltaPChange`.
4. Toggle one valve to `off` &mdash; the next `data.totalFlow` (or tick) routes 100% to the remaining valve.
5. Set the offlined valve back to operational &mdash; the next tick re-includes it in the split.
### Try the residual pass
After the group settles at `80 m³/h`:
1. Inject `data.totalFlow = 200` (a value beyond aggregate Kv capacity). Watch `lastFlowSolve.residual` &mdash; it stays large because the valves cap at their accept limits. `flow.predicted.atEquipment` will read the sum of caps, not 200.
2. Inject `data.totalFlow = 0`. Every valve receives `updateFlow('predicted', 0, 'downstream')`; `maxDeltaP` collapses to 0.
---
## Example: integration.flow.json
> [!IMPORTANT]
> **TODO: not yet validated.** Screenshot needed once validated &mdash; save under `wiki/_partial-screenshots/valveGroupControl/02-integration-editor.png`.
VGC + 2 valves + 1 upstream `rotatingMachine`. Demonstrates:
- Source registration: the rotatingMachine's `child.register` (softwareType `rotatingmachine` &rarr; canonical `machine`) lands on VGC; `_registerSource` subscribes to `flow.predicted.downstream`, `flow.measured.downstream`, etc.
- Source-driven flow: a pump-state change emits `flow.predicted.downstream`; VGC's handler converts to `updateFlow('predicted', value, 'atEquipment', unit)` and re-runs `calcValveFlows`.
- Fluid-contract resolution: the pump's `getFluidContract()` (or fallback `DEFAULT_SOURCE_SERVICE_TYPE.rotatingmachine = 'liquid'`) produces `serviceType: 'liquid'`; `fluidContract` resolves to `{status: 'resolved', serviceType: 'liquid'}`.
---
## Example: edge.flow.json
> [!IMPORTANT]
> **TODO: structural placeholder.** Consult the JSON directly for the scenarios it currently exercises. The edge scenarios this node ought to test (per `CONTRACT.md` + source review):
>
> - **No available valves** &mdash; all in `off` / `maintenance`. Expected: status badge `'No valves'` red, every valve pushed `0`, `lastFlowSolve.assignedTotal = 0`.
> - **Residual non-convergence** &mdash; valve curve where Kv-share is a bad first estimate. Expected: loop exits after `maxPasses: 2` with non-zero residual; assignedTotal &lt; target; behaviour graceful.
> - **Cascaded VGC** &mdash; upstream source is another VGC. Expected: source registered, `flow.*` events bound, `getFluidContract()` propagated up. **Not exercised in production.**
> - **Conflicting fluid contracts** &mdash; two sources advertise `liquid` vs `gas`. Expected: `fluidContract.status = 'conflict'`; `fluidContractChange` event emitted.
---
## Docker compose snippet
To bring up Node-RED + InfluxDB with EVOLV nodes pre-loaded:
```yaml
# docker-compose.yml (extract)
services:
nodered:
build: ./docker/nodered
ports: ['1880:1880']
volumes:
- ./docker/nodered/data:/data/evolv
influxdb:
image: influxdb:2.7
ports: ['8086:8086']
```
Full file: [EVOLV/docker-compose.yml](https://gitea.wbd-rd.nl/RnD/EVOLV/src/branch/development/docker-compose.yml).
---
## Debug recipes
| Symptom | First thing to check | Where to look |
|:---|:---|:---|
| All valves receive `assigned flow = 0` | `getAvailableValves()` returns empty list: check every valve's state (`off` / `maintenance` excludes), `currentMode !== 'maintenance'`, and `kv > 0`. | `src/groupOps/flowDistribution.isValveAvailable` |
| Residual never converges | Pathological valve curve: Kv-share is a bad first estimate. Check `vgc.lastFlowSolve.residual` and `.passes` &mdash; if `passes === maxPasses` and residual is large, the loop ran out. Raise `flowReconciliation.maxPasses` (no editor field yet &mdash; TODO Phase 7). | `src/groupOps/flowDistribution.solveFlowDistribution` |
| Group `maxDeltaP` stale | Child `deltaPChange` not subscribed: valve emitter not exposed via `child.emitter`, or valve never registered. | `src/specificClass._bindValveEvents` |
| Service-type stays `unknown` | No upstream source registered, or all advertise unknown type. Check `vgc.sources` &mdash; if empty, no Port-2 child.register reached VGC. | `src/sources/fluidContract.refreshFluidContract` |
| `data.totalFlow` silently ignored | Mode rejects the source id: check `mode.allowedSources` for the current mode. `maintenance` rejects every source. Warn in log: `Source '<src>' is not valid for mode '<mode>'.` | `src/specificClass.isValidSourceForMode` |
| `set.position` has no effect | Known: handler is a debug-logged no-op pending Phase 7. See [Limitations](Reference-Limitations#set-position-is-a-no-op). | `src/commands/handlers.setPosition` |
| `set.reconcileInterval = 0` (or negative) silently does nothing | The handler validates `Number.isFinite(nextSec) && nextSec > 0`. Otherwise warns and returns. | `src/commands/handlers.setReconcileInterval` |
| Two emitters fight on the same flow channel (one source publishes `atEquipment`, another publishes `atequipment`) | Both event names are wired; the **last write wins** on each `updateFlow`. There is no source-priority logic. | `src/sources/fluidContract.SOURCE_FLOW_EVENTS` |
| Cascaded VGC not propagating flow | Accepted by router, but no tests &mdash; treat as experimental. | `src/sources/fluidContract.SOURCE_SOFTWARE_TYPES` |
| Output port-0 key shape differs from rotatingMachine's | VGC uses `<position>_<variant>_<type>` (same as MGC) &mdash; the inverse of rotatingMachine's `<type>.<variant>.<position>.<childId>`. Don't mix. | `src/io/output.getOutput` |
> Never ship `enableLog: 'debug'` in a demo &mdash; fills the container log within seconds and obscures real errors.
---
## Output coverage (TODO)
> [!IMPORTANT]
> Per `.claude/rules/output-coverage.md`: every output on every layer needs a manifest entry + populated + degraded test. **`test/_output-manifest.md` does not yet exist** for VGC. Backfill tracked in `.agents/improvements/IMPROVEMENTS_BACKLOG.md`.
>
> Minimum coverage to land before declaring trial-ready:
>
> - Port 0 / 1 keys: `mode`, `maxDeltaP`, `atEquipment_predicted_flow`, `atEquipment_measured_flow`, `deltaMax_predicted_pressure`. Each tested in populated AND degraded states.
> - Port 2: `child.register` payload shape.
> - Example flow function-node outputs: each `outputs > 1` fan-out enumerated; verify no `payload: null` literals (lint via `npm run lint:flow-outputs`).
---
## Related pages
| Page | Why |
|:---|:---|
| [Home](Home) | Intuitive overview |
| [Reference &mdash; Contracts](Reference-Contracts) | Topic + config + child filters |
| [Reference &mdash; Architecture](Reference-Architecture) | Code map, flow-distribution loop, source aggregation |
| [Reference &mdash; Limitations](Reference-Limitations) | Known issues and open questions |
| [machineGroupControl &mdash; Examples](https://gitea.wbd-rd.nl/RnD/machineGroupControl/wiki/Reference-Examples) | Sibling group-control demo flows |
| [valve wiki](https://gitea.wbd-rd.nl/RnD/valve/wiki/Home) | The child node VGC coordinates |
| [EVOLV &mdash; Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) | Where valveGroupControl fits in a larger plant |