import { createOptimizationPresetRules } from './rule-service'; import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; import { vi } from '../../testing/vitest-compat'; import { createPlanningCalculation } from './leave-planner.service'; const balance = ( id: string, type: 'cp' | 'rtt', quantity: number, expiryDate: string, availableFrom = '2024-01-01', ) => ({ id, type, quantity, expiryDate, availableFrom }); beforeEach(() => vi.stubGlobal('window', undefined)); afterEach(() => vi.unstubAllGlobals()); beforeEach(() => vi.setSystemTime(new Date('2024-01-01T12:00:00Z'))); afterEach(() => vi.useRealTimers()); const presets = createOptimizationPresetRules(); describe('planner explanation and alternative dates', () => { it('delivers only the displayed plan, explanation and state needed for alternative dates', async () => { const calculation = createPlanningCalculation({ horizon: { start: '2024-01-02', end: '2024-01-10' }, balances: [balance('cp', 'cp', 1, '2024-01-09')], rules: [presets[0]], }); const outcome = await calculation.outcome; if (outcome.kind !== 'Plan') throw new Error('Expected a plan.'); expect(Object.keys(calculation).sort()).toEqual([ 'analyzeCounterfactual', 'cancel', 'explanation', 'explanationError', 'id', 'outcome', 'snapshot', 'timings', ]); const core = await import('./leave-planner.core'); let artifacts: import('./leave-planner.core').PlanningArtifacts = {}; expect( core.calculatePlanningInternal( calculation.snapshot, undefined, (value) => (artifacts = value), ), ).toEqual(outcome); expect(Object.keys(artifacts).sort()).toEqual([ 'explanation', 'explanationError', 'finalState', 'timings', ]); expect(artifacts.finalState?.placements).toEqual(outcome.placements); expect(Object.keys(artifacts.timings ?? {})).toEqual(['planningWorkMs']); const original = structuredClone(outcome); const analysis = await calculation.analyzeCounterfactual(outcome.placements[0]); expect(analysis.results).toHaveLength(8); for (const result of analysis.results) { expect(result).not.toHaveProperty('historicalStatus'); expect(result).not.toHaveProperty('historicalReason'); } expect(outcome).toEqual(original); }); it('excludes observers from the technical budget and keeps the outcome after explanation delivery fails', async () => { const core = await import('./leave-planner.core'); let elapsed = 0; const clock = vi.spyOn(performance, 'now').mockImplementation(() => elapsed); let delivered = false; let artifacts: import('./leave-planner.core').PlanningArtifacts = {}; try { const outcome = core.calculatePlanningInternal( { horizon: { start: '2024-01-02', end: '2024-01-10' }, balances: [], rules: [], deadlineMs: 10, progress: () => (elapsed += 100), }, () => (elapsed += 500), (value) => (artifacts = value), () => (delivered = true), () => { expect(delivered).toBe(true); elapsed += 1000; throw new Error('Explanation delivery failed.'); }, ); expect(outcome.kind).toBe('Plan'); expect(artifacts.explanationError).toMatchObject({ code: 'explanation-error', message: 'Explanation delivery failed.', }); expect(artifacts.finalState?.horizon).toEqual({ start: '2024-01-02', end: '2024-01-10', }); expect(artifacts.timings?.planningWorkMs).toBe(0); } finally { clock.mockRestore(); } }); it('keeps earned availability after a reported occurrence is extended', async () => { vi.setSystemTime(new Date('2024-05-03T12:00:00Z')); const core = await import('./leave-planner.core'); let artifacts: import('./leave-planner.core').PlanningArtifacts = {}; const outcome = core.calculatePlanningInternal( { horizon: { start: '2024-04-29', end: '2024-05-08' }, declaredDays: [ { date: '2024-04-29', type: 'cp' }, { date: '2024-04-30', type: 'cp' }, { date: '2024-05-03', type: 'other' }, ], balances: [balance('cp', 'cp', 1, '2025-12-31')], rules: [ { id: 'producer', type: 'if_then', force: 'obligation', dayOfWeek: 3, placement: 'before', placeDays: 2, placeType: 'cp', rewardDays: 1, rewardType: 'other', }, { id: 'extend', type: 'time_window', force: 'obligation', startDate: '2024-05-02', endDate: '2024-05-02', placeDays: 1, placeType: 'cp', }, { id: 'consumer', type: 'time_window', force: 'obligation', startDate: '2024-04-29', endDate: '2024-05-07', placeDays: 2, placeType: 'other', }, ], }, undefined, (value) => (artifacts = value), ); if (outcome.kind !== 'Plan' || !artifacts.finalState) throw new Error('Expected the extended generator to finance a plan.'); const earnedId = 'earned:producer:2024-05-01'; expect(artifacts.finalState.balances).toContainEqual( expect.objectContaining({ id: earnedId, availableFrom: '2024-05-06', expiryDate: '2025-05-06', remaining: 0, }), ); expect(outcome.placements.find(({ balanceId }) => balanceId === earnedId)?.date).toBe( '2024-05-07', ); expect(artifacts.finalState.placements).toEqual(outcome.placements); }); it.each([5, 8])( 'keeps %s unrelated obligations valid without duplicating a reported entitlement', async (count) => { vi.setSystemTime(new Date('2024-05-06T12:00:00Z')); const core = await import('./leave-planner.core'); const dates = [ '2024-04-17', '2024-04-18', '2024-04-19', '2024-04-22', '2024-04-23', '2024-04-24', '2024-04-25', '2024-04-26', ].slice(0, count); for (const rewardDays of [0, 1]) { const outcome = core.calculatePlanningInternal({ horizon: { start: '2024-04-17', end: '2024-05-08' }, declaredDays: [ { date: '2024-04-29', type: 'cp' }, { date: '2024-04-30', type: 'cp' }, ], balances: [ { id: 'other', type: 'other', quantity: dates.length, availableFrom: '2024-04-17', expiryDate: '2025-12-31', }, ], rules: [ ...dates.map((date) => ({ id: date, type: 'time_window' as const, force: 'obligation' as const, startDate: date, endDate: date, placeDays: 1, placeType: 'other' as const, })), { id: 'producer', type: 'if_then', force: 'preference', dayOfWeek: 3, placement: 'before', placeDays: 2, placeType: 'cp', rewardDays, rewardType: 'other', }, ], }); if (outcome.kind !== 'Plan') throw new Error('Expected a completed plan.'); expect(outcome.placements.map(({ date }) => date)).toEqual(dates); expect(outcome.remainingBalances).toEqual([]); if (rewardDays) expect(outcome.ifThenContributions?.[0].earnedEntitlement).toMatchObject({ availableFrom: '2024-05-02', added: false, }); } }, ); it('keeps reported entitlements out of the final state under explicit priorities', async () => { vi.setSystemTime(new Date('2024-05-06T12:00:00Z')); const core = await import('./leave-planner.core'); let artifacts: import('./leave-planner.core').PlanningArtifacts = {}; const outcome = core.calculatePlanningInternal( { horizon: { start: '2024-05-01', end: '2024-05-11' }, balances: [ { id: 'cp', type: 'cp', quantity: 1, availableFrom: '2024-05-06', expiryDate: '2024-05-07', }, ], declaredDays: [ { date: '2024-05-02', type: 'cp' }, { date: '2024-05-03', type: 'cp' }, ], rules: [ presets[0], ...[ { id: 'producer', type: 'if_then', force: 'preference', dayOfWeek: 3, placeDays: 2, placeType: 'cp', placement: 'after', rewardDays: 1, rewardType: 'other', }, ], ...presets.slice(1), ], }, undefined, (value) => (artifacts = value), ); if (outcome.kind !== 'Plan' || !artifacts.finalState) throw new Error('Expected a completed plan.'); expect(outcome.placements).toMatchObject([{ date: '2024-05-07', balanceId: 'cp' }]); expect(outcome.remainingBalances).toEqual([]); expect(outcome.ifThenContributions?.[0].earnedEntitlement).toMatchObject({ availableFrom: '2024-05-06', added: false, }); }); it('associates every joint obligation with its shared placement', async () => { const calculation = createPlanningCalculation({ horizon: { start: '2024-01-01', end: '2024-01-08' }, balances: [balance('cp', 'cp', 2, '2024-12-31')], rules: [ { id: 'joint', type: 'time_window', force: 'obligation', startDate: '2024-01-02', endDate: '2024-01-05', placeDays: 2, placeType: 'cp', }, { id: 'shared', type: 'time_window', force: 'obligation', startDate: '2024-01-03', endDate: '2024-01-03', placeDays: 1, placeType: 'cp', }, ], }); const outcome = await calculation.outcome; expect(outcome.kind).toBe('Plan'); if (outcome.kind !== 'Plan') throw new Error('Expected a plan.'); const sharedPlacement = outcome.placements.find( (placement) => placement.ruleIds.includes('joint') && placement.ruleIds.includes('shared'), ); expect(sharedPlacement).toBeDefined(); }); it('puts actual obligation validity first in stable plan explanations', async () => { const calculation = createPlanningCalculation({ horizon: { start: '2024-01-01', end: '2024-01-08' }, balances: [balance('cp', 'cp', 1, '2025-01-01')], rules: [ { id: 'mandatory', type: 'time_window', force: 'obligation', startDate: '2024-01-03', endDate: '2024-01-03', placeDays: 1, placeType: 'cp', }, ], }); const outcome = await calculation.outcome; const explanation = await calculation.explanation; expect(outcome.kind).toBe('Plan'); if (outcome.kind !== 'Plan') throw new Error('Expected a plan.'); expect(explanation?.kind).toBe('PlanExplanation'); if (explanation?.kind !== 'PlanExplanation') return; const placement = explanation.placements[0]; expect(placement.objectiveVector.objectives[0]).toMatchObject({ id: 'obligation-validity', direction: 'minimize', value: 0, }); expect(placement.comparison.before.objectives[0]).toMatchObject({ id: 'obligation-validity', value: 1, }); expect(placement.comparison.after.objectives[0]).toMatchObject({ id: 'obligation-validity', value: 0, }); expect(placement.firstDecisiveObjective).toBe('obligation-validity'); }); it('uses the earlier-expiring balance for shared ANY preferences', async () => { const calculation = createPlanningCalculation({ horizon: { start: '2024-01-01', end: '2024-01-08' }, balances: [ balance('cp', 'cp', 1, '2025-01-04'), balance('rtt-first', 'rtt', 1, '2025-01-05'), balance('rtt-second', 'rtt', 1, '2025-01-06'), ], rules: [ { id: 'any-preference', type: 'time_window', force: 'preference', startDate: '2024-01-03', endDate: '2024-01-03', placeDays: 1, placeType: 'any', }, { id: 'rtt-preference', type: 'time_window', force: 'preference', startDate: '2024-01-03', endDate: '2024-01-03', placeDays: 1, placeType: 'rtt', }, ], }); const outcome = await calculation.outcome; expect(outcome.kind).toBe('Plan'); if (outcome.kind !== 'Plan') throw new Error('Expected a plan.'); const sharedPlacement = outcome.placements.find((placement) => placement.ruleIds.includes('any-preference'), ); expect(sharedPlacement).toMatchObject({ date: '2024-01-03', balanceId: 'rtt-first', ruleIds: ['any-preference', 'rtt-preference'], }); }); it('keeps an earned obligation entitlement and its consumer chronological', async () => { const calculation = createPlanningCalculation({ horizon: { start: '2024-04-25', end: '2024-05-08' }, balances: [ balance('cp', 'cp', 1, '2024-12-31'), balance('rtt', 'rtt', 0, '2026-01-01'), ], rules: [ { id: 'producer', type: 'if_then', force: 'obligation', dayOfWeek: 3, placeDays: 1, placeType: 'cp', placement: 'after', rewardDays: 1, rewardType: 'rtt', }, { id: 'consumer', type: 'time_window', force: 'obligation', startDate: '2024-05-07', endDate: '2024-05-07', placeDays: 1, placeType: 'rtt', }, ], }); const outcome = await calculation.outcome; expect(outcome.kind).toBe('Plan'); if (outcome.kind !== 'Plan') throw new Error('Expected a plan.'); const earned = outcome.placements.find((placement) => placement.balanceId.startsWith('earned:'), ); expect(earned?.ruleIds).toContain('consumer'); }); it('rejects a producer move that delays its earned entitlement past the consumer', async () => { const calculation = createPlanningCalculation({ horizon: { start: '2024-04-25', end: '2024-05-08' }, balances: [ balance('cp', 'cp', 1, '2025-12-31'), balance('rtt', 'rtt', 0, '2026-01-01'), ], rules: [ { id: 'producer', type: 'if_then', force: 'obligation', dayOfWeek: 3, placeDays: 1, placeType: 'cp', placement: 'auto', rewardDays: 1, rewardType: 'rtt', }, { id: 'consumer', type: 'time_window', force: 'obligation', startDate: '2024-05-03', endDate: '2024-05-03', placeDays: 1, placeType: 'rtt', }, ], }); const outcome = await calculation.outcome; expect(outcome.kind).toBe('Plan'); if (outcome.kind !== 'Plan') throw new Error('Expected a plan.'); const producer = outcome.placements.find((placement) => placement.ruleIds.includes('producer'), ); expect(producer?.date).toBe('2024-04-30'); if (!producer) return; const analysis = await calculation.analyzeCounterfactual(producer); expect(analysis.kind).toBe('Counterfactual'); if (analysis.kind !== 'Counterfactual') return; expect(analysis.results.find((result) => result.date === '2024-05-02')).toMatchObject({ status: 'ineligible', reason: { code: 'unavailable' }, }); expect( analysis.results.find((result) => result.date === '2024-05-02')?.comparison, ).toBeUndefined(); }); it('rejects an at-risk move that would delay already-earned leave past its consumer', async () => { const calculation = createPlanningCalculation({ horizon: { start: '2024-04-25', end: '2024-05-08' }, balances: [ balance('cp1', 'cp', 1, '2025-12-31'), { ...balance('cp2', 'cp', 2, '2024-05-07'), availableFrom: '2024-04-26' }, balance('rtt', 'rtt', 0, '2026-01-01'), ], rules: [ presets[0], ...[ { id: 'anchor', type: 'time_window', force: 'obligation', startDate: '2024-04-25', endDate: '2024-04-25', placeDays: 1, placeType: 'cp', }, { id: 'producer', type: 'if_then', force: 'obligation', dayOfWeek: 3, placeDays: 1, placeType: 'cp', placement: 'after', rewardDays: 1, rewardType: 'rtt', }, { id: 'consumer', type: 'time_window', force: 'obligation', startDate: '2024-05-06', endDate: '2024-05-06', placeDays: 1, placeType: 'rtt', }, ], ...presets.slice(1), ], }); const outcome = await calculation.outcome; expect(outcome.kind).toBe('Plan'); if (outcome.kind !== 'Plan') throw new Error('Expected a plan.'); const atRisk = outcome.placements.find( (placement) => placement.balanceId === 'cp2' && placement.ruleIds.every((id) => id.startsWith('default-')), ); expect(atRisk?.date).toBeDefined(); if (!atRisk) return; const analysis = await calculation.analyzeCounterfactual(atRisk); expect(analysis.kind).toBe('Counterfactual'); if (analysis.kind !== 'Counterfactual') return; expect(analysis.results.find((result) => result.date === '2024-05-03')).toMatchObject({ status: 'ineligible', reason: { code: 'unavailable' }, }); expect( analysis.results.find((result) => result.date === '2024-05-03')?.comparison, ).toBeUndefined(); }); it('keeps earned balance metadata consistent when a shared period extends', async () => { const calculation = createPlanningCalculation({ horizon: { start: '2024-04-25', end: '2024-05-15' }, balances: [ balance('cp', 'cp', 3, '2024-12-31'), balance('rtt', 'rtt', 0, '2026-01-01'), ], rules: [ { id: 'producer', type: 'if_then', force: 'obligation', dayOfWeek: 3, placeDays: 1, placeType: 'cp', placement: 'after', rewardDays: 1, rewardType: 'rtt', }, { id: 'cp-preference', type: 'time_window', force: 'preference', startDate: '2024-05-03', endDate: '2024-05-03', placeDays: 1, placeType: 'cp', }, { id: 'rtt-preference', type: 'time_window', force: 'preference', startDate: '2024-05-07', endDate: '2024-05-07', placeDays: 1, placeType: 'rtt', }, ], }); const outcome = await calculation.outcome; expect(outcome.kind).toBe('Plan'); if (outcome.kind !== 'Plan') throw new Error('Expected a plan.'); const earnedBalanceId = outcome.placements.find((placement) => placement.ruleIds.includes('rtt-preference'), )?.balanceId; expect(earnedBalanceId).toBeDefined(); }); it('compares every horizon date while preserving the selected placement provenance', async () => { const calculation = createPlanningCalculation({ horizon: { start: '2024-12-27', end: '2025-01-07' }, balances: [balance('cp', 'cp', 2, '2025-01-03', '2024-12-30')], rules: [ { id: 'exact-date', type: 'time_window', force: 'preference', startDate: '2024-12-30', endDate: '2024-12-30', placeDays: 1, placeType: 'cp', }, ], }); const outcome = await calculation.outcome; expect(outcome.kind).toBe('Plan'); if (outcome.kind !== 'Plan') throw new Error('Expected a plan.'); const selected = outcome.placements.find((placement) => placement.ruleIds.includes('exact-date'), ); expect(selected).toBeDefined(); if (!selected) return; const before = structuredClone(outcome); const analysis = await calculation.analyzeCounterfactual(selected); expect(analysis.kind).toBe('Counterfactual'); if (analysis.kind !== 'Counterfactual') return; expect(analysis.results.map(({ date }) => date)).toEqual([ '2024-12-27', '2024-12-28', '2024-12-29', '2024-12-30', '2024-12-31', '2025-01-01', '2025-01-02', '2025-01-03', '2025-01-04', '2025-01-05', '2025-01-06', ]); expect(analysis.placement).toEqual(selected); expect(analysis.results.find((item) => item.date === '2024-12-27')?.reason?.code).toBe( 'unavailable', ); expect(analysis.results.find((item) => item.date === '2025-01-01')?.reason?.code).toBe( 'non-working', ); expect(analysis.results.find((item) => item.date === '2025-01-06')?.reason?.code).toBe( 'expired', ); for (const item of analysis.results.filter((result) => result.status === 'ineligible')) expect(item.comparison).toBeUndefined(); expect(outcome).toEqual(before); }); it('keeps typed, joint-date, and earned-order conflicts separate from invalid input', async () => { const scenarios = [ { horizon: { start: '2024-04-01', end: '2024-04-06' }, balances: [balance('rtt', 'rtt', 1, '2025-01-01')], rules: [ { id: 'cp-only', type: 'time_window' as const, force: 'obligation' as const, startDate: '2024-04-02', endDate: '2024-04-02', placeDays: 1, placeType: 'cp', }, ], }, { horizon: { start: '2024-04-01', end: '2024-04-06' }, balances: [ balance('cp', 'cp', 1, '2025-01-01'), balance('rtt', 'rtt', 1, '2025-01-01'), ], rules: ['cp', 'rtt'].map((type) => ({ id: `${type}-same-day`, type: 'time_window' as const, force: 'obligation' as const, startDate: '2024-04-02', endDate: '2024-04-02', placeDays: 1, placeType: type, })), }, { horizon: { start: '2024-04-25', end: '2024-05-15' }, balances: [ balance('cp', 'cp', 2, '2025-01-01'), balance('rtt', 'rtt', 3, '2026-01-01'), ], rules: [ { id: 'producer', type: 'if_then' as const, force: 'obligation' as const, dayOfWeek: 3, placeDays: 1, placeType: 'cp', placement: 'before', rewardDays: 1, rewardType: 'rtt', }, { id: 'consumer', type: 'if_then' as const, force: 'obligation' as const, dayOfWeek: 4, placeDays: 4, placeType: 'rtt', placement: 'before', rewardDays: 0, }, ], }, ]; const conflictCodes = [ 'typed-capacity', 'joint-date-conflict', 'earned-entitlement-bottleneck', ]; for (const [index, scenario] of scenarios.entries()) { const calculation = createPlanningCalculation(scenario); const outcome = await calculation.outcome; expect(outcome.kind).toBe('PlanningConflict'); if (outcome.kind !== 'PlanningConflict') continue; expect(outcome.reasons[0].code).toBe(conflictCodes[index]); expect((await calculation.explanation)?.kind).toBe('ConflictExplanation'); } const invalid = createPlanningCalculation({ horizon: { start: '2025-01-01', end: '2024-01-01' }, balances: [], rules: [], }); expect((await invalid.outcome).kind).toBe('InvalidInput'); expect(await invalid.explanation).toBeUndefined(); }); it('does not classify a joint-date conflict as earned when only a preference generates leave', async () => { const calculation = createPlanningCalculation({ horizon: { start: '2024-04-01', end: '2024-04-06' }, balances: [ balance('cp', 'cp', 1, '2025-01-01'), balance('rtt', 'rtt', 1, '2025-01-01'), ], rules: [ ...(['cp', 'rtt'] as const).map((type) => ({ id: `${type}-required`, type: 'time_window' as const, force: 'obligation' as const, startDate: '2024-04-02', endDate: '2024-04-02', placeDays: 1, placeType: type, })), { id: 'unrelated-reward-preference', type: 'if_then', force: 'preference', dayOfWeek: 3, placeDays: 1, placeType: 'cp', placement: 'auto', rewardDays: 1, rewardType: 'rtt', }, ], }); const outcome = await calculation.outcome; expect(outcome.kind).toBe('PlanningConflict'); if (outcome.kind === 'PlanningConflict') expect(outcome.reasons[0].code).toBe('joint-date-conflict'); }); it('recovers an incumbent explanation from the exact delivered checkpoint state', async () => { const core = await import('./leave-planner.core'); let checkpointPlan: import('./leave-planner.core').PlanOutcome | undefined; let checkpointState: import('./leave-planner.core').PlanningStateSnapshot | undefined; const request = { horizon: { start: '2024-01-01', end: '2024-01-10' }, balances: [balance('cp', 'cp', 2, '2024-01-31')], rules: presets, progress: (stage: string) => { if (stage === 'optimization:default-holiday-week-value') throw new Error('interrupted after checkpoint'); }, }; const outcome = core.calculatePlanningInternal(request, (plan, state) => { checkpointPlan = plan; checkpointState = state; }); expect(outcome.kind).toBe('PlanningIncomplete'); expect(checkpointPlan).toBeDefined(); expect(checkpointState).toBeDefined(); if (!checkpointPlan || !checkpointState) return; const recovered = core.explainIncompleteCheckpoint( request, checkpointPlan, checkpointState, { code: 'worker-error', message: 'The worker stopped after its checkpoint.' }, ); expect(recovered.explanation?.kind).toBe('PlanExplanation'); if (recovered.explanation?.kind !== 'PlanExplanation') return; expect(recovered.explanation.incomplete?.reason.code).toBe('worker-error'); expect(recovered.explanation.placements).toHaveLength(checkpointPlan.placements.length); let completedState: import('./leave-planner.core').PlanningStateSnapshot | undefined; const completed = core.calculatePlanningInternal( { ...request, progress: undefined }, (plan, state) => { if (plan.completedStages.at(-1) === 'dates') completedState = state; }, ); expect(completed.kind).toBe('Plan'); if (completed.kind !== 'Plan' || !completedState) return; const completedRecovery = core.explainIncompleteCheckpoint( { ...request, progress: undefined }, completed, completedState, { code: 'worker-error', message: 'The result arrived before the worker stopped.' }, ); expect(completedRecovery.explanation?.kind).toBe('PlanExplanation'); if (completedRecovery.explanation?.kind === 'PlanExplanation') expect(completedRecovery.explanation.incomplete).toBeUndefined(); }); it('explains incomplete calculations before and after a valid plan', async () => { const early = createPlanningCalculation({ horizon: { start: '2024-01-01', end: '2024-01-10' }, balances: [balance('cp', 'cp', 1, '2024-01-31')], rules: presets, deadlineMs: 0, }); const earlyOutcome = await early.outcome; expect(earlyOutcome.kind).toBe('PlanningIncomplete'); if (earlyOutcome.kind === 'PlanningIncomplete') expect(earlyOutcome.completedStages).toEqual(['validation']); const quality = createPlanningCalculation({ horizon: { start: '2024-01-01', end: '2024-02-01' }, balances: [balance('cp', 'cp', 1, '2024-01-31')], rules: presets, progress: (stage) => { if (stage === 'optimization:default-holiday-week-value') { throw new Error('deliberate interruption in quality phase'); } }, }); const qualityOutcome = await quality.outcome; expect(qualityOutcome.kind).toBe('PlanningIncomplete'); if (qualityOutcome.kind !== 'PlanningIncomplete') return; expect(qualityOutcome.reasons[0].code).toBe('planner-error'); expect(qualityOutcome.completedStages).toEqual([ 'validation', 'obligations', 'optimization:default-unplaced-at-risk', ]); }); });