diff --git a/docs/superpowers/specs/2026-04-18-blood-glucose-tracking-design.md b/docs/superpowers/specs/2026-04-18-blood-glucose-tracking-design.md new file mode 100644 index 000000000..e0c6b99be --- /dev/null +++ b/docs/superpowers/specs/2026-04-18-blood-glucose-tracking-design.md @@ -0,0 +1,207 @@ +# Blood Glucose Tracking — Design Spec + +**Datum:** 2026-04-18 +**Status:** Draft +**Scope:** Add `BLOOD_GLUCOSE` as a first-class measurement type with context-aware classification (fasting / postprandial / random / bedtime), user-selectable display unit (mg/dL ↔ mmol/L), reference ranges from ADA 2024 and DGIM 2023, and full integration across form, charts, targets, doctor PDF, and insights. + +## 1. Problem Statement + +Users repeatedly request blood-sugar logging — most recently in **GitHub issue #73, point 8**. Diabetics, pre-diabetics, and people on metabolic-impacting medication (steroids, atypical antipsychotics) need a single place to log glucose alongside weight, BP, and pulse so the doctor PDF reflects the complete metabolic picture. Today they fall back to spreadsheets or vendor-locked apps. Adding glucose to HealthLog closes the largest remaining gap in the "vital sign" set and unlocks correlation insights (e.g. weight ↔ fasting glucose, sleep ↔ glucose variability). + +## 2. Scope + +In v1 we ship: + +- A new `BLOOD_GLUCOSE` measurement type stored canonically in **mg/dL**. +- A required **context tag** per reading: `FASTING`, `POSTPRANDIAL`, `RANDOM`, `BEDTIME`. +- A user preference `glucoseUnit` (`MG_DL` | `MMOL_L`, default `MG_DL` for `de` users, can flip in settings) — display only; storage is canonical. +- Context-aware classification (see §4) and a target card on `/targets` for fasting glucose; secondary line for postprandial when data exists. +- Health chart with diabetes/pre-diabetes band overlays. +- Doctor PDF row + classification line. +- Insights descriptor "glucose control" (fasting avg + variability). + +**Stretch (v1.1, not in this spec):** HbA1c as a separate measurement type with its own classification (<5.7% / 5.7–6.4% / ≥6.5%, ADA 2024). Schema is forward-compatible (just add another enum value). + +## 3. Data Model + +```prisma +enum MeasurementType { + WEIGHT + BLOOD_PRESSURE_SYS + BLOOD_PRESSURE_DIA + PULSE + BODY_FAT + SLEEP_DURATION + ACTIVITY_STEPS + BLOOD_GLUCOSE // NEW +} + +enum GlucoseContext { // NEW + FASTING + POSTPRANDIAL + RANDOM + BEDTIME + + @@map("glucose_context") +} + +model Measurement { + // ...existing fields + glucoseContext GlucoseContext? @map("glucose_context") // NEW, only set when type = BLOOD_GLUCOSE +} + +model User { + // ...existing fields + glucoseUnit String @default("MG_DL") @map("glucose_unit") // "MG_DL" | "MMOL_L" +} +``` + +Migration `20260418_add_blood_glucose`: +1. `ALTER TYPE measurement_type ADD VALUE 'BLOOD_GLUCOSE';` +2. `CREATE TYPE glucose_context AS ENUM (...);` +3. `ALTER TABLE measurements ADD COLUMN glucose_context glucose_context;` +4. `ALTER TABLE users ADD COLUMN glucose_unit text NOT NULL DEFAULT 'MG_DL';` +5. Add `CHECK ((type = 'BLOOD_GLUCOSE') = (glucose_context IS NOT NULL))` to enforce coupling. + +The unique constraint `(userId, type, measuredAt, source)` already prevents duplicates; context is intentionally **not** part of the key — the same timestamp can't legally carry two contexts. + +Storage unit is always **mg/dL** (`unit = "mg/dL"` in the row) so downstream analytics, exports, and Withings ingest don't need to branch. + +## 4. Classifications (`src/lib/analytics/classifications.ts`) + +New helpers, ranges from **ADA Standards of Care 2024** (Diabetes Care, Vol. 47, Suppl. 1) and **DGIM/DDG S3-Leitlinie 2023**: + +```ts +export type GlucoseContext = "FASTING" | "POSTPRANDIAL" | "RANDOM" | "BEDTIME"; + +export interface GlucoseClassification { + category: string; + color: string; + severity: "info" | "normal" | "warning" | "danger"; +} + +export function classifyGlucose( + mgdl: number, + context: GlucoseContext, +): GlucoseClassification; +``` + +Boundaries (mg/dL — mmol/L conversion shown for traceability): + +| Context | Hypo (<70 / <3.9) | Normal | Pre-diabetes | Diabetes | +|---------|-------------------|--------|--------------|----------| +| FASTING | danger | 70–99 (3.9–5.5) normal | 100–125 (5.6–6.9) warning | ≥126 (≥7.0) danger | +| POSTPRANDIAL (2h) | danger | <140 (<7.8) normal | 140–199 (7.8–11.0) warning | ≥200 (≥11.1) danger | +| RANDOM | danger | <140 normal | 140–199 warning | ≥200 danger | +| BEDTIME | danger | 90–150 normal (info if outside) | — | ≥250 danger | + +Color tokens reuse the existing Dracula palette (`#50fa7b` normal, `#f1fa8c` info, `#ffb86c` / `#ff79c6` warning, `#ff5555` danger). Add `getGlucoseFastingRange()` returning `{ min: 70, max: 99 }` for the target card range bar, and `getGlucosePostprandialRange()` for the optional secondary bar. + +## 5. UI + +**Measurement form (`src/components/measurements/measurement-form.tsx`):** +- New entry in `MEASUREMENT_TYPES`: `{ value: "BLOOD_GLUCOSE", labelKey: "measurements.typeGlucose", unitKey: "measurements.unitGlucose", placeholder: "95" }`. +- When `type === "BLOOD_GLUCOSE"`, render an additional `