refactor(planning): remove the original decision trace
CI / policy (push) Successful in 7s
CI / validate (push) Successful in 1m15s
CI / test (push) Successful in 3m18s
CI / benchmark (push) Failing after 5m53s
CI / build (push) Successful in 1m23s
CI / security (push) Successful in 7m6s
CI / policy (pull_request) Successful in 7s
CI / validate (pull_request) Successful in 1m9s
CI / build (pull_request) Successful in 43s
CI / test (pull_request) Successful in 3m19s
CI / images (push) Successful in 3m7s
CI / required (push) Successful in 2s
CI / benchmark (pull_request) Failing after 5m28s
CI / security (pull_request) Successful in 6m37s
CI / images (pull_request) Successful in 3m18s
CI / required (pull_request) Successful in 1s
CI / policy (push) Successful in 7s
CI / validate (push) Successful in 1m15s
CI / test (push) Successful in 3m18s
CI / benchmark (push) Failing after 5m53s
CI / build (push) Successful in 1m23s
CI / security (push) Successful in 7m6s
CI / policy (pull_request) Successful in 7s
CI / validate (pull_request) Successful in 1m9s
CI / build (pull_request) Successful in 43s
CI / test (pull_request) Successful in 3m19s
CI / images (push) Successful in 3m7s
CI / required (push) Successful in 2s
CI / benchmark (pull_request) Failing after 5m28s
CI / security (pull_request) Successful in 6m37s
CI / images (pull_request) Successful in 3m18s
CI / required (pull_request) Successful in 1s
This commit is contained in:
1 parent
ea4256107f
commit
d9d9befa12
43 files changed
+729
-8880
No files matched your search
@@ -7,18 +7,13 @@ import type {
|
||||
PlanningExplanation,
|
||||
PlanningOutcome,
|
||||
PlanningReason,
|
||||
PlanningTrace,
|
||||
PlanningTraceUpdate,
|
||||
PlanningSnapshot,
|
||||
PlanningTraceState,
|
||||
TraceCandidateStatus,
|
||||
PlanningStateSnapshot,
|
||||
} from './leave-planner.service';
|
||||
import {
|
||||
analyzeCounterfactualInternal,
|
||||
calculatePlanningAsyncInternal,
|
||||
explainIncompleteCheckpoint,
|
||||
historicalCounterfactualReasons,
|
||||
type CounterfactualBaselineOptions,
|
||||
type PlanningArtifacts,
|
||||
} from './leave-planner.core';
|
||||
|
||||
@@ -39,271 +34,52 @@ type WorkerRequest =
|
||||
type: 'plan';
|
||||
request: WorkerPlanningRequest;
|
||||
}
|
||||
| { type: 'trace-ack'; count: number }
|
||||
| { type: 'cancel' }
|
||||
| {
|
||||
type: 'counterfactual';
|
||||
request: PlanningSnapshot;
|
||||
plan: PlanOutcome;
|
||||
placement: LeavePlacement;
|
||||
state?: PlanningTraceState;
|
||||
options?: CounterfactualBaselineOptions;
|
||||
}
|
||||
| {
|
||||
type: 'historical-eligibility';
|
||||
request: PlanningSnapshot;
|
||||
placement: LeavePlacement;
|
||||
state: PlanningTraceState;
|
||||
options?: CounterfactualBaselineOptions;
|
||||
state?: PlanningStateSnapshot;
|
||||
}
|
||||
| {
|
||||
type: 'explain-checkpoint';
|
||||
request: PlanningSnapshot;
|
||||
plan: PlanOutcome;
|
||||
state: PlanningTraceState;
|
||||
state: PlanningStateSnapshot;
|
||||
interruption: PlanningReason;
|
||||
};
|
||||
|
||||
type WorkerResponse =
|
||||
| { type: 'heartbeat' }
|
||||
| { type: 'progress'; stage: string }
|
||||
| { type: 'trace-overhead'; totalMs: number }
|
||||
| { type: 'checkpoint'; plan: PlanOutcome; state?: PlanningTraceState }
|
||||
| { type: 'checkpoint'; plan: PlanOutcome; state?: PlanningStateSnapshot }
|
||||
| { type: 'result'; outcome: PlanningOutcome }
|
||||
| { type: 'artifacts'; artifacts: PlanningArtifacts }
|
||||
| {
|
||||
type: 'explanation';
|
||||
explanation?: PlanningExplanation;
|
||||
explanationError?: PlanningReason;
|
||||
finalState?: PlanningTraceState;
|
||||
finalState?: PlanningStateSnapshot;
|
||||
}
|
||||
| {
|
||||
type: 'artifacts-start';
|
||||
artifacts: Omit<
|
||||
PlanningArtifacts,
|
||||
'trace' | 'explanation' | 'explanationError' | 'finalState'
|
||||
>;
|
||||
trace?: Omit<PlanningTrace, 'phases'>;
|
||||
}
|
||||
| {
|
||||
type: 'trace-update';
|
||||
totalMs: number;
|
||||
update: PlanningTraceUpdate;
|
||||
acknowledge?: true;
|
||||
}
|
||||
| {
|
||||
type: 'trace-status-index';
|
||||
statuses: ReadonlyMap<string, TraceCandidateStatus>;
|
||||
nonWorkingDates: readonly string[];
|
||||
}
|
||||
| { type: 'artifacts-complete'; traceTransferMs: number }
|
||||
| { type: 'counterfactual-result'; analysis: CounterfactualAnalysis }
|
||||
| {
|
||||
type: 'historical-eligibility-result';
|
||||
entries: ReturnType<typeof historicalCounterfactualReasons>;
|
||||
}
|
||||
| { type: 'error'; code?: string; message: string };
|
||||
|
||||
const worker = self as DedicatedWorkerGlobalScope;
|
||||
const TRACE_OPERATIONS_PER_MESSAGE = 64;
|
||||
const MAX_TRACE_PACKETS_IN_FLIGHT = 16;
|
||||
|
||||
interface StagedTraceUpdate {
|
||||
totalMs: number;
|
||||
update?: PlanningTraceUpdate;
|
||||
nextOperation: number;
|
||||
}
|
||||
|
||||
interface TraceDelivery {
|
||||
updates: StagedTraceUpdate[];
|
||||
nextUpdate: number;
|
||||
inFlight: number;
|
||||
traceTransferMs: number;
|
||||
artifacts?: PlanningArtifacts;
|
||||
calculationComplete: boolean;
|
||||
}
|
||||
|
||||
let activeTraceDelivery: TraceDelivery | undefined;
|
||||
let activeCalculation: { cancelled: boolean } | undefined;
|
||||
|
||||
function traceStatusIndex(trace: PlanningTrace): {
|
||||
statuses: Map<string, TraceCandidateStatus>;
|
||||
nonWorkingDates: string[];
|
||||
} {
|
||||
const priority: Record<TraceCandidateStatus, number> = {
|
||||
ineligible: 0,
|
||||
'not-examined': 1,
|
||||
pruned: 2,
|
||||
'evaluated-rejected': 3,
|
||||
chosen: 4,
|
||||
};
|
||||
const statuses = new Map<string, TraceCandidateStatus>();
|
||||
const nonWorkingDates = new Set(trace.nonWorkingDates?.map(({ date }) => date) ?? []);
|
||||
for (const phase of trace.phases) {
|
||||
for (const decision of phase.decisions) {
|
||||
for (const candidate of decision.candidates) {
|
||||
const dates = candidate.dates ?? (candidate.date ? [candidate.date] : []);
|
||||
for (const date of dates) {
|
||||
const previous = statuses.get(date);
|
||||
if (!previous || priority[candidate.status] > priority[previous])
|
||||
statuses.set(date, candidate.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { statuses, nonWorkingDates: [...nonWorkingDates] };
|
||||
}
|
||||
|
||||
function sendArtifacts(artifacts: PlanningArtifacts, deferredTraceTransferMs = 0): void {
|
||||
const trace = artifacts.trace;
|
||||
let traceMaterializationMs = artifacts.timings?.traceMaterializationMs ?? 0;
|
||||
let traceTransferMs = (artifacts.timings?.traceTransferMs ?? 0) + deferredTraceTransferMs;
|
||||
if (trace) {
|
||||
const materializationStarted = performance.now();
|
||||
const statusIndex = traceStatusIndex(trace);
|
||||
traceMaterializationMs += Math.max(0, performance.now() - materializationStarted);
|
||||
const transferStarted = performance.now();
|
||||
worker.postMessage({
|
||||
type: 'trace-status-index',
|
||||
statuses: statusIndex.statuses,
|
||||
nonWorkingDates: statusIndex.nonWorkingDates,
|
||||
} satisfies WorkerResponse);
|
||||
traceTransferMs += Math.max(0, performance.now() - transferStarted);
|
||||
}
|
||||
const rest: Omit<
|
||||
PlanningArtifacts,
|
||||
'trace' | 'explanation' | 'explanationError' | 'finalState'
|
||||
> = artifacts.timings
|
||||
? {
|
||||
timings: {
|
||||
...artifacts.timings,
|
||||
traceMaterializationMs,
|
||||
traceTransferMs,
|
||||
},
|
||||
}
|
||||
: {};
|
||||
const transferStarted = performance.now();
|
||||
worker.postMessage({
|
||||
type: 'artifacts-start',
|
||||
artifacts: rest,
|
||||
trace: trace
|
||||
? {
|
||||
status: trace.status,
|
||||
initialState: trace.initialState,
|
||||
completedStages: trace.completedStages,
|
||||
nonWorkingDates: trace.nonWorkingDates,
|
||||
error: trace.error,
|
||||
}
|
||||
: undefined,
|
||||
} satisfies WorkerResponse);
|
||||
traceTransferMs += Math.max(0, performance.now() - transferStarted);
|
||||
// Capture updates already carried every candidate in bounded operations.
|
||||
// The final packet closes the trace without cloning it a second time.
|
||||
worker.postMessage({ type: 'artifacts-complete', traceTransferMs } satisfies WorkerResponse);
|
||||
}
|
||||
|
||||
function finishTraceDelivery(delivery: TraceDelivery): void {
|
||||
if (
|
||||
!delivery.calculationComplete ||
|
||||
delivery.nextUpdate < delivery.updates.length ||
|
||||
delivery.inFlight > 0 ||
|
||||
!delivery.artifacts
|
||||
)
|
||||
return;
|
||||
if (activeTraceDelivery === delivery) activeTraceDelivery = undefined;
|
||||
try {
|
||||
sendArtifacts(delivery.artifacts, delivery.traceTransferMs);
|
||||
} catch (error) {
|
||||
worker.postMessage({
|
||||
type: 'error',
|
||||
code: 'trace-error',
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
} satisfies WorkerResponse);
|
||||
}
|
||||
delivery.updates = [];
|
||||
}
|
||||
|
||||
function pumpTraceUpdates(delivery: TraceDelivery): void {
|
||||
while (
|
||||
delivery.inFlight < MAX_TRACE_PACKETS_IN_FLIGHT &&
|
||||
delivery.nextUpdate < delivery.updates.length
|
||||
) {
|
||||
const staged = delivery.updates[delivery.nextUpdate];
|
||||
const update = staged.update;
|
||||
if (!update) {
|
||||
delivery.nextUpdate++;
|
||||
continue;
|
||||
}
|
||||
const start = staged.nextOperation;
|
||||
const end = Math.min(start + TRACE_OPERATIONS_PER_MESSAGE, update.operations.length);
|
||||
const firstPacket = start === 0;
|
||||
const packet: WorkerResponse = {
|
||||
type: 'trace-update',
|
||||
totalMs: staged.totalMs,
|
||||
acknowledge: true,
|
||||
update: {
|
||||
initialState: firstPacket ? update.initialState : undefined,
|
||||
nonWorkingDates: firstPacket ? update.nonWorkingDates : undefined,
|
||||
operations: update.operations.slice(start, end),
|
||||
},
|
||||
};
|
||||
const transferStarted = performance.now();
|
||||
try {
|
||||
worker.postMessage(packet satisfies WorkerResponse);
|
||||
} catch (error) {
|
||||
if (activeTraceDelivery === delivery) activeTraceDelivery = undefined;
|
||||
delivery.updates = [];
|
||||
worker.postMessage({
|
||||
type: 'error',
|
||||
code: 'trace-error',
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
} satisfies WorkerResponse);
|
||||
return;
|
||||
}
|
||||
delivery.traceTransferMs += Math.max(0, performance.now() - transferStarted);
|
||||
delivery.inFlight++;
|
||||
staged.nextOperation = end;
|
||||
if (update.operations.length === 0 || end >= update.operations.length) {
|
||||
// Drop the full drain array as soon as its last bounded packet is posted.
|
||||
staged.update = undefined;
|
||||
delivery.nextUpdate++;
|
||||
}
|
||||
}
|
||||
if (delivery.nextUpdate === delivery.updates.length) {
|
||||
// All queued operation arrays are released; only in-flight structured clones remain.
|
||||
delivery.updates = [];
|
||||
delivery.nextUpdate = 0;
|
||||
}
|
||||
finishTraceDelivery(delivery);
|
||||
}
|
||||
|
||||
worker.onmessage = async (event: MessageEvent<WorkerRequest>) => {
|
||||
try {
|
||||
if (event.data?.type === 'cancel') {
|
||||
if (activeCalculation) activeCalculation.cancelled = true;
|
||||
return;
|
||||
}
|
||||
if (event.data?.type === 'trace-ack') {
|
||||
const delivery = activeTraceDelivery;
|
||||
if (!delivery || !Number.isInteger(event.data.count) || event.data.count <= 0) return;
|
||||
delivery.inFlight = Math.max(0, delivery.inFlight - event.data.count);
|
||||
pumpTraceUpdates(delivery);
|
||||
return;
|
||||
}
|
||||
if (event.data?.type === 'plan') {
|
||||
const calculation = { cancelled: false };
|
||||
activeCalculation = calculation;
|
||||
let artifacts: PlanningArtifacts = {};
|
||||
let outcomePosted = false;
|
||||
let outcome: PlanningOutcome;
|
||||
const traceDelivery: TraceDelivery = {
|
||||
updates: [],
|
||||
nextUpdate: 0,
|
||||
inFlight: 0,
|
||||
traceTransferMs: 0,
|
||||
calculationComplete: false,
|
||||
};
|
||||
activeTraceDelivery = traceDelivery;
|
||||
// The cooperative driver yields during preparation and search, even within one phase.
|
||||
const heartbeat = () =>
|
||||
worker.postMessage({ type: 'heartbeat' } satisfies WorkerResponse);
|
||||
@@ -318,13 +94,6 @@ worker.onmessage = async (event: MessageEvent<WorkerRequest>) => {
|
||||
type: 'progress',
|
||||
stage,
|
||||
} satisfies WorkerResponse),
|
||||
traceProgress: (totalMs: number, update: PlanningTraceUpdate) => {
|
||||
worker.postMessage({
|
||||
type: 'trace-overhead',
|
||||
totalMs,
|
||||
} satisfies WorkerResponse);
|
||||
traceDelivery.updates.push({ totalMs, update, nextOperation: 0 });
|
||||
},
|
||||
},
|
||||
(plan, state) =>
|
||||
worker.postMessage({
|
||||
@@ -350,8 +119,6 @@ worker.onmessage = async (event: MessageEvent<WorkerRequest>) => {
|
||||
() => calculation.cancelled,
|
||||
);
|
||||
} catch (error) {
|
||||
if (activeTraceDelivery === traceDelivery) activeTraceDelivery = undefined;
|
||||
traceDelivery.updates = [];
|
||||
worker.postMessage({
|
||||
type: 'error',
|
||||
code: 'planner-error',
|
||||
@@ -364,9 +131,7 @@ worker.onmessage = async (event: MessageEvent<WorkerRequest>) => {
|
||||
}
|
||||
if (!outcomePosted)
|
||||
worker.postMessage({ type: 'result', outcome } satisfies WorkerResponse);
|
||||
traceDelivery.artifacts = artifacts;
|
||||
traceDelivery.calculationComplete = true;
|
||||
pumpTraceUpdates(traceDelivery);
|
||||
worker.postMessage({ type: 'artifacts', artifacts } satisfies WorkerResponse);
|
||||
return;
|
||||
}
|
||||
if (event.data?.type === 'explain-checkpoint') {
|
||||
@@ -379,44 +144,14 @@ worker.onmessage = async (event: MessageEvent<WorkerRequest>) => {
|
||||
worker.postMessage({ type: 'explanation', ...result } satisfies WorkerResponse);
|
||||
return;
|
||||
}
|
||||
if (event.data?.type === 'historical-eligibility') {
|
||||
worker.postMessage({
|
||||
type: 'historical-eligibility-result',
|
||||
entries: historicalCounterfactualReasons(
|
||||
event.data.request,
|
||||
event.data.state,
|
||||
event.data.placement,
|
||||
event.data.options,
|
||||
),
|
||||
} satisfies WorkerResponse);
|
||||
return;
|
||||
}
|
||||
if (event.data?.type !== 'counterfactual') return;
|
||||
try {
|
||||
let analysis = analyzeCounterfactualInternal(
|
||||
const analysis = analyzeCounterfactualInternal(
|
||||
event.data.request,
|
||||
event.data.plan,
|
||||
event.data.placement,
|
||||
event.data.state,
|
||||
event.data.options,
|
||||
);
|
||||
if (event.data.options && event.data.state) {
|
||||
const historicalReasons = new Map(
|
||||
historicalCounterfactualReasons(
|
||||
event.data.request,
|
||||
event.data.state,
|
||||
event.data.placement,
|
||||
event.data.options,
|
||||
).map(({ date, reason }) => [date, reason]),
|
||||
);
|
||||
analysis = {
|
||||
...analysis,
|
||||
results: analysis.results.map((result) => {
|
||||
const historicalReason = historicalReasons.get(result.date);
|
||||
return historicalReason ? { ...result, historicalReason } : result;
|
||||
}),
|
||||
};
|
||||
}
|
||||
worker.postMessage({
|
||||
type: 'counterfactual-result',
|
||||
analysis,
|
||||
|
||||
Reference in new issue
Block a user