Files
machineGroupControl/test/basic/pumpCombinations.basic.test.js

91 lines
3.5 KiB
JavaScript
Raw Permalink Normal View History

const test = require('node:test');
const assert = require('node:assert/strict');
// Local stub for groupCurves — replace once ../groupOps/groupCurves lands.
const groupCurves = {
groupFlow: (m) => m.predictFlow,
groupPower: (m) => m.predictPower,
groupNCog: (m) => m.NCog ?? 0,
groupCalcPower: (m, f) => m.inputFlowCalcPower(f),
};
const { validPumpCombinations, checkSpecialCases } =
require('../../src/combinatorics/pumpCombinations');
function makeMachine({ id, state = 'off', mode = 'auto',
fMin = 0, fMax = 100, pMax = 100,
NCog = 0.5, validAction = true } = {}) {
return {
config: { general: { id } },
state: { getCurrentState: () => state },
currentMode: mode,
NCog,
predictFlow: { currentFxyYMin: fMin, currentFxyYMax: fMax },
predictPower: { currentFxyYMin: 0, currentFxyYMax: pMax },
inputFlowCalcPower: (flow) => flow * 0.5,
isValidActionForMode: () => validAction,
};
}
const POSITIONS = { DOWNSTREAM: 'downstream' };
const baseCtx = (extra = {}) => ({
groupCurves,
logger: { warn: () => {}, debug: () => {}, error: () => {} },
readChildMeasurement: () => undefined,
POSITIONS,
unitPolicy: { canonical: { flow: 'm3/s' } },
...extra,
});
test('validPumpCombinations: 3 idle machines + Qd in range returns subsets that can deliver', () => {
const machines = {
a: makeMachine({ id: 'a', state: 'idle', fMin: 10, fMax: 50 }),
b: makeMachine({ id: 'b', state: 'idle', fMin: 10, fMax: 50 }),
c: makeMachine({ id: 'c', state: 'idle', fMin: 10, fMax: 50 }),
};
const combos = validPumpCombinations(machines, 40, baseCtx());
assert.ok(combos.length > 0, 'expected at least one combination');
// every combination must be able to deliver Qd
for (const subset of combos) {
const maxF = subset.reduce((s, id) => s + machines[id].predictFlow.currentFxyYMax, 0);
const minF = subset.reduce((s, id) => s + machines[id].predictFlow.currentFxyYMin, 0);
assert.ok(maxF >= 40);
assert.ok(minF <= 40);
}
});
test('validPumpCombinations: excludes machines in off/coolingdown/stopping/emergencystop', () => {
const machines = {
a: makeMachine({ id: 'a', state: 'off', fMin: 10, fMax: 50 }),
b: makeMachine({ id: 'b', state: 'coolingdown', fMin: 10, fMax: 50 }),
c: makeMachine({ id: 'c', state: 'stopping', fMin: 10, fMax: 50 }),
d: makeMachine({ id: 'd', state: 'emergencystop', fMin: 10, fMax: 50 }),
e: makeMachine({ id: 'e', state: 'idle', fMin: 10, fMax: 50 }),
};
const combos = validPumpCombinations(machines, 30, baseCtx());
// Only "e" can be in a combination
for (const subset of combos) {
for (const id of subset) assert.equal(id, 'e');
}
});
test('checkSpecialCases: reduces Qd by flow of manually controlled operational machines', () => {
const machines = {
a: makeMachine({ id: 'a', state: 'operational', mode: 'virtualControl' }),
b: makeMachine({ id: 'b', state: 'idle' }),
};
const ctx = baseCtx({
readChildMeasurement: (m, type, variant) => {
if (m.config.general.id === 'a' && variant === 'measured') return 12;
return undefined;
},
});
const adjusted = checkSpecialCases(machines, 50, ctx);
assert.equal(adjusted, 38);
});
test('validPumpCombinations: no machines returns empty array', () => {
const combos = validPumpCombinations({}, 10, baseCtx());
assert.deepEqual(combos, []);
});