perf(planning): restore the integrated benchmark gate
CI / policy (push) Successful in 7s
CI / validate (push) Successful in 1m23s
CI / test (push) Successful in 3m17s
CI / benchmark (push) Failing after 5m52s
CI / build (push) Successful in 1m31s
CI / security (push) Successful in 1m39s
CI / images (push) Skipped
CI / policy (pull_request) Successful in 7s
CI / validate (pull_request) Successful in 1m11s
CI / test (pull_request) Successful in 3m17s
CI / benchmark (pull_request) Failing after 5m57s
CI / build (pull_request) Successful in 1m30s
CI / security (pull_request) Successful in 1m40s
CI / images (pull_request) Skipped
CI / required (push) Failing after 2s
CI / required (pull_request) Failing after 1s

Reduce objective evaluation and calendar allocation work while preserving planner outcomes and deterministic explanations. Restore required benchmark gating and record the unchanged protocol, differential checks, and browser evidence for #78.
This commit is contained in:
nessar authored and Nessar committed 2026-10-16 14:01:32 +02:00
1 parent 0852cd4444
commit cbde23f572
9 files changed
+594 -118

No files matched your search

+138 -76
View File
@@ -501,9 +501,8 @@ const occurrenceCache = new WeakMap<SearchContext, Map<string, string[]>>();
const calendarPatternCache = new WeakMap<SearchContext, Map<string, string[][]>>();
const preferencePatternCache = new WeakMap<SearchContext, Map<string, string[][][]>>();
const declaredWindowCache = new WeakMap<SearchContext, Map<string, DeclaredNonWorkingDay[]>>();
type CalendarOptimizationObservations = Pick<
OptimizationObservations,
'publicHolidayWeeks' | 'timeOffPeriods' | 'workSequences'
type CalendarOptimizationObservations = Partial<
Pick<OptimizationObservations, 'publicHolidayWeeks' | 'timeOffPeriods' | 'workSequences'>
>;
const calendarObservationCache = new WeakMap<
SearchContext,
@@ -720,18 +719,20 @@ function reason(code: string, message: string, ruleIds?: string[]): PlanningReas
return ruleIds?.length ? { code, message, ruleIds } : { code, message };
}
function cloneState(state: State): State {
function cloneState(state: State, placements?: State['placements']): State {
return {
balances: state.balances.map((balance) => ({ ...balance })),
placements: new Map(
[...state.placements.entries()].map(([date, placement]) => [
date,
{
...placement,
ruleIds: new Set(placement.ruleIds),
},
]),
),
placements:
placements ??
new Map(
[...state.placements.entries()].map(([date, placement]) => [
date,
{
...placement,
ruleIds: new Set(placement.ruleIds),
},
]),
),
earnedActivations: state.earnedActivations.map((activation) => ({
...activation,
dates: [...activation.dates],
@@ -1513,7 +1514,8 @@ function assignDateToBalance(
) {
return null;
}
const result = cloneState(state);
// Existing placements stay read-only; this assignment owns only its new placement.
const result = cloneState(state, new Map(state.placements));
const target = result.balances.find((item) => item.id === balanceId)!;
target.remaining--;
result.placements.set(date, {
@@ -2105,8 +2107,10 @@ function finalizeRuleCandidate(state: State, context: SearchContext, before: Sta
const patternCredits = new Map<string, Set<string>>();
for (const placement of state.placements.values()) {
let lostCalendarRequest = false;
let hasCalendarRequest = false;
for (const id of placement.ruleIds) {
const rule = context.rules.find((item) => item.id === id);
if (rule?.type === 'optimization') continue;
const valid = counterfactualRuleValid(
placement.date,
placement.type,
@@ -2135,18 +2139,16 @@ function finalizeRuleCandidate(state: State, context: SearchContext, before: Sta
withinRequest = patternCredits.get(id)!.has(placement.date);
}
if (valid && withinRequest) {
hasCalendarRequest = true;
if (rule?.type === 'time_window') windowCredits.set(id, credited + 1);
continue;
}
if (!valid && rule?.force === 'obligation') return null;
if (result === state) result = cloneState(state);
result.placements.get(placement.date)!.ruleIds.delete(id);
lostCalendarRequest ||= rule?.type !== 'optimization';
lostCalendarRequest = true;
}
const current = result.placements.get(placement.date)!;
const hasCalendarRequest = [...current.ruleIds].some((id) =>
context.rules.some((rule) => rule.id === id && rule.type !== 'optimization'),
);
const previous = before.placements.get(placement.date);
let supporting = current.supporting;
if (hasCalendarRequest) supporting = false;
@@ -2272,7 +2274,8 @@ function* calendarPlacementCandidates(
if (index === missing.length) {
const benefit = supportingCalendarBenefit(before, current, context);
for (const date of additions) {
const placement = current.placements.get(date)!;
const placement = { ...current.placements.get(date)! };
current.placements.set(date, placement);
const balance = current.balances.find((item) => item.id === placement.balanceId)!;
placement.supporting = benefit && !isAtRisk(balance, context);
}
@@ -2367,18 +2370,40 @@ function evaluateRuleCandidate(
addition = false,
baseline = activeObjectiveVector(before, context, throughIndex),
incumbentVector = objectiveVector(before, context),
): { state: State; vector: PlanningObjectiveVector } | undefined {
): { state: State; vector?: PlanningObjectiveVector } | undefined {
if (!candidate) return undefined;
const state = finalizeRuleCandidate(candidate, context, before);
if (!state) return undefined;
const active = activeObjectiveVector(state, context);
const comparison = compareObjectiveLists(active.slice(0, throughIndex + 1), baseline);
const active: PlanningObjective[] = [];
let comparison = 0;
let incumbentComparison = 0;
for (const objective of activeObjectives(state, context)) {
const index = active.length;
active.push(objective);
if (index <= throughIndex && comparison === 0) {
comparison = compareObjectiveValues(
objective.direction,
objective.value,
baseline[index].value,
);
// A worse protected prefix cannot improve the jointly valid incumbent.
if (comparison > 0) return undefined;
}
if (incumbentComparison === 0)
incumbentComparison = compareObjectiveValues(
objective.direction,
objective.value,
incumbentVector.objectives[index + 1].value,
);
if (incumbentComparison > 0 && (comparison < 0 || index >= throughIndex))
return addition && comparison === 0 ? undefined : { state };
}
const vector = objectiveVector(state, context, active);
// Preserve the best valid candidate even if the active prefix rejects it.
if (compareObjectiveLists(vector.objectives, incumbentVector.objectives) < 0)
context.retain?.(state, undefined, vector);
// shortcut: single additions cannot cross equal-value plateaus; add coordinated balance bundles when a measured configuration needs them.
if (comparison > 0 || (addition && comparison === 0)) return undefined;
if (addition && comparison === 0) return undefined;
return { state, vector };
}
@@ -2622,6 +2647,7 @@ function* optimizeRulePlacements(
bestVector,
);
if (!evaluated) return false;
if (!evaluated.vector) return true;
const compared = compareObjectiveLists(
evaluated.vector.objectives,
bestVector.objectives,
@@ -2842,8 +2868,14 @@ function* optimizeRulePlacements(
(candidate) => {
if (rule?.type === 'optimization' && !isAtRisk(balance, context)) {
beforeCalendar ??= optimizationObservations(result, context);
candidate.placements.get(date)!.supporting =
supportingCalendarBenefit(beforeCalendar, candidate, context);
candidate.placements.set(date, {
...candidate.placements.get(date)!,
supporting: supportingCalendarBenefit(
beforeCalendar,
candidate,
context,
),
});
}
return consider(candidate, true);
},
@@ -3306,46 +3338,49 @@ function activeObjectiveVector(
context: SearchContext,
throughIndex = Number.POSITIVE_INFINITY,
): PlanningObjective[] {
const preferences = preferenceResultsForState(state, context);
let observations: OptimizationObservations | undefined;
return context.rules
.filter((rule) => rule.enabled && rule.force === 'preference')
.slice(0, throughIndex + 1)
.map((rule) => {
if (rule.type === 'optimization') {
observations ??= context.rules.some(
(item) =>
item.type === 'optimization' &&
item.configuration.elements !== 'leave-balances',
)
? optimizationObservations(state, context)
: {
return [...activeObjectives(state, context, throughIndex)];
}
function* activeObjectives(
state: State,
context: SearchContext,
throughIndex = Number.POSITIVE_INFINITY,
): Generator<PlanningObjective> {
let preferences: PreferenceResult[] | undefined;
let balances: OptimizationObservations | undefined;
let calendar: OptimizationObservations | undefined;
let index = 0;
for (const rule of context.rules) {
if (!rule.enabled || rule.force !== 'preference') continue;
if (index++ > throughIndex) return;
if (rule.type === 'optimization') {
const observations =
rule.configuration.elements === 'leave-balances'
? (balances ??= {
leaveBalances: optimizationBalanceObservations(state, context),
publicHolidayWeeks: [],
timeOffPeriods: [],
workSequences: [],
leavePlacements: [],
};
return {
id: `optimization:${rule.id}` as const,
direction: rule.configuration!.direction,
value: evaluateStateOptimization(
rule.configuration!,
state,
context,
observations,
),
label: rule.name,
};
}
return {
id: `preference:${rule.id}` as const,
direction: 'maximize' as const,
})
: (calendar ??= optimizationObservations(state, context));
yield {
id: `optimization:${rule.id}`,
direction: rule.configuration.direction,
value: evaluateStateOptimization(rule.configuration, state, context, observations),
label: rule.name,
};
} else {
preferences ??= preferenceResultsForState(state, context);
yield {
id: `preference:${rule.id}`,
direction: 'maximize',
value:
preferences.find((preference) => preference.ruleId === rule.id)?.satisfied ?? 0,
label: rule.name,
};
});
}
}
}
function tieBreakObjectives(state: State): PlanningObjective[] {
@@ -3513,11 +3548,15 @@ function optimizationObservations(
byDates = new Map();
calendarObservationCache.set(context, byDates);
}
let calendar = byDates.get(key);
if (calendar) byDates.delete(key);
else {
calendar = Object.freeze({
publicHolidayWeeks: Object.freeze(
const calendar = byDates.get(key) ?? {};
if (byDates.has(key)) byDates.delete(key);
else if (byDates.size >= 4096) byDates.delete(byDates.keys().next().value!);
byDates.set(key, calendar);
let bounds: ReturnType<typeof periodBounds> | undefined;
return {
leaveBalances: optimizationBalanceObservations(state, context),
get publicHolidayWeeks() {
return (calendar.publicHolidayWeeks ??= Object.freeze(
context.holidayWeeks.map((week) =>
Object.freeze({
value: week.value,
@@ -3526,37 +3565,60 @@ function optimizationObservations(
),
}),
),
),
timeOffPeriods: Object.freeze(
));
},
get timeOffPeriods() {
return (calendar.timeOffPeriods ??= Object.freeze(
periods
? periods.map((period) =>
Object.freeze({
nonWorkingDates: Object.freeze([...period.nonWorkingDates]),
}),
)
: periodBounds(state, context).map(({ startIndex, endIndex }) =>
: (bounds ??= periodBounds(state, context)).map(({ startIndex, endIndex }) =>
Object.freeze({
nonWorkingDates: calendarPeriod(startIndex, endIndex, context)
.nonWorkingDates,
}),
),
),
workSequences: Object.freeze(
workSequenceDates(state, context).map(({ dates, internal }) =>
Object.freeze({ length: dates.length, internal }),
));
},
get workSequences() {
return (calendar.workSequences ??= Object.freeze(
workSequenceLengths((bounds ??= periodBounds(state, context)), context).map(
(sequence) => Object.freeze(sequence),
),
),
});
if (byDates.size >= 4096) byDates.delete(byDates.keys().next().value!);
}
byDates.set(key, calendar);
return {
leaveBalances: optimizationBalanceObservations(state, context),
...calendar,
));
},
leavePlacements: [...state.placements.values()],
};
}
function workSequenceLengths(
bounds: ReturnType<typeof periodBounds>,
context: SearchContext,
): OptimizationObservations['workSequences'] {
if (!context.workingDays.length) return [];
let nextIndex = context.calendarIndex.get(context.horizon.start)!;
const sequences = bounds.map(({ anchors }, index) => {
const previousIndex = nextIndex;
nextIndex = context.calendarIndex.get(anchors[anchors.length - 1])! + 1;
return {
length:
context.workingPrefix[context.calendarIndex.get(anchors[0])!] -
context.workingPrefix[previousIndex],
internal: index > 0,
};
});
sequences.push({
length:
context.workingPrefix[context.calendarIndex.get(context.horizon.end)!] -
context.workingPrefix[nextIndex],
internal: false,
});
return sequences;
}
function workSequenceDates(state: State, context: SearchContext) {
const sequences: string[][] = [];
let current: string[] = [];