Files
EVOLV/.claude/refactor/WIKI_TEMPLATE.md

350 lines
16 KiB
Markdown
Raw Normal View History

# Wiki page template — every node uses this shape
Canonical structure for every node's Gitea wiki landing page. **Visual-first**, scannable, ≤ 60 words per paragraph anywhere on the page.
## Why this shape
The platform has 12 nodes that all share the same architectural skeleton (BaseDomain + BaseNodeAdapter + ChildRouter + commands registry). The wiki should mirror that uniformity: a reader flips between nodes and finds the same 14 sections in the same order. Diagrams lead. Tables annotate. Prose only fills gaps.
## Picking a visual
The default is Mermaid (Gitea renders it natively). It's the right tool for graph-shaped things — neighbours, lifecycles, state machines, file maps. But Mermaid doesn't render data: when a section is about *what a curve looks like* or *what the predicted vs measured signal does over time*, use:
| Need | Tool | Where the artifact lives |
|---|---|---|
| Graph (nodes + edges, hierarchy, state) | Mermaid `flowchart` / `sequenceDiagram` / `stateDiagram-v2` | inline in the wiki page |
| XY data (pump curves, prediction trace, drift over time) | Generated PNG/SVG via a small `npm run wiki:plots` script | committed under `wiki/_partial-plots/<NodeName>/*.svg` |
| Table of facts / config / topics | Markdown table | inline |
| Screenshot (dashboard, editor form) | PNG ≤ 200 KB | `wiki/_partial-screenshots/<NodeName>/*.png` |
| ASCII layout (when Mermaid is overkill) | code block | inline |
Lead with the visual that serves the section. Don't gate it on "is this Mermaid".
## Section list
Sections 19 and 1114 are mandatory for every node. Section 10 (State chart) is mandatory for stateful nodes (`rotatingMachine`, `valve`, `pumpingStation`, …) and skipped for pure aggregators (`measurement`, `dashboardAPI`).
| # | Section | Visual lead | Auto-gen? |
|---|---|---|---|
| 0 | Header band (git hash + regen date) | — | yes |
| 1 | What this node is | — (single paragraph) | no |
| 2 | Position in the platform | Mermaid `flowchart LR` | no |
| 3 | Capability matrix | table | no |
| 4 | Code map | Mermaid `flowchart TB` w/ subgraphs | no |
| 5 | Topic contract | table | **yes** (`wiki:contract`) |
| 6 | Child registration | Mermaid + table | no |
| 7 | Lifecycle | Mermaid `sequenceDiagram` | no |
| 8 | Data model — `getOutput()` | table + concrete sample | **yes** (`wiki:datamodel`) |
| 9 | Configuration — form ↔ config | Mermaid `flowchart TB` | no |
| 10 | State chart (stateful only) | Mermaid `stateDiagram-v2` | no |
| 11 | Examples | table + screenshots | no |
| 12 | Debug recipes | table | no |
| 13 | When NOT to use this node | bullets | no |
| 14 | Known limitations | table | no |
## Template — copy the block below as the seed for each node's wiki
(The block uses standard markdown syntax. The outer fence below is for visual delimitation in this README only; when seeding a new wiki page, copy the *content* between the `BEGIN TEMPLATE` / `END TEMPLATE` markers verbatim.)
```
<!-- BEGIN TEMPLATE — wiki/<NodeName>.md -->
# <Node name>
> **Reflects code as of `<git short hash>` · regenerated `<YYYY-MM-DD>` 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
One paragraph, ≤ 60 words. Plain English. State the *role*, not the *implementation*.
> Example: "**rotatingMachine** models a single pump or compressor. It takes pressure measurements from upstream and downstream, predicts the resulting flow + power from supplier-provided characteristic curves, and drives a state machine for startup/shutdown sequences. Used as a child of `machineGroupControl` when grouped, or directly under a `pumpingStation`."
## 2. Position in the platform
~~~mermaid
flowchart LR
parent[machineGroupControl<br/>Unit]:::unit -->|set.demand| this[rotatingMachine<br/>Equipment]:::equip
this -->|evt.state-change| parent
sensor_up[measurement up]:::ctrl -->|data.pressure| this
sensor_dn[measurement down]:::ctrl -->|data.pressure| this
this -->|child.register| parent
classDef proc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
~~~
release: palette redesign + CoreSync scaffolding + dashboardAPI MODULE_NOT_FOUND fix PALETTE REDESIGN (2026-05-21) Sidebar swatches switched from S88 level (all blue) to domain-hue per node. Family hue = function (rotating=orange, valves=teal, biology=green/olive, sampling=violet, sensor=amber, aeration=sky-blue, infrastructure=slate); within a family, darker = higher S88 / "more controller-ish." Editor-group rectangles in flow.json still follow S88 — only the registerType colour changed. Submodule bumps for palette: rotatingMachine, machineGroupControl, pumpingStation, valve, valveGroupControl, reactor, settler, monster, measurement, diffuser, dashboardAPI. Docs touched: - CLAUDE.md: palette swatch vs. editor-group bullets split out. - .claude/rules/node-red-flow-layout.md: new §10.0 introduces the two color systems, full 12-row palette table, and explicit warning not to mix the two hexes. - .claude/refactor/MODULE_SPLIT.md: per-node headers annotated with both `group #XXX` and `palette #XXX`. - .claude/refactor/WIKI_HOME_TEMPLATE.md + WIKI_TEMPLATE.md: clarify Mermaid classDefs visualize hierarchy, not palette swatches. - .claude/refactor/OPEN_QUESTIONS.md: dated decision entry with rationale, file list, and follow-ups. CORESYNC SUBMODULE (new) nodes/coresync added pointing at https://gitea.wbd-rd.nl/RnD/coresync. FROST/SensorThings handoff path — first version forwards FROST-ready HTTP request messages on the dbase output; a downstream http-request node performs the POST and feeds responses back on msg.topic = "frost.response". Lazy stream resolver, latest-wins queue (keep first + latest, drop middle), knot-emit on slope change, provenance preserved in Observation parameters. - .gitmodules: add nodes/coresync entry. - package.json: register coresync as a Node-RED node. - generalFunctions bump: new frostFormatter + 4 node config schemas expose the dbase format option. - measurement bump: "frost" option added to dbaseOutputFormat dropdown (plus the in-flight data.measurement unit-handling work). - machineGroupControl bump: small editor compact-fields tweak alongside the palette change. - CORESYNC_FROST_INTERVIEW_HANDOFF.md added at root with interview state (Q20 open: slope angle vs. relative delta comparison). DASHBOARDAPI MODULE_NOT_FOUND FIX package.json: dashboardapi entry path corrected to nodes/dashboardAPI/dashboardAPI.js. Commit e04c4a1 renamed the files to camelCase but missed package.json; on case-sensitive filesystems (Linux/Docker, where the tarball lands) the require resolved to nothing and the node showed MODULE_NOT_FOUND in the Node-RED palette. MISC CLEANUP - examples/README.md + examples/pumpingstation-complete-example/ removal (build_flow.py, flow.json, README.md superseded by per-node examples). - jest.config.js: in-progress tweak. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:09:33 +02:00
S88 colours are mandatory **inside hierarchy diagrams** (Mermaid `classDef`, flow.json group `style.fill`). They are NOT the node-palette swatch hexes shown in the Node-RED sidebar — those are domain-hue per node. Map (hierarchy use): Process Cell `#0c99d9`, Unit `#50a8d9`, Equipment `#86bbdd`, Control Module `#a9daee`. Source of truth: `.claude/rules/node-red-flow-layout.md` (§10.0 for palette, §10.1 for groups/lanes).
## 3. Capability matrix
| Capability | Status | Notes |
|---|---|---|
| Predicts flow from pressure | ✅ | |
| Receives manual setpoint | ✅ | Topic `set.setpoint` |
| Auto-start on demand from parent | ✅ | |
| Self-calibrating | ❌ | Calibration is operator-triggered (`cmd.calibrate`) |
| Supports multi-parent registration | ⚠️ | Possible but not fully tested — see CONTRACT.md |
Cap at 10 rows. Longer inventories link out.
## 4. Code map
~~~mermaid
flowchart TB
subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"]
nc["buildDomainConfig()<br/>static DomainClass, commands"]
end
subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
sc["Machine.configure()<br/>declares ChildRouter rules"]
end
subgraph concerns["src/ concern modules"]
curves["curves/<br/>characteristic curve loader"]
prediction["prediction/<br/>flow + power predictor"]
drift["drift/<br/>prediction-vs-measured assessor"]
flow["flow/<br/>aggregation + smoothing"]
state["state/<br/>FSM transitions"]
io["io/<br/>output formatting helpers"]
display["display/<br/>status badge composition"]
end
nc --> sc
sc --> concerns
~~~
| Module | Owns | Read first if you're changing… |
|---|---|---|
| `curves/` | Supplier characteristic curves, interpolation | Curve fitting, asset selection |
| `prediction/` | Flow + power predictors | Predicted output values |
| `drift/` | Quality of prediction vs measurement | Health status / alarms |
| `flow/` | Aggregation, smoothing | Flow reporting |
| `state/` | FSM (off → idle → operational → …) | Startup / shutdown behaviour |
Update this section when you rename or split a directory.
## 5. Topic contract
> **Auto-generated** from `src/commands/index.js`. Do NOT hand-edit between the markers. Re-run `npm run wiki:contract`.
The **Unit** column reflects the descriptor's `units: { measure, default }` declaration, rendered as `<measure> (default <unit>)`. Topics without a `units` field (non-quantity payloads — mode strings, child ids, sequence triggers) show `—`. The default unit is what the commandRegistry coerces incoming `msg.unit` values to before the handler runs. The **Effect** column is sourced from the descriptor's `description` field; topics without one fall back to a generic per-prefix sentence.
<!-- BEGIN AUTOGEN: topic-contract -->
| Canonical topic | Aliases | Payload | Unit | Effect |
|---|---|---|---|---|
| `set.mode` | `setMode` | `string` (`auto`\|`manual`\|`maintenance`) | — | Switches operating mode. |
| `set.demand` | `Qd` | `number` | `volumeFlowRate` (default `m3/h`) | Sets the manual demand setpoint. |
| `cmd.startup` | `execSequence` (with `payload.action='startup'`) | `{source: string}` | — | Triggers startup sequence. |
<!-- END AUTOGEN: topic-contract -->
## 6. Child registration
What children this node accepts and what it does with each event the child can emit. Mirrors the `ChildRouter` declarations in `specificClass.js``configure()`.
~~~mermaid
flowchart LR
subgraph kids["accepted children (softwareType)"]
m_up["measurement<br/>type=pressure<br/>position=upstream"]:::ctrl
m_dn["measurement<br/>type=pressure<br/>position=downstream"]:::ctrl
end
m_up -->|data.pressure| handler1[pressure handler<br/>updates measurements/upstream]
m_dn -->|data.pressure| handler2[pressure handler<br/>updates measurements/downstream]
handler1 --> recompute[prediction.recompute]
handler2 --> recompute
recompute --> emit[emitter.emit 'output-changed']
classDef ctrl fill:#a9daee,color:#000
~~~
| softwareType | filter | wired to | side-effect |
|---|---|---|---|
| `measurement` | `type=pressure, position=upstream` | `pressureHandlers.onUpstream` | prediction recomputes |
| `measurement` | `type=pressure, position=downstream` | `pressureHandlers.onDownstream` | prediction recomputes |
## 7. Lifecycle — what one event (or tick) does
~~~mermaid
sequenceDiagram
participant parent
participant node as this node
participant sensor as measurement child
participant out as Port-0 output
sensor->>node: data.pressure (3.4 bar, upstream)
node->>node: ChildRouter → pressure handler
node->>node: prediction recomputes
node->>node: drift assesses prediction vs measured
node->>node: getOutput() composes snapshot
node->>out: msg{topic, payload, [process|influx]}
parent->>node: set.demand (15 m³/h)
node->>node: state.handleInput → maybe transition
~~~
One screen max. For multiple distinct flows (idle vs running vs error), pick the most common and link out to the rest.
## 8. Data model — `getOutput()`
What lands on Port 0. Composed in domain `getOutput()`, then delta-compressed by `outputUtils.formatMsg`.
**Abstract schema** (always include):
<!-- BEGIN AUTOGEN: datamodel-schema -->
| Key | Type | Unit | Source |
|---|---|---|---|
| `<type>.<variant>.<position>.<childId>` | number | per `UnitPolicy.output(type)` | MeasurementContainer |
| `state` | string | — | `state/` |
| `predictionHealth.level` | 03 | — | `drift/` |
| `predictionHealth.flags` | string[] | — | `drift/` |
<!-- END AUTOGEN: datamodel-schema -->
**Concrete sample** (include only when the *shape* is hard to grok from the schema — e.g. nested objects, sparse keys, or unit conventions a newcomer would get wrong):
~~~json
{
"flow.measured.downstream.default": 12.4,
"pressure.measured.upstream.default": 3.4,
"power.measured.atequipment.default": 18.2,
"state": "operational",
"predictionHealth": { "level": 1, "flags": ["pressure_init_warming"], "message": "warmup phase", "source": "rotatingMachine#pump-A" }
}
~~~
Concrete samples must come from a known-good test run — never made-up values. Regenerate when concern modules change shape.
## 9. Configuration — editor form ↔ config keys
~~~mermaid
flowchart TB
subgraph editor["Node-RED editor form"]
f1[Mode dropdown]
f2[Demand input]
f3[Threshold %]
end
subgraph config["Domain config slice"]
c1[control.mode]
c2[control.targets.demand]
c3[safety.thresholdPercent]
end
f1 --> c1
f2 --> c2
f3 --> c3
~~~
| Form field | Config key | Default | Range | Where used |
|---|---|---|---|---|
| Mode | `control.mode` | `auto` | enum | `control/strategies.js` |
| Demand | `control.targets.demand` | `0` | ≥ 0 | `dispatch/` |
| Threshold % | `safety.thresholdPercent` | `95` | 0100 | `safety/guards.js` |
## 10. State chart (stateful nodes only)
~~~mermaid
stateDiagram-v2
[*] --> off
off --> idle: cmd.startup
idle --> warmingup: setpoint > 0
warmingup --> operational: warmup_time elapsed
operational --> coolingdown: cmd.shutdown
coolingdown --> off: cooldown_time elapsed
operational --> emergencystop: cmd.estop
emergencystop --> off: cmd.reset
~~~
Skip this section for stateless nodes (`measurement`, `dashboardAPI`).
## 11. Examples
| Tier | File | What it shows | Mandatory? |
|---|---|---|---|
| Basic | `examples/01-Basic.json` | Inject + dashboard, no parent | ✅ |
| Integration | `examples/02-Integration.json` | Wired to `<parent>` + 1 child | ✅ if has parent |
| Dashboard | `examples/03-Dashboard.json` | Live FlowFuse charts | ⭕ optional |
One screenshot per tier where helpful. PNG ≤ 200 KB under `wiki/_partial-screenshots/<NodeName>/`. Docker compose snippet under `examples/README.md`.
## 12. Debug recipes
How to diagnose the common failure modes. One table row per recipe.
| Symptom | First thing to check | Where to look |
|---|---|---|
| Status badge stuck on `⚠ no input` | Did the measurement child register? Watch Port 2. | Editor debug tap on Port 2 |
| `flow.measured.downstream` not updating | Confirm the child's emitted topic matches the `ChildRouter` filter. | `specificClass.js``configure()` |
| Prediction `level=3` | Run `enableLog: 'debug'` *temporarily*; look for drift evaluator output. | container log |
> 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
Two or three bullets, one sentence each. Forces explicit non-goals.
- Use rotatingMachine for a **single** pump. For groups of 2+ pumps with load sharing, use `machineGroupControl` as the parent.
- Don't use rotatingMachine to model a passive non-return valve — use `valve` (no curve, no FSM-driven motor).
## 14. Known limitations / current issues
| # | Issue | Tracked in |
|---|---|---|
| 1 | Drift confidence drops to 0 when pressure missing > 30 s | `.claude/refactor/OPEN_QUESTIONS.md` |
| 2 | Multi-parent teardown ordering | Gitea issue #42 |
Link to repo issues when they exist. Keep this table living — it's the contract with the user about what "works".
<!-- END TEMPLATE -->
```
## Hard rules for editors
1. Section 2 (Position in the platform) appears **before any prose**. Diagrams lead.
2. Every section opens with a diagram, table, or chart. Prose annotates the visual; never the other way round.
3. **Max 60 words per paragraph.** A paragraph longer than that splits into bullets or moves into a table.
4. The topic contract (section 5) and data-model schema (section 8) are **auto-generated** between the `BEGIN AUTOGEN` / `END AUTOGEN` markers. Don't hand-edit between markers.
5. Mermaid is the default for graph structures. Use generated SVG/PNG for XY data (curves, time series). Use tables for facts.
6. Skip `classDiagram` (we don't expose classes to users) and `gantt` (no schedules in node docs).
7. **Concrete sample payloads must come from a known-good test run.** Made-up numbers rot silently.
8. S88 colour codes are non-negotiable in section 2. Match the palette in `.claude/rules/node-red-flow-layout.md`.
## Archive banner — paste at the top of every archived page
```
> **⚠️ ARCHIVED — pre-refactor (Tier 14, 2026-05)**
>
> This page describes the architecture before the platform refactor.
> The current page is **[<NodeName>](../<NodeName>)**.
>
> Kept for historical reference only. **Do not update.**
```
Archived pages move to `Archive/<NodeName>-pre-refactor.md` in the Gitea wiki repo. After moving, the page is read-only — corrections go on the current page, not the archive.
## Auto-generation — Phase 9 follow-up
Two scripts per node, wired in `package.json`:
```json
"scripts": {
"wiki:contract": "node scripts/generate-contract.js > wiki/_partial-topics.md",
"wiki:datamodel": "node scripts/generate-datamodel.js > wiki/_partial-datamodel.md",
"wiki:all": "npm run wiki:contract && npm run wiki:datamodel"
}
```
- **`generate-contract.js`** walks `src/commands/index.js`, emits one table row per descriptor between the topic-contract markers.
- **`generate-datamodel.js`** instantiates the domain with the default config, calls `getOutput()`, emits the abstract schema between the datamodel-schema markers. If `wiki/sample-output.fixture.json` exists, the concrete-sample block below the markers is also overwritten.
- `describeSchema` walks the lightweight `{type, properties}` schema and produces a one-line readable form.
## What lives where
| Artifact | Location | Hand-edited? |
|---|---|---|
| Canonical page source | `wiki/<NodeName>.md` in the node's repo | Yes (except inside AUTOGEN markers) |
| Auto-generated partials | written inline between AUTOGEN markers | No — generated |
| Plots | `wiki/_partial-plots/<NodeName>/*.svg` | No — generated |
| Screenshots | `wiki/_partial-screenshots/<NodeName>/*.png` | Yes (committed) |
| Gitea wiki UI | mirror — re-rendered from `wiki/` on push | No |
| Archived pre-refactor pages | `Archive/<NodeName>-pre-refactor.md` in the wiki repo | No (read-only after archival) |
The Gitea wiki repo is separate from each node's source repo. The `wiki/` directory in each node's repo is canonical; a `wiki-sync` workflow (not yet built) mirrors it into the Gitea wiki repo on each push to `development` / `main`.