Files
EVOLV/.claude/refactor/WIKI_TEMPLATE.md
Rene De Ren e03a7a51b7 P9 follow-up: expand wiki template + add Home/Archive template
WIKI_TEMPLATE.md — extend the canonical per-node page from 9 to 14 sections:
  + Header band (commit hash + regen date)
  + Code map (flowchart TB w/ subgraphs over concern modules)
  + Child registration (mirrors ChildRouter declarations)
  + Data model — getOutput() (abstract schema + optional concrete sample)
  + Debug recipes (symptom → first thing to check)
  + AUTOGEN markers around topic-contract + data-model schema so the
    Phase 9 regen script can rewrite in place.
  + 'Picking a visual' table: Mermaid is default, plots/SVG/screenshots
    allowed where they serve the data.
  + Archive banner snippet.

WIKI_HOME_TEMPLATE.md (new) — Home.md + Archive.md templates:
  - Platform-wide Mermaid graph of 11 active nodes, S88-coloured.
  - Navigation table grouped by S88 level.
  - Standards-pointer table to .claude/rules + .claude/refactor docs.
  - Live refactor-status table for returning visitors.
  - Archive index template with archival-date column.

No wiki pages written yet — next step is one worked example
(pumpingStation) before any change to the Gitea wiki repo.

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

16 KiB
Raw Blame 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
~~~

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`.

<!-- BEGIN AUTOGEN: topic-contract -->

| Canonical topic | Aliases | Payload | Effect |
|---|---|---|---|
| `set.mode` | `setMode` | `string` (`auto`\|`manual`\|`maintenance`) | Switches operating mode. |
| `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:

"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.