feat(planning): explain and trace planning decisions

This commit is contained in:
nessar committed 2026-08-31 00:02:45 +02:00
1 parent 609b3dece9
commit d7a13b0b1e
24 files changed
+6073 -342

No files matched your search

+36 -8
View File
@@ -1,4 +1,9 @@
import { planLeave, PlanningOutcome, PlanningRequest } from '../services/leave-planner.service';
import {
createPlanningCalculation,
PlanningOutcome,
PlanningRequest,
PlanningTrace,
} from '../services/leave-planner.service';
export interface BenchmarkScenario {
name: string;
@@ -9,12 +14,15 @@ export interface BenchmarkMeasurement {
scenario: string;
repetition: number;
preparationMs: number;
calculationMs: number;
planningMs: number;
traceDeliveryMs: number;
wallMs: number;
outcome: PlanningOutcome['kind'];
lastCompletedStage?: string;
placements: number;
deterministic: boolean;
traceStatus: PlanningTrace['status'] | 'not-produced';
traceCandidates: number;
}
const balance = (
@@ -166,18 +174,35 @@ export async function runLeavePlannerBenchmark(repetitions = 1): Promise<Benchma
const request = structuredClone(scenario.request);
const preparationMs = performance.now() - preparationStart;
const calculationStart = performance.now();
const outcome = await planLeave(request);
const calculationMs = performance.now() - calculationStart;
const calculation = createPlanningCalculation(request);
const outcome = await calculation.outcome;
const planningMs = performance.now() - calculationStart;
const traceStart = performance.now();
const trace = await calculation.trace;
const traceDeliveryMs = performance.now() - traceStart;
const summary = resultSummary(outcome);
const stable = JSON.stringify(outcome);
measurements.push({
scenario: scenario.name,
repetition,
preparationMs,
calculationMs,
wallMs: preparationMs + calculationMs,
planningMs,
traceDeliveryMs,
wallMs: preparationMs + planningMs + traceDeliveryMs,
...summary,
deterministic: previous === undefined || previous === stable,
traceStatus: trace?.status ?? 'not-produced',
traceCandidates:
trace?.phases.reduce(
(total, phase) =>
total +
phase.decisions.reduce(
(decisionTotal, decision) =>
decisionTotal + decision.candidates.length,
0,
),
0,
) ?? 0,
});
previous = stable;
}
@@ -187,7 +212,7 @@ export async function runLeavePlannerBenchmark(repetitions = 1): Promise<Benchma
export function benchmarkReport(measurements: BenchmarkMeasurement[]): string {
const header =
'scenario\trepetition\tpreparation_ms\tcalculation_ms\twall_ms\toutcome\tlast_stage\tplacements\tdeterministic';
'scenario\trepetition\tpreparation_ms\tplanning_ms\ttrace_delivery_ms\twall_ms\toutcome\tlast_stage\tplacements\tdeterministic\ttrace_status\ttrace_candidates';
return [
header,
...measurements.map((item) =>
@@ -195,12 +220,15 @@ export function benchmarkReport(measurements: BenchmarkMeasurement[]): string {
item.scenario,
item.repetition,
item.preparationMs.toFixed(1),
item.calculationMs.toFixed(1),
item.planningMs.toFixed(1),
item.traceDeliveryMs.toFixed(1),
item.wallMs.toFixed(1),
item.outcome,
item.lastCompletedStage ?? '',
item.placements,
item.deterministic,
item.traceStatus,
item.traceCandidates,
].join('\t'),
),
].join('\n');