Files
machineGroupControl/CONTRACT.md
znetsixe 619b1311d2 P4 wave 1: extract MGC concerns into focused modules
src/groupOps/        groupOperatingPoint + groupCurves (pure functions)
  src/totals/          totalsCalculator (dynamic + absolute + active)
  src/combinatorics/   pumpCombinations (validPumpCombinations + checkSpecialCases)
  src/optimizer/       bestCombination (CoG) + bepGravitation (BEP-G + marginal-cost)
  src/efficiency/      groupEfficiency (calc + distance helpers)
  src/dispatch/        demandDispatcher (LatestWinsGate-based; replaces
                       _dispatchInFlight + _delayedCall)
  src/commands/        canonical names from start (set.mode/scaling/demand,
                       child.register) + legacy aliases
  CONTRACT.md          inputs/outputs/events surface

53 basic tests pass (52 new + 1 pre-existing).
specificClass.js / nodeClass.js untouched — integration in P4 wave 2.

Findings flagged via agents (TODO append to OPEN_QUESTIONS.md):
  - calcGroupEfficiency.maxEfficiency is actually the mean (misleading name)
  - checkSpecialCases has a no-op `return false` inside forEach
  - MGC doesn't route cmd.startup/shutdown/estop — confirm if station broadcasts need it

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 20:45:23 +02:00

3.8 KiB

machineGroupControl — Contract

Hand-maintained for Phase 4; the ## Inputs table is generated from src/commands/index.js (see Phase 9 generator). Keep ≤ 80 lines.

Inputs (msg.topic on Port 0)

Canonical Aliases (deprecated) Payload Effect
set.mode setMode string — one of prioritycontrol, optimalcontrol, dynamiccontrol, … Switches the control strategy via source.setMode(payload).
set.scaling setScaling string — one of absolute, normalized Sets the demand-scaling convention via source.setScaling(payload).
child.register registerChild string — the child node's Node-RED id Resolves the child via RED.nodes.getNode and registers it through childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent).
set.demand Qd numeric (number or numeric string) Calls source.handleInput('parent', parseFloat(payload)). On success, replies on Port 0 with topic = source.config.general.name, payload = 'done'. Non-numeric payloads log error and are skipped.

Aliases log a one-time deprecation warning the first time they fire.

Outputs (msg.topic on Port 0/1/2)

  • Port 0 (process): msg.topic = config.general.name. Payload built by outputUtils.formatMsg(..., 'process') from getOutput() — delta-compressed (only changed fields are emitted). On a successful set.demand dispatch the node additionally emits { topic: <name>, payload: 'done' } as an acknowledgement.
  • Port 1 (InfluxDB telemetry): same shape as Port 0, formatted with the 'influxdb' formatter.
  • Port 2 (registration): at startup the node sends one { topic: 'registerChild', payload: <node.id>, positionVsParent } to the upstream parent.

Events emitted by source.measurements.emitter

The MeasurementContainer fires <type>.<variant>.<position> whenever the corresponding series receives a new value. Parents subscribe via the generic child.measurements.emitter.on(eventName, ...) handshake. machineGroupControl publishes:

  • flow.predicted.atequipment — aggregated predicted group flow (sum of member-machine predicted flows at the group operating point).
  • flow.predicted.downstream — mirror of the live group flow seen at the discharge header (written by handlePressureChange for downstream consumers such as pumpingStation).
  • power.predicted.atequipment — aggregated predicted group power.
  • efficiency.predicted.atequipment — group efficiency = flow/power at the selected operating point.
  • Ncog.predicted.atequipment — group normalised cost-of-goods score.
  • pressure.measured.upstream, pressure.measured.downstream, pressure.measured.differential — mirrored from header-side measurement children (asset.type='pressure'), when registered.

The exact set is data-driven by which children register and what they publish; downstream consumers should subscribe by event name, not assume a fixed catalogue.

Children registered by this node

machineGroupControl accepts two softwareTypes through the childRegistrationUtils handshake:

  • machine — a rotatingMachine. Stored in source.machines[id]. The group subscribes to its child's pressure.measured.differential, pressure.measured.downstream, and flow.predicted.downstream events to trigger handlePressureChange.
  • measurement — a header-side sensor (typically a pressure transmitter at the discharge or suction manifold). The group subscribes to the matching <asset.type>.measured.<positionVsParent> event and mirrors the value into its own MeasurementContainer; pressure events also trigger handlePressureChange so optimalControl can use ONE header operating point for all pumps.

Position labels accepted from children are upstream, downstream, atequipment (and case variants — normalised internally).