const test = require('node:test'); const assert = require('node:assert/strict'); const TotalsCalculator = require('../../src/totals/totalsCalculator'); const unitPolicy = { canonical: { pressure: 'Pa', flow: 'm3/s', power: 'W', temperature: 'K' }, output: { pressure: 'Pa', flow: 'm3/s', power: 'W', temperature: 'K' }, }; const silent = { debug() {}, info() {}, warn() {}, error() {} }; function predictView(min, max) { return { currentF: (min + max) / 2, currentFxyYMin: min, currentFxyYMax: max }; } function makeMachine(id, opts = {}) { const { flowMin = 0.0, flowMax = 1.0, powerMin = 100, powerMax = 1000, state = 'operational', hasCurve = true, NCog = 0.5, // Input-curve envelope (for calcAbsoluteTotals): { [pressureKey]: { y: [...] } } inputCurve = null, actFlow = 0, actPower = 0, } = opts; const fakeInput = inputCurve || { '50000': { y: [flowMin, (flowMin + flowMax) / 2, flowMax] }, }; const fakePower = inputCurve ? Object.fromEntries(Object.keys(inputCurve).map(k => [k, { y: [powerMin, (powerMin + powerMax) / 2, powerMax] }])) : { '50000': { y: [powerMin, (powerMin + powerMax) / 2, powerMax] } }; return { config: { general: { id } }, hasCurve, state: { getCurrentState: () => state }, NCog, predictFlow: { inputCurve: fakeInput, ...predictView(flowMin, flowMax) }, predictPower: { inputCurve: fakePower, ...predictView(powerMin, powerMax) }, _actFlow: actFlow, _actPower: actPower, }; } function fakeOperatingPoint(/* machines */) { return { readChild(machine, type, _variant, _position /*, _unit */) { if (type === 'flow') return machine._actFlow; if (type === 'power') return machine._actPower; return null; }, }; } test('calcAbsoluteTotals returns zeros when no machines', () => { const tc = new TotalsCalculator({ machines: {}, unitPolicy, logger: silent }); const t = tc.calcAbsoluteTotals(); assert.deepEqual(t, { flow: { min: 0, max: 0 }, power: { min: 0, max: 0 } }); }); test('calcAbsoluteTotals scans curve envelope (sum of maxes, min of mins)', () => { const machines = { a: makeMachine('a', { flowMin: 0.1, flowMax: 0.5, powerMin: 100, powerMax: 500 }), b: makeMachine('b', { flowMin: 0.2, flowMax: 0.8, powerMin: 200, powerMax: 700 }), }; const tc = new TotalsCalculator({ machines, unitPolicy, logger: silent }); const t = tc.calcAbsoluteTotals(); assert.equal(t.flow.min, 0.1); assert.equal(t.power.min, 100); // max is summed across all machines assert.equal(t.flow.max, 0.5 + 0.8); assert.equal(t.power.max, 500 + 700); }); test('calcDynamicTotals sums across machines and skips machines with no valid curve', () => { const machines = { a: makeMachine('a', { flowMin: 0.1, flowMax: 0.5, powerMin: 100, powerMax: 500, actFlow: 0.3, actPower: 300 }), b: makeMachine('b', { flowMin: 0.2, flowMax: 0.7, powerMin: 200, powerMax: 600, actFlow: 0.4, actPower: 400 }), skip: makeMachine('skip', { hasCurve: false }), }; const tc = new TotalsCalculator({ machines, unitPolicy, logger: silent, operatingPoint: fakeOperatingPoint(machines), }); const t = tc.calcDynamicTotals(); assert.equal(t.flow.min, 0.1); assert.equal(t.flow.max, 0.5 + 0.7); assert.equal(t.flow.act, 0.3 + 0.4); assert.equal(t.power.min, 100); assert.equal(t.power.max, 500 + 600); assert.equal(t.power.act, 300 + 400); assert.equal(t.NCog, machines.a.NCog + machines.b.NCog); }); test('activeTotals skips machines whose state is off or maintenance', () => { const machines = { a: makeMachine('a', { flowMin: 0.1, flowMax: 0.5, powerMin: 100, powerMax: 500, state: 'operational' }), b: makeMachine('b', { flowMin: 0.2, flowMax: 0.7, powerMin: 200, powerMax: 600, state: 'off' }), c: makeMachine('c', { flowMin: 0.3, flowMax: 0.9, powerMin: 300, powerMax: 900, state: 'maintenance' }), d: makeMachine('d', { flowMin: 0.05, flowMax: 0.4, powerMin: 50, powerMax: 400, state: 'accelerating' }), }; const tc = new TotalsCalculator({ machines, unitPolicy, logger: silent }); const t = tc.activeTotals(); assert.equal(t.countActiveMachines, 2); // a + d assert.equal(t.flow.min, 0.1 + 0.05); assert.equal(t.flow.max, 0.5 + 0.4); assert.equal(t.power.min, 100 + 50); assert.equal(t.power.max, 500 + 400); }); test('activeTotals honours the injected isMachineActive override', () => { const machines = { a: makeMachine('a', { flowMin: 0.1, flowMax: 0.5, powerMin: 100, powerMax: 500, state: 'operational' }), b: makeMachine('b', { flowMin: 0.2, flowMax: 0.7, powerMin: 200, powerMax: 600, state: 'operational' }), }; const tc = new TotalsCalculator({ machines, unitPolicy, logger: silent, isMachineActive: (id) => id === 'b', }); const t = tc.activeTotals(); assert.equal(t.countActiveMachines, 1); assert.equal(t.flow.max, 0.7); });