Replaces the agent-written placeholder inside Reference-Contracts.md with the authoritative table generated from src/commands/index.js. Both the BEGIN and END markers are normalized to the canonical form used by `@evolv/wiki-gen`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
Reference — Contracts
Note
Full topic contract, configuration schema, and child-registration filters for
monster. Source of truth:src/commands/index.js,src/specificClass.jsconfigure(), and the schema atgeneralFunctions/src/configs/monster.json.For an intuitive overview, return to the Home.
Note
Pending full node review (2026-05). Content reflects
CONTRACT.mdand 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.
| 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. |
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 — on monster, every topic dispatches unconditionally.
Data model — 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
| 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. |
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 — 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 — 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 — 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 — 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 — informational. |
| Intake diameter (mm) | constraints.intakeDiameter |
12 |
≥ 0 | Schema only — 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 = 0blocks every run.validateFlowBoundsrequires0 ≤ nominalFlowMin < flowMax. Out of the box the bounds are invalid and_beginRunnever fires. SetflowMaxto 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 | Intuitive overview |
| Reference — Architecture | Code map, sampling-program loop, prediction + cooldown pipeline |
| Reference — Examples | Shipped flows + debug recipes |
| Reference — Limitations | Known issues and open questions |
| EVOLV — Topic Conventions | Platform-wide topic rules |
| EVOLV — Telemetry | Port 0 / 1 / 2 InfluxDB layout |