Files
EVOLV/.claude/refactor/MODULE_SPLIT.md
znetsixe 025bdb4c7e release: palette redesign + CoreSync scaffolding + dashboardAPI MODULE_NOT_FOUND fix
PALETTE REDESIGN (2026-05-21)
  Sidebar swatches switched from S88 level (all blue) to domain-hue per node.
  Family hue = function (rotating=orange, valves=teal, biology=green/olive,
  sampling=violet, sensor=amber, aeration=sky-blue, infrastructure=slate);
  within a family, darker = higher S88 / "more controller-ish."
  Editor-group rectangles in flow.json still follow S88 — only the
  registerType colour changed.

  Submodule bumps for palette: rotatingMachine, machineGroupControl,
  pumpingStation, valve, valveGroupControl, reactor, settler, monster,
  measurement, diffuser, dashboardAPI.

  Docs touched:
    - CLAUDE.md: palette swatch vs. editor-group bullets split out.
    - .claude/rules/node-red-flow-layout.md: new §10.0 introduces the two
      color systems, full 12-row palette table, and explicit warning not to
      mix the two hexes.
    - .claude/refactor/MODULE_SPLIT.md: per-node headers annotated with
      both `group #XXX` and `palette #XXX`.
    - .claude/refactor/WIKI_HOME_TEMPLATE.md + WIKI_TEMPLATE.md: clarify
      Mermaid classDefs visualize hierarchy, not palette swatches.
    - .claude/refactor/OPEN_QUESTIONS.md: dated decision entry with
      rationale, file list, and follow-ups.

CORESYNC SUBMODULE (new)
  nodes/coresync added pointing at https://gitea.wbd-rd.nl/RnD/coresync.
  FROST/SensorThings handoff path — first version forwards FROST-ready HTTP
  request messages on the dbase output; a downstream http-request node
  performs the POST and feeds responses back on msg.topic = "frost.response".
  Lazy stream resolver, latest-wins queue (keep first + latest, drop middle),
  knot-emit on slope change, provenance preserved in Observation parameters.

    - .gitmodules: add nodes/coresync entry.
    - package.json: register coresync as a Node-RED node.
    - generalFunctions bump: new frostFormatter + 4 node config schemas
      expose the dbase format option.
    - measurement bump: "frost" option added to dbaseOutputFormat dropdown
      (plus the in-flight data.measurement unit-handling work).
    - machineGroupControl bump: small editor compact-fields tweak alongside
      the palette change.
    - CORESYNC_FROST_INTERVIEW_HANDOFF.md added at root with interview state
      (Q20 open: slope angle vs. relative delta comparison).

DASHBOARDAPI MODULE_NOT_FOUND FIX
  package.json: dashboardapi entry path corrected to
  nodes/dashboardAPI/dashboardAPI.js. Commit e04c4a1 renamed the files to
  camelCase but missed package.json; on case-sensitive filesystems
  (Linux/Docker, where the tarball lands) the require resolved to nothing
  and the node showed MODULE_NOT_FOUND in the Node-RED palette.

MISC CLEANUP
  - examples/README.md + examples/pumpingstation-complete-example/ removal
    (build_flow.py, flow.json, README.md superseded by per-node examples).
  - jest.config.js: in-progress tweak.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:09:33 +02:00

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, group #0c99d9 · palette #8B4513)

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, group #a9daee · palette #D4A02E)

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, group #50a8d9 · palette #B5651D)

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, group #86bbdd · palette #E89B3A)

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.

Palette swatches for these (sidebar): valve #3CAEA3, valveGroupControl #2A8A82, reactor #6FAE5F, settler #8FAD3F, monster #9C5BB0, diffuser #6EB5E5, dashboardAPI #7A8BA3. Group-box hex still follows S88 level (see .claude/rules/node-red-flow-layout.md §10.0).

The "skeleton" refactor for these is just:

  • Convert nodeClass.js to extend BaseNodeAdapter.
  • Convert specificClass.js to extend BaseDomain.
  • Move the input switch to commands/.
  • Add getStatusBadge() if not present.
  • Use ChildRouter for 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.