diff --git a/docs/diagrams/01-data-flow.svg b/docs/diagrams/01-data-flow.svg new file mode 100644 index 000000000..388d3d883 --- /dev/null +++ b/docs/diagrams/01-data-flow.svg @@ -0,0 +1,170 @@ + + HealthLog data flow — device to insight + Five-stage pipeline: sources (Withings, Apple Health export, iPhone HealthKit, manual entry, moodLog.app) feed ingest endpoints, which write to Postgres Measurement and MeasurementRollup tables, then surface on Dashboard tiles, Insights cards, the AI Coach, and the Doctor PDF. + + + + + + From device to insight + Every reading walks the same five-stage pipeline: source → ingest → store → read → surface + + + + SOURCES + INGEST + STORE + READ + SURFACE + + + + + + + + + + + + + + Withings devices + Scales · BPM · ScanWatch + + + Apple Health export + export.zip · multi-GB + + + iPhone HealthKit + native batch (v1.5) + + + Manual entry + PWA · iOS Shortcuts + + + moodLog.app + webhook + + + + + + /api/withings/webhook + + /sync + + + /import/apple-health-export + streaming · idempotent + + + /api/measurements/batch + native batch ingest + + + Dedup + source-priority + resolver + + + + + + Measurement + one row per sample + (userId, type, source, t) + + + MeasurementRollup + DAY · WEEK · MONTH + pre-aggregated buckets + + + PostgreSQL 16 + encrypted at rest + + + + + + Rollup probe + DAY-bucket fast path + + + Live SQL fallback + slope · r² · sd + + + Wide-event cache + cache.<name>.outcome + + + + + + Dashboard tiles + trends · ranges + + + Insights cards + evidence-grounded + + + AI Coach + cited replies + + + Doctor PDF + client-side jsPDF + + + + + + + + + + + + + + + + + + + + + + + + pg-boss worker + + + + + + + + + + + + + + + + + + Source-priority ladder runs at ingest. Live SQL is the safety net when the rollup is cold or the metric ask is too slope/variance-heavy for a DAY bucket. + + + + + + + + + diff --git a/docs/diagrams/02-coach-pipeline.svg b/docs/diagrams/02-coach-pipeline.svg new file mode 100644 index 000000000..6a131cb18 --- /dev/null +++ b/docs/diagrams/02-coach-pipeline.svg @@ -0,0 +1,148 @@ + + AI Coach prompt pipeline + A user question feeds the snapshot builder, which pulls the recent metric, medication and mood windows. The system prompt assembler combines them with a locale-aware structured-output schema, then the provider chain (ChatGPT OAuth, OpenAI, Anthropic, Local) tries each entry in order. The response parser extracts claims plus citations, validates against the schema, and renders a cited reply with mini-charts. + + + + AI Coach prompt pipeline + Why every reply cites the measurement it came from — not just an LLM call. + + + + + User question + /insights drawer + "Why is my BP higher this week?" + + + + + + Snapshot builder + last-N days metrics + + medication schedule + + mood + targets + 60s LRU per (userId, scope) + + + + + + Prompt assembler + system prompt + + locale (en/de) + + Zod citation schema + PROMPT_VERSION 4.36.0 + + + + + + Provider chain + first non-error wins · hard fail cascades + + + + + 1. + ChatGPT OAuth (Codex) + subscription + + + 2. + OpenAI direct (BYOK) + per-token + + + 3. + Anthropic Claude (BYOK) + per-token + + + 4. + Local (Ollama · LM Studio · vLLM) + on network + + + 5. Admin-shared OpenAI (fallback) + + + + + + + Response parser + Zod schema validate + extract claims + cites + server-computed score + corrective retry on 422 + + + + + + Cited reply + prose first + collapsible evidence + mini-charts + ESH/ESC/WHO/DGE link + + + + + + /insights drawer + streaming render + source chips · feedback + + + + + + 24h insight cache + snapshot hash + version + short-circuits provider call + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hit → render + miss → assemble + + + Every claim renders with its source chip (metric · window · n) so the user can audit which slice of data backed each sentence. + + + + + + + + + + + diff --git a/docs/diagrams/03-self-hosting-topology.svg b/docs/diagrams/03-self-hosting-topology.svg new file mode 100644 index 000000000..4b2a227c4 --- /dev/null +++ b/docs/diagrams/03-self-hosting-topology.svg @@ -0,0 +1,132 @@ + + HealthLog self-hosting topology + Internet traffic terminates at a reverse proxy (Caddy, Traefik or Nginx) for TLS, then reaches the Next.js app on port 3000. The app shares a Postgres 16 database with the pg-boss worker process for reminders, insights, backups and the Apple Health import. GHCR ships the multi-arch image; Coolify watches the digest and pulls; off-host S3-compatible storage is optional for backups. + + + + Self-hosting topology + What runs where on a single-host install — and what the optional pieces add. + + + + + Your host (NAS · homelab · VPS) + + + + + + Internet + browsers · iOS app + + + + + + Reverse proxy + Caddy · Traefik · Nginx + TLS · HSTS · CSP + + + + + + Next.js app + port 3000 · App Router + HEALTHLOG_PROCESS_TYPE=web + + + + + + pg-boss worker + reminders · insights · imports + HEALTHLOG_PROCESS_TYPE=worker + + + + + + PostgreSQL 16 + 26 Prisma models + + pg-boss queues + + + + + + GitHub Container Registry + multi-arch image (amd64/arm64) + ghcr.io/mbombeck/healthlog + + + + + + Coolify (optional) + watch digest · pull · deploy + pull_policy: always + + + + + + S3-compatible backup + R2 · B2 · MinIO · AWS + AES-GCM weekly off-host + + + + + + Withings + Apple Health + moodLog.app + AI provider + (per-user) + + + + + + + HTTPS + + + + + + + + + + + shared DB + + + + + + image pull + + + + + nightly dump + + + + + outbound integrations + + Single-process default: web + worker share one container. Split via HEALTHLOG_PROCESS_TYPE for horizontal scale. + + + + + + + + + + diff --git a/docs/diagrams/04-source-priority.svg b/docs/diagrams/04-source-priority.svg new file mode 100644 index 000000000..4ed28fc1a --- /dev/null +++ b/docs/diagrams/04-source-priority.svg @@ -0,0 +1,126 @@ + + Source-priority ladder + Three sources (Apple Watch via HealthKit, Withings device, manual entry) can all submit a reading for the same metric. The resolver picks one canonical value per user-day based on metric type — cumulative metrics (steps, energy, distance) prefer Apple Health, point measurements (weight, BP, SpO₂) prefer Withings. Losing rows stay in the audit trail but drop out of aggregation. + + + + Source-priority ladder + Why your steps don't triple-count when an iPhone, a Withings watch and a manual entry all log the same day. + + + + + Same day · same metric · three sources + + + + + + Apple Watch (HK) + 10 432 steps · 76 kg · 132/81 mmHg + source = APPLE_HEALTH + + + + + + Withings scale + BPM + 9 870 steps · 75.8 kg · 134/82 mmHg + source = WITHINGS + + + + + + Manual entry + 76.0 kg (after-shower weigh-in) + source = MANUAL + + + + + + Legacy CSV / JSON import + source = IMPORT (lowest rank) + + + + + + Source-priority resolver + src/lib/validations/source-priority.ts + + Cumulative metrics + APPLE_HEALTH ≻ WITHINGS + ≻ MANUAL ≻ IMPORT + + Point measurements + WITHINGS ≻ APPLE_HEALTH + ≻ MANUAL ≻ IMPORT + + + + + + Per-user override + User.sourcePriorityJson + /settings/thresholds → Sources + + + + + + Kept — counted in aggregation + + + Steps → Apple Health + 10 432 (cumulative winner) + + + Weight → Withings + 75.8 kg (point winner) + + + BP → Withings + 134/82 mmHg (point winner) + + + + + + Dropped from aggregation — audit trail kept + + + 9 870 steps (Withings) + + + 76 kg (Apple Health) + + + 132/81 mmHg (Apple Health) + + + 76 kg (Manual) + + All rows visible in /measurements list. None feed dashboard, insights, or Coach for the conflict day. + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/diagrams/05-security-model.svg b/docs/diagrams/05-security-model.svg new file mode 100644 index 000000000..07e581c66 --- /dev/null +++ b/docs/diagrams/05-security-model.svg @@ -0,0 +1,119 @@ + + HealthLog security model + Three concentric layers: an outer auth perimeter (passkey-first WebAuthn, password fallback with Argon2id), a session perimeter (HttpOnly SameSite=Strict cookie, Postgres-backed sessions with sliding expiry), and the encrypted core (AES-256-GCM with versioned keys for secrets at rest). Side rails cover the rate limiter, audit log, HMAC API tokens, CSP/HSTS headers and SSRF-guarded outbound test endpoints. + + + + Security model + The path a request takes through the auth, session and encryption layers. + + + + + Layer 1 — Auth perimeter (WebAuthn first) + + + + + Layer 2 — Session perimeter (server-side sessions) + + + + + Layer 3 — Encrypted core (AES-256-GCM) + + + Secrets at rest + Withings tokens · provider keys · VAPID · Codex + + + Versioned keys + ENCRYPTION_KEYS + + + Online rotation + rotate-encryption-key + + + Bearer tokens stored as HMAC-SHA256 digests + + + + + + Passkey (WebAuthn) + SimpleWebAuthn v13 + + + Password fallback + Argon2id + zxcvbn + + + + + + HttpOnly · SameSite=Strict + cookie · 30d sliding expiry + + + Postgres sessions + requireAuth · requireAdmin + + + + + + Rate limiter + sliding window · per IP+user + + + Audit log + every sensitive op + IP + + + CSP + HSTS + nonce CSP · X-Frame-Options + + + SSRF guard + outbound test endpoints + + + API tokens + hlk_* Bearer · HMAC stored + + + Refresh tokens + 24h access · rotated refresh + + + Off-host backup + AES-GCM · S3-compatible + + + Per-user scope + no cross-account reads + + + + + + + + + + + + + + + + + + No data leaves the host unless an integration explicitly asks for it. Bearer tokens never elevate to admin — admin actions require a cookie session. + + + + + + diff --git a/docs/diagrams/README.md b/docs/diagrams/README.md new file mode 100644 index 000000000..5311e5ed7 --- /dev/null +++ b/docs/diagrams/README.md @@ -0,0 +1,27 @@ +# Diagrams + +Five SVG diagrams that anchor the architecture story. Hand-drawn-style, +Dracula palette, no external dependencies. Embed in README and doc-site +pages as `` tags. Mirrored into `healthlog-docs/src/assets/diagrams/` +and `healthlog-landing/public/diagrams/` so each surface ships its own +copy. + +| File | Story it tells | +| ---- | -------------- | +| `01-data-flow.svg` | Sources (Withings, Apple Health export.zip, iPhone HealthKit, manual, moodLog) → ingest endpoints → Measurement + MeasurementRollup → reads (rollup probe / live SQL fallback / cache) → surfaces (dashboard, insights, Coach, doctor PDF). | +| `02-coach-pipeline.svg` | User question → snapshot builder → prompt assembler → provider chain (ChatGPT OAuth / OpenAI / Anthropic / local / admin-shared) → response parser → cited reply with mini-charts. | +| `03-self-hosting-topology.svg` | Internet → reverse proxy (TLS) → Next.js app (port 3000) ↔ pg-boss worker ↔ Postgres 16, plus GHCR image pull, optional Coolify autodeploy, optional S3 off-host backup. | +| `04-source-priority.svg` | Three sources for the same metric on the same day → resolver picks one canonical row per (cumulative vs point) ladder. Losing rows stay in the audit trail. | +| `05-security-model.svg` | Three concentric layers — auth perimeter (passkey + password fallback), session perimeter (HttpOnly cookie + Postgres sessions), encrypted core (AES-256-GCM versioned keys). Side rails: rate limiter, audit log, HMAC tokens, CSP/HSTS, SSRF guard. | + +## Editing + +Open in any text editor. Each file is hand-authored SVG ≤ 12 KB; the +viewport, the `defs` block of arrow markers, and the labelled groups are +all readable inline. Keep the Dracula palette (`#282a36` background, +`#f8f8f2` text, accents `#8be9fd / #ffb86c / #50fa7b / #bd93f9 / +#ff79c6 / #ff5555`) and the `viewBox="0 0 1200 620"` constraint so the +diagrams stay consistent across the three sites. + +After editing, copy the file to the sister repos so all three surfaces +ship the updated version.