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.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'src/lib/shared/routine-metrics.ts') diff --git a/src/lib/shared/routine-metrics.ts b/src/lib/shared/routine-metrics.ts index 891a0c9..cffeb85 100644 --- a/src/lib/shared/routine-metrics.ts +++ b/src/lib/shared/routine-metrics.ts @@ -39,7 +39,7 @@ export interface RoutineState { /** Time past due, in units of the routine's own frequency (0 if not due). */ overdueRatio: number; status: RoutineStatus; - /** 100 on schedule, 0 when overdue by a full frequency, floor at 0. */ + /** 100 on schedule, 50 when a full period overdue, 0 at two periods, floor at 0. */ health: number; } @@ -61,17 +61,17 @@ export function computeRoutineState( const dueMs = startOfUtcDay(referenceMs) + periodMs; const rawRatio = (now - dueMs) / periodMs; const overdueRatio = Math.max(0, rawRatio); - // Buckets on overdue time in units of the routine's own frequency: - // not due / due now / late (half a period overdue) / very late (a full - // period overdue — a weekly routine two weeks old lands here). + // Buckets on overdue time in units of the routine's own frequency: due + // until a full period is missed (a daily done yesterday is due today, not + // late), late once an occurrence was skipped entirely, very late after two. // A routine that was never done is always at least due: there is // something to do, even right after creation. const status: RoutineStatus = rawRatio < 0 && lastDoneMs !== null ? 'ok' - : overdueRatio < 0.5 + : overdueRatio < 1 ? 'due' - : overdueRatio < 1 + : overdueRatio < 2 ? 'late' : 'veryLate'; @@ -85,7 +85,7 @@ export function computeRoutineState( overdueDays: Math.floor(overdueRatio * routine.frequencyDays), overdueRatio, status, - health: Math.max(0, Math.round(100 - overdueRatio * 100)), + health: Math.max(0, Math.round(100 - overdueRatio * 50)), }; } @@ -107,12 +107,13 @@ export interface HealthPoint { function healthAtPoint(refMs: number, periodMs: number, t: number): number { const dueMs = startOfUtcDay(refMs) + periodMs; const rawRatio = (t - dueMs) / periodMs; - return Math.max(0, Math.round(100 - Math.max(0, rawRatio) * 100)); + return Math.max(0, Math.round(100 - Math.max(0, rawRatio) * 50)); } /** * Daily health samples from the routine's creation to now: 100 right after a - * completion, then a linear decay to 0 once a full period is overdue (sawtooth). + * completion, then a linear decay to 0 once two full periods are overdue + * (sawtooth). * Each sample anchors on the latest completion at or before it (or creation). * Uses the routine's current frequency across the whole history. */ -- cgit v1.3