diff options
Diffstat (limited to 'src/lib/shared/routine-metrics.ts')
| -rw-r--r-- | src/lib/shared/routine-metrics.ts | 59 |
1 files changed, 59 insertions, 0 deletions
diff --git a/src/lib/shared/routine-metrics.ts b/src/lib/shared/routine-metrics.ts index 2ac245e..99e5c56 100644 --- a/src/lib/shared/routine-metrics.ts +++ b/src/lib/shared/routine-metrics.ts @@ -84,6 +84,65 @@ export function computeHealthScore(states: { health: number }[]): number { return Math.round(total / states.length); } +export interface HealthPoint { + /** Epoch ms of the sample. */ + at: number; + /** Health (0-100) at that instant. */ + health: number; +} + +/** + * 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). + * Each sample anchors on the latest completion at or before it (or creation). + * Uses the routine's current frequency across the whole history. + */ +export function computeHealthSeries( + routine: RoutineInput, + completions: CompletionInput[], + now: number, +): HealthPoint[] { + const createdMs = Date.parse(routine.createdAt); + const startMs = Number.isFinite(createdMs) ? createdMs : now; + const periodMs = routine.frequencyDays * DAY_MS; + + const completionMs = completions + .filter((c) => c.routineId === routine.id) + .map((c) => Date.parse(c.doneAt)) + .sort((a, b) => a - b); + + const points: HealthPoint[] = []; + if (now <= startMs) { + return [{ at: now, health: 100 }]; + } + + // Reference anchor: latest completion at-or-before the sampled instant. + let refMs = startMs; + let nextCompletion = 0; + const sampleAt = (t: number): number => { + while ( + nextCompletion < completionMs.length && + completionMs[nextCompletion] <= t + ) { + refMs = completionMs[nextCompletion]; + nextCompletion++; + } + const dueMs = refMs + periodMs; + const rawRatio = (t - dueMs) / periodMs; + return Math.max(0, Math.round(100 - Math.max(0, rawRatio) * 100)); + }; + + for (let t = startMs; t <= now; t += DAY_MS) { + points.push({ at: t, health: sampleAt(t) }); + } + + // Final unaligned sample so the curve ends at the present instant. + if (points[points.length - 1].at < now) { + points.push({ at: now, health: sampleAt(now) }); + } + return points; +} + export function formatFrequency(days: number): string { if (days === 1) return 'chaque jour'; if (days === 7) return 'chaque semaine'; |
