Files
vac-optimizer/src/app/services/leave-planner-supporting-placements.spec.ts
T
nessarandClaude Opus 5.5 8168137cd5 perf(planning): plan from the best-ranked obligation solution
With several obligation solutions, the planner ran the whole preference
chain on candidate solutions as previews: every solution after a
complete enumeration, or the eight best-ranked after a truncated window
search. It now orders all solutions by the existing pre-preference
ranking (active objectives, then tie-breaks, of each obligation-only
plan; equal ranks keep enumeration order) and runs the preference
stages once, on the first. If that run ends with earned leave available
only after its final merged time-off period, the stages restart on the
next solution, so a request that planned before never becomes a
conflict.

The loaded year drops from 43 to 5.5 seconds and typed obligations from
3.3 to 0.1 seconds, with byte-identical benchmark plans. Over the 5,000
seeded requests, 23 plans become worse (9 on a real objective) and 1
better; over 50 loaded-year-sized requests, 4 become worse on a real
objective. ADR 0010 records the decision; docs/benchmark/issue-302.md
records the comparison and the opt-in runner that reproduces it.

The frozen-engine check fails until the frozen engine is rebuilt from
this commit in the next one.

Part of #302.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 21:18:06 +02:00

738 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { vi } from '../../testing/vitest-compat';
import {
calculatePlanningAsyncInternal,
calculatePlanningInternal,
type OptimizationPlanningRule,
type PlanOutcome,
type PlanningArtifacts,
type PlanningRequest,
} from './leave-planner.core';
import { OPTIMIZATION_PRESETS } from './planning-objectives';
import { createPlanningCalculation } from './leave-planner.service';
const economy: OptimizationPlanningRule = {
id: 'economy',
name: 'Économiser les jours d’appoint',
type: 'optimization',
configuration: OPTIMIZATION_PRESETS.supportingPlacements,
};
const gain: OptimizationPlanningRule = {
id: 'gain',
type: 'optimization',
configuration: OPTIMIZATION_PRESETS.nonWorkingDayGain,
};
const requested: PlanningRequest['rules'][number] = {
id: 'requested',
type: 'time_window',
startDate: '2024-01-05',
endDate: '2024-01-05',
placeDays: 1,
placeType: 'cp',
};
const weekend: PlanningRequest = {
horizon: { start: '2024-01-05', end: '2024-01-10' },
balances: [{ id: 'cp', type: 'cp', quantity: 1, expiryDate: '2025-01-01' }],
rules: [],
};
const bundles: {
name: string;
rule: OptimizationPlanningRule;
request: PlanningRequest;
dates: string[];
}[] = [
{
name: 'complete public-holiday week',
rule: {
id: 'holiday',
type: 'optimization',
configuration: OPTIMIZATION_PRESETS.holidayWeekValue,
},
request: {
horizon: { start: '2026-11-09', end: '2026-11-14' },
balances: [{ id: 'cp', type: 'cp', quantity: 2, expiryDate: '2027-01-01' }],
declaredDays: ['2026-11-09', '2026-11-10'].map((date) => ({ date, type: 'other' })),
rules: [],
},
dates: ['2026-11-12', '2026-11-13'],
},
{
name: 'non-working-day gain',
rule: gain,
request: weekend,
dates: ['2024-01-05'],
},
{
name: 'maximum non-working gain of a joined period',
rule: {
...gain,
configuration: { ...gain.configuration, aggregation: 'maximum' },
},
request: {
horizon: { start: '2024-01-05', end: '2024-01-13' },
balances: [{ id: 'cp', type: 'cp', quantity: 4, expiryDate: '2025-01-01' }],
declaredDays: ['2024-01-05', '2024-01-12'].map((date) => ({ date, type: 'other' })),
rules: [],
},
dates: ['2024-01-08', '2024-01-09', '2024-01-10', '2024-01-11'],
},
{
name: 'complete short internal work sequence',
rule: {
id: 'deficit',
type: 'optimization',
configuration: OPTIMIZATION_PRESETS.shortSequenceDeficit,
},
request: {
horizon: { start: '2024-01-08', end: '2024-01-13' },
balances: [{ id: 'cp', type: 'cp', quantity: 3, expiryDate: '2024-02-01' }],
declaredDays: ['2024-01-08', '2024-01-12'].map((date) => ({ date, type: 'other' })),
rules: [],
},
dates: ['2024-01-09', '2024-01-10', '2024-01-11'],
},
{
name: 'tied longest work sequences',
rule: {
id: 'longest',
type: 'optimization',
configuration: OPTIMIZATION_PRESETS.longestWorkSequence,
},
request: {
horizon: { start: '2024-01-08', end: '2024-01-13' },
balances: [{ id: 'cp', type: 'cp', quantity: 2, expiryDate: '2024-02-01' }],
declaredDays: [{ date: '2024-01-10', type: 'other' }],
rules: [],
},
dates: ['2024-01-08', '2024-01-11'],
},
];
function plan(request: PlanningRequest): PlanOutcome {
const outcome = calculatePlanningInternal(request);
if (outcome.kind !== 'Plan') throw new Error(`Expected a plan, got ${outcome.kind}`);
return outcome;
}
describe('supporting placement economy and provenance', () => {
beforeEach(() => vi.setSystemTime(new Date('2024-01-01T12:00:00Z')));
afterEach(() => vi.useRealTimers());
it.each(bundles)(
'compares the whole $name bundle against economy in either order',
({ rule, request, dates }) => {
const improve = plan({ ...request, rules: [rule, economy] });
const preserve = plan({ ...request, rules: [economy, rule] });
expect(improve.placements.map(({ date }) => date)).toEqual(dates);
expect(improve.placements.every(({ supporting }) => supporting)).toBe(true);
expect(improve.metrics.supportingPlacements).toBe(dates.length);
expect(improve.remainingBalances).toEqual([]);
expect(preserve.placements).toEqual([]);
expect(preserve.metrics.supportingPlacements).toBe(0);
expect(preserve.remainingBalances[0]).toMatchObject({
id: 'cp',
quantity: request.balances[0].quantity,
classification: 'deferred',
reason: { code: 'preserved' },
});
},
);
it.each(bundles)(
'makes disabled economy identical to absence for the $name bundle',
({ rule, request }) => {
const absent = plan({ ...request, rules: [rule] });
for (const rules of [
[{ ...economy, enabled: false }, rule],
[rule, { ...economy, enabled: false }],
])
expect(plan({ ...request, rules })).toEqual(absent);
},
);
it('does not add placements merely because economy is disabled or removed', () => {
for (const rules of [[], [economy], [{ ...economy, enabled: false }]]) {
const outcome = plan({ ...weekend, rules });
expect(outcome.placements).toEqual([]);
expect(outcome.remainingBalances[0].quantity).toBe(1);
}
});
it('credits an earlier supporting placement to a later explicit window without counting an extra day', () => {
const outcome = plan({
...weekend,
rules: [gain, economy, requested],
});
expect(outcome.placements).toEqual([
expect.objectContaining({
date: '2024-01-05',
balanceId: 'cp',
ruleIds: ['gain', 'requested'],
supporting: false,
}),
]);
expect(outcome.metrics).toMatchObject({ nonWorkingDayGain: 2, supportingPlacements: 0 });
expect(outcome.preferences[0].satisfied).toBe(1);
});
it('exempts only the requested window quantity, including fixed declared days', () => {
const outcome = plan({
...weekend,
horizon: { start: '2024-01-05', end: '2024-01-20' },
balances: [{ ...weekend.balances[0], quantity: 3 }],
declaredDays: [{ date: '2024-01-09', type: 'cp' }],
rules: [gain, economy, { ...requested, endDate: '2024-01-19', placeDays: 2 }],
});
expect(outcome.placements.map(({ date, supporting }) => ({ date, supporting }))).toEqual([
{ date: '2024-01-05', supporting: false },
{ date: '2024-01-12', supporting: true },
{ date: '2024-01-19', supporting: true },
]);
expect(outcome.metrics.supportingPlacements).toBe(2);
expect(outcome.timeWindowContributions?.[0]).toMatchObject({
requested: 2,
declaredDays: [{ date: '2024-01-09', type: 'cp' }],
placementDates: ['2024-01-05'],
});
});
it.each([false, true])(
'bounds explicit calendar credit before accepting further window improvements (declared: %s)',
(declared) => {
const windowRule = { ...requested, endDate: '2024-01-19' };
const request: PlanningRequest = {
...weekend,
horizon: { start: '2024-01-05', end: '2024-01-20' },
balances: [{ ...weekend.balances[0], quantity: 4 }],
declaredDays: declared ? [{ date: '2024-01-09', type: 'cp' }] : [],
rules: [economy, gain, windowRule],
};
const preserve = plan(request);
expect(preserve.placements).toEqual(
declared
? []
: [
expect.objectContaining({
date: '2024-01-05',
ruleIds: ['requested'],
supporting: false,
}),
],
);
expect(preserve.metrics.supportingPlacements).toBe(0);
expect(preserve.preferences[0].satisfied).toBe(1);
expect(preserve.remainingBalances[0]).toMatchObject({
id: 'cp',
quantity: declared ? 4 : 3,
});
const improve = plan({ ...request, rules: [gain, economy, windowRule] });
expect(improve.placements.map(({ date }) => date)).toEqual([
'2024-01-05',
'2024-01-12',
'2024-01-19',
]);
expect(
improve.placements.filter(({ ruleIds }) => ruleIds.includes('requested')),
).toHaveLength(declared ? 0 : 1);
expect(improve.metrics.supportingPlacements).toBe(declared ? 3 : 2);
expect(
plan({ ...request, rules: [{ ...economy, enabled: false }, gain, windowRule] }),
).toEqual(plan({ ...request, rules: [gain, windowRule] }));
},
);
it('restores supporting classification when a moved placement loses its explicit calendar request', () => {
const outcome = plan({
...weekend,
horizon: { start: '2024-01-05', end: '2024-01-20' },
balances: [{ ...weekend.balances[0], quantity: 4 }],
rules: [
gain,
{ ...requested, startDate: '2024-01-08', endDate: '2024-01-09' },
bundles[4].rule,
economy,
],
});
expect(outcome.metrics).toMatchObject({
nonWorkingDayGain: 6,
longestWorkSequence: 3,
supportingPlacements: 3,
});
expect(outcome.placements.filter(({ supporting }) => !supporting)).toEqual([
expect.objectContaining({
date: '2024-01-08',
ruleIds: ['gain', 'requested'],
}),
]);
});
it.each(['preference', 'obligation'] as const)(
'keeps explicitly requested deferred days outside economy (%s)',
(force) => {
const outcome = plan({ ...weekend, rules: [economy, { ...requested, force }, gain] });
expect(outcome.placements).toEqual([
expect.objectContaining({
date: '2024-01-05',
ruleIds: ['requested'],
supporting: false,
}),
]);
expect(outcome.metrics).toMatchObject({
nonWorkingDayGain: 2,
supportingPlacements: 0,
});
},
);
it('credits the complete explicit IF_THEN pattern without treating its deferred day as support', () => {
const outcome = plan({
horizon: { start: '2024-05-06', end: '2024-05-11' },
balances: [{ id: 'cp', type: 'cp', quantity: 1, expiryDate: '2025-01-01' }],
rules: [
gain,
economy,
{
id: 'thursday',
type: 'if_then',
dayOfWeek: 4,
placeDays: 1,
placeType: 'cp',
placement: 'after',
rewardDays: 0,
},
],
});
expect(outcome.placements).toEqual([
expect.objectContaining({
date: '2024-05-10',
ruleIds: ['gain', 'thursday'],
supporting: false,
}),
]);
expect(outcome.metrics.supportingPlacements).toBe(0);
expect(outcome.preferences[0].satisfied).toBe(1);
});
it.each(['preference', 'obligation'] as const)(
'limits IF_THEN credit to its declared producer pattern when another complete pattern improves rest (%s)',
(force) => {
const request: PlanningRequest = {
horizon: { start: '2024-05-06', end: '2024-05-11' },
balances: [{ id: 'cp', type: 'cp', quantity: 4, expiryDate: '2025-01-01' }],
declaredDays: [{ date: '2024-05-07', type: 'cp' }],
rules: [
economy,
gain,
{
id: 'producer',
type: 'if_then',
force,
dayOfWeek: 3,
placeDays: 1,
placeType: 'cp',
placement: 'auto',
rewardDays: 1,
rewardType: 'rtt',
},
],
};
const preserve = plan(request);
expect(preserve.placements).toEqual([]);
expect(preserve.metrics.supportingPlacements).toBe(0);
expect(preserve.remainingBalances).toEqual([
expect.objectContaining({ id: 'cp', quantity: 4 }),
expect.objectContaining({
id: 'earned:producer:2024-05-08',
quantity: 1,
availableFrom: '2024-05-10',
}),
]);
expect(preserve.ifThenContributions?.[0]).toMatchObject({
declaredDays: [{ date: '2024-05-07', type: 'cp' }],
placementDates: [],
earnedEntitlement: { availableFrom: '2024-05-10', added: true },
});
const improve = plan({ ...request, rules: [gain, economy, request.rules[2]] });
expect(improve.placements).toEqual([
expect.objectContaining({
date: '2024-05-06',
ruleIds: ['gain'],
supporting: true,
}),
expect.objectContaining({
date: '2024-05-10',
// The best-ranked obligation solution placed this date for the producer's
// after-pattern before gain ran, so the date keeps no gain credit (ADR 0010).
ruleIds: force === 'obligation' ? [] : ['gain'],
supporting: true,
}),
]);
expect(improve.metrics).toMatchObject({
nonWorkingDayGain: 6,
supportingPlacements: 2,
});
expect(improve.remainingBalances).toEqual([
expect.objectContaining({ id: 'cp', quantity: 2 }),
expect.objectContaining({
id: 'earned:producer:2024-05-08',
quantity: 1,
availableFrom: '2024-05-13',
}),
]);
expect(improve.ifThenContributions?.[0]).toMatchObject({
declaredDays: [{ date: '2024-05-07', type: 'cp' }],
placementDates: [],
earnedEntitlement: { availableFrom: '2024-05-13', added: true },
});
expect(
plan({
...request,
rules: [{ ...economy, enabled: false }, gain, request.rules[2]],
}),
).toEqual(plan({ ...request, rules: [gain, request.rules[2]] }));
},
);
it('uses expiry then dates when active rules tie, without restoring a removed gain objective', () => {
const request: PlanningRequest = {
horizon: { start: '2024-01-09', end: '2024-01-13' },
balances: [
{ id: 'later', type: 'cp', quantity: 1, expiryDate: '2024-03-01' },
{ id: 'earlier', type: 'cp', quantity: 1, expiryDate: '2024-02-01' },
],
rules: [economy, { ...requested, startDate: '2024-01-09', endDate: '2024-01-12' }],
};
const outcome = plan(request);
expect(outcome.placements).toEqual([
expect.objectContaining({
date: '2024-01-09',
balanceId: 'earlier',
supporting: false,
}),
]);
expect(outcome.metrics.nonWorkingDayGain).toBe(0);
expect(
plan({ ...request, rules: [{ ...gain, enabled: false }, ...request.rules] }),
).toEqual(outcome);
});
it('reclassifies a deferred support addition when an eligible earlier-expiring balance replaces it', () => {
const outcome = calculatePlanningInternal({
horizon: { start: '2024-05-06', end: '2024-05-11' },
balances: [
{ id: 'deferred', type: 'cp', quantity: 3, expiryDate: '2025-01-01' },
{
id: 'at-risk',
type: 'rtt',
quantity: 1,
availableFrom: '2024-05-07',
expiryDate: '2024-05-07',
},
],
rules: [bundles[0].rule, economy],
});
if (outcome.kind !== 'Plan') throw new Error('Expected a plan');
expect(
outcome.placements.map(({ date, balanceId, supporting }) => ({
date,
balanceId,
supporting,
})),
).toEqual([
{ date: '2024-05-06', balanceId: 'deferred', supporting: true },
{ date: '2024-05-07', balanceId: 'at-risk', supporting: false },
{ date: '2024-05-10', balanceId: 'deferred', supporting: true },
]);
expect(outcome.metrics.supportingPlacements).toBe(2);
expect(outcome.remainingBalances[0]).toMatchObject({ id: 'deferred', quantity: 1 });
});
it('compares new deferred support when at-risk holiday placements relocate into an explicit window', async () => {
const holiday = bundles[0].rule;
const early: PlanningRequest['rules'][number] = {
id: 'early',
type: 'time_window',
startDate: '2026-11-02',
endDate: '2026-11-03',
placeDays: 2,
placeType: 'cp',
};
const request: PlanningRequest = {
horizon: { start: '2026-11-02', end: '2026-11-14' },
balances: [
{ id: 'at-risk', type: 'cp', quantity: 2, expiryDate: '2026-11-13' },
{
id: 'deferred',
type: 'cp',
quantity: 2,
availableFrom: '2026-11-12',
expiryDate: '2027-01-01',
},
],
declaredDays: ['2026-11-09', '2026-11-10'].map((date) => ({ date, type: 'other' })),
rules: [],
};
const economyFirst = plan({ ...request, rules: [holiday, economy, early] });
let artifacts: PlanningArtifacts | undefined;
const windowFirst = calculatePlanningInternal(
{
...request,
rules: [holiday, early, economy],
},
(checkpoint, state) => expect(state?.placements).toEqual(checkpoint.placements),
(value) => (artifacts = value),
);
if (windowFirst.kind !== 'Plan') throw new Error('Expected a plan');
expect(economyFirst.placements).toEqual([
expect.objectContaining({
date: '2026-11-12',
balanceId: 'at-risk',
supporting: false,
}),
expect.objectContaining({
date: '2026-11-13',
balanceId: 'at-risk',
supporting: false,
}),
]);
expect(economyFirst.metrics.supportingPlacements).toBe(0);
expect(economyFirst.preferences[0].satisfied).toBe(0);
expect(economyFirst.remainingBalances[0]).toMatchObject({ id: 'deferred', quantity: 2 });
expect(windowFirst.placements).toEqual([
expect.objectContaining({
date: '2026-11-02',
balanceId: 'at-risk',
ruleIds: ['early'],
supporting: false,
}),
expect.objectContaining({
date: '2026-11-03',
balanceId: 'at-risk',
ruleIds: ['early'],
supporting: false,
}),
expect.objectContaining({
date: '2026-11-12',
balanceId: 'deferred',
ruleIds: ['holiday'],
supporting: true,
}),
expect.objectContaining({
date: '2026-11-13',
balanceId: 'deferred',
ruleIds: ['holiday'],
supporting: true,
}),
]);
expect(windowFirst.metrics.supportingPlacements).toBe(2);
expect(windowFirst.preferences[0].satisfied).toBe(2);
expect(windowFirst.metrics.holidayWeekValue).toBe(3);
if (artifacts?.explanation?.kind !== 'PlanExplanation' || !artifacts.finalState)
throw new Error('Expected matching explanation and state snapshots');
expect(artifacts.finalState.placements).toEqual(windowFirst.placements);
expect(artifacts.explanation.placements.map(({ placement }) => placement)).toEqual(
windowFirst.placements,
);
for (const placement of artifacts.explanation.placements) {
const contribution = expect.objectContaining({ kind: 'rule', ruleId: 'holiday' });
if (placement.supporting) {
expect(placement.ruleIds).toEqual(['holiday']);
expect(placement.contributions).toContainEqual(contribution);
expect(placement.firstDecisiveObjective).toBe('optimization:holiday');
} else {
expect(placement.ruleIds).toEqual(['early']);
expect(placement.contributions).not.toContainEqual(contribution);
}
}
let cancelled = false;
let cancelledArtifacts: PlanningArtifacts | undefined;
let published: PlanOutcome | undefined;
const partial = await calculatePlanningAsyncInternal(
{
...request,
rules: [holiday, early, economy],
progress: (stage: string) => {
if (stage === 'optimization:economy') cancelled = true;
},
},
(checkpoint, state) => {
expect(state?.placements).toEqual(checkpoint.placements);
if (checkpoint.lastCompletedStage === 'preference:early') published = checkpoint;
},
(value) => (cancelledArtifacts = value),
undefined,
undefined,
() => cancelled,
);
if (
partial.kind !== 'PlanningIncomplete' ||
!partial.incumbent ||
cancelledArtifacts?.explanation?.kind !== 'PlanExplanation'
)
throw new Error('Expected an explained cancelled incumbent');
expect(partial.reasons[0].code).toBe('user-cancelled');
expect(partial.incumbent.placements).toEqual(windowFirst.placements);
expect(partial.incumbent.placements).toEqual(published?.placements);
expect(partial.incumbent.preferences).toMatchObject([{ ruleId: 'early', satisfied: 2 }]);
expect(cancelledArtifacts.finalState?.placements).toEqual(partial.incumbent.placements);
expect(cancelledArtifacts.explanation.placements.map(({ placement }) => placement)).toEqual(
partial.incumbent.placements,
);
for (const placement of cancelledArtifacts.explanation.placements.slice(0, 2)) {
expect(placement.ruleIds).toEqual(['early']);
expect(placement.contributions).toContainEqual(
expect.objectContaining({ kind: 'rule', ruleId: 'early' }),
);
}
});
it('keeps established maximum credit when a later earned period masks its local contribution', () => {
let artifacts: PlanningArtifacts | undefined;
const outcome = calculatePlanningInternal(
{
horizon: { start: '2024-05-01', end: '2024-05-11' },
balances: [{ id: 'cp', type: 'cp', quantity: 2, expiryDate: '2024-05-03' }],
rules: [
{ ...gain, configuration: { ...gain.configuration, aggregation: 'maximum' } },
{
id: 'producer',
type: 'if_then',
dayOfWeek: 3,
placement: 'after',
placeDays: 1,
placeType: 'cp',
rewardDays: 2,
rewardType: 'rtt',
},
],
},
undefined,
(value) => (artifacts = value),
);
if (outcome.kind !== 'Plan' || artifacts?.explanation?.kind !== 'PlanExplanation')
throw new Error('Expected an explained plan');
expect(outcome.placements).toMatchObject([
{ date: '2024-05-02', ruleIds: ['gain', 'producer'], supporting: false },
{ date: '2024-05-03', ruleIds: ['gain'], supporting: false },
{ date: '2024-05-10', ruleIds: ['gain'], supporting: true },
]);
for (const placement of artifacts.explanation.placements.slice(0, 2)) {
expect(placement.comparison.delta).toContainEqual(
expect.objectContaining({ id: 'optimization:gain', value: 0 }),
);
expect(placement.contributions).toContainEqual(
expect.objectContaining({ kind: 'rule', ruleId: 'gain' }),
);
}
});
it('counts reverse deferred reassignment for an edited optimization without explicit calendar credit', () => {
const request = bundles[0].request;
const forgoAtRisk: OptimizationPlanningRule = {
id: 'forgo',
type: 'optimization',
configuration: { ...OPTIMIZATION_PRESETS.unplacedAtRisk, direction: 'maximize' },
};
const outcome = plan({
...request,
balances: [
{ ...request.balances[0], id: 'at-risk', expiryDate: '2026-11-13' },
{ ...request.balances[0], id: 'deferred' },
],
rules: [bundles[0].rule, forgoAtRisk, economy],
});
expect(
outcome.placements.map(({ balanceId, supporting }) => ({ balanceId, supporting })),
).toEqual([
{ balanceId: 'deferred', supporting: true },
{ balanceId: 'deferred', supporting: true },
]);
expect(outcome.metrics).toMatchObject({
holidayWeekValue: 3,
unplacedAtRisk: 2,
supportingPlacements: 2,
});
});
it('keeps earned support chronological and preserves the captured economy rule in explanations and counterfactuals', async () => {
vi.stubGlobal('window', undefined);
try {
const capturedEconomy = structuredClone(economy);
const request: PlanningRequest = {
horizon: { start: '2024-04-29', end: '2024-05-08' },
balances: [{ id: 'cp', type: 'cp', quantity: 1, expiryDate: '2025-01-01' }],
rules: [
gain,
capturedEconomy,
{
id: 'producer',
type: 'if_then',
force: 'obligation',
dayOfWeek: 3,
placeDays: 1,
placeType: 'cp',
placement: 'after',
rewardDays: 1,
rewardType: 'rtt',
},
],
};
const calculation = createPlanningCalculation(request);
capturedEconomy.configuration.conditions = [];
capturedEconomy.name = 'Changed later';
const outcome = await calculation.outcome;
if (outcome.kind !== 'Plan') throw new Error('Expected a plan');
expect(outcome.placements).toEqual([
expect.objectContaining({
date: '2024-05-02',
ruleIds: ['producer'],
supporting: false,
}),
expect.objectContaining({
date: '2024-05-06',
balanceId: 'earned:producer:2024-05-01',
supporting: true,
}),
]);
expect(outcome.metrics).toMatchObject({
nonWorkingDayGain: 3,
supportingPlacements: 1,
});
expect(calculation.snapshot.rules[1]).toMatchObject({
name: 'Économiser les jours d’appoint',
configuration: OPTIMIZATION_PRESETS.supportingPlacements,
});
const explanation = await calculation.explanation;
if (explanation?.kind !== 'PlanExplanation') throw new Error('Expected an explanation');
const supporting = explanation.placements.find(({ supporting }) => supporting)!;
expect(supporting.firstDecisiveObjective).toBe('optimization:gain');
expect(supporting.objectiveVector.objectives.map(({ id }) => id)).toEqual([
'obligation-validity',
'optimization:gain',
'optimization:economy',
'balance-expiry',
'placement-date',
]);
expect(supporting.contributions).toContainEqual(
expect.objectContaining({ kind: 'supporting' }),
);
const analysis = await calculation.analyzeCounterfactual(outcome.placements[1]);
if (analysis.kind !== 'Counterfactual') throw new Error('Expected a counterfactual');
expect(analysis.results.find(({ date }) => date === '2024-05-03')).toMatchObject({
status: 'ineligible',
reason: { code: 'unavailable' },
});
expect(
analysis.results.find(({ date }) => date === '2024-05-06')?.comparison?.after
.objectives,
).toContainEqual(expect.objectContaining({ id: 'optimization:economy', value: 1 }));
expect(
analysis.results.find(({ date }) => date === '2024-05-07')?.comparison?.after
.objectives,
).toContainEqual(expect.objectContaining({ id: 'optimization:economy', value: 1 }));
expect(outcome.placements[1]).toMatchObject({ date: '2024-05-06', supporting: true });
} finally {
vi.unstubAllGlobals();
}
});
});