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>
9.7 KiB
Reference — Examples
Note
Pending full node review (2026-05). Content reflects
CONTRACT.mdand 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 — TODO consult examples/edge.flow.json for actual scenarios. |
Important
The Tier-1 / Tier-2 / Tier-3 naming convention (
01 - <name>.json) used byrotatingMachinehas not yet been applied to VGC. Filenames are still the legacybasic/integration/edgetriad. Migration tracked in.agents/improvements/IMPROVEMENTS_BACKLOG.md. When renaming, updateexamples/README.mdin the same commit.
Loading a flow
Via the editor
- Open the Node-RED editor at
http://localhost:1880. - Menu → Import → drag the JSON file.
- Click Deploy.
Via the Admin API
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. Consultexamples/basic.flow.jsondirectly for exact node ids and inject payloads. Screenshot needed once validated — save underwiki/_partial-screenshots/valveGroupControl/01-basic-editor.png.
Single VGC with 2 valve children. Demonstrates:
- Wire each valve's Port 2 to VGC's input so
child.registerarrives automatically on deploy. - Inject
data.totalFlowwith payload{ value: 80, unit: "m3/h", position: "atEquipment", variant: "measured" }. VGC runscalcValveFlows:- 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: 2until|residual| < 0.001.
- Watch Port 0 debug:
atEquipment_predicted_flowsettles at the assigned total;deltaMax_predicted_pressureupdates as each valve emitsdeltaPChange. - Toggle one valve to
off— the nextdata.totalFlow(or tick) routes 100% to the remaining valve. - Set the offlined valve back to operational — the next tick re-includes it in the split.
Try the residual pass
After the group settles at 80 m³/h:
- Inject
data.totalFlow = 200(a value beyond aggregate Kv capacity). WatchlastFlowSolve.residual— it stays large because the valves cap at their accept limits.flow.predicted.atEquipmentwill read the sum of caps, not 200. - Inject
data.totalFlow = 0. Every valve receivesupdateFlow('predicted', 0, 'downstream');maxDeltaPcollapses to 0.
Example: integration.flow.json
Important
TODO: not yet validated. Screenshot needed once validated — save under
wiki/_partial-screenshots/valveGroupControl/02-integration-editor.png.
VGC + 2 valves + 1 upstream rotatingMachine. Demonstrates:
- Source registration: the rotatingMachine's
child.register(softwareTyperotatingmachine→ canonicalmachine) lands on VGC;_registerSourcesubscribes toflow.predicted.downstream,flow.measured.downstream, etc. - Source-driven flow: a pump-state change emits
flow.predicted.downstream; VGC's handler converts toupdateFlow('predicted', value, 'atEquipment', unit)and re-runscalcValveFlows. - Fluid-contract resolution: the pump's
getFluidContract()(or fallbackDEFAULT_SOURCE_SERVICE_TYPE.rotatingmachine = 'liquid') producesserviceType: 'liquid';fluidContractresolves 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 — all in
off/maintenance. Expected: status badge'No valves'red, every valve pushed0,lastFlowSolve.assignedTotal = 0.- Residual non-convergence — valve curve where Kv-share is a bad first estimate. Expected: loop exits after
maxPasses: 2with non-zero residual; assignedTotal < target; behaviour graceful.- Cascaded VGC — upstream source is another VGC. Expected: source registered,
flow.*events bound,getFluidContract()propagated up. Not exercised in production.- Conflicting fluid contracts — two sources advertise
liquidvsgas. Expected:fluidContract.status = 'conflict';fluidContractChangeevent emitted.
Docker compose snippet
To bring up Node-RED + InfluxDB with EVOLV nodes pre-loaded:
# 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.
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 — if passes === maxPasses and residual is large, the loop ran out. Raise flowReconciliation.maxPasses (no editor field yet — 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 — 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. | 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 — 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) — the inverse of rotatingMachine's <type>.<variant>.<position>.<childId>. Don't mix. |
src/io/output.getOutput |
Never ship
enableLog: 'debug'in a demo — 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.mddoes 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.registerpayload shape.- Example flow function-node outputs: each
outputs > 1fan-out enumerated; verify nopayload: nullliterals (lint vianpm run lint:flow-outputs).
Related pages
| Page | Why |
|---|---|
| Home | Intuitive overview |
| Reference — Contracts | Topic + config + child filters |
| Reference — Architecture | Code map, flow-distribution loop, source aggregation |
| Reference — Limitations | Known issues and open questions |
| machineGroupControl — Examples | Sibling group-control demo flows |
| valve wiki | The child node VGC coordinates |
| EVOLV — Topology Patterns | Where valveGroupControl fits in a larger plant |