# monster
  
A `monster` models the physical "monsternamekast" (composite-sampling cabinet) on a wastewater treatment plant. It runs an AQUON-scheduled, flow-proportional sampling program: aggregates measured + manual flow, blends in a rain-scaled prediction, integrates volume, and emits a `pulse` event whenever the integrated volume crosses `m³ per pulse`. Used downstream of `reactor` / `settler` for compliance + process diagnostics — but note that the node only **integrates flow** in the current implementation; multi-constituent reporting (NH4, NO3, COD, TSS, …) is a downstream consumer concern, not produced by this node.
> [!NOTE]
> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only.
---
## At a glance
| Thing | Value |
|:---|:---|
| What it represents | One composite-sampling cabinet running AQUON-scheduled, flow-proportional sampling |
| S88 level | Unit |
| Use it when | You need scheduled composite samples with flow-proportional pulses for a benchtop analyser / lab pickup |
| Don't use it for | Generic flow totalising (use `measurement`), ad-hoc grab samples (fire `cmd.start` once instead), or analyser results — monster emits pulses, not analyte values |
| Children it accepts | `measurement` with `asset.type='flow'` (or unset) |
| Parents it talks to | Any node that issues `cmd.start` / `set.schedule` / `set.rain` / `data.flow` (typically a Process Cell coordinator) |
---
## How it fits
```mermaid
flowchart LR
parent[plant parent
Process Cell]:::pc
monster[monster
Unit]:::unit
flow_up[measurement
type=flow
position=upstream]:::ctrl
flow_at[measurement
type=flow
position=atequipment]:::ctrl
flow_dn[measurement
type=flow
position=downstream]:::ctrl
op[(operator / AQUON)]
weather[(Open-Meteo)]
flow_up -->|flow.measured.upstream| monster
flow_at -->|flow.measured.atequipment| monster
flow_dn -->|flow.measured.downstream| monster
op -->|set.schedule / data.flow / cmd.start| monster
weather -->|set.rain| monster
monster -->|child.register| parent
monster -.evt output (pulse / bucketVol).-> parent
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef ctrl fill:#a9daee,color:#000
```
S88 colours are anchored in `.claude/rules/node-red-flow-layout.md`. The editor node tile in `monster.html` is currently `#4f8582` (teal) and pending cleanup — Section 16 of the layout rules tracks it.
---
## Try it — 3-minute demo
Import the basic example flow, deploy, and drive the sampler through a single run.
```bash
curl -X POST -H 'Content-Type: application/json' \
--data @nodes/monster/examples/basic.flow.json \
http://localhost:1880/flow
```
What to click after deploy (the inject buttons map to the topics in [Reference — Contracts](Reference-Contracts#topic-contract)):
1. `set.rain` — push an Open-Meteo precipitation snapshot so `sumRain` / `avgRain` populate (otherwise the rain-scaled prediction stays at `nominalFlowMin`).
2. `set.schedule` — push an AQUON row array so `nextDate` arms.
3. `data.flow = {value: 240, unit: 'm3/h'}` — supply a manual flow value (or wire a `measurement` child for the measured path).
4. `cmd.start = true` — releases the start gate. On the next 1000 ms tick the sampling program checks `validateFlowBounds`; if `nominalFlowMin < flowMax`, `_beginRun` fires, `running` flips to `true`, and the bucket integrator starts.
5. Watch Port 0: `pulse` blips to `true` for one tick whenever `temp_pulse` crosses 1; `bucketVol` rises in 50 mL steps (the hard-coded `subSampleVolume`); `sumPuls` increments.
6. After `constraints.samplingtime` hours `_endRun` fires and `running` flips to `false`.
> [!IMPORTANT]
> **GIF needed.** Demo recording of steps 1–6 with the live status panel. Save as `wiki/_partial-gifs/monster/basic-demo.gif`, target ≤ 1 MB after `gifsicle -O3 --lossy=80`.
---
## The seven things you'll send
| Topic | Aliases | Payload | What it does |
|:---|:---|:---|:---|
| `cmd.start` | `i_start` | truthy / falsy | Sets `source.i_start`. On the next tick a sampling run begins if `validateFlowBounds` passes. |
| `set.schedule` | `monsternametijden` | array of AQUON rows (`SAMPLE_NAME`, `DESCRIPTION`, `SAMPLED_DATE`, `START_DATE`, `END_DATE`) | Stores the schedule and recomputes `nextDate` + `daysPerYear` for the configured `aquonSampleName`. |
| `set.rain` | `rain_data` | per-location rain forecast (Open-Meteo shape) | Aggregates hourly precipitation into `sumRain` / `avgRain`; feeds the rain-scaled flow prediction. Ignored while `running=true`. |
| `data.flow` | `input_q` | `{ value: number, unit: string }` | Converts to m³/h and pushes into `flow.manual.atequipment`. Blends with measured-child flow in `getEffectiveFlow()`. |
| `set.mode` | `setMode` | string | Reserved — handler delegates to `source.setMode()` which is currently undefined. No-op today. |
| `set.model-prediction` | `model_prediction` | numeric | Reserved — handler delegates to `source.setModelPrediction()` which is currently undefined. No-op today. |
| `child.register` | `registerChild` | string (child node id) | Register a `measurement` child. Port 2 wiring does this automatically. |
There is no `query.*` topic on monster (unlike `rotatingMachine`'s `query.curves` / `query.cog`). All state is on Port 0.
---
## What you'll see come out
Sample Port 0 message (mid-run snapshot, delta-compressed):
```json
{
"topic": "monster#cabinet_1",
"payload": {
"running": true,
"pulse": false,
"bucketVol": 1.25,
"bucketWeight": 4.25,
"sumPuls": 25,
"m3PerPuls": 4,
"m3Total": 100.0,
"q": 215.4,
"predFlow": 240.0,
"predM3PerSec": 0.067,
"timeLeft": 12340,
"timePassed": 600,
"targetVolumeM3": 0.005,
"targetProgressPct": 41.6,
"targetDeltaL": -2.93,
"predictedRateM3h": 240.0,
"sumRain": 3.2,
"avgRain": 0.13,
"nextDate": 1746940800000,
"daysPerYear": 12,
"missedSamples": 0,
"sampleCooldownMs": 0,
"invalidFlowBounds": false
}
}
```
| Field | Meaning |
|:---|:---|
| `running` | Run/idle flag — monster has no formal FSM, just this boolean and the timer fields below. |
| `pulse` | `true` for the tick on which a pulse is emitted; falls back to `false` on the next tick. |
| `bucketVol` / `bucketWeight` | Accumulated composite volume (L) and total bucket mass (kg = vol + `emptyWeightBucket`). |
| `sumPuls` / `pulsesRemaining` | Pulses fired this run vs target ceiling. |
| `m3PerPuls` | Volume per pulse, set at `_beginRun` from `predFlow / targetPuls`. |
| `m3Total` | Total integrated flow this run (m³). |
| `q` | Effective flow (m³/h) — `flowTracker.getEffectiveFlow()` blends measured + manual. |
| `predFlow` / `predM3PerSec` | Predicted total volume over the run window (m³) and its average rate. |
| `timeLeft` / `timePassed` | Run-window seconds remaining / elapsed. |
| `targetVolumeM3` / `targetProgressPct` / `targetDeltaL` | Target composite volume (m³), % of target accumulated, signed L delta against target. |
| `predictedRateM3h` | Rain-scaled flow prediction (m³/h) — between `nominalFlowMin` and `flowMax`. |
| `sumRain` / `avgRain` | Probability-weighted hourly precipitation, summed / averaged across locations. |
| `nextDate` / `daysPerYear` | Next scheduled run epoch ms; count of remaining runs this calendar year. |
| `missedSamples` / `sampleCooldownMs` | Cooldown-blocked pulse count; ms remaining on the active cooldown. |
| `invalidFlowBounds` | True when `nominalFlowMin >= flowMax` — gates `_beginRun`. |
Full Port-0 key list: see [Reference — Contracts — Data model](Reference-Contracts#data-model--getoutput-shape).
Key shape is **flat scalars on the snapshot** plus a `MeasurementContainer.getFlattenedOutput()` flush of `flow..` entries. monster does **not** use the four-segment `...` shape that `rotatingMachine` emits — no `childId` is appended because monster fans incoming children into the same three positions and the latest value wins.
---
## Status badge
| State | Badge | Fill |
|:---|:---|:---|
| `invalidFlowBounds=true` | `Config error: nominalFlowMin (…) >= flowMax (…)` | red |
| `running=true` + cooldown active | `SAMPLING (Ns) · / L` | yellow ring |
| `running=true` + normal | `AI: RUNNING · / L` | green dot |
| idle | `AI: IDLE` | grey ring |
---
## Need more?
| Page | What you'll find |
|:---|:---|
| [Reference — Contracts](Reference-Contracts) | Full topic contract, config schema, child registration filters |
| [Reference — Architecture](Reference-Architecture) | Code map, sampling-program loop, prediction + cooldown pipeline |
| [Reference — Examples](Reference-Examples) | Shipped example flows + debug recipes |
| [Reference — Limitations](Reference-Limitations) | When not to use, known limitations, open questions |
[EVOLV master wiki](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Home) · [Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) · [Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions)