# monster > **Reflects code as of `8540328` · regenerated `2026-05-11` via `npm run wiki:all`** > If this banner is stale, the page may be out of date. Treat as informative, not authoritative. ## 1. What this node is **monster** is an S88 Unit that runs an AQUON-scheduled flow-proportional sampling program. It aggregates measured + manual flow, blends a rain-scaled prediction, and emits a `pulse` whenever the integrated volume crosses `m³ per pulse`. Drives the physical "monsternamekast" (composite sampling cabinet) on a wastewater treatment plant. ## 2. Position in the platform ```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 op[(operator / AQUON)] weather[(Open-Meteo)] flow_up -->|flow.measured.upstream| monster flow_at -->|flow.measured.atequipment| monster op -->|set.schedule / data.flow / cmd.start| monster weather -->|set.rain| monster monster -->|child.register| parent monster -.evt.output.-> parent classDef pc fill:#0c99d9,color:#fff classDef unit fill:#50a8d9,color:#000 classDef ctrl fill:#a9daee,color:#000 ``` S88 colours: Process Cell `#0c99d9`, Unit `#50a8d9`, Control Module `#a9daee`. Source of truth: `.claude/rules/node-red-flow-layout.md`. ## 3. Capability matrix | Capability | Status | Notes | |---|---|---| | Flow-proportional sampling pulse | ✅ | Triggered when integrated `temp_pulse` ≥ 1. | | Time-bounded sampling run | ✅ | Run length governed by `constraints.samplingtime` (hours). | | AQUON schedule auto-start | ✅ | `set.schedule` parses AQUON rows; `nextDate` arms the next run. | | Rain-scaled flow prediction | ✅ | `set.rain` aggregates Open-Meteo hourly precipitation into `sumRain` / `avgRain`. | | Measured + manual flow blend | ✅ | `flowTracker.getEffectiveFlow()` picks measured if recent, else manual. | | Minimum sample-interval cooldown | ✅ | Skips pulses inside `minSampleIntervalSec` window. | | Bucket / weight tracking | ✅ | `bucketVol`, `bucketWeight` track the composite container. | | Sub-sample volume override | ❌ | `subSampleVolume` fixed at 50 mL per schema. | | Stateful FSM | ❌ | Monster is run/idle only — no formal state machine. | ## 4. Code map ```mermaid flowchart TB subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"] nc["buildDomainConfig()
static DomainClass = Monster
static commands"] end subgraph domain["specificClass.js — orchestrator (BaseDomain)"] sc["Monster.configure()
wires concerns, ChildRouter rules
tick() → flowCalc → samplingProgram"] end subgraph concerns["src/ concern modules"] parameters["parameters/
bounds + targets + rain index"] flow["flow/
FlowTracker (measured + manual)"] rain["rain/
RainAggregator (sum + avg)"] schedule["schedule/
AQUON next-date parser"] sampling["sampling/
samplingProgram + flowCalc"] io["io/
output + statusBadge"] commands["commands/
topic registry + handlers"] end nc --> sc sc --> parameters sc --> flow sc --> rain sc --> schedule sc --> sampling sc --> io nc --> commands ``` | Module | Owns | Read first if you're changing… | |---|---|---| | `parameters/` | Bounds (`minPuls`, `absMaxPuls`, `targetPuls`), rain-index lookup, predicted-flow rate. | Sampling bounds, rain scaling math. | | `flow/` | `FlowTracker` — measured-child latch + manual flow + effective-flow pick. | Flow source priority, dead-band logic. | | `rain/` | `RainAggregator` — fold hourly Open-Meteo rows into `sumRain` / `avgRain`. | Rain inputs, forecast horizon. | | `schedule/` | AQUON schedule parsing, `nextDate` + `daysPerYear` derivation. | Schedule arming, sample-name filtering. | | `sampling/` | `_beginRun` / `_endRun` / `_maybeEmitPulse` — pulse integrator + cooldown guard. | Pulse timing, cooldown behaviour. | | `io/` | `buildOutput`, `buildStatusBadge`. | Port-0 payload shape, status badge text. | | `commands/` | Topic registry + payload validation + unit conversion. | New input topics, alias deprecation. | ## 5. Topic contract > **Auto-generated** from `src/commands/index.js`. Do NOT hand-edit between the markers. Re-run `npm run wiki: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. | ## 6. Child registration Mirrors the `ChildRouter` declaration in `specificClass.js → configure()`. Monster only accepts `measurement` children whose `asset.type` is `flow` (or unset). ```mermaid flowchart LR subgraph kids["accepted children (softwareType)"] m_up["measurement
asset.type=flow
position=upstream"]:::ctrl m_at["measurement
asset.type=flow
position=atequipment"]:::ctrl m_dn["measurement
asset.type=flow
position=downstream"]:::ctrl end m_up -->|flow.measured.upstream| ft[FlowTracker.handleMeasuredFlow] m_at -->|flow.measured.atequipment| ft m_dn -->|flow.measured.downstream| ft ft --> eff[getEffectiveFlow] classDef ctrl fill:#a9daee,color:#000 ``` | softwareType | filter | wired to | side-effect | |---|---|---|---| | `measurement` | `asset.type=flow` (or missing) | `flowTracker.handleMeasuredFlow` | Latches latest measured flow for `getEffectiveFlow`. | | `measurement` | `asset.type` anything else | _ignored_ | Logged once at register. | ## 7. Lifecycle — the sampling-program loop ```mermaid sequenceDiagram participant child as measurement child participant ops as operator / AQUON participant monster as monster participant ft as flowTracker participant sp as samplingProgram participant out as Port-0 output child->>monster: flow.measured. ops->>monster: set.schedule / cmd.start / data.flow Note over monster: every 1000 ms tick monster->>ft: getEffectiveFlow() monster->>monster: flowCalc → m3PerTick monster->>sp: samplingProgram alt cmd.start OR Date.now() ≥ nextDate sp->>sp: validateFlowBounds → _beginRun end alt running AND stop_time > now sp->>sp: integrate temp_pulse += m3PerTick / m3PerPuls sp->>sp: _maybeEmitPulse (cooldown-guarded) else stop_time elapsed sp->>sp: _endRun end monster->>out: msg{topic, payload (delta-compressed)} ``` One pulse per integrated `m³ per pulse`. The cooldown guard suppresses pulses inside `minSampleIntervalSec` and increments `missedSamples`. ## 8. Data model — `getOutput()` What lands on Port 0. Built in `buildOutput()` from `m.measurements.getFlattenedOutput()` plus the sampling-run snapshot. | Key | Type | Unit | Sample | |---|---|---|---| | `avgRain` | number | — | `0` | | `bucketVol` | number | — | `0` | | `bucketWeight` | number | — | `0` | | `daysPerYear` | number | — | `0` | | `flowMax` | number | — | `0` | | `flowToNextPulseM3` | number | — | `0` | | `invalidFlowBounds` | boolean | — | `false` | | `m3PerPuls` | number | — | `0` | | `m3PerPulse` | number | — | `0` | | `m3Total` | number | — | `0` | | `maxVolume` | number | — | `20` | | `minSampleIntervalSec` | number | — | `60` | | `minVolume` | number | — | `5` | | `missedSamples` | number | — | `0` | | `nextDate` | undefined | — | `null` | | `nominalFlowMin` | number | — | `0` | | `predFlow` | number | — | `0` | | `predM3PerSec` | number | — | `0` | | `predictedRateM3h` | number | — | `0` | | `pulse` | boolean | — | `false` | | `pulseFraction` | number | — | `0` | | `pulsesRemaining` | number | — | `200` | | `q` | number | — | `0` | | `running` | boolean | — | `false` | | `sampleCooldownMs` | number | — | `0` | | `sumPuls` | number | — | `0` | | `sumRain` | number | — | `0` | | `targetDeltaL` | number | — | `-10` | | `targetDeltaM3` | number | — | `-0.01` | | `targetProgressPct` | number | — | `0` | | `targetVolumeM3` | number | — | `0.01` | | `timeLeft` | number | — | `0` | | `timePassed` | number | — | `0` | | `timeToNextPulseSec` | number | — | `0` | **Concrete sample** (mid-run snapshot): ```json { "pulse": false, "running": true, "bucketVol": 1.25, "sumPuls": 25, "predFlow": 240.0, "m3PerPuls": 4, "q": 215.4, "timeLeft": 12340, "targetVolumeM3": 0.005, "targetProgressPct": 41.6, "sumRain": 3.2, "avgRain": 0.13, "nextDate": 1746940800000 } ``` Concrete samples must come from a known-good test run. Regenerate when concern modules change shape. ## 9. Configuration — editor form ↔ config keys ```mermaid flowchart TB subgraph editor["Node-RED editor form"] f1[Sampling time hr] f2[Sampling period hr] f3[Min volume L] f4[Max weight kg] f5[Empty bucket weight kg] f6[Flowmeter on/off] f7[Min sample interval s] end subgraph config["Domain config slice"] c1[constraints.samplingtime] c2[constraints.samplingperiod] c3[constraints.minVolume] c4[constraints.maxWeight] c5[asset.emptyWeightBucket] c6[constraints.flowmeter] c7[constraints.minSampleIntervalSec] end f1 --> c1 f2 --> c2 f3 --> c3 f4 --> c4 f5 --> c5 f6 --> c6 f7 --> c7 ``` | Form field | Config key | Default | Range | Where used | |---|---|---|---|---| | Sampling time (hr) | `constraints.samplingtime` | `0` | ≥ 0 | run length, `predFlow` | | Sampling period (hr) | `constraints.samplingperiod` | `24` | ≥ 1 | schedule arming | | Min volume (L) | `constraints.minVolume` | `5` | ≥ 5 | bounds + invalid-flow guard | | Max weight (kg) | `constraints.maxWeight` | `23` | ≤ 23 | bucket overload | | Empty bucket weight (kg) | `asset.emptyWeightBucket` | `3` | ≥ 0 | `bucketWeight` | | Sub-sample volume (mL) | `constraints.subSampleVolume` | `50` | fixed | per-pulse volume | | Flowmeter present | `constraints.flowmeter` | `true` | bool | ⚠️ In schema but NOT wired in `buildDomainConfig` — effectively always `true` at runtime. | | Min sample interval (s) | `constraints.minSampleIntervalSec` | `60` | ≥ 0 | cooldown guard | | Nominal flow min (m³/h) | `constraints.nominalFlowMin` | `0` | ≥ 0 | lower bound for rain-driven flow prediction band | | Flow max (m³/h) | `constraints.flowMax` | `0` | ≥ 0 | upper bound for rain-driven flow prediction band | | Max rain reference | `constraints.maxRainRef` | `10` | > 0 | rain-index that maps to `flowMax` | | Intake speed (m/s) | `constraints.intakeSpeed` | `0.3` | ≥ 0 | informational | | Intake diameter (mm) | `constraints.intakeDiameter` | `12` | ≥ 0 | informational | ## 10. State chart Skipped — monster has no formal state machine. The `running` boolean toggles when `_beginRun` / `_endRun` fire. See section 7 for the sampling-program sequence, which is the closest analogue. ## 11. Examples | Tier | File | What it shows | Status | |---|---|---|---| | Basic | `examples/basic.flow.json` | Inject + manual flow + dashboard, no parent | ✅ in repo | | Integration | `examples/integration.flow.json` | monster + measurement child + AQUON schedule | ✅ in repo | | Dashboard | `examples/monster-dashboard.flow.json` | Live FlowFuse charts (pulse, bucket, predFlow) | ✅ in repo | | Edge | `examples/edge.flow.json` | Cooldown guard + invalid-flow bounds | ✅ in repo | | API | `examples/monster-api-dashboard.flow.json` | dashboardAPI consumer wired in | ✅ in repo | One screenshot per tier where helpful. PNG ≤ 200 KB under `wiki/_partial-screenshots/monster/`. ## 12. Debug recipes | Symptom | First thing to check | Where to look | |---|---|---| | `pulse` never fires | Is `running` true? Check `validateFlowBounds` log — invalid bounds short-circuits the run. | `parameters/parameters.js` | | Pulses arrive too fast / skipped | Cooldown guard active. Inspect `missedSamples` + `minSampleIntervalSec`. | `sampling/samplingProgram.js → _maybeEmitPulse` | | `q` always zero | Measured-flow child not registered, manual flow not pushed. Watch Port 2 + `data.flow` history. | `flow/flowTracker.js` | | Measurement child ignored at register | Child's `config.asset.type` is set to something other than `"flow"` (e.g. `"flow-electromagnetic"`). Monster only accepts `asset.type = "flow"` or unset. Set `asset.type: "flow"` on the measurement node. | `specificClass.js → _wireMeasurementChild` | | `nextDate` not arming | `set.schedule` payload didn't include matching `aquonSampleName` row. | `schedule/schedule.js → regNextDate` | | `sumRain` zero with rain input | `rainAggregator.update` ran while `running=true` (skipped). | `specificClass.js → updateRainData` | | Bucket overfilled before `stop_time` | `m3PerPuls` rounded down; check `predFlow` vs effective `q`. | `sampling/samplingProgram.js → _beginRun` | | `flowmeter=false` has no effect | `constraints.flowmeter` is in the schema but not wired in `buildDomainConfig`. The sampling program always runs in proportional mode. | `src/nodeClass.js → buildDomainConfig` | > Never ship `enableLog: 'debug'` in a demo — fills the container log within seconds and obscures real errors. Use only for live debugging. ## 13. When you would NOT use this node - Use monster for **AQUON-scheduled composite sampling** on a wastewater plant. For a single grab sample on demand, fire `cmd.start` once and tear it down — monster is overkill for ad-hoc work. - Don't use monster as a generic flow totaliser. Use a `measurement` child with the right type/variant for raw flow integration. - Skip monster if you don't need pulse-proportional dosing — the time-mode (`flowmeter=false`) is currently informational only. ## 14. Known limitations / current issues | # | Issue | Tracked in | |---|---|---| | 1 | Edge test `sampling-guards.edge.test.js` cooldown-guard case is a pre-existing failure — the cooldown skip increments `missedSamples` but the assertion expects a different timing. | `test/edge/sampling-guards.edge.test.js` | | 2 | `set.mode` and `set.model-prediction` are reserved — handlers delegate to optional methods that don't exist yet. | `commands/handlers.js` | | 3 | Time-only mode (`flowmeter=false`) is not exercised — the sampling program assumes a flow source. `constraints.flowmeter` is also not forwarded in `buildDomainConfig`. | `sampling/samplingProgram.js`, `src/nodeClass.js` | | 4 | Sub-sample volume hard-coded at 50 mL (schema enforces `min=max=50`). | `generalFunctions/src/configs/monster.json` | | 5 | S88 colour cleanup pending: node editor colour is `#4f8582` (teal) in `monster.html`. Correct Unit-level colour is `#50a8d9`. Section 2 diagram already uses the correct colour. The editor node tile must be updated separately. | `monster.html` → `color:` field |