Files
machineGroupControl/src/control/strategies.js

186 lines
8.3 KiB
JavaScript
Raw Normal View History

'use strict';
// Priority-based control strategies for machineGroupControl.
//
// equalFlowControl: distribute demand equally across priority-ordered active
// machines, falling back to start/stop the next priority when the current
// active set can't deliver.
//
governance + unit-self-describing demand + dashboard fixes Two governance items from the 2026-05-14 quality review: - test/_output-manifest.md enumerates every Port 0/1/2 key MGC emits, its source, type, range, and which tests cover it in populated/degraded states (per .claude/rules/output-coverage.md). - src/control/strategies.js extracts computeEqualFlowDistribution as a pure function so the equal-flow algorithm is testable without an MGC fixture. test/basic/equalFlowDistribution.basic.test.js (6 tests) covers all three demand branches and pins the legacy quirk where the default branch counts active machines but iterates priority-ordered first-N (documented in the test so the future cleanup is a deliberate change). Plus rolled-up session work that landed alongside: - set.demand is now unit-self-describing ({value, unit:'m3/h'|'l/s'|'%'|...} or bare number = %); setScaling/scaling.current removed from MGC, commands, editor (mgc.html), specificClass. - _optimalControl + equalFlowControl now compute eta = (Q*dP)/P_shaft rather than Q/P, keeping the metric in the same scale as each child's cog. - groupEfficiency.calcRelativeDistanceFromPeak returns undefined (was 1) when pumps are homogeneous (|max-min| < 1e-9). Dashboard treats undefined as '-' instead of showing a misleading 100% / 0% reading. - examples/02-Dashboard.json: auto-init inject so the dashboard populates at deploy, NCog formatter normalizes the SUM emitted by MGC by machineCountActive, Q-H fanout trims the flat-Q tail so the H axis isn't stretched to 40m by curve-envelope clamp points, num/pct treat null AND undefined as no-data (closes the +null === 0 trap). - new test/integration/dashboard-fanout.integration.test.js (17 tests), bep-distance-demand-sweep.integration.test.js (3 tests), group-bep-cascade.integration.test.js -- total suite now 108/108 green. - .gitignore: wiki/test.gif (143 MB screen recording, kept locally only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:31:25 +02:00
// Extracted from specificClass during the P4 refactor; the orchestrator
// wires it in via the strategies map below. It depends on the same
// group-curve helpers the optimizer uses, so allocation and power
// evaluation stay on the equalised group operating point.
const { POSITIONS } = require('generalFunctions');
const { groupFlow, groupCalcPower } = require('../groupOps/groupCurves');
function sortMachinesByPriority(machines, priorityList) {
if (priorityList && Array.isArray(priorityList)) {
return priorityList
.filter(id => machines[id])
.map(id => ({ id, machine: machines[id] }));
}
return Object.entries(machines)
.map(([id, machine]) => ({ id, machine }))
.sort((a, b) => a.id - b.id);
}
function filterOutUnavailableMachines(list) {
return list.filter(({ machine }) => {
const state = machine.state.getCurrentState();
const validActionForMode = machine.isValidActionForMode('execsequence', 'auto');
return !(state === 'off' || state === 'coolingdown' || state === 'stopping'
|| state === 'emergencystop' || !validActionForMode);
});
}
function capFlowDemand(Qd, dynamicTotals, logger) {
if (Qd < dynamicTotals.flow.min && Qd > 0) {
logger?.warn?.(`Flow demand ${Qd} below min ${dynamicTotals.flow.min}; capping.`);
return dynamicTotals.flow.min;
}
if (Qd > dynamicTotals.flow.max) {
logger?.warn?.(`Flow demand ${Qd} above max ${dynamicTotals.flow.max}; capping.`);
return dynamicTotals.flow.max;
}
return Qd;
}
governance + unit-self-describing demand + dashboard fixes Two governance items from the 2026-05-14 quality review: - test/_output-manifest.md enumerates every Port 0/1/2 key MGC emits, its source, type, range, and which tests cover it in populated/degraded states (per .claude/rules/output-coverage.md). - src/control/strategies.js extracts computeEqualFlowDistribution as a pure function so the equal-flow algorithm is testable without an MGC fixture. test/basic/equalFlowDistribution.basic.test.js (6 tests) covers all three demand branches and pins the legacy quirk where the default branch counts active machines but iterates priority-ordered first-N (documented in the test so the future cleanup is a deliberate change). Plus rolled-up session work that landed alongside: - set.demand is now unit-self-describing ({value, unit:'m3/h'|'l/s'|'%'|...} or bare number = %); setScaling/scaling.current removed from MGC, commands, editor (mgc.html), specificClass. - _optimalControl + equalFlowControl now compute eta = (Q*dP)/P_shaft rather than Q/P, keeping the metric in the same scale as each child's cog. - groupEfficiency.calcRelativeDistanceFromPeak returns undefined (was 1) when pumps are homogeneous (|max-min| < 1e-9). Dashboard treats undefined as '-' instead of showing a misleading 100% / 0% reading. - examples/02-Dashboard.json: auto-init inject so the dashboard populates at deploy, NCog formatter normalizes the SUM emitted by MGC by machineCountActive, Q-H fanout trims the flat-Q tail so the H axis isn't stretched to 40m by curve-envelope clamp points, num/pct treat null AND undefined as no-data (closes the +null === 0 trap). - new test/integration/dashboard-fanout.integration.test.js (17 tests), bep-distance-demand-sweep.integration.test.js (3 tests), group-bep-cascade.integration.test.js -- total suite now 108/108 green. - .gitignore: wiki/test.gif (143 MB screen recording, kept locally only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:31:25 +02:00
// Pure distribution math: given the demand, group envelope, priority list, and
// per-machine curve helpers, return the {machineId, flow} mapping plus running
// totals. No side effects, no mgc reference — testable without an MGC fixture.
//
// Inputs:
// machines: dict {id → machine} (machine objects need group-curve fields set)
// Qd: demand in canonical m³/s
// dynamicTotals: {flow: {min, max}} — envelope across ALL registered pumps
// activeTotals: {flow: {min, max}} — envelope across currently-active pumps
// priorityList: optional array of ids; null = default ordering
// isMachineActive: (id) → boolean (state-aware predicate)
// groupFlow: (machine) → {currentFxyYMin, currentFxyYMax}
// groupCalcPower: (machine, flow) → number (W)
// logger: { warn, error, … } or null
//
// Returns: { flowDistribution: [{machineId, flow}], totalFlow, totalPower, totalCog }
function computeEqualFlowDistribution({
machines, Qd, dynamicTotals, activeTotals, priorityList,
isMachineActive, groupFlow, groupCalcPower, logger,
}) {
Qd = capFlowDemand(Qd, dynamicTotals, logger);
let machinesInPriorityOrder = sortMachinesByPriority(machines, priorityList);
machinesInPriorityOrder = filterOutUnavailableMachines(machinesInPriorityOrder);
const flowDistribution = [];
let totalFlow = 0;
let totalPower = 0;
// Equal-flow doesn't compute a meaningful cog — only BEP-Gravitation does.
// Preserved at 0 for backwards-compat; pinned by a basic test so a future
// change that introduces a fake non-zero value will fail loudly.
const totalCog = 0;
switch (true) {
case (Qd < activeTotals.flow.min && activeTotals.flow.min !== 0): {
let availableFlow = activeTotals.flow.min;
for (let i = machinesInPriorityOrder.length - 1; i >= 0 && availableFlow > Qd; i--) {
const m = machinesInPriorityOrder[i];
if (isMachineActive(m.id)) {
flowDistribution.push({ machineId: m.id, flow: 0 });
availableFlow -= groupFlow(m.machine).currentFxyYMin;
}
}
governance + unit-self-describing demand + dashboard fixes Two governance items from the 2026-05-14 quality review: - test/_output-manifest.md enumerates every Port 0/1/2 key MGC emits, its source, type, range, and which tests cover it in populated/degraded states (per .claude/rules/output-coverage.md). - src/control/strategies.js extracts computeEqualFlowDistribution as a pure function so the equal-flow algorithm is testable without an MGC fixture. test/basic/equalFlowDistribution.basic.test.js (6 tests) covers all three demand branches and pins the legacy quirk where the default branch counts active machines but iterates priority-ordered first-N (documented in the test so the future cleanup is a deliberate change). Plus rolled-up session work that landed alongside: - set.demand is now unit-self-describing ({value, unit:'m3/h'|'l/s'|'%'|...} or bare number = %); setScaling/scaling.current removed from MGC, commands, editor (mgc.html), specificClass. - _optimalControl + equalFlowControl now compute eta = (Q*dP)/P_shaft rather than Q/P, keeping the metric in the same scale as each child's cog. - groupEfficiency.calcRelativeDistanceFromPeak returns undefined (was 1) when pumps are homogeneous (|max-min| < 1e-9). Dashboard treats undefined as '-' instead of showing a misleading 100% / 0% reading. - examples/02-Dashboard.json: auto-init inject so the dashboard populates at deploy, NCog formatter normalizes the SUM emitted by MGC by machineCountActive, Q-H fanout trims the flat-Q tail so the H axis isn't stretched to 40m by curve-envelope clamp points, num/pct treat null AND undefined as no-data (closes the +null === 0 trap). - new test/integration/dashboard-fanout.integration.test.js (17 tests), bep-distance-demand-sweep.integration.test.js (3 tests), group-bep-cascade.integration.test.js -- total suite now 108/108 green. - .gitignore: wiki/test.gif (143 MB screen recording, kept locally only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:31:25 +02:00
const remaining = machinesInPriorityOrder.filter(({ id }) =>
isMachineActive(id) && !flowDistribution.some(it => it.machineId === id));
const distributedFlow = Qd / remaining.length;
for (const m of remaining) {
flowDistribution.push({ machineId: m.id, flow: distributedFlow });
totalFlow += distributedFlow;
totalPower += groupCalcPower(m.machine, distributedFlow);
}
break;
}
case (Qd > activeTotals.flow.max): {
let i = 1;
while (totalFlow < Qd && i <= machinesInPriorityOrder.length) {
Qd = Qd / i;
if (groupFlow(machinesInPriorityOrder[i - 1].machine).currentFxyYMax >= Qd) {
for (let i2 = 0; i2 < i; i2++) {
if (!isMachineActive(machinesInPriorityOrder[i2].id)) {
flowDistribution.push({ machineId: machinesInPriorityOrder[i2].id, flow: Qd });
totalFlow += Qd;
totalPower += groupCalcPower(machinesInPriorityOrder[i2].machine, Qd);
}
}
}
governance + unit-self-describing demand + dashboard fixes Two governance items from the 2026-05-14 quality review: - test/_output-manifest.md enumerates every Port 0/1/2 key MGC emits, its source, type, range, and which tests cover it in populated/degraded states (per .claude/rules/output-coverage.md). - src/control/strategies.js extracts computeEqualFlowDistribution as a pure function so the equal-flow algorithm is testable without an MGC fixture. test/basic/equalFlowDistribution.basic.test.js (6 tests) covers all three demand branches and pins the legacy quirk where the default branch counts active machines but iterates priority-ordered first-N (documented in the test so the future cleanup is a deliberate change). Plus rolled-up session work that landed alongside: - set.demand is now unit-self-describing ({value, unit:'m3/h'|'l/s'|'%'|...} or bare number = %); setScaling/scaling.current removed from MGC, commands, editor (mgc.html), specificClass. - _optimalControl + equalFlowControl now compute eta = (Q*dP)/P_shaft rather than Q/P, keeping the metric in the same scale as each child's cog. - groupEfficiency.calcRelativeDistanceFromPeak returns undefined (was 1) when pumps are homogeneous (|max-min| < 1e-9). Dashboard treats undefined as '-' instead of showing a misleading 100% / 0% reading. - examples/02-Dashboard.json: auto-init inject so the dashboard populates at deploy, NCog formatter normalizes the SUM emitted by MGC by machineCountActive, Q-H fanout trims the flat-Q tail so the H axis isn't stretched to 40m by curve-envelope clamp points, num/pct treat null AND undefined as no-data (closes the +null === 0 trap). - new test/integration/dashboard-fanout.integration.test.js (17 tests), bep-distance-demand-sweep.integration.test.js (3 tests), group-bep-cascade.integration.test.js -- total suite now 108/108 green. - .gitignore: wiki/test.gif (143 MB screen recording, kept locally only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:31:25 +02:00
i++;
}
governance + unit-self-describing demand + dashboard fixes Two governance items from the 2026-05-14 quality review: - test/_output-manifest.md enumerates every Port 0/1/2 key MGC emits, its source, type, range, and which tests cover it in populated/degraded states (per .claude/rules/output-coverage.md). - src/control/strategies.js extracts computeEqualFlowDistribution as a pure function so the equal-flow algorithm is testable without an MGC fixture. test/basic/equalFlowDistribution.basic.test.js (6 tests) covers all three demand branches and pins the legacy quirk where the default branch counts active machines but iterates priority-ordered first-N (documented in the test so the future cleanup is a deliberate change). Plus rolled-up session work that landed alongside: - set.demand is now unit-self-describing ({value, unit:'m3/h'|'l/s'|'%'|...} or bare number = %); setScaling/scaling.current removed from MGC, commands, editor (mgc.html), specificClass. - _optimalControl + equalFlowControl now compute eta = (Q*dP)/P_shaft rather than Q/P, keeping the metric in the same scale as each child's cog. - groupEfficiency.calcRelativeDistanceFromPeak returns undefined (was 1) when pumps are homogeneous (|max-min| < 1e-9). Dashboard treats undefined as '-' instead of showing a misleading 100% / 0% reading. - examples/02-Dashboard.json: auto-init inject so the dashboard populates at deploy, NCog formatter normalizes the SUM emitted by MGC by machineCountActive, Q-H fanout trims the flat-Q tail so the H axis isn't stretched to 40m by curve-envelope clamp points, num/pct treat null AND undefined as no-data (closes the +null === 0 trap). - new test/integration/dashboard-fanout.integration.test.js (17 tests), bep-distance-demand-sweep.integration.test.js (3 tests), group-bep-cascade.integration.test.js -- total suite now 108/108 green. - .gitignore: wiki/test.gif (143 MB screen recording, kept locally only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:31:25 +02:00
break;
}
default: {
const countActive = machinesInPriorityOrder.filter(({ id }) => isMachineActive(id)).length;
Qd /= countActive;
for (let i = 0; i < countActive; i++) {
flowDistribution.push({ machineId: machinesInPriorityOrder[i].id, flow: Qd });
totalFlow += Qd;
totalPower += groupCalcPower(machinesInPriorityOrder[i].machine, Qd);
}
governance + unit-self-describing demand + dashboard fixes Two governance items from the 2026-05-14 quality review: - test/_output-manifest.md enumerates every Port 0/1/2 key MGC emits, its source, type, range, and which tests cover it in populated/degraded states (per .claude/rules/output-coverage.md). - src/control/strategies.js extracts computeEqualFlowDistribution as a pure function so the equal-flow algorithm is testable without an MGC fixture. test/basic/equalFlowDistribution.basic.test.js (6 tests) covers all three demand branches and pins the legacy quirk where the default branch counts active machines but iterates priority-ordered first-N (documented in the test so the future cleanup is a deliberate change). Plus rolled-up session work that landed alongside: - set.demand is now unit-self-describing ({value, unit:'m3/h'|'l/s'|'%'|...} or bare number = %); setScaling/scaling.current removed from MGC, commands, editor (mgc.html), specificClass. - _optimalControl + equalFlowControl now compute eta = (Q*dP)/P_shaft rather than Q/P, keeping the metric in the same scale as each child's cog. - groupEfficiency.calcRelativeDistanceFromPeak returns undefined (was 1) when pumps are homogeneous (|max-min| < 1e-9). Dashboard treats undefined as '-' instead of showing a misleading 100% / 0% reading. - examples/02-Dashboard.json: auto-init inject so the dashboard populates at deploy, NCog formatter normalizes the SUM emitted by MGC by machineCountActive, Q-H fanout trims the flat-Q tail so the H axis isn't stretched to 40m by curve-envelope clamp points, num/pct treat null AND undefined as no-data (closes the +null === 0 trap). - new test/integration/dashboard-fanout.integration.test.js (17 tests), bep-distance-demand-sweep.integration.test.js (3 tests), group-bep-cascade.integration.test.js -- total suite now 108/108 green. - .gitignore: wiki/test.gif (143 MB screen recording, kept locally only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:31:25 +02:00
break;
}
governance + unit-self-describing demand + dashboard fixes Two governance items from the 2026-05-14 quality review: - test/_output-manifest.md enumerates every Port 0/1/2 key MGC emits, its source, type, range, and which tests cover it in populated/degraded states (per .claude/rules/output-coverage.md). - src/control/strategies.js extracts computeEqualFlowDistribution as a pure function so the equal-flow algorithm is testable without an MGC fixture. test/basic/equalFlowDistribution.basic.test.js (6 tests) covers all three demand branches and pins the legacy quirk where the default branch counts active machines but iterates priority-ordered first-N (documented in the test so the future cleanup is a deliberate change). Plus rolled-up session work that landed alongside: - set.demand is now unit-self-describing ({value, unit:'m3/h'|'l/s'|'%'|...} or bare number = %); setScaling/scaling.current removed from MGC, commands, editor (mgc.html), specificClass. - _optimalControl + equalFlowControl now compute eta = (Q*dP)/P_shaft rather than Q/P, keeping the metric in the same scale as each child's cog. - groupEfficiency.calcRelativeDistanceFromPeak returns undefined (was 1) when pumps are homogeneous (|max-min| < 1e-9). Dashboard treats undefined as '-' instead of showing a misleading 100% / 0% reading. - examples/02-Dashboard.json: auto-init inject so the dashboard populates at deploy, NCog formatter normalizes the SUM emitted by MGC by machineCountActive, Q-H fanout trims the flat-Q tail so the H axis isn't stretched to 40m by curve-envelope clamp points, num/pct treat null AND undefined as no-data (closes the +null === 0 trap). - new test/integration/dashboard-fanout.integration.test.js (17 tests), bep-distance-demand-sweep.integration.test.js (3 tests), group-bep-cascade.integration.test.js -- total suite now 108/108 green. - .gitignore: wiki/test.gif (143 MB screen recording, kept locally only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:31:25 +02:00
}
return { flowDistribution, totalFlow, totalPower, totalCog };
}
// Orchestrator: equalize the operating point, call the pure distribution math,
// write outputs, dispatch children. The mgc reaches happen here, not in the
// algorithm — see computeEqualFlowDistribution above for the part that's
// testable in isolation.
async function equalFlowControl(ctx, Qd, _powerCap = Infinity, priorityList = null) {
const { mgc } = ctx;
try {
mgc.equalizePressure();
const dynamicTotals = mgc.calcDynamicTotals();
const activeTotals = mgc.totals.activeTotals();
governance + unit-self-describing demand + dashboard fixes Two governance items from the 2026-05-14 quality review: - test/_output-manifest.md enumerates every Port 0/1/2 key MGC emits, its source, type, range, and which tests cover it in populated/degraded states (per .claude/rules/output-coverage.md). - src/control/strategies.js extracts computeEqualFlowDistribution as a pure function so the equal-flow algorithm is testable without an MGC fixture. test/basic/equalFlowDistribution.basic.test.js (6 tests) covers all three demand branches and pins the legacy quirk where the default branch counts active machines but iterates priority-ordered first-N (documented in the test so the future cleanup is a deliberate change). Plus rolled-up session work that landed alongside: - set.demand is now unit-self-describing ({value, unit:'m3/h'|'l/s'|'%'|...} or bare number = %); setScaling/scaling.current removed from MGC, commands, editor (mgc.html), specificClass. - _optimalControl + equalFlowControl now compute eta = (Q*dP)/P_shaft rather than Q/P, keeping the metric in the same scale as each child's cog. - groupEfficiency.calcRelativeDistanceFromPeak returns undefined (was 1) when pumps are homogeneous (|max-min| < 1e-9). Dashboard treats undefined as '-' instead of showing a misleading 100% / 0% reading. - examples/02-Dashboard.json: auto-init inject so the dashboard populates at deploy, NCog formatter normalizes the SUM emitted by MGC by machineCountActive, Q-H fanout trims the flat-Q tail so the H axis isn't stretched to 40m by curve-envelope clamp points, num/pct treat null AND undefined as no-data (closes the +null === 0 trap). - new test/integration/dashboard-fanout.integration.test.js (17 tests), bep-distance-demand-sweep.integration.test.js (3 tests), group-bep-cascade.integration.test.js -- total suite now 108/108 green. - .gitignore: wiki/test.gif (143 MB screen recording, kept locally only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:31:25 +02:00
const { flowDistribution, totalFlow, totalPower, totalCog } = computeEqualFlowDistribution({
machines: mgc.machines,
Qd, dynamicTotals, activeTotals, priorityList,
isMachineActive: (id) => mgc.isMachineActive(id),
groupFlow, groupCalcPower,
logger: mgc.logger,
});
const pUnit = mgc.unitPolicy.canonical.power;
const fUnit = mgc.unitPolicy.canonical.flow;
mgc.operatingPoint.writeOwn('power', 'predicted', POSITIONS.AT_EQUIPMENT, totalPower, pUnit);
mgc.operatingPoint.writeOwn('flow', 'predicted', POSITIONS.AT_EQUIPMENT, totalFlow, fUnit);
// Hydraulic efficiency η = (Q·ΔP)/P_shaft, same scale as child cogs.
const dP = mgc.operatingPoint.headerDiffPa;
if (Number.isFinite(dP) && dP > 0 && totalPower > 0) {
mgc.measurements.type('efficiency').variant('predicted').position(POSITIONS.AT_EQUIPMENT)
.value((totalFlow * dP) / totalPower);
}
mgc.measurements.type('Ncog').variant('predicted').position(POSITIONS.AT_EQUIPMENT).value(totalCog);
await Promise.all(flowDistribution.map(async ({ machineId, flow }) => {
const machine = mgc.machines[machineId];
const currentState = machine.state.getCurrentState();
if (flow > 0) {
await machine.handleInput('parent', 'flowmovement', mgc._canonicalToOutputFlow(flow));
if (currentState === 'idle') {
await machine.handleInput('parent', 'execsequence', 'startup');
}
} else if (currentState === 'operational' || currentState === 'accelerating' || currentState === 'decelerating') {
await machine.handleInput('parent', 'execsequence', 'shutdown');
}
}));
} catch (err) {
mgc.logger?.error?.(err);
}
}
governance + unit-self-describing demand + dashboard fixes Two governance items from the 2026-05-14 quality review: - test/_output-manifest.md enumerates every Port 0/1/2 key MGC emits, its source, type, range, and which tests cover it in populated/degraded states (per .claude/rules/output-coverage.md). - src/control/strategies.js extracts computeEqualFlowDistribution as a pure function so the equal-flow algorithm is testable without an MGC fixture. test/basic/equalFlowDistribution.basic.test.js (6 tests) covers all three demand branches and pins the legacy quirk where the default branch counts active machines but iterates priority-ordered first-N (documented in the test so the future cleanup is a deliberate change). Plus rolled-up session work that landed alongside: - set.demand is now unit-self-describing ({value, unit:'m3/h'|'l/s'|'%'|...} or bare number = %); setScaling/scaling.current removed from MGC, commands, editor (mgc.html), specificClass. - _optimalControl + equalFlowControl now compute eta = (Q*dP)/P_shaft rather than Q/P, keeping the metric in the same scale as each child's cog. - groupEfficiency.calcRelativeDistanceFromPeak returns undefined (was 1) when pumps are homogeneous (|max-min| < 1e-9). Dashboard treats undefined as '-' instead of showing a misleading 100% / 0% reading. - examples/02-Dashboard.json: auto-init inject so the dashboard populates at deploy, NCog formatter normalizes the SUM emitted by MGC by machineCountActive, Q-H fanout trims the flat-Q tail so the H axis isn't stretched to 40m by curve-envelope clamp points, num/pct treat null AND undefined as no-data (closes the +null === 0 trap). - new test/integration/dashboard-fanout.integration.test.js (17 tests), bep-distance-demand-sweep.integration.test.js (3 tests), group-bep-cascade.integration.test.js -- total suite now 108/108 green. - .gitignore: wiki/test.gif (143 MB screen recording, kept locally only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:31:25 +02:00
module.exports = {
equalFlowControl, computeEqualFlowDistribution,
capFlowDemand, sortMachinesByPriority, filterOutUnavailableMachines,
};