# Topic Conventions > **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`** Naming rules, unit policy, and S88 colour palette. Source of truth: `.claude/refactor/CONTRACTS.md` §1. ## Topic prefixes Every topic is `.` lowercase. Five prefixes only. ```mermaid flowchart LR ui[UI / parent / driver]:::neutral node[Node]:::tier3 child[Child]:::tier1 ui -->|set.x / cmd.x| node node -->|evt.x| ui child -->|data.x| node node -->|data.x| child child -->|child.register| node classDef neutral fill:#dddddd classDef tier3 fill:#50a8d9,color:#000 classDef tier1 fill:#a9daee,color:#000 ``` | Prefix | Direction | Semantics | Examples | |---|---|---|---| | `set.` | inbound | Set a configurable value. **Idempotent**, no side-effects beyond storing the value. | `set.mode`, `set.demand`, `set.position` | | `cmd.` | inbound | Trigger an action. **Has side-effects** (state transitions, motor commands). | `cmd.startup`, `cmd.shutdown`, `cmd.calibrate`, `cmd.estop` | | `data.` | bidirectional | Carries measurement / process data. Used by `measurement → parent` and emitters. | `data.pressure`, `data.flow`, `data.temperature` | | `evt.` | outbound | Announces something happened. Consumer-driven. | `evt.state-change`, `evt.alarm`, `evt.health` | | `child.` | inbound (parent) | Child node lifecycle. | `child.register` (with legacy alias `registerChild`) | **Anti-patterns to avoid:** - ❌ A topic that does two things (`setStartup` to both set a flag *and* trigger startup). Split into `set.` + `cmd.`. - ❌ Reusing a `cmd.` topic for both inbound trigger and outbound ack — make a paired `evt.-complete`. - ❌ Per-node prefixes (`pump.set.demand`). The prefix is the *kind*, not the *target*. ## Alias deprecation Legacy topic names (pre-refactor) are still accepted as aliases. The current alias map per node lives in `src/commands/index.js`. Common aliases: | Canonical | Legacy aliases | |---|---| | `set.mode` | `setMode` | | `set.demand` | `Qd`, `setDemand` | | `cmd.startup` | `execSequence` with `payload.action='startup'` | | `cmd.shutdown` | `execSequence` with `payload.action='shutdown'` | | `child.register` | `registerChild` | | `data.pressure` | `pressure` | | `data.flow` | `flow` | Aliases are logged at debug level on use. Plan is to remove them in a future major version. Update integrations to canonical names. ## Unit policy Every node declares canonical + output units via `UnitPolicy.declare({canonical, output})`. The command registry coerces incoming `msg.unit` to the canonical unit before the handler runs. Outputs are emitted in the declared output unit (often human-friendly). ```mermaid flowchart LR ui[UI message
e.g. 50 m³/h]:::neutral coerce[unit coercion
m³/h → m³/s]:::tier1 sc[specificClass
canonical m³/s]:::tier3 out[output
renders back to m³/h]:::tier2 ui --> coerce --> sc --> out classDef neutral fill:#dddddd classDef tier1 fill:#a9daee,color:#000 classDef tier3 fill:#50a8d9,color:#000 classDef tier2 fill:#86bbdd,color:#000 ``` | Quantity | Canonical (internal) | Common output | |---|---|---| | Flow | `m3/s` | `m3/h`, `l/s`, `gpm` | | Pressure | `Pa` | `bar`, `mbar`, `kPa` | | Power | `W` | `kW`, `MW` | | Temperature | `K` | `degC`, `degF` | | Level | `m` | `m`, `cm` | | Volume | `m3` | `m3`, `l` | **Rule:** anywhere in `specificClass`, treat values as canonical. Conversion happens at the boundary (input coercion + output formatting). ## S88 colour palette ```mermaid flowchart TB A[Area
#0f52a5]:::area PC[Process Cell
#0c99d9]:::pc UN[Unit
#50a8d9]:::unit EM[Equipment Module
#86bbdd]:::equip CM[Control Module
#a9daee]:::ctrl UT[Utility / neutral
#dddddd]:::neutral A --> PC --> UN --> EM --> CM UT -.- A classDef area fill:#0f52a5,color:#fff classDef pc fill:#0c99d9,color:#fff classDef unit fill:#50a8d9,color:#000 classDef equip fill:#86bbdd,color:#000 classDef ctrl fill:#a9daee,color:#000 classDef neutral fill:#dddddd,color:#000 ``` | Hex | S88 level | Used by | |---|---|---| | `#0f52a5` | Area | (reserved — not in use yet) | | `#0c99d9` | Process Cell | pumpingStation | | `#50a8d9` | Unit | machineGroupControl, valveGroupControl, reactor, settler, monster | | `#86bbdd` | Equipment Module | rotatingMachine, valve, diffuser | | `#a9daee` | Control Module | measurement | | `#dddddd` | Utility / neutral | dashboardAPI, helper function nodes | **Rule:** every Mermaid diagram in this wiki, every Node-RED node's editor colour, and every dashboard grouping uses this palette. Source of truth: `.claude/rules/node-red-flow-layout.md` §14. **Known outliers** (pending cleanup, tracked in OPEN_QUESTIONS.md): - `settler` editor colour is `#e4a363` (orange) — should be `#50a8d9`. - `monster` editor colour is `#4f8582` (teal) — should be `#50a8d9`. - `diffuser` editor colour was missing pre-refactor; now `#86bbdd`. - `dashboardAPI` registers under category `'wbd typical'` instead of `'EVOLV'`. ## Measurement key shape The `MeasurementContainer` stores values under composite keys: ``` ... ``` | Segment | Examples | |---|---| | `type` | `flow`, `pressure`, `power`, `temperature`, `level` | | `variant` | `measured`, `predicted`, `setpoint`, `min`, `max` | | `position` | `upstream`, `downstream`, `atequipment`, `inlet`, `outlet` (always lowercase in keys) | | `childId` | The registering child's id, OR `default` for internal computations | Examples: - `flow.measured.downstream.dashboard-sim-downstream` — externally measured downstream flow. - `flow.predicted.downstream.default` — node's own prediction. - `power.measured.atequipment.default` — measured power at the equipment. - `pressure.measured.upstream.` — pressure from a specific measurement child. **Gotcha:** `position` is **always lowercase in keys**. The configuration form may use mixed case (`atEquipment`); the container normalises. ## Status badge `statusBadge.compose(state)` returns `{fill, shape, text}` for `node.status(...)`: | level | shape | fill | meaning | |---|---|---|---| | `info` | dot | blue | normal operation | | `success` | dot | green | success / running optimally | | `warning` | ring | yellow | degraded, attention needed | | `error` | ring | red | fault, operator action required | | `pending` | dot | grey | initialising / no data yet | Composer reads from `HealthStatus.level` (0–3) — kept centralised so all nodes show consistent badges. ## HealthStatus shape ```json { "level": 0, "flags": ["pressure_init_warming"], "message": "warmup phase", "source": "rotatingMachine#pump-A" } ``` | Field | Range | Meaning | |---|---|---| | `level` | 0..3 | 0 = healthy, 1 = degraded, 2 = warning, 3 = error | | `flags` | string[] | Machine-readable reason codes. | | `message` | string | Human-readable summary (one line). | | `source` | string | `#` — for routing UI / alarm correlation. | ## Related pages - [Architecture](Architecture) — generalFunctions API surface - [Telemetry](Telemetry) — Port-1 InfluxDB schema (where these conventions appear in stored data) - [Topology-Patterns](Topology-Patterns) — what topics flow where