OPEN_QUESTIONS.md: summary table of every decision (ramp foot, naming,
isStable fix, monster guard, plain-dicts→declareChildGetter, VGC→
ChildRouter, LatestWinsGate fireAndWait, drop mAbs, per-listener fan-out,
commandRegistry 'none' + description, UnitPolicy dual-shape, Phase 10
test rewrites).
TASKS.md §Phase 11: unit-aware commands. Every numeric setter declares
units: { measure, default }. commandRegistry normalises msg.payload +
msg.unit; warns + lists accepted units for bad input; falls back to
default. New query.units topic returns the spec per node.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
294 lines
16 KiB
Markdown
294 lines
16 KiB
Markdown
# Task list
|
|
|
|
Phased and ordered. The TaskCreate tracker mirrors this list and is the
|
|
active, mutable view; this file is the durable plan.
|
|
|
|
A task is **done** when:
|
|
- The code matches the contracts in `CONTRACTS.md`.
|
|
- All the affected node's tests are green (`node --test test/basic
|
|
test/integration test/edge`).
|
|
- A short note is appended in the task tracker if anything was deferred
|
|
to `OPEN_QUESTIONS.md`.
|
|
|
|
## Phase 1 — `generalFunctions` additive infra
|
|
|
|
Goal: add the new platform pieces. Nothing is removed; nothing existing
|
|
changes shape. All existing nodes continue to work unchanged.
|
|
|
|
| # | Task | Notes |
|
|
|---|---|---|
|
|
| 1.1 | Add `src/domain/UnitPolicy.js` + tests | Extracted from `rotatingMachine._buildUnitPolicy`. |
|
|
| 1.2 | Add `src/domain/ChildRouter.js` + tests | Built on existing `childRegistrationUtils`. |
|
|
| 1.3 | Add `src/domain/LatestWinsGate.js` + tests | Extracted from MGC `_dispatchInFlight`/`_delayedCall`. |
|
|
| 1.4 | Add `src/domain/HealthStatus.js` + tests | Standardise the `{level, flags, message, source}` shape. |
|
|
| 1.5 | Add `src/domain/BaseDomain.js` + tests | Constructor boilerplate; calls subclass `configure()`/`_init()`. |
|
|
| 1.6 | Add `src/nodered/commandRegistry.js` + tests | Topic dispatch + alias warnings. |
|
|
| 1.7 | Add `src/nodered/statusBadge.js` + tests | `compose`, `error`, `idle`, `byState` helpers. |
|
|
| 1.8 | Add `src/nodered/statusUpdater.js` + tests | 1 Hz poller calling `source.getStatusBadge()`. |
|
|
| 1.9 | Add `src/nodered/BaseNodeAdapter.js` + tests | The thing every nodeClass extends. |
|
|
| 1.10 | Add `src/stats/index.js` + tests | Promote mean/stdDev/median/mad/lerp from `measurement`. |
|
|
| 1.11 | Update `generalFunctions/index.js` (additive) | New exports under existing pattern. |
|
|
| 1.12 | Run all 12 nodes' tests against the bumped `generalFunctions` | Sanity gate before phase 2. |
|
|
|
|
Phase-1 commit cadence: one commit per task on the `development` branch
|
|
of `generalFunctions`. Submodule pointer in parent EVOLV bumps **once**
|
|
at end of phase.
|
|
|
|
## Phase 2 — pumpingStation pilot
|
|
|
|
Goal: prove the new infrastructure end-to-end. Pumping station is a
|
|
mid-complexity node — bigger than measurement, smaller than the
|
|
curve-driven nodes.
|
|
|
|
| # | Task | Notes |
|
|
|---|---|---|
|
|
| 2.1 | Move standalone demo from `specificClass.js` to `examples/standalone-demo.js` | Pure deletion + move; tests unchanged. |
|
|
| 2.2 | Extract `basin/` (BasinGeometry + thresholdValidator) | Pure functions. |
|
|
| 2.3 | Extract `measurement/flowAggregator.js` (incl. `_updatePredictedVolume`) | Centerpiece of the tick loop. |
|
|
| 2.4 | Extract `measurement/measurementRouter.js` + `measurement/calibration.js` | |
|
|
| 2.5 | Extract `control/` strategies + dispatcher | levelBased, flowBased (stub), manual. |
|
|
| 2.6 | Extract `safety/safetyController.js` | dryRunRule + overfillRule split internally. |
|
|
| 2.7 | Add `getStatusBadge()` on `PumpingStation`; remove badge logic from nodeClass | |
|
|
| 2.8 | Convert `nodeClass.js` to extend `BaseNodeAdapter` | |
|
|
| 2.9 | Convert `specificClass.js` to extend `BaseDomain` | Use `ChildRouter`, `UnitPolicy`. |
|
|
| 2.10 | Extract `commands/` registry + handlers | Old topic names become aliases. |
|
|
| 2.11 | Extract `editor.js` from `pumpingStation.html` (the SVG redraw logic) | Served via a `/pumpingStation/editor.js` admin endpoint. |
|
|
| 2.12 | Generate `CONTRACT.md` from `commands/` + handwritten events section | |
|
|
| 2.13 | Tests: 3-tier per extracted module + the existing suite still green | Add edge tests for any regression discovered. |
|
|
| 2.14 | Docker E2E (deploy `01-basic`/`02-integration`/`03-dashboard` flows on a running Node-RED) | Required for "trial-ready" claim. |
|
|
|
|
## Phase 3 — measurement
|
|
|
|
| # | Task | Notes |
|
|
|---|---|---|
|
|
| 3.1 | Promote stats helpers to `generalFunctions/src/stats/` (already done in 1.10) | |
|
|
| 3.2 | Convert analog mode to use `Channel` internally (with `key=null`) | Removes the ~400-line inline pipeline duplication. |
|
|
| 3.3 | Extract `simulation/simulator.js` | |
|
|
| 3.4 | Extract `calibration/calibrator.js` | |
|
|
| 3.5 | Add `getStatusBadge()` on `Measurement` | |
|
|
| 3.6 | Convert `nodeClass.js` to `BaseNodeAdapter`; `specificClass.js` to `BaseDomain` | |
|
|
| 3.7 | Extract `commands/` | |
|
|
| 3.8 | `CONTRACT.md` | |
|
|
| 3.9 | Tests + Docker E2E | |
|
|
|
|
## Phase 4 — machineGroupControl
|
|
|
|
| # | Task | Notes |
|
|
|---|---|---|
|
|
| 4.1 | Extract `groupOps/` (groupOperatingPoint + groupCurves) | The cluster of `_group*` helpers. |
|
|
| 4.2 | Extract `totals/totalsCalculator.js` | |
|
|
| 4.3 | Extract `combinatorics/pumpCombinations.js` | |
|
|
| 4.4 | Extract `optimizer/bestCombination.js` + `optimizer/bepGravitation.js` | |
|
|
| 4.5 | Extract `efficiency/groupEfficiency.js` | |
|
|
| 4.6 | Extract `dispatch/demandDispatcher.js` using `LatestWinsGate` | Replaces `_dispatchInFlight`/`_delayedCall` directly. |
|
|
| 4.7 | Add `getStatusBadge()` | |
|
|
| 4.8 | Convert nodeClass + specificClass to base classes; use `ChildRouter` | |
|
|
| 4.9 | `commands/` + `CONTRACT.md` | |
|
|
| 4.10 | Tests + Docker E2E | |
|
|
|
|
## Phase 5 — rotatingMachine
|
|
|
|
| # | Task | Notes |
|
|
|---|---|---|
|
|
| 5.1 | Extract `curves/` (loader + normalizer + reverseCurve) | |
|
|
| 5.2 | Extract `prediction/` (predictors + groupPredictors + operatingPoint) | |
|
|
| 5.3 | Extract `drift/` using `HealthStatus` | |
|
|
| 5.4 | Extract `pressure/` (virtual children + initialization + router) | |
|
|
| 5.5 | Extract `state/stateBindings.js` (adapter to existing `generalFunctions/state`) | |
|
|
| 5.6 | Extract `measurement/measurementHandlers.js` | |
|
|
| 5.7 | Extract `flow/flowController.js` | |
|
|
| 5.8 | Extract `display/workingCurves.js` | |
|
|
| 5.9 | Add `getStatusBadge()` (replaces the 100-line nodeClass version) | |
|
|
| 5.10 | Convert nodeClass + specificClass | |
|
|
| 5.11 | `commands/` + `CONTRACT.md` | |
|
|
| 5.12 | Tests + Docker E2E | |
|
|
|
|
## Phase 6 — remaining nodes
|
|
|
|
For each: skeleton refactor only — extend `BaseNodeAdapter` + `BaseDomain`, use `ChildRouter`, move the input switch to `commands/`, add
|
|
`getStatusBadge()`. Domain-specific module split only if `specificClass` > 300 lines after the platform refactor.
|
|
|
|
| # | Task |
|
|
|---|---|
|
|
| 6.1 | `valve` |
|
|
| 6.2 | `valveGroupControl` |
|
|
| 6.3 | `diffuser` |
|
|
| 6.4 | `monster` |
|
|
| 6.5 | `settler` |
|
|
| 6.6 | `reactor` |
|
|
| 6.7 | `dashboardAPI` (special — likely no `BaseDomain`, it's a passive HTTP server) |
|
|
|
|
These are parallelisable — each can be its own agent.
|
|
|
|
## Phase 7 — remove legacy topic aliases
|
|
|
|
> **Note:** canonical names (`set.*`, `cmd.*`, `data.*`, `child.*`,
|
|
> `query.*`, `evt.*`) are used **from Phase 1 onwards** — see
|
|
> `CONTRACTS.md §1`. Each `commands/index.js` declares the canonical
|
|
> name as `topic` and lists pre-refactor names in `aliases`. So Phase 7
|
|
> is just the deprecation-window sweep.
|
|
|
|
| # | Task | Notes |
|
|
|---|---|---|
|
|
| 7.1 | Audit aliases across all `commands/` files; confirm one release cycle has elapsed | If any alias was added recently, defer that node's removal another cycle. |
|
|
| 7.2 | Remove `aliases` entries; canonical name only | Each removal is a single PR. |
|
|
| 7.3 | Update example flows that still used legacy names | Should already have been updated in their phase. |
|
|
| 7.4 | Document the removal in each `CONTRACT.md` | "Removed legacy topic X (replaced by canonical Y) on YYYY-MM-DD". |
|
|
|
|
## Phase 8 — promotion to main
|
|
|
|
When every node is on the new infra and Docker E2E green:
|
|
1. Bump submodule pointers in parent EVOLV `development`.
|
|
2. Open a PR per submodule (`development` → `main`).
|
|
3. Open the parent EVOLV PR last (`development` → `main`).
|
|
4. Merge in dependency order (`generalFunctions` first, then nodes that
|
|
depend on it, finally `EVOLV`).
|
|
|
|
## Phase 8.5 — `generalFunctions` deprecated path cleanup
|
|
|
|
Removes the deprecated paths flagged in `OPEN_QUESTIONS.md`. Runs after
|
|
promotion to `main` (so callers have stopped depending on the old
|
|
paths via the platform's own consumers).
|
|
|
|
### Targets to remove
|
|
|
|
| Path | Replaced by | First flagged |
|
|
|---|---|---|
|
|
| `src/helper/menuUtils_DEPRECATED.js` | `src/menu/` (the active menu manager) | pre-refactor |
|
|
| `loadCurve` export (in `index.js` + `datasets/assetData/curves/`) | `loadModel` | pre-refactor |
|
|
| Any `*_DEPRECATED.*` file added during the refactor | (per-file note) | refactor |
|
|
|
|
### Tasks
|
|
|
|
| # | Task | Notes |
|
|
|---|---|---|
|
|
| 8.5.1 | Audit consumers of `loadCurve` across all nodes | Should be zero after Phase 5 (rotatingMachine) — verify. |
|
|
| 8.5.2 | Remove `loadCurve` export + the underlying file | Single PR. Test all nodes. |
|
|
| 8.5.3 | Remove `menuUtils_DEPRECATED.js` | Verify zero imports first. |
|
|
| 8.5.4 | Sweep `generalFunctions/src/` for `_DEPRECATED.*` files; remove with consumer audit | One PR per file. |
|
|
| 8.5.5 | Update `generalFunctions` README to drop deprecated references | |
|
|
|
|
## Phase 9 — wiki cleanup (post-refactor)
|
|
|
|
Goal: each node's gitea wiki becomes **visual-first**, scannable, and
|
|
follows one shared template. Today's wiki has lots of prose and varies
|
|
per node — once the platform is uniform, the wiki should be too.
|
|
|
|
Don't start phase 9 until phase 8 is done (the wiki documents the
|
|
post-refactor shape, not the in-flight transition).
|
|
|
|
### Standard wiki template (one file per node, this is the spec)
|
|
|
|
```
|
|
1. One-paragraph "what is this node" (≤ 60 words).
|
|
2. Position in the platform — a Mermaid block showing the node and its
|
|
typical neighbours (parent + child types, with arrows for
|
|
data direction).
|
|
3. Capability matrix — small table of "what this node can do" with
|
|
✅ / ❌ / partial.
|
|
4. Topic contract — auto-generated from src/commands/index.js
|
|
(set.* / cmd.* / evt.* / data.* — payload schema and example).
|
|
5. Output payload — a Mermaid sequence-diagram of a typical tick
|
|
(parent → child → measurement → tick → port-0 emit).
|
|
6. Configuration — a Mermaid block diagram of the editor form sections
|
|
plus a table mapping each form field to the config key it lands at.
|
|
7. Examples — links to examples/01-basic, 02-integration, 03-dashboard
|
|
with one screenshot each.
|
|
8. State / mode chart — Mermaid stateDiagram for any node with
|
|
non-trivial states (rotatingMachine, pumpingStation, MGC).
|
|
9. "When you would NOT use this node" — explicit non-goals.
|
|
10. Issues / known limitations — single-line items with links to
|
|
repo issues.
|
|
```
|
|
|
|
### Tasks
|
|
|
|
| # | Task | Notes |
|
|
|---|---|---|
|
|
| 9.1 | Author the canonical wiki template at `.claude/refactor/WIKI_TEMPLATE.md` (or the repo-mem rule path) | Source of truth. |
|
|
| 9.2 | Build the auto-generator: `commands/index.js` → "Topic contract" markdown section | Run via a small `npm run wiki:contract` script per node. |
|
|
| 9.3 | Pilot on `pumpingStation` wiki: replace existing pages with the new template | Visual-first, prune prose. |
|
|
| 9.4 | Apply to other 3 core nodes (`measurement`, `MGC`, `rotatingMachine`) | |
|
|
| 9.5 | Apply to remaining nodes (one per repo) | |
|
|
| 9.6 | Update parent EVOLV wiki: top-level platform overview with a Mermaid block of all 13 nodes and how they connect (S88 hierarchy + data direction) | |
|
|
| 9.7 | Add a wiki style guide (max prose per section, where Mermaid is required, screenshot conventions) | |
|
|
| 9.8 | Audit pass: every page renders, every Mermaid block compiles, every link resolves | |
|
|
|
|
### Visual primitives we'll lean on (Mermaid)
|
|
|
|
- `flowchart LR` — node connections (parent ↔ child, data direction).
|
|
- `sequenceDiagram` — tick-to-port-0 lifecycle.
|
|
- `stateDiagram-v2` — rotatingMachine / pumpingStation state machines.
|
|
- `erDiagram` — only if a node has a complex internal data model worth
|
|
visualising.
|
|
|
|
Skip: classDiagram (we don't expose classes to users); gantt (no
|
|
schedules in a node's docs).
|
|
|
|
### Hard rules
|
|
|
|
- Every page leads with the Mermaid platform-position block. No "intro
|
|
paragraph then later a diagram" — diagram first.
|
|
- Each section opens with the diagram or table; prose annotates the
|
|
visual, not the other way round.
|
|
- No more than 60 words of unbroken prose anywhere on a page.
|
|
- One canonical source of truth for the topic contract: `commands/index.js`.
|
|
The wiki page is generated from it. No hand-written drift.
|
|
|
|
## Phase 10 — test-suite refactor (post-wiki)
|
|
|
|
Goal: bring every node's test layout in line with `CONVENTIONS.md §Testing`
|
|
now that the platform is uniform. Pre-existing test debt logged in
|
|
`OPEN_QUESTIONS.md` gets cleaned up here.
|
|
|
|
### Tasks
|
|
|
|
| # | Task | Notes |
|
|
|---|---|---|
|
|
| 10.1 | Audit each node: basic / integration / edge split, naming, helpers | One pass; produce a per-node punch list. |
|
|
| 10.2 | Convert any Mocha-style tests (`describe`/`it`) to `node:test` | Specifically `dashboardAPI/test/basic/structure-module-load.basic.test.js`. |
|
|
| 10.3 | Address `reactor` mathjs load (per OPEN_QUESTIONS): tree-shake or hoist | If hoisted, document the pattern as a CONVENTION addition. |
|
|
| 10.4 | Promote shared test helpers to `generalFunctions/test/helpers/` | Common fakes: fake Node-RED node, fake child, fake RED. |
|
|
| 10.5 | Add missing edge tests for each refactored module flagged during P2-P5 | Edge cases discovered during refactor land here. |
|
|
| 10.6 | Make every basic-test runner exit cleanly in batch (`node --test test/basic/`) | No leaked timers, no real `setInterval` outliving the assertions. Mirrors the BaseNodeAdapter test fix. |
|
|
| 10.7 | Standard CI shape: each node has `npm run test:basic`, `test:integration`, `test:edge` (consistent across nodes) | Allows uniform CI invocation. |
|
|
| 10.8 | Audit pass: every node's test suite green in batch under one wall-clock budget (≤ 60 s for basic) | The new platform-wide gate. |
|
|
|
|
## Phase 11 — unit-aware commands
|
|
|
|
Goal: every numeric setter / data topic carries an explicit unit; the user
|
|
can supply any compatible unit and the commandRegistry normalises before
|
|
the handler runs. Unknown units warn + list accepted alternatives.
|
|
|
|
### Tasks
|
|
|
|
| # | Task | Notes |
|
|
|---|---|---|
|
|
| 11.1 | `generalFunctions/src/convert/`: add `possibilities(measure)` helper | Returns the list of accepted unit names for a measure (`volumeFlowRate`, `pressure`, etc.). |
|
|
| 11.2 | `generalFunctions/src/nodered/commandRegistry.js`: handle `descriptor.units` | Normalisation pipeline: extract value+unit from msg, validate against `units.measure`, convert to `units.default`, warn + fall back on bad input. Tests for all 4 paths (no-unit / valid / wrong-measure / unknown). |
|
|
| 11.3 | `generalFunctions/src/nodered/BaseNodeAdapter.js`: auto-wire `query.units` topic | Returns `{ topic → { measure, default, accepted: [...] } }` from the registry. No per-node wiring needed. |
|
|
| 11.4 | `generalFunctions/scripts/wikiGen.js`: render `units` column | Topic-contract auto-gen table grows a Unit column showing `measure (default <unit>)`. |
|
|
| 11.5 | Per-node `src/commands/index.js`: declare `units` on every numeric setter | ~10 nodes. See proposed default-units table in interview reply. |
|
|
| 11.6 | Regenerate every `CONTRACT.md` + wiki `Home.md` via `npm run wiki:all` | Automated. |
|
|
| 11.7 | Tests: commandRegistry unit-handling paths | 4 scenarios per the validation table. |
|
|
|
|
### Default units per topic (proposed)
|
|
|
|
| Topic | Default | Why |
|
|
|---|---|---|
|
|
| pumpingStation `set.inflow` | `m3/h` | Operator-friendly scale |
|
|
| pumpingStation `set.demand` | `m3/h` | same |
|
|
| pumpingStation `set.outflow` | `m3/h` | symmetric |
|
|
| pumpingStation `cmd.calibrate.volume` | `m3` | basin volume |
|
|
| pumpingStation `cmd.calibrate.level` | `m` | basin height |
|
|
| MGC `set.demand` | `m3/h` | matches PS |
|
|
| rotatingMachine `set.setpoint` | `%` | control% |
|
|
| rotatingMachine `set.flow-setpoint` | `m3/h` | flow target |
|
|
| rotatingMachine `data.simulate-measurement` | per `payload.type` | dispatch by sensor type |
|
|
| valve `set.position` | `%` | valve open-% |
|
|
| measurement `data.measurement` | mode-dependent | analog → Channel scaling; digital → per-channel cfg |
|
|
| monster `data.flow` | `m3/h` | already enforced |
|
|
| reactor `data.influent` | flow=m3/h, concentrations=mg/L | engine internals |
|
|
| settler `data.influent` | flow=m3/h, concentrations=mg/L | matches reactor |
|
|
| diffuser `data.flow` | `m3/h` | air flow scale |
|