'use strict'; // Aggregates per-machine efficiency (cog) into group-level metrics and // computes distance-from-peak. Extracted verbatim from specificClass.js // (calcGroupEfficiency / calcDistanceFromPeak / calcRelativeDistanceFromPeak / // calcDistanceBEP) so the orchestrator can delegate without inheriting // the arithmetic. class GroupEfficiency { constructor(ctx = {}) { this.ctx = ctx; this.logger = ctx.logger || null; this.interpolation = ctx.interpolation || null; this.measurements = ctx.measurements || null; this.machines = ctx.machines || null; } // Average of per-machine cog plus the worst-performing machine's cog. // `maxEfficiency` is misleadingly named — it is in fact the MEAN cog // across all machines, treated as the group-level "peak" target. // Kept that way for behavioural parity with the original. calcGroupEfficiency(machines) { const target = machines || this.machines; let cumEfficiency = 0; let machineCount = 0; let lowestEfficiency = Infinity; Object.entries(target || {}).forEach(([_id, machine]) => { cumEfficiency += machine.cog; if (machine.cog < lowestEfficiency) { lowestEfficiency = machine.cog; } machineCount++; }); const maxEfficiency = cumEfficiency / machineCount; const currentEfficiency = this._readCurrentEfficiency(); return { maxEfficiency, lowestEfficiency, currentEfficiency }; } calcDistanceFromPeak(currentEfficiency, peakEfficiency) { return Math.abs(currentEfficiency - peakEfficiency); } // Maps current efficiency onto [0..1] across [maxEfficiency..minEfficiency]. // Returns undefined for any case where the metric is meaningless: // - currentEfficiency missing // - the [max..min] band has collapsed (homogeneous pump group, OR float // noise so |max-min| < DEGENERATE_EPS). // Consumers must treat undefined as "no data" and display accordingly, // not as 0% / 100% — both readings would be misleading. calcRelativeDistanceFromPeak(currentEfficiency, maxEfficiency, minEfficiency) { const DEGENERATE_EPS = 1e-9; // η points are 0..1, so 1e-9 catches float noise. if (currentEfficiency == null) return undefined; if (!this.interpolation) return undefined; if (!Number.isFinite(maxEfficiency) || !Number.isFinite(minEfficiency)) return undefined; if (Math.abs(maxEfficiency - minEfficiency) < DEGENERATE_EPS) return undefined; return this.interpolation.interpolate_lin_single_point( currentEfficiency, maxEfficiency, minEfficiency, 0, 1, ); } // Returns both abs + rel; orchestrator decides whether to mirror onto // its own this.absDistFromPeak / this.relDistFromPeak fields. calcDistanceBEP(currentEfficiency, maxEfficiency, minEfficiency) { const absDistFromPeak = this.calcDistanceFromPeak(currentEfficiency, maxEfficiency); const relDistFromPeak = this.calcRelativeDistanceFromPeak( currentEfficiency, maxEfficiency, minEfficiency, ); return { absDistFromPeak, relDistFromPeak }; } // Pull the latest measured efficiency from the container if one was // provided. Optional convenience — orchestrator may read it directly. _readCurrentEfficiency() { if (!this.measurements) return null; try { return this.measurements .type('efficiency') .variant('predicted') .position('atequipment') .getCurrentValue(); } catch (_err) { return null; } } } module.exports = GroupEfficiency;