Compare commits

6 Commits

Author SHA1 Message Date
znetsixe
965e3ba305 fix(examples): add required ui-chart props to basic.flow.json (flow-lint)
4 charts were missing interpolation + colors — FlowFuse ui-chart renders blank
without them. Added the canonical required-property set. Lint-clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 19:18:42 +02:00
znetsixe
9427b64bbe feat(commands): route data.flow through the registry unit normaliser; wiki sync
- data.flow: declare unit:'m3/h' (payloadSchema any) and drop the handler's own
  convert() call — the registry now converts any number/string/{value,unit} to
  m3/h before the handler. Handler reads the normalised scalar.
- Regenerate wiki topic-contract.

data.flow integration test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 18:41:33 +02:00
znetsixe
6c88b6464d style: palette swatch → (domain-hue redesign 2026-05-21)
Sidebar swatch now follows function family rather than S88 level, so the
palette is visually identifiable instead of monochromatically blue. Editor-group
rectangles in flow.json still follow S88 — only the registerType color changed.
Full table + rationale: superproject .claude/rules/node-red-flow-layout.md §10.0
and .claude/refactor/OPEN_QUESTIONS.md (2026-05-21 entry).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:05:57 +02:00
znetsixe
4eb286771e docs(framing): reframe monster as a sampling-cabinet pulse counter
The repo's prior docs (CLAUDE.md, MEMORY notes, superproject overview)
framed monster as a "multi-parameter biological process monitor"
suggesting NH4/NO3/COD/TSS measurement. The source code only emits
volumetric pulse + bucket state — no constituent analysis. The
framing was misleading. Reframed to match the code.

Also updated examples/README.md: removed references to four flow
files that don't exist in the repo (`integration.flow.json`,
`edge.flow.json`, `monster-dashboard.flow.json`,
`monster-api-dashboard.flow.json`) and documented the actual two
files (`basic.flow.json`, `02-integrated-e2e.json`). The missing
flows stay tracked in wiki/Reference-Examples.md as TODOs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:05:52 +02:00
znetsixe
fa4f065104 fix(CONTRACT): add child.register row — was in registry but not the table
The commands registry has `child.register` (with `registerChild` alias);
CONTRACT.md mentioned it only in the Port 2 prose. contract-verify
required it in the canonical Inputs table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:59:06 +02:00
znetsixe
aff866bd9b docs(wiki): regenerate topic-contract AUTOGEN block via wiki-gen
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>
2026-05-19 10:11:48 +02:00
8 changed files with 105 additions and 57 deletions

View File

@@ -1,6 +1,6 @@
# monster — Claude Code context
Multi-parameter biological process monitoring.
Sampling-cabinet pulse counter — measures sampled volumes and pulse cadence; does *not* analyze constituents (NH4 / NO3 / COD / TSS). For multi-parameter analyses, wire the output to an upstream/downstream analyzer node.
Part of the [EVOLV](https://gitea.wbd-rd.nl/RnD/EVOLV) wastewater-automation platform.
## S88 classification

View File

@@ -13,6 +13,7 @@ Hand-maintained for Phase 6; the `## Inputs` table is generated from
| `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 | Delegated to `source.setMode()` if defined. Reserved for future use. |
| `set.model-prediction` | `model_prediction` | numeric | Delegated to `source.setModelPrediction()` if defined. Reserved for future use. |
| `child.register` | `registerChild` | `string` — the child node's Node-RED id | Resolves the child via `RED.nodes.getNode` and registers it through `childRegistrationUtils` at the supplied `msg.positionVsParent`. |
Aliases log a one-time deprecation warning the first time they fire.

View File

@@ -1,31 +1,34 @@
# Monster Example Flows
Import-ready Node-RED examples for `monster`.
Import-ready Node-RED examples for `monster` — a sampling-cabinet pulse
counter (volumetric, not an analytical multi-parameter monitor; see
the framing note in `CONTRACT.md`).
## Files present in this repo
## Files
- `basic.flow.json`
- Purpose: quick-start flow with dashboard charts for key monster outputs.
- `integration.flow.json`
- Purpose: lightweight integration contract example (`registerChild` path).
- `edge.flow.json`
- Purpose: unknown-topic/edge handling smoke example.
- `monster-dashboard.flow.json`
- Purpose: richer dashboard-focused visualization of process output.
- Includes:
- manual flow input
- manual start trigger
- seeded `rain_data` and `monsternametijden`
- parsed report fields (`m3Total`, `m3PerPuls`, `pulse`, `running`)
- `monster-api-dashboard.flow.json`
- Purpose: full orchestration template around `monster` with API paths and dashboard output.
- Includes:
- Open-Meteo weather fetch -> `rain_data`
- Aquon SFTP CSV fetch -> `monsternametijden`
- Z-Info token + import payload builder for `m3Total`/`m3PerPuls`
- dashboard API publish template (Grafana)
- placeholder-only credentials/hosts (`__SET_*__`)
- Inject-driven quick-start flow with the dashboard widgets for the
main monster outputs (`pulse`, `bucketVol`, `m3PerPuls`, `running`,
`predFlow`).
- `02-integrated-e2e.json`
- End-to-end orchestration template: wires a flow source, a schedule,
rain-data input, and the dashboard surface. **Not yet validated
against live Node-RED** — treat as a wiring reference until smoke-
tested. Placeholder credentials (`__SET_*__`) need to be replaced
before any real deployment.
## Notes
- `basic.flow.json` and `monster-dashboard.flow.json` are intentionally API-free.
- `monster-api-dashboard.flow.json` is the full API template variant and must be hardened with environment-backed secrets before production use.
- `ui-chart` uses series by `msg.topic` (`category: "topic"`, `categoryType: "msg"`).
## Files referenced in earlier docs but not yet shipped
`integration.flow.json`, `edge.flow.json`, `monster-dashboard.flow.json`,
and `monster-api-dashboard.flow.json` were mentioned by prior versions
of this README; they do not exist in the repo at this commit. They are
tracked as TODOs in `wiki/Reference-Examples.md`. When generated, follow
the tier convention used by the rotatingMachine examples
(`01-Basic.json`, `02-Integration.json`, `03-Dashboard.json`).
## Conventions
- `ui-chart` uses series by `msg.topic` (`category: "topic"`,
`categoryType: "msg"`).
- API templates (rain fetch / Aquon SFTP / Z-Info import) must be
hardened with environment-backed secrets before production use.

View File

@@ -239,7 +239,19 @@
"action": "append",
"x": 1170,
"y": 120,
"wires": []
"wires": [],
"interpolation": "linear",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
]
},
{
"id": "monster_basic_chart_total",
@@ -264,7 +276,19 @@
"action": "append",
"x": 1180,
"y": 180,
"wires": []
"wires": [],
"interpolation": "linear",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
]
},
{
"id": "monster_basic_chart_bucket",
@@ -289,7 +313,19 @@
"action": "append",
"x": 1180,
"y": 240,
"wires": []
"wires": [],
"interpolation": "linear",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
]
},
{
"id": "monster_basic_chart_pulse",
@@ -314,7 +350,19 @@
"action": "append",
"x": 1190,
"y": 300,
"wires": []
"wires": [],
"interpolation": "linear",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
]
},
{
"id": "monster_basic_text_status",

View File

@@ -5,7 +5,7 @@
<script>
RED.nodes.registerType("monster", {
category: "EVOLV",
color: "#4f8582",
color: "#9C5BB0",
defaults: {
// Define default properties

View File

@@ -5,8 +5,6 @@
// handler (the legacy nodeClass did it inline) — anything else inbound
// is passed straight through to source.handleInput.
const { convert } = require('generalFunctions');
exports.cmdStart = (source, msg) => {
source.handleInput('i_start', Boolean(msg.payload));
};
@@ -20,17 +18,15 @@ exports.setRain = (source, msg) => {
};
exports.dataFlow = (source, msg, ctx) => {
// The registry has already normalised any accepted shape (number, numeric
// string, or { value, unit }) to a number in m3/h and tagged msg.unit.
const log = ctx?.logger || source.logger;
const value = Number(msg.payload?.value);
const unit = msg.payload?.unit;
if (!Number.isFinite(value) || !unit) {
log?.warn?.('data.flow payload must include numeric value and unit.');
const value = Number(msg.payload);
if (!Number.isFinite(value)) {
log?.warn?.(`data.flow payload must be numeric, got '${JSON.stringify(msg.payload)}'.`);
return;
}
let converted = value;
try { converted = convert(value).from(unit).to('m3/h'); }
catch (err) { log?.warn?.(`data.flow unit conversion failed: ${err.message}`); return; }
source.handleInput('input_q', { value: converted, unit: 'm3/h' });
source.handleInput('input_q', { value, unit: msg.unit || 'm3/h' });
};
exports.setMode = (source, msg) => {

View File

@@ -32,11 +32,11 @@ module.exports = [
{
topic: 'data.flow',
aliases: ['input_q'],
payloadSchema: { type: 'object' },
// Compound payload `{value, unit}` — handler converts internally to m3/h.
// Registry-level normalisation is skipped (the handler reads payload.value /
// payload.unit directly; flattening would break it).
description: 'Push the upstream flow measurement (payload: {value, unit}).',
// any: number, numeric string, or { value, unit } — the registry normalises
// all of them to a number in `unit` (m3/h) before the handler runs.
payloadSchema: { type: 'any' },
unit: 'm3/h',
description: 'Push the upstream flow measurement (payload: number or {value, unit}).',
handler: handlers.dataFlow,
},
{

View File

@@ -16,19 +16,19 @@
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 — populate via wiki-gen tool (TODO) -->
<!-- BEGIN AUTOGEN: topic-contract -->
| Canonical topic | Aliases | Payload | Unit | Effect |
|:---|:---|:---|:---|:---|
| `cmd.start` | `i_start` | `any` (coerced to `Boolean`) | &mdash; | 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`) | &mdash; | Stores the schedule and recomputes `nextDate` + `daysPerYear` for the configured `aquonSampleName`. |
| `set.rain` | `rain_data` | per-location rain forecast (Open-Meteo shape) | &mdash; | Aggregates hourly precipitation into `sumRain` / `avgRain`. **Skipped while `running=true`.** |
| `data.flow` | `input_q` | `{ value: number, unit: string }` | volumeFlowRate (any unit `convert()` accepts) | Converts to m³/h, pushes into `flow.manual.atequipment`. Blends with measured-child flow in `getEffectiveFlow()`. |
| `set.mode` | `setMode` | any | &mdash; | **Reserved.** Handler delegates to `source.setMode()` which is currently undefined; no-op today. |
| `set.model-prediction` | `model_prediction` | any | &mdash; | **Reserved.** Handler delegates to `source.setModelPrediction()` which is currently undefined; no-op today. |
| `child.register` | `registerChild` | string (child node id) | &mdash; | Register a `measurement` child with this monster. Port 2 wiring does this automatically in normal flows. |
|---|---|---|---|---|
| `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` | any | `volumeFlowRate` (default `m3/h`) | Push the upstream flow measurement (payload: number or {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 -->
<!-- END AUTOGEN: topic-contract -->
### Mode / source / action allow-lists