From 6d69c7b1e6a8051338e052cc9d1836f4f1a20f51 Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Tue, 8 Sep 2026 15:53:07 +0200 Subject: Late only after a full missed period Status buckets shift a full period out: due until an occurrence is skipped entirely, late after one, very late after two. Health decay halves (100 - ratio*50) so 50 marks late and 0 marks veryLate. --- src/lib/shared/routine-metrics.test.ts | 49 +++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 12 deletions(-) (limited to 'src/lib/shared/routine-metrics.test.ts') diff --git a/src/lib/shared/routine-metrics.test.ts b/src/lib/shared/routine-metrics.test.ts index 57b8c38..d18fd18 100644 --- a/src/lib/shared/routine-metrics.test.ts +++ b/src/lib/shared/routine-metrics.test.ts @@ -45,24 +45,31 @@ describe('computeRoutineState', () => { expect(state.health).toBe(100); }); - it('is very urgent (veryLate) when two weeks old for a weekly routine', () => { - const now = Date.parse(CREATED) + 15 * DAY_MS; + it('is very urgent (veryLate) when two full periods overdue', () => { + const now = Date.parse(CREATED) + 22 * DAY_MS; const state = computeRoutineState(routine, [], now); expect(state.status).toBe('veryLate'); - expect(state.overdueDays).toBe(8); + expect(state.overdueDays).toBe(15); expect(state.health).toBe(0); }); - it('is late when overdue by half a frequency', () => { + it('is due (not late) when overdue by half a frequency', () => { const now = Date.parse(CREATED) + 10.5 * DAY_MS; const state = computeRoutineState(routine, [], now); + expect(state.status).toBe('due'); + }); + + it('is late when a full period overdue', () => { + const now = Date.parse(CREATED) + 14 * DAY_MS; + const state = computeRoutineState(routine, [], now); expect(state.status).toBe('late'); + expect(state.health).toBe(50); }); - it('health of 75 means a quarter of the period overdue', () => { - const now = Date.parse(CREATED) + 7 * DAY_MS + 1.75 * DAY_MS; + it('health of 50 means a full period overdue', () => { + const now = Date.parse(CREATED) + 7 * DAY_MS + 7 * DAY_MS; const state = computeRoutineState(routine, [], now); - expect(state.health).toBe(75); + expect(state.health).toBe(50); }); it('is due the calendar day after completion for a daily routine', () => { const daily = { ...routine, frequencyDays: 1 }; @@ -72,6 +79,24 @@ describe('computeRoutineState', () => { expect(state.status).toBe('due'); }); + it('a daily done yesterday is late tomorrow, not today', () => { + const daily = { ...routine, frequencyDays: 1 }; + const doneAt = '2026-09-07T20:00:00.000Z'; // yesterday evening + const completions = [{ routineId: 1, doneAt }]; + const today = computeRoutineState( + daily, + completions, + Date.parse('2026-09-08T20:00:00.000Z'), + ); + expect(today.status).toBe('due'); + const tomorrow = computeRoutineState( + daily, + completions, + Date.parse('2026-09-09T20:00:00.000Z'), + ); + expect(tomorrow.status).toBe('late'); + }); + it('is ok later the same day it was done for a daily routine', () => { const daily = { ...routine, frequencyDays: 1 }; const doneAt = '2026-09-08T08:00:00.000Z'; @@ -82,15 +107,15 @@ describe('computeRoutineState', () => { }); describe('computeHealthSeries', () => { - it('decays from 100 at creation to 0 two periods late when never done', () => { - const now = Date.parse(CREATED) + 15 * DAY_MS; + it('decays from 100 at creation to 0 two periods overdue when never done', () => { + const now = Date.parse(CREATED) + 22 * DAY_MS; const series = computeHealthSeries(routine, [], now); expect(series[0].health).toBe(100); const at8d = series.find((p) => p.at === Date.parse(CREATED) + 8 * DAY_MS); - expect(at8d?.health).toBe(86); // 1/7 of a period overdue + expect(at8d?.health).toBe(93); // 1/7 of a period overdue const last = series[series.length - 1]; expect(last.at).toBe(now); - expect(last.health).toBe(0); // more than a full period overdue + expect(last.health).toBe(0); // more than two full periods overdue }); it('resets to 100 right after a completion, then decays again (sawtooth)', () => { @@ -105,7 +130,7 @@ describe('computeHealthSeries', () => { expect(rightAfter?.health).toBe(100); // 8 days after the completion: 1 day into the 7-day period overdue. const at8d = series.find((p) => p.at === doneAt + 8 * DAY_MS); - expect(at8d?.health).toBe(86); + expect(at8d?.health).toBe(93); }); it('a second completion resets the decay again', () => { -- cgit v1.3