Files
EVOLV/.claude/refactor/WIKI_TEMPLATE.md
znetsixe f9f1cceb82 docs: finalise CONTRACTS.md §4 + WIKI_TEMPLATE.md tweaks
CONTRACTS.md §4: full payloadSchema.type table including 'none', plus
the optional description field example. Matches the B3.2 implementation.

WIKI_TEMPLATE.md §5: Unit column appears with explanatory paragraph.
Matches the P11.4 wikiGen output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 20:22:05 +02:00

350 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
~~~
S88 colours are mandatory. Map: Process Cell `#0c99d9`, Unit `#50a8d9`, Equipment `#86bbdd`, Control Module `#a9daee`. Source of truth: `.claude/rules/node-red-flow-layout.md`.
## 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`.