From 3d314e84fd20ecc3808d7506a3ab3c5457b996ad Mon Sep 17 00:00:00 2001 From: Ferdinand Urban Date: Tue, 28 Apr 2026 10:07:22 +0000 Subject: [PATCH] feat(algo): skip day when candidates < slotsPerDay --- js/algorithm.js | 9 ++++++--- tests/algorithm.test.mjs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/js/algorithm.js b/js/algorithm.js index 45c1039..9a64ad3 100644 --- a/js/algorithm.js +++ b/js/algorithm.js @@ -86,14 +86,17 @@ export function generatePlan(workdays, employees, history, randomFn = Math.rando const candidates = employees.filter(e => isEligible(e, date, monthAssignments, history) ); - if (candidates.length === 0) continue; + + if (candidates.length < slotsPerDay) { + skippedDates.push(date); + continue; + } const scored = candidates .map(e => ({ employee: e, score: scoreCandidate(e, date, history, randomFn) })) .sort((a, b) => b.score - a.score); - const take = Math.min(slotsPerDay, scored.length); - for (let i = 0; i < take; i++) { + for (let i = 0; i < slotsPerDay; i++) { monthAssignments.push({ date, employeeId: scored[i].employee.id, manual: false }); } } diff --git a/tests/algorithm.test.mjs b/tests/algorithm.test.mjs index 5fbda63..8b9c957 100644 --- a/tests/algorithm.test.mjs +++ b/tests/algorithm.test.mjs @@ -154,4 +154,37 @@ function emp(id, constraints = {}) { } } +// slotsPerDay=2 with only 1 eligible employee → day skipped, listed in skippedDates +{ + const employees = [emp('alice')]; + const result = generatePlan(['2026-04-01'], employees, [], fixed, 2); + assert.equal(result.assignments.length, 0, 'no assignments when candidates < slotsPerDay'); + assert.deepEqual(result.skippedDates, ['2026-04-01'], 'date listed in skippedDates'); +} + +// slotsPerDay=1 with 0 candidates → still skipped, listed +{ + // 2026-04-01 = Wednesday (3), only employee blocks Wednesday + const employees = [emp('alice', { neverDays: [3] })]; + const result = generatePlan(['2026-04-01'], employees, [], fixed, 1); + assert.equal(result.assignments.length, 0, 'no assignments when 0 candidates'); + assert.deepEqual(result.skippedDates, ['2026-04-01'], 'date listed in skippedDates'); +} + +// Mixed: some days fully assigned, some skipped +{ + // 2026-04-01 = Wed, 2026-04-02 = Thu + // alice can only Wed, bob can always work + const employees = [ + emp('alice', { onlyDays: [3] }), + emp('bob') + ]; + const result = generatePlan(['2026-04-01','2026-04-02'], employees, [], fixed, 2); + // 2026-04-01: 2 candidates → 2 entries + assert.equal(result.assignments.filter(a => a.date === '2026-04-01').length, 2, 'Wed has 2 entries'); + // 2026-04-02: only bob → skipped + assert.equal(result.assignments.filter(a => a.date === '2026-04-02').length, 0, 'Thu skipped'); + assert.deepEqual(result.skippedDates, ['2026-04-02'], 'Thu in skippedDates'); +} + console.log('All algorithm tests passed.');