import { describe, expect, it } from 'bun:test'; import { calculatePlanningInternal, type PlanningRequest } from './leave-planner.core'; import { createOptimizationPresetRules } from './rule-service'; const presets = createOptimizationPresetRules(); const preset = (id: string) => presets.find((rule) => rule.id === id)!; describe('choosing among obligation solutions', () => { it('plans from the solution ranked best before the preferences', () => { // The obligation day can take either balance. Before the preferences, the // at-risk RTT ranks first because it leaves no at-risk leave unplaced. Funding // it with the CP instead would let the RTT gain three days around Easter // later; ADR 0010 accepts that loss to run the preferences only once. const request: PlanningRequest & { calculationDate: string } = { calculationDate: '2026-04-03', horizon: { start: '2026-04-03', end: '2026-05-24' }, balances: [ { id: 'cp', type: 'cp', quantity: 1, expiryDate: '2026-06-30' }, { id: 'rtt', type: 'rtt', quantity: 1, expiryDate: '2026-05-23' }, ], rules: [ preset('default-unplaced-at-risk'), preset('default-supporting-placements'), preset('default-non-working-day-gain'), { id: 'thursday', type: 'time_window', force: 'obligation', startDate: '2026-04-09', endDate: '2026-04-09', placeDays: 1, placeType: 'any', }, ], }; const outcome = calculatePlanningInternal(request); expect(outcome.kind).toBe('Plan'); if (outcome.kind !== 'Plan') return; expect(outcome.placements).toEqual([ { date: '2026-04-09', balanceId: 'rtt', type: 'rtt', ruleIds: ['thursday'], supporting: false, }, ]); expect(outcome.metrics.unplacedAtRisk).toBe(0); }); it('plans from the next solution when the best-ranked one earns its leave too late', () => { const request: PlanningRequest & { calculationDate: string } = { calculationDate: '2026-07-09', horizon: { start: '2026-07-09', end: '2026-07-25' }, balances: [ { id: 'cp', type: 'cp', quantity: 3, availableFrom: '2026-07-13', expiryDate: '2026-08-10', }, ], rules: [ { id: 'tuesday', type: 'if_then', force: 'preference', dayOfWeek: 2, placeDays: 1, placeType: 'any', placement: 'auto', rewardDays: 2, rewardType: 'rtt', }, { id: 'late-july', type: 'time_window', force: 'obligation', startDate: '2026-07-23', endDate: '2026-08-02', placeDays: 2, placeType: 'any', }, ], }; const outcome = calculatePlanningInternal(request); expect(outcome.kind).toBe('Plan'); if (outcome.kind !== 'Plan') return; expect( outcome.placements.map(({ date, balanceId, ruleIds }) => [date, balanceId, ruleIds]), ).toEqual([ ['2026-07-13', 'cp', ['tuesday']], ['2026-07-23', 'cp', ['late-july']], ['2026-07-24', 'cp', ['late-july']], ]); }); });