Platform-wide refactor plan: README, CONVENTIONS, CONTRACTS, MODULE_SPLIT, TASKS, OPEN_QUESTIONS. Source of truth for the phased refactor across all 12 submodules. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
10 KiB
Per-node module split
Where each concern lives after the refactor. All paths are relative
to nodes/<nodeName>/src/.
Generic node template (any node post-refactor)
nodes/<name>/
<name>.js # Node-RED entry: registerType + admin endpoints (≤ 50 lines)
<name>.html # Form template + thin oneditprepare/oneditsave (≤ 250 lines)
CONTRACT.md # Generated from commands/ + hand-written events
examples/
01-basic.json
02-integration.json
03-dashboard.json # optional
src/
nodeClass.js # extends BaseNodeAdapter; ~25 lines
specificClass.js # extends BaseDomain; orchestrator only; ~150 lines
editor.js # client-side JS for HTML, served via admin endpoint (only if non-trivial UI)
commands/
index.js # the command registry array
handlers.js # the handler functions
<concern>/ # one folder per domain concern (see per-node sections below)
...
test/
basic/
integration/
edge/
pumpingStation (Process Cell — L5, #0c99d9)
src/
nodeClass.js # ~25 lines, extends BaseNodeAdapter
specificClass.js # ~150 lines, orchestrator
editor.js # extracted SVG/redraw logic from the .html (~260 lines)
commands/
index.js # set.mode | set.demand | set.inflow | calibrate.* | child.register
handlers.js
basin/
BasinGeometry.js # initBasinProperties + level<->volume conversions
thresholdValidator.js # _validateThresholdOrdering — pure function
measurement/
flowAggregator.js # _selectBestNetFlow + _updatePredictedVolume + _computeRemainingTime + _levelRate + _deriveDirection
measurementRouter.js # _handleMeasurement + _onLevelMeasurement + _onPressureMeasurement
calibration.js # calibratePredictedVolume + calibratePredictedLevel + setManualInflow
control/
levelBased.js # _controlLevelBased + _scaleLevelToFlowPercent + _applyMachineGroupLevelControl
flowBased.js # placeholder for the flow mode; clearly stubbed
manual.js # forwardDemandToChildren
index.js # { 'levelbased': ..., 'flowbased': ..., 'manual': ... }
safety/
safetyController.js # evaluate() — split internally into dryRunRule + overfillRule
io/
statusBadge.js # getStatusBadge composition (was nodeClass._updateNodeStatus)
output.js # getOutput, mostly a pass-through to measurements + basin snapshot
configBuilder.js # extracted _loadConfig mapping
examples/
standalone-demo.js # extracted from the bottom of specificClass.js
measurement (Control Module — L2, #a9daee)
The good news: Channel.js already exists and is pure. Most of the
analog mode in specificClass.js is duplication that vanishes when the
analog path also goes through Channel.
src/
nodeClass.js # extends BaseNodeAdapter
specificClass.js # ~150 lines, orchestrator over modes
channel/
Channel.js # KEEP — already clean, the model for everything else
modes/
analogMode.js # one Channel built from flat config; routes msg.payload number
digitalMode.js # N channels from config.channels[]; routes msg.payload object
index.js # { analog, digital }
simulation/
simulator.js # simulateInput — random walk over the configured range
calibration/
calibrator.js # calibrate + isStable + standardDeviation helpers (drop duplicates of the static helpers in Channel)
commands/
index.js # set.simulator | set.outlierDetection | cmd.calibrate | data.measurement
handlers.js
statistics/ (mean/stdDev/median/etc.) — promote to
generalFunctions/src/stats/. Both Channel.static helpers and the
calibrator use them.
machineGroupControl (Unit — L4, #50a8d9)
src/
nodeClass.js # extends BaseNodeAdapter
specificClass.js # ~200 lines orchestrator; tick/handlePressureChange/handleInput
groupOps/
groupOperatingPoint.js # _equalizeOperatingPoint, _readChildMeasurement, _writeMeasurement
groupCurves.js # _groupFlow, _groupPower, _groupNCog, _groupCalcPower
totals/
totalsCalculator.js # calcDynamicTotals, calcAbsoluteTotals, activeTotals
combinatorics/
pumpCombinations.js # validPumpCombinations + checkSpecialCases
optimizer/
bestCombination.js # calcBestCombination (CoG-based)
bepGravitation.js # calcBestCombinationBEPGravitation + redistributeFlowBySlope + estimateSlopesAtBEP
index.js # picks the optimizer by config
efficiency/
groupEfficiency.js # calcGroupEfficiency + calcDistanceBEP + helpers
dispatch/
demandDispatcher.js # uses LatestWinsGate; handleInput + per-machine fanout
registration/ # auto via ChildRouter — file may be tiny
commands/
index.js # set.mode | set.scaling | set.demand | child.register
handlers.js
rotatingMachine (Equipment Module — L3, #86bbdd)
The biggest specificClass (1760 lines). The split mirrors the natural boundaries the existing comments suggest.
src/
nodeClass.js # extends BaseNodeAdapter
specificClass.js # ~250 lines orchestrator
curves/
curveLoader.js # loadCurve wrapper + model resolution
curveNormalizer.js # _normalizeMachineCurve + _normalizeCurveSection (unit conversion + anomaly detection)
reverseCurve.js # the existing reverseCurve helper
prediction/
predictors.js # owns predictFlow / predictPower / predictCtrl (delegates to generalFunctions/predict)
groupPredictors.js # group-scope predictors used when an MGC parent calls setGroupOperatingPoint
operatingPoint.js # current operating point: pressure source, derived flow & power
drift/
driftAssessor.js # _updateMetricDrift + assessDrift + _applyDriftPenalty
predictionHealth.js # composes flow/power/pressure drift into a HealthStatus
pressure/
virtualChildren.js # _initVirtualPressureChildren + dashboard-sim children
pressureInitialization.js # getPressureInitializationStatus + tracking real children
pressureRouter.js # updateMeasuredPressure + per-position handling
state/ # adapter to generalFunctions/state — thin glue, lifecycle hooks
stateBindings.js # the position/state event handlers that fire _updateState etc.
measurement/
measurementHandlers.js # updateMeasured{Flow,Power,Temperature} + _callMeasurementHandler
flow/
flowController.js # handleInput dispatch by source/action/parameter — feeds state machine
display/
workingCurves.js # showWorkingCurves + showCoG (admin endpoints)
commands/
index.js # set.mode | cmd.startup | cmd.shutdown | cmd.estop | cmd.setpoint | cmd.flow-setpoint | data.simulate-measurement | query.curves | query.cog
handlers.js
remaining nodes (skeleton — they get the platform refactor only)
| Node | Notes |
|---|---|
valve |
Equipment Module. Smaller than rotatingMachine — concern split likely just state/, commands/, position/. |
valveGroupControl |
Unit. Similar to MGC but no flow-power optimization — straightforward position-aggregator + commands/. |
reactor |
Unit. Domain is biological kinetics (ASM); will need a kinetics/ folder. Big — second-tier candidate for deeper split. |
settler |
Unit. Has the recently-fixed _connectReactor integration; keep that wired through ChildRouter. |
monster |
Unit. Multi-parameter monitoring; the parameter set itself is config-driven. |
diffuser |
Equipment Module. Aeration controller. Likely small. |
dashboardAPI |
Utility. InfluxDB endpoints. Likely no BaseDomain — it's a passive HTTP server. |
The "skeleton" refactor for these is just:
- Convert
nodeClass.jsto extendBaseNodeAdapter. - Convert
specificClass.jsto extendBaseDomain. - Move the input switch to
commands/. - Add
getStatusBadge()if not present. - Use
ChildRouterfor registration. - File splits driven by file size — if
specificClass< 300 lines, leave it alone for now.
generalFunctions itself
src/
configs/ # unchanged — JSON schemas per node
helper/ # eventually split into infra/ + domain/, but not in this refactor
measurements/ # MeasurementContainer — unchanged
nodered/ # NEW — node-RED-side infra
BaseNodeAdapter.js
commandRegistry.js
statusBadge.js # composition helpers
statusUpdater.js # the 1 Hz status-loop wrapper
index.js
domain/ # NEW — domain-side infra
BaseDomain.js
UnitPolicy.js
ChildRouter.js
LatestWinsGate.js
HealthStatus.js
index.js
stats/ # NEW — promoted from measurement (mean, std, median, mad, lerp)
index.js
Existing exports (logger, configManager, outputUtils,
MeasurementContainer, predict, interpolation, state, …) stay
exactly where they are. Imports keep working unchanged.
generalFunctions/index.js adds new exports alongside existing ones.
Nothing is removed in this refactor.