docs: document the v1.11.0 features

Add WHOOP as a synced provider (per-user BYO-keys OAuth2 + HMAC-signed
webhooks), the longitudinal coach (narrative + per-metric trajectory),
and the read-only FHIR R4 REST API plus the scoped, time-limited
clinician share-link record across README, the self-hosting and ops
runbooks, and a new WHOOP integration page. Note the two optional
WHOOP instance env vars, that migrations 0110-0114 are additive, and
the two known limitations (dual-source standard-vital dedup deferred;
coach durable-memory deferred).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.8 committed 2026-06-04 04:25:22 +02:00
1 parent df5f1d170b
commit 65c7012590
4 files changed
+114 -3

No files matched your search

+28 -3
View File
@@ -21,7 +21,7 @@
<p align="center">
Self-hosted health tracker. Weight, blood pressure, glucose, mood, medications.<br/>
Withings and Apple Health sync, multi-provider AI Insights you own, doctor-report PDF.
Withings, WHOOP, and Apple Health sync, multi-provider AI Insights you own, doctor-report PDF.
</p>
<p align="center">
@@ -36,7 +36,7 @@
## What it is
HealthLog is a self-hosted personal health tracker that runs from a single `docker compose up`. It covers the metrics most people actually log -- weight, blood pressure, pulse, body composition, blood glucose, sleep, mood, and medication compliance -- and brings them together in one dashboard with reference ranges from ESH 2023, ADA 2024, and NICE NG115. Withings devices sync automatically; an `export.zip` import folds your full Apple Health history into the same timeline; a native SwiftUI iOS client (public-beta via [TestFlight](https://testflight.apple.com/join/bucuTBpa)) streams HealthKit live; multi-provider AI Insights (BYOK or local) explain what the numbers mean; a doctor-report PDF generates client-side. EN/DE end-to-end. AGPL-3.0.
HealthLog is a self-hosted personal health tracker that runs from a single `docker compose up`. It covers the metrics most people actually log -- weight, blood pressure, pulse, body composition, blood glucose, sleep, mood, and medication compliance -- and brings them together in one dashboard with reference ranges from ESH 2023, ADA 2024, and NICE NG115. Withings and WHOOP devices sync automatically over OAuth2; an `export.zip` import folds your full Apple Health history into the same timeline; a native SwiftUI iOS client (public-beta via [TestFlight](https://testflight.apple.com/join/bucuTBpa)) streams HealthKit live; multi-provider AI Insights (BYOK or local) explain what the numbers mean; a doctor-report PDF generates client-side. EN/DE end-to-end. AGPL-3.0.
> **Status**: active. New releases roughly weekly -- see [CHANGELOG](CHANGELOG.md). Current line: v1.10 — a derived-metrics tier (fitness-age, vascular-age delta, HRV balance, sleep score, readiness and recovery scores, an early-strain flag), each computed from your own measurements and citing its inputs, on top of the v1.7 health-record export (PDF + FHIR R4), flexible medication schedules, and full HealthKit metric coverage. The native iOS client is public-beta via [TestFlight](https://testflight.apple.com/join/bucuTBpa).
@@ -98,7 +98,7 @@ HealthLog is the right fit when you want your wearable and clinical numbers in o
**Apple Health import** -- Drop your iOS `export.zip` on the import page. A streaming parser handles multi-gigabyte archives (Zip64), folds every `<Record>`, `<Workout>`, `<Correlation>`, and `<ClinicalRecord>` into the same timeline as your other metrics, and stays idempotent on re-upload. Per-type ingestion stats plus a live status endpoint so you can watch the progress on a long historical drain.
**AI Coach + Insights** -- A conversational Coach grounded in your own data, a daily briefing, a weekly report, and a Health Score tile on the dashboard. Pick OpenAI, Anthropic Claude, ChatGPT via Codex device-OAuth (no API key needed), or any OpenAI-compatible local endpoint (Ollama, LM Studio, vLLM). BYOK or admin-shared. Feed the Coach a chosen set of data clusters (cardiovascular, body composition, activity, workouts, sleep, mood, glucose, medication, mobility, environment) with a soft budget cap that degrades the lowest-signal clusters first. Every claim links back to the measurements that produced it. Local endpoints keep all data on your network.
**AI Coach + Insights** -- A conversational Coach grounded in your own data, a daily briefing, a weekly report, and a Health Score tile on the dashboard. Pick OpenAI, Anthropic Claude, ChatGPT via Codex device-OAuth (no API key needed), or any OpenAI-compatible local endpoint (Ollama, LM Studio, vLLM). BYOK or admin-shared. Feed the Coach a chosen set of data clusters (cardiovascular, body composition, activity, workouts, sleep, mood, glucose, medication, mobility, environment) with a soft budget cap that degrades the lowest-signal clusters first. Every claim links back to the measurements that produced it. The Coach now reads your history longitudinally — a narrative of how a metric has moved and a per-metric trajectory framing — so an answer reflects the direction of travel rather than only the latest reading. Local endpoints keep all data on your network.
**Derived wellness metrics** -- A transparent metrics tier computed from your own measurements: a fitness-age band from VO₂max, a vascular-age delta, an HRV-balance band, a sleep score, a daily readiness and a stored recovery score, plus a coincident-deviation early-strain flag. Each one re-frames or blends signals you already recorded against age/sex norms or your personal baseline, cites the inputs and the published method behind it, and returns "not enough data" rather than a fabricated value when its minimum inputs are missing. The recovery score persists as a daily 0–100 series the dashboard, charts, and native client read without recomputing. These are descriptive wellness framings — not clinical or training-grade assessments.
@@ -106,6 +106,8 @@ HealthLog is the right fit when you want your wearable and clinical numbers in o
**Health-record export (PDF + FHIR R4)** -- `POST /api/export/health-record` produces a selectable export: an enriched clinical PDF, a machine-readable HL7 FHIR R4 document bundle (LOINC-coded observations, a BP panel, medication statements, a diagnostic report), or both packaged as one zip. The selection chooses date range and per-domain sections; both formats read the same aggregator so they describe identical numbers, and the optional AI summary is an explicit opt-in section marked as not clinically validated. Optional patient identity (name, insurer, insurance number) on Account feeds the report cover and the FHIR `Patient`.
**Clinician-grade FHIR API + shareable record** -- A read-only HL7 FHIR R4 REST API (`GET /api/fhir/{metadata,Patient,Observation,MedicationStatement,MedicationAdministration,$everything}`) lets a clinician system pull your record in a standard shape, gated behind a dedicated `fhir:read` token scope. Alongside it, a shareable clinician record: create a scoped, time-limited share link, hand a clinician the read-only `/c/<token>` view, and revoke it when the visit is over. The two are not yet wired together — a share link does not expose the FHIR API (`capabilities.share.fhirApi=false`) — but each stands on its own.
**Built-in Feedback** -- Send bug reports and feature requests from inside the app. Stored in your HealthLog database — no GitHub config required. Optional GitHub escalation for admins.
**PWA with Offline Support** -- Installable on iOS and Android. Service worker with intelligent caching strategies for reliable offline access. A paginated, opaque-cursor sync delta feed (`GET /api/sync/changes`) with measurement tombstones lets native and offline clients reconcile against the server incrementally.
@@ -213,6 +215,8 @@ HealthLog is designed for people who take data ownership seriously.
| `WITHINGS_CLIENT_SECRET` | Withings OAuth2 client secret |
| `WITHINGS_REDIRECT_URI` | OAuth callback URL |
| `WITHINGS_WEBHOOK_SECRET` | Webhook URL hardening secret |
| `WHOOP_REDIRECT_URI` | WHOOP OAuth callback URL (client id/secret set in Settings) |
| `WHOOP_WEBHOOK_SECRET` | HMAC secret for WHOOP webhook signature verification |
| `TELEGRAM_WEBHOOK_SECRET` | Telegram bot webhook secret |
Telegram bot token, ntfy settings, Web Push VAPID keys, Umami, and GlitchTip URLs are configured in the **Admin Panel** and stored encrypted in the database.
@@ -437,6 +441,24 @@ All mutations require authentication via session cookie. External ingest uses Be
</details>
<details>
<summary><strong>FHIR + clinician sharing (v1.11)</strong></summary>
Read-only HL7 FHIR R4 REST API, gated behind the `fhir:read` token scope:
| Method | Endpoint | Description |
| ------ | -------------------------------------- | ---------------------------------------------------- |
| `GET` | `/api/fhir/metadata` | FHIR CapabilityStatement |
| `GET` | `/api/fhir/Patient` | Patient resource for the token's user |
| `GET` | `/api/fhir/Observation` | Vitals + labs as LOINC-coded observations |
| `GET` | `/api/fhir/MedicationStatement` | Medication regimen |
| `GET` | `/api/fhir/MedicationAdministration` | Logged intake events |
| `GET` | `/api/fhir/$everything` | Bundle of every resource above |
Clinician share-link lifecycle — create a scoped, time-limited link, hand a clinician the read-only `/c/<token>` view, and revoke it after the visit. Share links do not expose the FHIR API (`capabilities.share.fhirApi=false`).
</details>
---
## Integrations
@@ -444,6 +466,7 @@ All mutations require authentication via session cookie. External ingest uses Be
| Integration | Setup | Purpose |
| --------------- | ------------- | ---------------------------------------- |
| **Withings** | Env vars | Auto-sync weight, BP, and activity |
| **WHOOP** | User Settings | OAuth2 sync of recovery, strain, and sleep (BYO-keys) |
| **Telegram** | Admin Panel | Medication reminders with inline buttons |
| **ntfy** | User Settings | Self-hosted push notifications |
| **Web Push** | Admin Panel | Browser-native VAPID notifications |
@@ -508,6 +531,8 @@ For a single-process default the same container hosts both the web and worker (`
| **v1.8 – v1.10** (current) | Insights redesign with selectable time ranges (v1.8 – v1.9), then a derived-metrics tier (v1.10): fitness-age, vascular-age delta, HRV balance, sleep score, readiness and recovery scores, and a coincident-deviation early-strain flag — each computed from your own measurements, each citing its inputs and degrading to "not enough data" rather than fabricating a value. |
| **v2.x** (planned) | Multi-tenant hardening, expanded device passthrough (Garmin / Polar), opt-in cross-user aggregate research mode (off by default; never enabled without explicit consent). |
Two known limitations carry forward from v1.11: dedup across two sources of the same standard vital is deferred (both rows persist, source priority decides display), and the coach's durable conversation-summary / remembered-facts layer is deferred — each answer reasons from your data afresh rather than from a running memory.
The detailed changelog lives in [`CHANGELOG.md`](CHANGELOG.md).
---
+71
View File
@@ -0,0 +1,71 @@
# WHOOP integration
HealthLog connects to WHOOP via OAuth2 plus HMAC-signed webhooks for
near-real-time sync of recovery, strain, sleep, and the underlying
heart-rate / HRV signals. Unlike Withings, WHOOP credentials are
brought per user: each user (or the operator on their behalf)
registers their own WHOOP developer app and pastes the client
id/secret into Settings.
## Why bring your own keys
WHOOP caps the number of users a single developer app may authorise.
Per-user BYO-keys sidestep that per-app cap: every user authorises
against their own WHOOP app, so a shared HealthLog instance never
runs into one app's user ceiling. It also keeps each user's WHOOP
grant scoped to credentials they control.
## 1. Register a WHOOP developer app
1. Sign in at <https://developer.whoop.com/> and create a new app.
2. **Redirect URI:** `https://your-instance.example.com/api/whoop/callback`
(matches `WHOOP_REDIRECT_URI`, below). Add one entry per
environment you connect from.
3. Request the scopes for recovery, cycle/strain, sleep, and the
workout/heart-rate reads the sync uses.
4. Save the app. WHOOP issues a **Client ID** and **Client Secret** —
keep the secret out of any chat log; it grants read access to the
linked account's full WHOOP history.
## 2. Wire the credentials in Settings → Integrations
WHOOP credentials are stored per user, not in `.env`. Sign in as the
target user, open **Settings → Integrations → WHOOP**, and paste the
client ID and secret. They are encrypted AES-256-GCM at rest.
## 3. Connect
Click **Connect WHOOP**. The flow:
1. The Connect button redirects to WHOOP's OAuth authorize endpoint.
2. The user signs in on WHOOP and grants the requested scopes.
3. WHOOP redirects back to `/api/whoop/callback` with an auth code.
4. HealthLog exchanges the code for an access + refresh token, stores
them encrypted, registers the webhook, and triggers an initial
sync of recent history.
5. Subsequent syncs run on every webhook delivery plus a safety-net
cron pull.
## Optional instance env vars
Two server-level env vars tune the OAuth callback and webhook
verification. They are optional — the per-user client id/secret in
Settings drive the connection itself.
```env
WHOOP_REDIRECT_URI="https://your-instance.example.com/api/whoop/callback"
WHOOP_WEBHOOK_SECRET="$(openssl rand -hex 32)"
```
- `WHOOP_REDIRECT_URI` — the OAuth callback URL handed to WHOOP. Must
match a redirect URI registered in the WHOOP developer app.
- `WHOOP_WEBHOOK_SECRET` — the secret HealthLog verifies the WHOOP
webhook HMAC signature against. WHOOP signs each webhook body; the
handler validates the signature before processing. Generate a fresh
random value and restart the `app` container after setting it.
## Disconnect
The Settings page **Disconnect** button revokes the stored tokens and
removes the connection. Existing measurements synced from WHOOP stay
in the database — disconnect does not delete data.
+14
View File
@@ -30,6 +30,20 @@ step 2 is redundant in normal Coolify flow. It's listed explicitly for
the manual case so an operator can verify the migration result before
flipping traffic.
### v1.11.0 notes
Migrations `0110`–`0114` are additive (new tables / columns for the
WHOOP provider hub, the longitudinal coach, and the clinician FHIR
record). They require no special steps — `prisma migrate deploy` runs
them in order on boot like any other.
WHOOP introduces two optional instance env vars, `WHOOP_REDIRECT_URI`
and `WHOOP_WEBHOOK_SECRET` (HMAC secret for WHOOP webhook signature
verification). Per-user WHOOP client id/secret live in Settings, not
in the environment. See `docs/integrations/whoop.md`. Add the two vars
to the compose `environment:` whitelist if you set them — vars not on
the whitelist never reach the container.
### What goes wrong if you reverse it
If the new app container starts before migrations run, the legacy
+1
View File
@@ -151,6 +151,7 @@ NEXT_PUBLIC_DASHBOARD_SNAPSHOT=false pnpm build
| Off-host encrypted backups to S3/R2/B2 | `docs/ops/backup-restore.md` |
| Encryption-key rotation procedure | `docs/ops/encryption-key-rotation.md` |
| Withings device sync | `docs/integrations/withings.md` |
| WHOOP device sync (per-user BYO-keys) | `docs/integrations/whoop.md` |
| Apple Health `export.zip` import | `docs/integrations/apple-health.md` |
| AI provider setup (OpenAI / Anthropic / local / ChatGPT OAuth) | `docs/integrations/ai-providers.md` |