perf(planning): reuse immutable non-working date counts

This commit is contained in:
nessar committed 2026-10-16 21:19:58 +02:00
1 parent c7e7abcd24
commit 433a8b0ade
3 files changed
+95 -3

No files matched your search

@@ -75,6 +75,29 @@ describe('composable planning objectives', () => {
).toEqual([3, 7, 4, 6, 5, 2]);
});
it.each(['sum', 'count', 'maximum'] as const)(
'keeps %s distinct-date scores current with frozen and mutable period dates',
(aggregation) => {
const mutableDates = ['2024-05-08'];
const observations: OptimizationObservations = {
...empty,
timeOffPeriods: Object.freeze([
Object.freeze({
nonWorkingDates: Object.freeze(['2024-01-06', '2024-01-07', '2024-01-07']),
}),
Object.freeze({ nonWorkingDates: mutableDates }),
]),
};
const configuration = { ...OPTIMIZATION_PRESETS.nonWorkingDayGain, aggregation };
const initial = { sum: 3, count: 2, maximum: 2 };
expect(evaluateOptimization(configuration, observations)).toBe(initial[aggregation]);
expect(evaluateOptimization(configuration, observations)).toBe(initial[aggregation]);
mutableDates.push('2024-05-09', '2024-05-09', '2024-05-11');
const updated = { sum: 5, count: 2, maximum: 3 };
expect(evaluateOptimization(configuration, observations)).toBe(updated[aggregation]);
},
);
it.each(['sum', 'count', 'maximum'] as const)(
'defines empty %s as zero for every measure',
(aggregation) => {
+11 -3
View File
@@ -203,6 +203,9 @@ export interface OptimizationObservations {
leavePlacements: readonly { supporting: boolean }[];
}
// Calendar geometry reuses frozen date arrays across candidate plans.
const nonWorkingDateCounts = new WeakMap<readonly string[], number>();
export function evaluateOptimization(
configuration: OptimizationConfiguration,
observations: OptimizationObservations,
@@ -224,9 +227,14 @@ export function evaluateOptimization(
.map((week) => week.value);
break;
case 'time-off-periods':
values = observations.timeOffPeriods.map(
(period) => new Set(period.nonWorkingDates).size,
);
values = observations.timeOffPeriods.map(({ nonWorkingDates }) => {
const cached = nonWorkingDateCounts.get(nonWorkingDates);
if (cached !== undefined) return cached;
const count = new Set(nonWorkingDates).size;
if (Object.isFrozen(nonWorkingDates))
nonWorkingDateCounts.set(nonWorkingDates, count);
return count;
});
break;
case 'work-sequences':
values = observations.workSequences