Files
vac-optimizer/src/app/benchmark/leave-planner-benchmark.spec.ts
T
nessarandClaude Opus 5.5 3bbe989eca
CI / policy (push) Successful in 7s
CI / validate (push) Successful in 1m5s
CI / build (push) Successful in 1m38s
CI / policy (pull_request) Successful in 10s
CI / security (push) Successful in 1m19s
CI / test (push) Successful in 3m29s
CI / validate (pull_request) Successful in 1m42s
CI / benchmark (push) Successful in 4m13s
CI / security (pull_request) Successful in 56s
CI / build (pull_request) Successful in 1m42s
CI / test (pull_request) Successful in 3m8s
CI / images (push) Successful in 1m15s
CI / required (push) Successful in 1s
CI / benchmark (pull_request) Successful in 3m37s
CI / images (pull_request) Successful in 1m11s
CI / required (pull_request) Successful in 1s
test(benchmark): print a readable summary table
Print the calibration and a Markdown table with one aligned row per
scenario as soon as it finishes: both planning times, mean, budget, ratio,
multiplier, outcome, placements, determinism and budget status. Write
directly to stdout so Vitest adds no per-line headers. The complete TSV
report still follows the table.

Refs #281, #285

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 02:09:54 +02:00

139 lines
5.8 KiB
TypeScript

import { vi } from 'vitest';
import { calculatePlanningAsyncInternal } from '../services/frozen-engine/leave-planner.core.frozen.mjs';
import { createPlanningCalculation } from '../services/leave-planner.service';
import {
BENCHMARK_BUDGET_MULTIPLIERS,
benchmarkReport,
benchmarkScenarios,
benchmarkSummaryHeader,
benchmarkSummaryRow,
budgetViolations,
runLeavePlannerBenchmark,
} from './leave-planner-benchmark';
describe('leave planner benchmark protocol', () => {
beforeEach(() => vi.setSystemTime(new Date('2024-01-01T12:00:00Z')));
afterEach(() => vi.useRealTimers());
// Warm-up and two repetitions include explanation delivery after planning.
it('keeps seven deterministic scenarios including two and three years and a later earned consumer', async () => {
const scenarios = benchmarkScenarios();
expect(scenarios).toHaveLength(7);
for (const [name, end] of [
['two years', '2026-01-01'],
['three years', '2027-01-01'],
]) {
const scenario = scenarios.find((item) => item.name === name)!;
expect(scenario.request.horizon).toEqual({ start: '2024-01-01', end });
expect(
scenario.request.balances.some(
(item) => String(item.availableFrom) >= '2025-01-01',
),
).toBe(true);
expect(scenario.request.deadlineMs).toBeUndefined();
}
const typedObligations = scenarios.find(
(scenario) => scenario.name === 'typed obligations',
);
const cpRule = typedObligations?.request.rules?.find((rule) => rule.id === 'cp-window');
const rttRule = typedObligations?.request.rules?.find((rule) => rule.id === 'rtt-window');
expect(cpRule).toMatchObject({ placeType: 'cp', placeDays: 2 });
expect(rttRule).toMatchObject({ placeType: 'rtt', placeDays: 2 });
expect(cpRule?.type).toBe('time_window');
expect(rttRule?.type).toBe('time_window');
if (cpRule && 'startDate' in cpRule && rttRule && 'startDate' in rttRule)
expect(
String(cpRule.startDate) <= String(rttRule.endDate) &&
String(rttRule.startDate) <= String(cpRule.endDate),
).toBe(true);
const chain = scenarios.find((scenario) => scenario.name === 'chained earned entitlements');
expect(
chain?.request.rules
?.filter(({ type }) => type !== 'optimization')
.map((rule) => rule.id),
).toEqual(['wednesday-holidays', 'thursday-consumer']);
const browserWindow = globalThis.window;
vi.stubGlobal('window', undefined);
try {
const typedOutcome = await createPlanningCalculation(typedObligations!.request).outcome;
expect(typedOutcome.kind).toBe('Plan');
if (typedOutcome.kind === 'Plan') {
expect(
typedOutcome.placements.filter((placement) =>
placement.ruleIds.includes('cp-window'),
),
).toHaveLength(2);
expect(
typedOutcome.placements.filter((placement) =>
placement.ruleIds.includes('rtt-window'),
),
).toHaveLength(2);
}
// Frozen engine (a871f8f) on 'two years', best of three: the runner-speed yardstick.
const calibrationRequest = scenarios.find((item) => item.name === 'two years')!.request;
let calibrationMs = Infinity;
for (let run = 0; run < 3; run++) {
const request = structuredClone(calibrationRequest);
const start = performance.now();
await calculatePlanningAsyncInternal({ ...request, calculationDate: '2024-01-01' });
calibrationMs = Math.min(calibrationMs, performance.now() - start);
}
// Direct writes skip Vitest's per-call "stdout |" headers and appear immediately.
const print = (text: string) => process.stdout.write(`${text}\n`);
print(
`\nCalibration: frozen engine on "two years", best of 3 = ${calibrationMs.toFixed(1)} ms` +
'\nBudget = limit x calibration; ratio = mean / calibration.\n',
);
print(benchmarkSummaryHeader());
const measurements = await runLeavePlannerBenchmark(2, (rows) =>
print(benchmarkSummaryRow(rows, calibrationMs)),
);
for (const [name, placements] of [
['two years', 60],
['three years', 90],
] as const) {
const longer = measurements.filter((measurement) => measurement.scenario === name);
expect(longer).toHaveLength(2);
expect(longer.map((item) => item.outcome)).toEqual(['Plan', 'Plan']);
expect(longer.map((item) => item.lastCompletedStage)).toEqual(['dates', 'dates']);
expect(longer.map((item) => item.placements)).toEqual([placements, placements]);
expect(longer.map((item) => item.deterministic)).toEqual(['baseline', 'yes']);
}
const chained = measurements.find(
(measurement) => measurement.scenario === 'chained earned entitlements',
);
const loaded = measurements.filter(
(measurement) => measurement.scenario === 'loaded year',
);
expect(chained?.outcome).toBe('Plan');
expect(chained?.placements).toBeGreaterThanOrEqual(3);
expect(loaded).toHaveLength(2);
expect(loaded[0].outcome).toBe('Plan');
expect(loaded[0].deterministic).toBe('baseline');
expect(loaded[1].deterministic).toBe('yes');
expect(loaded[0].planningMs).toBeGreaterThanOrEqual(0);
expect(loaded[0].planningWorkMs).toBeGreaterThanOrEqual(0);
expect(loaded[0].planningWorkMs).toBeLessThanOrEqual(loaded[0].planningMs);
expect(loaded[0].maxUiDelayMs).toBeGreaterThanOrEqual(0);
expect(loaded[0].wallMs).toBeGreaterThanOrEqual(
loaded[0].preparationMs + loaded[0].planningMs,
);
const report = benchmarkReport(measurements);
print(`\nRaw report (TSV):\n${report}\n`);
expect(report.split('\n')).toHaveLength(15);
expect(report).toContain('planning_ms\twall_ms');
expect(report).toContain('planning_work_ms\tpeak_main_thread_heap_bytes');
expect(report).toContain('\tbaseline\t');
const violations = budgetViolations(
measurements,
calibrationMs,
BENCHMARK_BUDGET_MULTIPLIERS,
);
if (violations.length)
throw new Error(`Benchmark over budget:\n${violations.join('\n')}`);
} finally {
vi.stubGlobal('window', browserWindow);
}
}, 570_000);
});