diff --git a/src/specificClass.js b/src/specificClass.js index cd78b67..bf98c45 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -283,6 +283,18 @@ class MachineGroup { this.logger.debug(`Dynamic Totals after pressure change - Flow: Min ${flow.min}, Max ${flow.max}, Act ${flow.act} | Power: Min ${power.min}, Max ${power.max}, Act ${power.act}`); this._writeMeasurement("flow", "predicted", POSITIONS.AT_EQUIPMENT, flow.act, this.unitPolicy.canonical.flow); + // Mirror the aggregate flow onto DOWNSTREAM as well. PS subscribes to + // flow.predicted.downstream from MGC and uses it as the outflow + // estimate for net-flow computation. Without this mirror, the only + // place DOWNSTREAM gets written is optimalControl's bestFlow (the + // optimizer's TARGET, not the achieved aggregate). During transients + // — e.g. demand dropping to dead-band keep-alive while pumps are + // still ramping down from full throttle — PS would see a stale + // 25 m³/h target while pumps are physically delivering 500+ m³/h, + // making netFlow look small and stable when the basin is actually + // draining fast. flow.act here is the sum of every pump's current + // predicted output, so it IS the achieved aggregate. + this._writeMeasurement("flow", "predicted", POSITIONS.DOWNSTREAM, flow.act, this.unitPolicy.canonical.flow); this._writeMeasurement("power", "predicted", POSITIONS.AT_EQUIPMENT, power.act, this.unitPolicy.canonical.power); const { maxEfficiency, lowestEfficiency } = this.calcGroupEfficiency(this.machines);