# pumpingStation output manifest

> Single source of truth for **what this node emits and where it is tested**, per
> [`.claude/rules/output-coverage.md`](https://gitea.wbd-rd.nl/RnD/EVOLV/src/branch/development/.claude/rules/output-coverage.md).
> Generated against code-ref `a83a85e`. Regenerate the wiki contract with
> `npm run wiki:all` and re-check this table whenever `getOutput()`,
> `src/commands/index.js`, or an `examples/*.json` fan-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 said `registerChild`; 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.js` exercising **every** key above
      in both populated and degraded (pre-tick / null) states.
- [ ] Port-1 line-protocol assertion (field names + tag).
- [ ] Port-2 `child.register` payload-shape test.
- [ ] `fn_status_split` degraded/empty-input fan-out test (no `payload:null` to
      any `ui-chart`) — the failure mode the rule was written for. The structure
      test in `basic-dashboard-flow.test.js` covers the populated path only.
