feat(insights): follow every score over time with one range selector

The health score had no history anywhere, readiness and the sleep score
showed a fixed sparkline, and only the stored nightly scores offered the
range tabs. Every score page now charts its course with the same
7 / 30 / 90 / All tabs, remembered per page, and each point opens its day.

- GET /api/insights/score-history?score=&days= serves the daily course of
  the health score (its stored day rows), readiness (the nightly blend,
  filed on its wake day) and the sleep score (each night against the
  trailing window ending on it), so a point equals what the day view shows
  for that day. MANAGE record read, module-gated for readiness and sleep,
  days 1 to 3650, additive.
- The health score's line breaks where its recipe changed (configuration
  or algorithm version); the usual range is formed from the newest side
  of the last seam.
- The range tabs move into one shared component used by every chart;
  recovery, stress and strain keep their measurement chart, now with the
  range remembered and no overlay dropdown.
- New page /insights/health-score, linked from the score panel and from
  the day view's score tile.
This commit is contained in:
Marc-André Bombeck committed 2026-10-09 16:45:39 +02:00
1 parent 1d080ee166
commit 62140a4aee
38 files changed
+2437 -183

No files matched your search

@@ -210,6 +210,8 @@ export const DATA_ROUTES: Readonly<Record<string, string>> = {
"src/app/api/insights/ecg/[id]/route.ts": "One device recording's waveform.",
"src/app/api/insights/rhythm-events/route.ts":
"The device's own flagged events, verbatim.",
"src/app/api/insights/score-history/route.ts":
"A score's stored or computed daily values; no model text.",
"src/app/api/insights/cards/route.ts":
"Rule alerts from the threshold engine; `provider` is `rules`.",
"src/app/api/insights/correlations/route.ts":
@@ -67,6 +67,10 @@ const DELIBERATELY_OPEN: Record<string, { count: number; reason: string }> = {
reason: "hand-built query object",
},
"insights/derived/route.ts": { count: 1, reason: "hand-built query object" },
"insights/score-history/route.ts": {
count: 1,
reason: "hand-built query object",
},
"insights/metric-status/route.ts": {
count: 1,
reason: "hand-built query object",