diff --git a/.claude/refactor/CONTRACTS.md b/.claude/refactor/CONTRACTS.md index 54277a1..45b7dc2 100644 --- a/.claude/refactor/CONTRACTS.md +++ b/.claude/refactor/CONTRACTS.md @@ -233,12 +233,17 @@ class PumpingStation extends BaseDomain { } // What the Node-RED status badge shows — see section 7. + // Aggregators (no clean state machine) use compose. State-machine + // nodes (rotatingMachine) use byState. Both return {fill, shape, text}. getStatusBadge() { - return statusBadge.fromState({ - direction: this.flowAggregator.direction, - vol: this.measurements.type('volume').variant('measured').position('atequipment').getCurrentValue('m3'), - maxVol: this.basin.maxVolAtOverflow, - }); + const direction = this.flowAggregator.direction; + const vol = this.measurements.type('volume').variant('measured').position('atequipment').getCurrentValue('m3'); + const pct = (vol / this.basin.maxVolAtOverflow * 100).toFixed(1); + const arrow = direction === 'filling' ? '⬆️' : direction === 'draining' ? '⬇️' : '⏸️'; + return statusBadge.compose([ + `${arrow} ${pct}%`, + `V=${vol.toFixed(2)}/${this.basin.maxVolAtOverflow.toFixed(2)} m³`, + ]); } } diff --git a/.claude/refactor/OPEN_QUESTIONS.md b/.claude/refactor/OPEN_QUESTIONS.md index 2670695..0df15f6 100644 --- a/.claude/refactor/OPEN_QUESTIONS.md +++ b/.claude/refactor/OPEN_QUESTIONS.md @@ -97,3 +97,65 @@ serialisation uses `LatestWinsGate` internally. See `CONTRACTS.md §2` for the BaseNodeAdapter shape. --- + +## 2026-05-10 — ChildRouter wildcard subscriptions monkey-patch `emit` + +**Context:** P1.2 implementation. EventEmitter has no native wildcard. +Subscriptions with a partial filter (`{type}`-only or `{position}`-only) +install a per-variant `emit` proxy on the child's emitter; concrete +`{type, position}` filters use plain `emitter.on`. + +**Question:** Multi-parent children. `child.parent` is already an array +in `childRegistrationUtils`, so a child can be registered under several +parents. If two parents each install ChildRouter wildcard proxies on +the same `child.measurements.emitter`, the wraps stack — but +`tearDown` only unwraps when its own bookkeeping is empty. Is this +correct semantics for multi-parent teardown ordering? Or should we +switch to per-listener fan-out (subscribe to every known +`..` enumerated from a registry)? + +**Default chosen:** Stacked wrappers. The current `childRegistrationUtils` +multi-parent path is rarely exercised in production. Revisit if +Phase 2 / Phase 4 hits a real multi-parent case. + +**Decision needed by:** Phase 4. + +--- + +## 2026-05-10 — `predictionHealth` migration in rotatingMachine + +**Context:** P1.4 implementation flagged that the existing +`rotatingMachine.predictionHealth` carries `quality` (string) + +`confidence` (0..1 numeric) on top of the new `HealthStatus` shape's +`{level, flags, message, source}`. + +**Question:** Where does `confidence` live after migration? + +**Default chosen:** Keep `confidence` on the per-metric drift +container as a sibling to a `health: HealthStatus` field. Drift +diagnostics (`nrmse`, `longTermNRMSD`, `immediateLevel`) stay as +siblings too. `HealthStatus` carries only the standardised five fields. + +**Decision needed by:** Phase 5 (`rotatingMachine` refactor). + +--- + +## 2026-05-10 — `dashboardAPI` basic test broken (pre-existing) + +**Context:** P1.12 sanity gate. `dashboardAPI/test/basic/structure-module-load.basic.test.js` uses Mocha-style `describe()` globals which don't exist under `node:test`. Reports 0 pass / 1 fail with `ReferenceError: describe is not defined`. + +**Action:** Pre-existing — not caused by Phase 1. Convert to `node:test` form during Phase 6 when `dashboardAPI` gets its skeleton refactor. Tracked here so it isn't lost. + +--- + +## 2026-05-10 — `reactor` test runtime is mathjs-bound (pre-existing) + +**Context:** P1.12 sanity gate. Every reactor test file takes ~13 s because `require('mathjs')` alone is ~12.5 s on this machine (mathjs is huge and loads its full operator set eagerly). With basic tests parallelised by `node --test`, each subprocess pays the cost. A 90 s outer timeout doesn't accommodate the parallel load. + +**Action:** Pre-existing — not caused by Phase 1. Two options to track for Phase 5/6 cleanup: +1. Switch to a tree-shaken mathjs subset (only ops actually used). +2. Cache the mathjs instance at module top and pass into Reactor classes. + +Tracked; not blocking the refactor. + +---