From 591a2773cc140aed61b6f4f1d6ddf707d78218f3 Mon Sep 17 00:00:00 2001 From: Kenearos Date: Tue, 12 May 2026 00:14:21 +0200 Subject: [PATCH] feat: implement variant1 (1 fr+so + 3 weekday, fr-priority) --- test-suite.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++ variants.js | 53 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/test-suite.js b/test-suite.js index a33943c..ba43229 100644 --- a/test-suite.js +++ b/test-suite.js @@ -671,6 +671,69 @@ runner.test('variant3: threshold-Shape ist {pool: 2} normal, {pool: 1} im Urlaub t.assertEqual(r2.threshold.pool, 1, 'vacation pool=1'); }); +// ============================================================================ +// Variants - variant1 (1 fr+so + 3 weekday) +// ============================================================================ + +runner.test('variant1: Schwelle nicht erreicht (fr+so=0)', (t) => { + const r = variant1({ fr: 0, sa: 5, so: 0, weekday: 3 }, false); + t.assertFalse(r.eligible, 'eligible=false'); + t.assertEqual(r.bonus, 0, 'bonus=0'); +}); + +runner.test('variant1: Schwelle nicht erreicht (weekday<3)', (t) => { + const r = variant1({ fr: 1, sa: 5, so: 0, weekday: 2 }, false); + t.assertFalse(r.eligible, 'eligible=false'); + t.assertEqual(r.bonus, 0, 'bonus=0'); +}); + +runner.test('variant1: Spec-Beispiel fr=2,sa=1,so=0,weekday=4 -> 1150', (t) => { + const r = variant1({ fr: 2, sa: 1, so: 0, weekday: 4 }, false); + t.assertTrue(r.eligible, 'eligible=true'); + t.assertEqual(r.deduction.fr, 1, 'fr-deduction=1 (Fr-Prio)'); + t.assertEqual(r.deduction.so, 0, 'so-deduction=0'); + t.assertEqual(r.deduction.sa, 0, 'sa nicht abgezogen'); + t.assertEqual(r.deduction.weekday, 3, 'weekday-deduction=3'); + t.assertEqual(r.paidShares.fr, 1, 'fr-paid=1'); + t.assertEqual(r.paidShares.sa, 1, 'sa-paid=1'); + t.assertEqual(r.paidShares.so, 0, 'so-paid=0'); + t.assertEqual(r.paidShares.weekday, 1, 'weekday-paid=1'); + t.assertEqual(r.bonus, 1150, 'bonus = (1+1+0)*450 + 1*250 = 1150'); +}); + +runner.test('variant1: nur so vorhanden -> 1 von so abgezogen', (t) => { + const r = variant1({ fr: 0, sa: 0, so: 1, weekday: 3 }, false); + t.assertTrue(r.eligible, 'eligible=true'); + t.assertEqual(r.deduction.fr, 0, 'fr-deduction=0'); + t.assertEqual(r.deduction.so, 1, 'so-deduction=1'); + t.assertEqual(r.deduction.weekday, 3, 'weekday-deduction=3'); + t.assertEqual(r.bonus, 0, 'bonus=0'); +}); + +runner.test('variant1: sa wird voll bezahlt, nicht abgezogen', (t) => { + const r = variant1({ fr: 1, sa: 2, so: 0, weekday: 3 }, false); + t.assertEqual(r.deduction.sa, 0, 'sa-deduction=0'); + t.assertEqual(r.paidShares.sa, 2, 'sa-paid=2'); + // bonus = (0+2+0)*450 + 0*250 = 900 + t.assertEqual(r.bonus, 900, 'bonus=900'); +}); + +runner.test('variant1: Urlaubsmodus halbiert Schwellen (0.5 + 1.5)', (t) => { + const r = variant1({ fr: 0.5, sa: 0, so: 0, weekday: 1.5 }, true); + t.assertTrue(r.eligible, 'eligible=true im Urlaub'); + t.assertEqual(r.threshold.frSo, 0.5, 'threshold.frSo=0.5'); + t.assertEqual(r.threshold.weekday, 1.5, 'threshold.weekday=1.5'); + t.assertEqual(r.deduction.fr, 0.5, 'fr-deduction=0.5'); + t.assertEqual(r.deduction.weekday, 1.5, 'weekday-deduction=1.5'); + t.assertEqual(r.bonus, 0, 'bonus=0'); +}); + +runner.test('variant1: threshold-Shape normal {frSo:1, weekday:3}', (t) => { + const r = variant1({ fr: 1, sa: 0, so: 0, weekday: 3 }, false); + t.assertEqual(r.threshold.frSo, 1, 'threshold.frSo=1'); + t.assertEqual(r.threshold.weekday, 3, 'threshold.weekday=3'); +}); + // ============================================================================ // Display Functions // ============================================================================ diff --git a/variants.js b/variants.js index 2e87960..82ea181 100644 --- a/variants.js +++ b/variants.js @@ -34,7 +34,58 @@ function classifyDuties(duties, holidayProvider) { } function variant1(classified, isVacation) { - throw new Error('variant1: not implemented'); + const RATE_NORMAL = 250; + const RATE_WEEKEND = 450; + const frSoThreshold = isVacation ? 0.5 : 1; + const weekdayThreshold = isVacation ? 1.5 : 3; + const frSoDeduction = isVacation ? 0.5 : 1; + const weekdayDeduction = isVacation ? 1.5 : 3; + + const frSoPool = classified.fr + classified.so; + const eligible = (frSoPool >= frSoThreshold - 1e-9) + && (classified.weekday >= weekdayThreshold - 1e-9); + + if (!eligible) { + return { + variantId: 1, + eligible: false, + threshold: { frSo: frSoThreshold, weekday: weekdayThreshold }, + deduction: { fr: 0, sa: 0, so: 0, weekday: 0 }, + paidShares: { fr: 0, sa: 0, so: 0, weekday: 0 }, + bonus: 0, + isWinner: false + }; + } + + // Friday priority within fr+so pool: fr first, then so + let remaining = frSoDeduction; + const deduction = { fr: 0, sa: 0, so: 0, weekday: weekdayDeduction }; + for (const slot of ['fr', 'so']) { + const take = Math.min(remaining, classified[slot]); + deduction[slot] = take; + remaining -= take; + if (remaining <= 1e-9) break; + } + + const paidShares = { + fr: Math.max(0, classified.fr - deduction.fr), + sa: classified.sa, // sa never deducted in V1 + so: Math.max(0, classified.so - deduction.so), + weekday: Math.max(0, classified.weekday - deduction.weekday) + }; + + const bonus = (paidShares.fr + paidShares.sa + paidShares.so) * RATE_WEEKEND + + paidShares.weekday * RATE_NORMAL; + + return { + variantId: 1, + eligible: true, + threshold: { frSo: frSoThreshold, weekday: weekdayThreshold }, + deduction, + paidShares, + bonus, + isWinner: false + }; } function variant2(classified, isVacation) {