aboutsummaryrefslogtreecommitdiff
path: root/src/lib/shared/routine-metrics.ts
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-09-08 15:53:07 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-09-08 15:53:07 +0200
commit6d69c7b1e6a8051338e052cc9d1836f4f1a20f51 (patch)
tree33af9e78d93afe34f113aad116d3e5e4ed2a5fc1 /src/lib/shared/routine-metrics.ts
parent212c3adc8a584e6517a2816fc6683688d5ac5d7a (diff)
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.
Diffstat (limited to 'src/lib/shared/routine-metrics.ts')
-rw-r--r--src/lib/shared/routine-metrics.ts19
1 files changed, 10 insertions, 9 deletions
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.
*/