perf(planning): validate each optimization configuration once

Every objective evaluation revalidated its optimization configuration.
Rule normalization already validates each configuration once per
calculation and keeps its own copy, the presets are constant, and the
summed variant of a valid configuration stays valid, so the core now
scores them without revalidating. evaluateOptimization still validates
for other callers.

Part of #302.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nessarandClaude Opus 5.5 committed 2026-10-18 20:03:22 +02:00
1 parent b7ec411c92
commit 9d9404c3db
4 files changed
+43 -14

No files matched your search

+19
View File
@@ -89,3 +89,22 @@ largest gap stayed between 58 and 67 ms in every run. Cancelling `three years`
one second after the request returned the retained plan 3.9 to 4.0 ms after the one second after the request returned the retained plan 3.9 to 4.0 ms after the
cancel message reached the worker, against 7.2 to 8.7 ms before. This is desktop cancel message reached the worker, against 7.2 to 8.7 ms before. This is desktop
evidence, not a reference-phone measurement. evidence, not a reference-phone measurement.
### Validate each optimization configuration once
Every objective evaluation revalidated its optimization configuration against
the catalog. Rule normalization already validates each configuration once per
calculation and keeps its own copy. The presets are constant, and the summed
variant of a valid configuration is valid because every catalog row accepts
the sum. The core therefore scores them without revalidating;
`evaluateOptimization` still validates for its other callers.
| Scenario | Before (s) | After (s) |
| --------------------------- | ---------: | --------: |
| representative year | 0.35 | 0.30 |
| typed obligations | 4.03 | 3.46 |
| chained earned entitlements | 0.01 | 0.01 |
| deliberate conflict | 0.00 | 0.00 |
| loaded year | 48.12 | 44.03 |
| two years | 1.04 | 0.96 |
| three years | 3.35 | 3.03 |
+13 -12
View File
@@ -24,7 +24,7 @@ import { addPlanningYears, isPlanningDate, planningDateInParis } from './plannin
import { import {
compareObjectiveLists, compareObjectiveLists,
compareObjectiveValues, compareObjectiveValues,
evaluateOptimization, evaluateValidatedOptimization,
firstDifferentObjective, firstDifferentObjective,
OPTIMIZATION_PRESETS, OPTIMIZATION_PRESETS,
validateOptimizationConfiguration, validateOptimizationConfiguration,
@@ -1896,11 +1896,12 @@ function reconcileOptimizationProvenance(
// Another tied maximum can mask a placement's improvement to its own element. // Another tied maximum can mask a placement's improvement to its own element.
observations ??= optimizationObservations(state, context); observations ??= optimizationObservations(state, context);
const withoutObservations = optimizationObservations(withoutState, context); const withoutObservations = optimizationObservations(withoutState, context);
// Every catalog row accepts the sum, so this stays a valid configuration.
const summed = { ...rule.configuration, aggregation: 'sum' as const }; const summed = { ...rule.configuration, aggregation: 'sum' as const };
const summedComparison = compareObjectiveValues( const summedComparison = compareObjectiveValues(
objective.direction, objective.direction,
evaluateOptimization(summed, observations), evaluateValidatedOptimization(summed, observations),
evaluateOptimization(summed, withoutObservations), evaluateValidatedOptimization(summed, withoutObservations),
); );
contributes = contributes =
summedComparison < 0 || summedComparison < 0 ||
@@ -1930,14 +1931,14 @@ function supportingCalendarBenefit(
): boolean { ): boolean {
const observations = optimizationObservations(after, context); const observations = optimizationObservations(after, context);
return ( return (
evaluateOptimization(OPTIMIZATION_PRESETS.holidayWeekValue, observations) > evaluateValidatedOptimization(OPTIMIZATION_PRESETS.holidayWeekValue, observations) >
evaluateOptimization(OPTIMIZATION_PRESETS.holidayWeekValue, before) || evaluateValidatedOptimization(OPTIMIZATION_PRESETS.holidayWeekValue, before) ||
evaluateOptimization(OPTIMIZATION_PRESETS.nonWorkingDayGain, observations) > evaluateValidatedOptimization(OPTIMIZATION_PRESETS.nonWorkingDayGain, observations) >
evaluateOptimization(OPTIMIZATION_PRESETS.nonWorkingDayGain, before) || evaluateValidatedOptimization(OPTIMIZATION_PRESETS.nonWorkingDayGain, before) ||
evaluateOptimization(OPTIMIZATION_PRESETS.longestWorkSequence, observations) < evaluateValidatedOptimization(OPTIMIZATION_PRESETS.longestWorkSequence, observations) <
evaluateOptimization(OPTIMIZATION_PRESETS.longestWorkSequence, before) || evaluateValidatedOptimization(OPTIMIZATION_PRESETS.longestWorkSequence, before) ||
evaluateOptimization(OPTIMIZATION_PRESETS.shortSequenceDeficit, observations) < evaluateValidatedOptimization(OPTIMIZATION_PRESETS.shortSequenceDeficit, observations) <
evaluateOptimization(OPTIMIZATION_PRESETS.shortSequenceDeficit, before) evaluateValidatedOptimization(OPTIMIZATION_PRESETS.shortSequenceDeficit, before)
); );
} }
@@ -2654,7 +2655,7 @@ function* optimizeRulePlacements(
item.configuration.measure === 'public-holiday-week-value' && item.configuration.measure === 'public-holiday-week-value' &&
item.configuration.direction === 'maximize' item.configuration.direction === 'maximize'
) )
return evaluateOptimization(item.configuration, { return evaluateValidatedOptimization(item.configuration, {
leaveBalances: [], leaveBalances: [],
publicHolidayWeeks: context.holidayWeeks.map(({ value }) => ({ publicHolidayWeeks: context.holidayWeeks.map(({ value }) => ({
value, value,
+3 -2
View File
@@ -1,7 +1,7 @@
import { isWorkingPlanningDate } from './declared-non-working-days'; import { isWorkingPlanningDate } from './declared-non-working-days';
import type { TimeOffPeriod } from './leave-planner.core'; import type { TimeOffPeriod } from './leave-planner.core';
import { import {
evaluateOptimization, evaluateValidatedOptimization,
type OptimizationConfiguration, type OptimizationConfiguration,
type OptimizationObservations, type OptimizationObservations,
} from './planning-objectives'; } from './planning-objectives';
@@ -683,11 +683,12 @@ export function workSequenceDates(state: ObservedPlan, context: ObservationCalen
})); }));
} }
/** The configuration was validated when the calculation's rules were normalized. */
export function evaluateStateOptimization( export function evaluateStateOptimization(
configuration: OptimizationConfiguration, configuration: OptimizationConfiguration,
state: ObservedPlan, state: ObservedPlan,
context: ObservationCalendar, context: ObservationCalendar,
observations = optimizationObservations(state, context), observations = optimizationObservations(state, context),
): number { ): number {
return evaluateOptimization(configuration, observations); return evaluateValidatedOptimization(configuration, observations);
} }
+8
View File
@@ -212,6 +212,14 @@ export function evaluateOptimization(
): number { ): number {
const validation = validateOptimizationConfiguration(configuration); const validation = validateOptimizationConfiguration(configuration);
if (!validation.valid) throw new Error(`${validation.code}: ${validation.message}`); if (!validation.valid) throw new Error(`${validation.code}: ${validation.message}`);
return evaluateValidatedOptimization(configuration, observations);
}
/** Scores a configuration that {@link validateOptimizationConfiguration} already accepted. */
export function evaluateValidatedOptimization(
configuration: OptimizationConfiguration,
observations: OptimizationObservations,
): number {
const selected = (condition: OptimizationCondition) => const selected = (condition: OptimizationCondition) =>
configuration.conditions.includes(condition); configuration.conditions.includes(condition);
let values: number[]; let values: number[];