# Topic Conventions ![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![source](https://img.shields.io/badge/source-CONTRACTS.md_%C2%A71-orange) > [!NOTE] > Every `msg.topic` in EVOLV uses one of six prefixes. The prefix tells you the kind (setter vs trigger vs data vs query vs lifecycle vs event), not the target. Units are coerced before handlers run. S88 colours are mandatory in every diagram. Source of truth: `.claude/refactor/CONTRACTS.md` §1. --- ## The six prefixes ```mermaid flowchart LR ui["UI / parent / driver / function node"] node[Node] child["Child node"] ext["external consumers"] ui -- "set. / cmd. / query." --> node node -. "evt." .-> ui node -. "evt." .-> ext child -- "data." --> node node -- "data." --> child child <-->|child.register| node class ui,ext neutral class node tier3 class child tier1 classDef neutral fill:#dddddd classDef tier3 fill:#50a8d9,color:#000 classDef tier1 fill:#a9daee,color:#000 ``` ### Inbound (the node accepts on its input) | Prefix | Idempotent | Meaning | Examples | |:---|:---|:---|:---| | `set.` | Yes | Setter. Replaces a state value with the supplied payload. Repeating with the same payload does nothing extra. | `set.mode`, `set.scaling`, `set.demand`, `set.inflow` | | `cmd.` | No | Imperative action. Triggers a transition or sequence. Repeating triggers it again (or is rejected). | `cmd.startup`, `cmd.shutdown`, `cmd.estop`, `cmd.calibrate` | | `data.` | n/a (values flow) | Bulk data input. Sensor readings, measurement values, raw streams. The node consumes them. | `data.measurement`, `data.flow`, `data.pressure` | | `query.` | Yes (read-only) | Synchronous query. The node responds on the same msg (or a sibling output). For dashboards / debug. | `query.curves`, `query.cog`, `query.snapshot` | | `child.` | n/a (plumbing) | Parent / child plumbing. Routed via Port 2. | `child.register`, `child.unregister` | ### Outbound (the node emits) | Prefix | Meaning | Where it appears | |:---|:---|:---| | `evt.` | Event. A fact about something that just happened. Fire-and-forget — no consumer required. | `msg.topic` on Port 0; also fired on `this.emitter` for sibling modules | The default measurement output (delta-compressed payload from `outputUtils.formatMsg`) keeps `msg.topic = config.general.name` per existing convention. `evt.*` is for additional event-shaped emissions, not the per-tick measurement stream. > [!TIP] > The prefix is the kind, never the target. Don't write `pump.set.demand` — write `set.demand` and let routing handle which pump. The prefix system says explicitly what the message does; the target is identified by node id, not by topic. > [!CAUTION] > One topic, two effects is a bug magnet. A topic like `setStartup` that both sets a flag and triggers startup should be split into `set.` and `cmd.`. --- ## Aliases and deprecation Each `commands/index.js` declares the canonical name as `topic` and lists pre-refactor names in `aliases`. First use of each alias logs a one-time deprecation warning. Aliases are removed in Phase 7 after one release cycle. ```js { topic: 'set.mode', aliases: ['setMode', 'changemode'], payloadSchema: { type: 'string' }, description: 'Switch the node between auto and manual control modes.', handler: handlers.setMode, } ``` ### Common alias map | Canonical | Legacy aliases | |:---|:---| | `set.mode` | `setMode`, `changemode` | | `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` | > [!IMPORTANT] > Update integrations to canonical names before Phase 7 ships. Aliases work today; they will be removed next major release. --- ## Payload schemas `payloadSchema.type` accepts six values. Source: `.claude/refactor/CONTRACTS.md` §4. | Type | Meaning | |:---|:---| | `'string'` | `typeof payload === 'string'` | | `'number'` | `typeof payload === 'number'` | | `'boolean'` | `typeof payload === 'boolean'` | | `'object'` | Non-null object. Optional `properties: { key: 'typeName' }` enforces per-key `typeof` (missing keys allowed) | | `'any'` | Anything passes. Use when handler accepts heterogeneous payloads | | `'none'` | Trigger-only. Handler invoked regardless of payload. If `msg.payload` is anything but `undefined` / `null`, registry logs a `warn` and still invokes the handler | --- ## Unit coercion (pre-dispatch) A descriptor for a numeric setter or data topic may declare: ```js { topic: 'set.demand', units: { measure: 'volumeFlowRate', default: 'm3/h' }, payloadSchema: { type: 'number' }, handler: handlers.setDemand, } ``` ```mermaid flowchart LR in["Inbound msg — payload=50, unit='m3/h'"] parse["Extract value+unit — 3 payload shapes accepted"] convert["convert(value).from(unit).to(default)"] handler["Handler receives msg.payload = canonical number, msg.unit = units.default"] in --> parse --> convert --> handler class in,handler neutral class parse,convert step classDef neutral fill:#dddddd classDef step fill:#a9daee,color:#000 ``` ### Three accepted payload shapes | Shape | Example | |:---|:---| | Plain number | `msg.payload = 50; msg.unit = 'l/s'` | | Object with explicit unit | `msg.payload = { value: 50, unit: 'l/s' }` | | Object without unit (falls back to `msg.unit`) | `msg.payload = { value: 50 }; msg.unit = 'l/s'` | ### Behaviour on unit mismatch | Situation | What the registry does | |:---|:---| | No unit supplied | Silently assume `units.default` | | Unit recognised + correct measure | Convert and rewrite payload | | Unit recognised, wrong measure | Log `warn` with accepted-unit list; fall through | | Unit unrecognised | Log `warn` with accepted-unit list; fall through | The handler always sees a plain number in `units.default`. Source: `.claude/refactor/CONTRACTS.md` §4 ("Determine the unit-of-record"). --- ## S88 colour palette Every Mermaid diagram, every Node-RED node editor colour, every FlowFuse dashboard group uses this palette. Source: `.claude/rules/node-red-flow-layout.md` §14. | Hex | S88 level | Used by | |:---|:---|:---| | `#0f52a5` | Area | Reserved — not used yet | | `#0c99d9` | Process Cell | pumpingStation | | `#50a8d9` | Unit | MGC, VGC, reactor, settler, monster | | `#86bbdd` | Equipment Module | rotatingMachine, valve, diffuser | | `#a9daee` | Control Module | measurement | | `#dddddd` | Utility / neutral | dashboardAPI, helper functions | > [!WARNING] > Known palette outliers (pending cleanup, tracked in `.claude/refactor/OPEN_QUESTIONS.md`): > - `settler` editor colour is `#e4a363` (orange) — should be `#50a8d9`. > - `monster` editor colour is `#4f8582` (teal) — should be `#50a8d9`. > - `diffuser` registers under category `'wbd typical'` instead of `'EVOLV'`. > > Wiki diagrams use the correct S88 colour regardless of the editor mismatch. --- ## Measurement key shape `MeasurementContainer` stores values under composite keys: ``` ... | | | | | | | +-- child id (or 'default' for internal computations) | | +------------- 'upstream' / 'downstream' / 'atequipment' / ... (always lowercase in keys) | +------------------------ 'measured' / 'predicted' / 'setpoint' / 'min' / 'max' +-------------------------------- 'flow' / 'pressure' / 'power' / 'temperature' / 'level' ``` ### Examples | Key | Meaning | |:---|:---| | `flow.measured.downstream.dashboard-sim-downstream` | Externally measured downstream flow | | `flow.predicted.downstream.default` | The node's own prediction | | `power.measured.atequipment.default` | Measured power at the equipment | | `pressure.measured.upstream.` | Pressure from a specific measurement child | > [!WARNING] > `position` is always lowercase in keys. The configuration form may use mixed case (`atEquipment`); the container normalises. Don't rely on the casing the form shows you. --- ## Status badge `statusBadge.compose(state)` returns `{fill, shape, text}` for `node.status(...)`. ```js const { statusBadge } = require('generalFunctions'); statusBadge.compose(['OK', `flow=${flow.toFixed(1)} m3/h`]) statusBadge.error(message) statusBadge.idle(label) ``` | `fill` | `shape` | Meaning | |:---|:---|:---| | `blue` | `dot` | Normal / running | | `green` | `dot` | Success / running optimally | | `yellow` | `ring` | Degraded — attention needed | | `red` | `ring` | Fault — operator action required | | `grey` | `dot` | Initialising / no data yet | > [!IMPORTANT] > Badges live in the domain, not the adapter. `nodeClass` calls `this.source.getStatusBadge()` once per second; the domain owns the shape. Source: `.claude/refactor/CONTRACTS.md` §7. --- ## HealthStatus A standardised shape for nodes that compute prediction quality, drift, or general health. Source: `.claude/refactor/CONTRACTS.md` §9. ```json { "level": 1, "flags": ["pressure_init_warming"], "message": "warmup phase", "source": "rotatingMachine#pump-A" } ``` | Field | Type | Meaning | |:---|:---|:---| | `level` | `0 \| 1 \| 2 \| 3` | 0 = fine, 3 = unusable | | `flags` | `string[]` | Machine-readable tags (e.g. `no_pressure_input`) | | `message` | `string` | Single-line human summary | | `source` | `string \| null` | `#` — for routing UI / alarm correlation | Helpers compose multiple sub-statuses (flow drift + power drift + pressure init) into one node-level status. --- ## Canonical units (used in code) Every node declares its `UnitPolicy`: what canonical (internal) unit it uses and what output unit to render to. Source: `.claude/refactor/CONTRACTS.md` §6. | 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` | > [!TIP] > Inside `specificClass`, treat values as canonical. Conversion happens at the boundary: input coercion by the commands registry; output formatting by `outputUtils`. ### Dual access form `UnitPolicy` exposes each accessor as both a method and a frozen property bag. ```js policy.canonical('flow') // 'm3/s' (method form) policy.canonical.flow // 'm3/s' (property form — preferred in hot paths) policy.output.pressure // 'mbar' ``` --- ## Related pages | Page | Why | |:---|:---| | [Architecture](Architecture) | Where these conventions are implemented in code | | [Telemetry](Telemetry) | What these keys look like in InfluxDB | | [Topology Patterns](Topology-Patterns) | Which topics flow between which nodes | | [Glossary](Glossary) | Domain terms used here |