From a8d9895cbf3c5ef68a8140882f73cff18e7dca46 Mon Sep 17 00:00:00 2001 From: znetsixe Date: Wed, 27 May 2026 10:07:25 +0200 Subject: [PATCH] fix(rotatingmachine): seed operating-point flow/power telemetry at boot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The operating-point series (flow.predicted.{downstream,atequipment}, power.predicted.atequipment) were only written by calcFlow/calcPower while operational, or by _updateState on a state transition. A machine that boots into idle and never runs therefore emitted these keys NEVER — so InfluxDB carried only the flow envelope (max/min) and dashboard panels querying the operating point rendered blank, unable to show even the off/0 state. Seed them to 0 in _init() alongside max/min, so telemetry always carries the operating point: 0 while idle, real values once the pump runs. Verified end to end: keys now present in InfluxDB, the Grafana flow panel resolves, and the real prediction path produces non-zero values (~98 m3/h, ~13 kW) that flow through getOutput to Port 1. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/specificClass.js | 8 ++++++++ test/edge/output-format.edge.test.js | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/specificClass.js b/src/specificClass.js index 17d0744..8650ae1 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -229,10 +229,18 @@ class Machine extends BaseDomain { this.measurements.type('temperature').variant('measured').position('atEquipment').value(15, Date.now(), tu); this.measurements.type('atmPressure').variant('measured').position('atEquipment').value(101325, Date.now(), 'Pa'); const fu = this.unitPolicy.canonical.flow; + const pu = this.unitPolicy.canonical.power; const fmin = this.predictFlow ? this.predictFlow.currentFxyYMin : 0; const fmax = this.predictFlow ? this.predictFlow.currentFxyYMax : 0; this.measurements.type('flow').variant('predicted').position('max').value(fmax, Date.now(), fu); this.measurements.type('flow').variant('predicted').position('min').value(fmin, Date.now(), fu); + // Seed the operating-point series at boot so telemetry always carries them + // (0 while idle, real values once calcFlow/calcPower run when operational). + // Without this an idle-from-boot machine never emits these keys — the + // dashboard can't even show the off/0 state. Mirrors max/min above. + this.measurements.type('flow').variant('predicted').position('downstream').value(0, Date.now(), fu); + this.measurements.type('flow').variant('predicted').position('atEquipment').value(0, Date.now(), fu); + this.measurements.type('power').variant('predicted').position('atEquipment').value(0, Date.now(), pu); } _callMeasurementHandler(measurementType, value, position, context = {}) { diff --git a/test/edge/output-format.edge.test.js b/test/edge/output-format.edge.test.js index 723c2f2..0a6ace8 100644 --- a/test/edge/output-format.edge.test.js +++ b/test/edge/output-format.edge.test.js @@ -36,6 +36,31 @@ test('getOutput contains all required fields in idle state', () => { assert.ok('pressureDriftFlags' in output); }); +test('getOutput seeds operating-point flow/power telemetry at boot (idle = 0, not absent)', () => { + // Regression: an idle-from-boot machine must still emit the operating-point + // series so dashboards can show the off/0 state. These keys are otherwise + // only written once the pump runs (calcFlow/calcPower) or on a state + // transition, leaving them absent in telemetry for a pump that never starts. + const machine = new Machine(makeMachineConfig(), makeStateConfig()); + const output = machine.getOutput(); + + const hasPrefix = (p) => Object.keys(output).some((k) => k.startsWith(p)); + const valueFor = (p) => output[Object.keys(output).find((k) => k.startsWith(p))]; + + for (const prefix of [ + 'flow.predicted.downstream', + 'flow.predicted.atequipment', + 'power.predicted.atequipment', + ]) { + assert.ok(hasPrefix(prefix), `${prefix}.* must be present at boot (idle)`); + assert.equal(valueFor(prefix), 0, `${prefix}.* should be 0 while idle`); + } + + // The envelope keys remain present too. + assert.ok(hasPrefix('flow.predicted.max')); + assert.ok(hasPrefix('flow.predicted.min')); +}); + test('getOutput flow drift fields appear after sufficient measured flow samples', async () => { const machine = new Machine(makeMachineConfig(), makeStateConfig());