Files
monster/wiki/Reference-Contracts.md

205 lines
12 KiB
Markdown
Raw Normal View History

# Reference — Contracts
![code-ref](https://img.shields.io/badge/code--ref-cd185dc-blue)
> [!NOTE]
> Full topic contract, configuration schema, and child-registration filters for `monster`. Source of truth: `src/commands/index.js`, `src/specificClass.js` `configure()`, and the schema at `generalFunctions/src/configs/monster.json`.
>
> For an intuitive overview, return to the [Home](Home).
> [!NOTE]
> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only.
---
## Topic contract
The registry lives in `src/commands/index.js`. Each descriptor maps a canonical `msg.topic` to its handler; aliases emit a one-time deprecation warning the first time they fire.
<!-- BEGIN AUTOGEN: topic-contract -->
| Canonical topic | Aliases | Payload | Unit | Effect |
|---|---|---|---|---|
| `cmd.start` | `i_start` | any | — | Trigger / release the sampler start gate. |
| `set.schedule` | `monsternametijden` | any | — | Replace the sampling-times schedule. |
| `set.rain` | `rain_data` | any | — | Push current rain-event data into the sampler logic. |
| `data.flow` | `input_q` | `object` | — | Push the upstream flow measurement (payload: {value, unit}). |
| `set.mode` | `setMode` | any | — | Switch the monster between auto / manual modes. |
| `set.model-prediction` | `model_prediction` | any | — | Push the upstream rain-prediction snapshot used by the sampler. |
| `child.register` | `registerChild` | `string` | — | Register a child node (typically a measurement) with this monster. |
<!-- END AUTOGEN: topic-contract -->
### Mode / source / action allow-lists
monster has **no allow-list enforcement**. There is no `flowController.handle` equivalent and no `mode.allowedActions` / `mode.allowedSources` config slice. The `set.mode` handler is a placeholder. Compare `rotatingMachine`, which gates every topic through the mode matrix &mdash; on monster, every topic dispatches unconditionally.
---
## Data model &mdash; `getOutput()` shape
Composed each tick by `src/io/output.js` `buildOutput()`. Delta-compressed: consumers see only the keys that changed.
### Flat measurement keys
For every `(type, variant, position)` stored in MeasurementContainer, `getFlattenedOutput()` emits the three-segment key (note: monster does **not** add a `<childId>` segment, unlike `rotatingMachine`):
| Key | Type | Unit | Notes |
|:---|:---|:---|:---|
| `flow.manual.atequipment` | number | m³/h | Last `data.flow` value (after conversion). |
| `flow.measured.upstream` | number | m³/h | Last measured-child reading at this position. |
| `flow.measured.atequipment` | number | m³/h | Same. |
| `flow.measured.downstream` | number | m³/h | Same. |
### Scalar keys
<!-- BEGIN AUTOGEN: data-model — populate via wiki-gen tool (TODO) -->
| Key | Type | Source | Notes |
|:---|:---|:---|:---|
| `running` | boolean | `m.running` | True between `_beginRun` and `_endRun`. |
| `pulse` | boolean | `m.pulse` | True only on the tick a pulse is emitted; false otherwise. |
| `bucketVol` | number (L) | `m.bucketVol` | Composite volume accumulated this run. |
| `bucketWeight` | number (kg) | `m.bucketWeight` | `bucketVol + emptyWeightBucket`. |
| `sumPuls` | number | `m.sumPuls` | Pulses emitted this run. |
| `pulsesRemaining` | number | `targetPuls - sumPuls` | Clamped to ≥ 0. |
| `m3PerPuls` | number (m³) | `m.m3PerPuls` | Volume per pulse; set in `_beginRun` from `predFlow / targetPuls`. |
| `m3PerPulse` | number (m³) | (alias of `m3PerPuls`) | Both keys emitted; kept for legacy consumers. |
| `m3Total` | number (m³) | `m.m3Total` | Integrated total flow this run. |
| `q` | number (m³/h) | `m.q` | Effective flow (`getEffectiveFlow`). |
| `predFlow` | number (m³) | `m.predFlow` | Predicted total volume over the run window. |
| `predM3PerSec` | number (m³/s) | `m.predM3PerSec` | Predicted average rate during the run. |
| `predictedRateM3h` | number (m³/h) | `params.getPredictedFlowRate` | Rain-scaled flow band between `nominalFlowMin` and `flowMax`. |
| `timePassed` | number (s) | `m.timePassed` | Seconds since `start_time`. |
| `timeLeft` | number (s) | `m.timeLeft` | Seconds remaining until `stop_time`. |
| `pulseFraction` | number | `m.temp_pulse` | Sub-pulse integrator value (0..1+). |
| `flowToNextPulseM3` | number (m³) | derived | Volume left to integrate before the next pulse trigger. |
| `timeToNextPulseSec` | number (s) | derived | ETA to next pulse at current `q`; 0 when `q=0`. |
| `targetVolumeM3` | number (m³) | derived from `targetVolume` (L) | Target composite volume converted to m³. |
| `targetProgressPct` | number | derived | `bucketVol / targetVolume × 100`, 2-dp. |
| `targetDeltaL` | number (L) | derived | Signed L difference vs `targetVolume`. |
| `targetDeltaM3` | number (m³) | derived | Same in m³, 4-dp. |
| `nextDate` | number (epoch ms) | `m.nextDate` | Next scheduled START_DATE for `aquonSampleName`. `null` if never set. |
| `daysPerYear` | number | `m.daysPerYear` | Count of remaining scheduled runs this calendar year. |
| `sumRain` | number | `m.rainAggregator.sumRain` | Probability-weighted hourly precipitation sum. |
| `avgRain` | number | `m.rainAggregator.avgRain` | `sumRain / numberOfLocations`. |
| `nominalFlowMin` | number (m³/h) | config | Lower band for prediction. |
| `flowMax` | number (m³/h) | config | Upper band for prediction. |
| `minVolume` | number (L) | config | Lower bucket bound. |
| `maxVolume` | number (L) | derived | `maxWeight - emptyWeightBucket`. |
| `invalidFlowBounds` | boolean | derived | True when `nominalFlowMin >= flowMax`. |
| `missedSamples` | number | `m.missedSamples` | Pulse attempts blocked by cooldown. |
| `sampleCooldownMs` | number (ms) | derived | Ms remaining on the active cooldown; 0 when none. |
| `minSampleIntervalSec` | number (s) | config | Cooldown window. |
<!-- END AUTOGEN -->
### Status badge
`buildStatusBadge` in `io/statusBadge.js`:
| Condition | Badge | Fill | Shape |
|:---|:---|:---|:---|
| `invalidFlowBounds=true` | `Config error: nominalFlowMin (…) >= flowMax (…)` | red | (error preset) |
| `running=true` + `sampleCooldownMs > 0` | `SAMPLING (Ns) · <bucketVol>/<maxVolume> L` | yellow | ring |
| `running=true` + cooldown clear | `AI: RUNNING · <bucketVol>/<maxVolume> L` | green | dot |
| idle | `AI: IDLE` | grey | (idle preset) |
---
## Configuration schema &mdash; editor form to config keys
Source of truth: `generalFunctions/src/configs/monster.json` plus `nodeClass.buildDomainConfig`.
### General (`config.general`)
| Form field | Config key | Default | Notes |
|:---|:---|:---|:---|
| Name | `general.name` | `"Monster Configuration"` | Free-text. |
| (auto-assigned) | `general.id` | `null` | Node-RED node id. |
| Default unit | `general.unit` | `unitless` | Not used by the sampling program. |
| Enable logging | `general.logging.enabled` | `true` | Master switch. |
| Log level | `general.logging.logLevel` | `info` | `debug` / `info` / `warn` / `error`. |
### Functionality (`config.functionality`)
| Form field | Config key | Default | Notes |
|:---|:---|:---|:---|
| (hidden) | `functionality.softwareType` | `monster` | Constant. |
| (hidden) | `functionality.role` | `samplingCabinet` | Constant. |
| AQUON sample name | `functionality.aquonSampleName` | _unset_ | Forwarded to `source.aquonSampleName` in `extraSetup`. Falls back to `'112100'` in `_initState`. |
### Asset (`config.asset`)
| Form field | Config key | Default | Notes |
|:---|:---|:---|:---|
| Asset UUID | `asset.uuid` | `null` | Globally-unique identifier. |
| Geolocation | `asset.geoLocation` | `{x:0, y:0, z:0}` | |
| Supplier | `asset.supplier` | `"Unknown"` | Schema only &mdash; not used by the sampling program. |
| Type | `asset.type` | `"sensor"` (enum) | Schema only. |
| SubType | `asset.subType` | `"pressure"` | Schema only; misleading default for a sampling cabinet (flag). |
| Model | `asset.model` | `"Unknown"` | Schema only. |
| Empty bucket weight (kg) | `asset.emptyWeightBucket` | `3` | Used in `bucketWeight = bucketVol + emptyWeightBucket` and in `maxVolume = maxWeight - emptyWeightBucket`. |
### Constraints (`config.constraints`)
| Form field | Config key | Default | Range | Notes |
|:---|:---|:---|:---|:---|
| Sampling time (hr) | `constraints.samplingtime` | `0` | ≥ 0 | Run length. Used as `samplingtime · 3600 · 1000` ms for `stop_time`. |
| Sampling period (hr) | `constraints.samplingperiod` | `24` | ≥ 1 | Documented as the fixed composite-collection period; not enforced by `samplingProgram` &mdash; the AQUON schedule arms the run. |
| Min volume (L) | `constraints.minVolume` | `5` | ≥ 5 | Used in `targetVolume = minVolume · √(maxVolume / minVolume)`. |
| Max weight (kg) | `constraints.maxWeight` | `23` | ≤ 23 | Bucket-overload bound; `maxVolume = maxWeight - emptyWeightBucket`. |
| Sub-sample volume (mL) | `constraints.subSampleVolume` | `50` | fixed | Schema enforces `min=max=50`. Hard-coded as `volume_pulse = 0.05` in domain. |
| Storage temperature (°C) | `constraints.storageTemperature.min/max` | `{1, 5}` | per-leg | Schema only &mdash; informational. |
| Flowmeter present | `constraints.flowmeter` | `true` | bool | ⚠️ In schema but **not** wired in `buildDomainConfig`. Effectively always `true`. |
| Closed system | `constraints.closedSystem` | `false` | bool | Schema only. |
| Intake speed (m/s) | `constraints.intakeSpeed` | `0.3` | ≥ 0 | Schema only &mdash; informational. |
| Intake diameter (mm) | `constraints.intakeDiameter` | `12` | ≥ 0 | Schema only &mdash; informational. |
| Nominal flow min (m³/h) | `constraints.nominalFlowMin` | `0` | ≥ 0 | Lower bound of rain-driven flow prediction. |
| Flow max (m³/h) | `constraints.flowMax` | `0` | ≥ 0 | Upper bound of rain-driven flow prediction. |
| Max rain reference | `constraints.maxRainRef` | `10` | > 0 | Rain index that maps to `flowMax`. |
| Min sample interval (s) | `constraints.minSampleIntervalSec` | `60` | ≥ 0 | Cooldown guard. |
> [!WARNING]
> **Default `flowMax = 0` blocks every run.** `validateFlowBounds` requires `0 ≤ nominalFlowMin < flowMax`. Out of the box the bounds are invalid and `_beginRun` never fires. Set `flowMax` to a realistic upper bound before deploying.
### Unit policy
monster has **no `requireUnitForTypes` policy** declared in `specificClass`. Conversions happen at the boundary:
| Quantity | Canonical (internal) | Carried as | Notes |
|:---|:---|:---|:---|
| Flow (`data.flow`) | m³/h | m³/h | `handlers.dataFlow` converts inbound via `convert(value).from(unit).to('m3/h')`. |
| Flow (measured-child) | as supplied | as supplied | `flowTracker.handleMeasuredFlow` defaults to `'m3/h'` when `unit` is missing; **does not convert**. Wire children that emit in m³/h. |
| Volume (`bucketVol`) | L | L | Output also exposes m³ derivations (`targetVolumeM3`, `targetDeltaM3`). |
| Weight | kg | kg | |
| Time | s (timers) / ms (timestamps) | mixed | `timePassed` / `timeLeft` in s, `nextDate` in epoch ms. |
---
## Child registration
Source: `src/specificClass.js` `_wireMeasurementChild`. The registrar subscribes to all three `flow.measured.<position>` events on the child's measurement emitter as long as the child's `config.asset.type` is `'flow'` or unset.
| Software type | Filter | Wired to | Side-effect |
|:---|:---|:---|:---|
| `measurement` | `asset.type='flow'` (or missing) | `flowTracker.handleMeasuredFlow` (handles all three positions) | Latches latest measured flow per position; `getEffectiveFlow` blends across positions and with `manualFlow`. |
| `measurement` | `asset.type` anything else | _ignored_ | The branch returns early; no listener is attached. |
monster has **no position-based filtering**. Unlike `rotatingMachine` (which routes upstream pressure separately from downstream), all three flow positions are wired to the same handler and the latest value per position wins.
There are **no auto-registered virtual children** (no `dashboard-sim-*` equivalent). Inject simulated flow via `data.flow` instead.
---
## Related pages
| Page | Why |
|:---|:---|
| [Home](Home) | Intuitive overview |
| [Reference &mdash; Architecture](Reference-Architecture) | Code map, sampling-program loop, prediction + cooldown pipeline |
| [Reference &mdash; Examples](Reference-Examples) | Shipped flows + debug recipes |
| [Reference &mdash; Limitations](Reference-Limitations) | Known issues and open questions |
| [EVOLV &mdash; Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions) | Platform-wide topic rules |
| [EVOLV &mdash; Telemetry](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Telemetry) | Port 0 / 1 / 2 InfluxDB layout |