Files
vac-optimizer/src/app/services/leave-planner-supporting-placements.spec.ts
T
nessarandClaude Opus 5.5 94d57add2c test: run the suite and benchmark with Bun's native test runner
Replace Vitest with bun test across the whole suite. A preload compiles
components through Angular's JIT transform with inlined templates, sets
up jsdom, TestBed and zoneless change detection, and a small shim covers
the Vitest APIs bun:test lacks. Files run isolated, real-worker specs
spawn the running Bun, and line coverage from lcov is converted to the
existing HTML and Cobertura reports. The CI benchmark replaces its heap
cap with a container memory limit. Vitest, Jest, Nx and their Vite
configuration are removed.

Refs #291

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:40:42 +02:00

736 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',
ruleIds: ['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();
}
});
});