- wiki/Reference-Contracts.md: regenerate data-model (npm run wiki:all) so the two live getOutput() keys `mode` and `manualDemand` are documented; refresh stale sample values; bump code-ref badge -> a83a85e; add human note describing the two control-state keys. - CONTRACT.md: fix Port-2 outgoing topic registerChild -> child.register (registerChild is the deprecated *input* alias, not what the node emits). - test/_output-manifest.md: add the mandatory output manifest (Port 0/1/2 + emitter events + the 15-way fn_status_split fan-out) with honest coverage gaps. - test/integration/basic-dashboard-flow.test.js: fix stale fan-out count 14->15 (output 14 = percControl chart added upstream); assert out[14]. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.6 KiB
pumpingStation output manifest
Single source of truth for what this node emits and where it is tested, per
.claude/rules/output-coverage.md. Generated against code-refa83a85e. Regenerate the wiki contract withnpm run wiki:alland re-check this table whenevergetOutput(),src/commands/index.js, or anexamples/*.jsonfan-out changes.
Null convention for this node: a Port-0 key whose source is not yet
available is emitted as explicit null (e.g. timeleft, flowSource,
manualDemand outside manual mode), never silently absent. Delta-compression on
Port 0 then drops keys whose value is unchanged since the previous tick.
Port 0 (process data) — specificClass.getOutput() → outputUtils.formatMsg(..., 'process')
msg.topic = config.general.name. Keys below are the full pre-delta-compression set.
| Key | Source | Type | States tested | Test file |
|---|---|---|---|---|
mode |
getOutput ← this.mode |
string (levelbased/manual/flowbased/none) |
populated (manual) |
test/basic/specificClass.test.js |
manualDemand |
getOutput ← _manualDemand |
number m³/h, null outside manual |
populated, null | test/basic/specificClass.test.js |
direction |
getOutput ← state.direction |
string (filling/draining/steady) |
present | test/basic/specificClass.test.js |
flowSource |
getOutput ← state.flowSource |
string, null when no source |
null (pre-child) | test/basic/specificClass.test.js |
timeleft |
getOutput ← state.seconds |
number s, null when steady |
present, null | test/basic/specificClass.test.js |
percControl |
getOutput ← controlState.percControl |
number % 0..100 | 0, 25, 50, 75, 85, 100 | test/basic/specificClass.test.js |
dryRunLevel |
_computeSafetyPoints |
number m | populated | test/basic/specificClass.test.js |
dryRunSafetyVol |
_computeSafetyPoints |
number m³ | populated | test/basic/specificClass.test.js |
highVolumeSafetyLevel |
_computeSafetyPoints |
number m | populated | test/basic/specificClass.test.js |
highVolumeSafetyVol |
_computeSafetyPoints |
number m³ | populated | test/basic/specificClass.test.js |
predictedOverflowVolume |
measurements overflowVolume |
number m³ | populated, 0 | test/basic/specificClass.test.js |
predictedOverflowRate |
measurements flow.overflow |
number m³/s | populated, 0 | test/basic/specificClass.test.js |
predictedUnderflowVolume |
measurements underflowVolume |
number m³ | 0 | test/basic/specificClass.test.js |
volume.predicted.atequipment.<childId> |
measurements.getFlattenedOutput |
number m³ | populated | test/basic/specificClass.test.js |
basin geometry: heightBasin, surfaceArea, maxVol, minVol, maxVolAtOverflow, minVolAtInflow, minVolAtOutflow, volEmptyBasin, inflowLevel, outflowLevel, overflowLevel, inletPipeDiameter, outletPipeDiameter, minHeightBasedOn |
basin.snapshot() |
number (m/m²/m³) / string | populated | test/basic/specificClass.test.js, test/basic/BasinGeometry.basic.test.js |
Port 1 (InfluxDB telemetry) — formatMsg(..., 'influxdb')
Same key set as Port 0 (formatted via the influxdb formatter rather than
process). Field names == Port-0 keys; config.general.name is the measurement
tag. No Port-1-only fields. Covered transitively by the Port-0 tests above; a
dedicated Port-1 line-protocol assertion is a gap (see below).
Port 2 (registration / control plumbing) — BaseNodeAdapter._scheduleRegistration
| Topic | Source | Payload shape | States tested | Test file |
|---|---|---|---|---|
child.register |
BaseNodeAdapter.js:122 |
{ topic:'child.register', payload:<node.id>, positionVsParent, distance } |
— | (gap — see below) |
Note: the canonical outgoing topic is
child.register(matching the input registry). Earlier docs saidregisterChild; that is the deprecated input alias, not what this node emits.
Child-facing events — measurements.emitter
Fired as <type>.<variant>.<position> when a series receives a value. Parents
subscribe by event name (data-driven, not a fixed catalogue):
| Event | When | Test file |
|---|---|---|
volume.predicted.atequipment |
each integrator tick | test/basic/flowAggregator.basic.test.js |
level.predicted.atequipment |
recomputed from volume | test/basic/specificClass.test.js |
flow.predicted.in (child manual-qin) |
set.inflow handler |
test/basic/measurementRouter.basic.test.js |
overflowVolume/underflowVolume/flow.predicted.overflow |
integrator hits a physical bound | test/basic/flowAggregator.basic.test.js |
Example-flow function-node fan-out
examples/02-Dashboard.json :: fn_status_split (outputs: 15)
| # | Target widget | Payload | Populated | Degraded/null |
|---|---|---|---|---|
| 0 | ui-text "Mode" | string | ✔ structure | gap |
| 1 | ui-text "Direction" | string | ✔ | gap |
| 2 | ui-text "Level" | number m | ✔ | gap |
| 3 | ui-text "Volume" | number m³ | ✔ | gap |
| 4 | ui-text "Volume %" | number % | ✔ | gap |
| 5 | ui-text "percControl" | number % | ✔ | gap |
| 6 | ui-text "Manual demand" | number m³/h or — | gap | gap |
| 7 | ui-chart "Level (m)" | {topic,payload:number} or no-msg |
✔ | gap |
| 8 | ui-chart "Volume (m³)" | ″ | ✔ | gap |
| 9 | ui-chart "Volume %" | ″ | ✔ | gap |
| 10 | ui-chart "Flow (m³/h)" — Inflow | ″ | ✔ | gap |
| 11 | ui-chart "Flow (m³/h)" — Outflow | ″ | ✔ | gap |
| 12 | ui-chart "Flow (m³/h)" — Net | ″ | ✔ | gap |
| 13 | ui-template "Raw output table" | whole object (array) | ✔ | gap |
| 14 | ui-chart "percControl" | {topic:'percControl',payload:number} |
✔ | gap |
Populated/structure coverage: test/integration/basic-dashboard-flow.test.js
(asserts output count = 15 and routes outputs 0–14). Degraded/empty-input
coverage (no payload:null reaching any ui-chart) is still a gap — see below.
Known coverage gaps (tracked, prospective per the rule)
The output-coverage rule applies prospectively. Outstanding items for this node:
- Dedicated
test/basic/output-port0.test.jsexercising every key above in both populated and degraded (pre-tick / null) states. - Port-1 line-protocol assertion (field names + tag).
- Port-2
child.registerpayload-shape test. fn_status_splitdegraded/empty-input fan-out test (nopayload:nullto anyui-chart) — the failure mode the rule was written for. The structure test inbasic-dashboard-flow.test.jscovers the populated path only.