# Doctor Report The doctor report is a printable PDF summary of a user's tracked vitals, medication compliance, glucose context, and mood over a configurable window (default 90 days, max 365). It is intended as discussion material for a doctor's appointment, **not** a medical diagnosis. ## Two endpoints The same aggregation logic backs both endpoints — `collectDoctorReportData` in `src/lib/doctor-report-data.ts` is the single source of truth — so the JSON payload and the server-rendered PDF are guaranteed to be in sync. ### `POST /api/doctor-report` — JSON payload Returns the aggregated `DoctorReportData` envelope. Used by the PWA's client-side renderer (`src/lib/doctor-report-pdf.ts`, jsPDF + autotable). Best on desktop browsers and on Android, where local generation keeps the PDF off the wire and works offline (cached page). - Auth: session required. - Rate-limit: 10/h per user. - Body: `{ days?: number }` (default 90). - Response: `200 application/json` with envelope `{ data, error: null }`. - Audit action: `doctor-report.generate`. ### `POST /api/doctor-report/pdf` — server-rendered PDF Returns the ready-to-download PDF bytes. Used primarily on iOS/Safari, where the client-side jsPDF download UX is unreliable (Safari sometimes opens the PDF in a new tab without preserving the filename or fails the blob save). - Auth: session required (`401` otherwise). - Rate-limit: 10/h per user, shared with the JSON endpoint (`429` on excess). - Body: `{ days?: number; locale?: "de" | "en" }`. Body is optional — empty bodies are accepted. - Locale resolution: explicit `body.locale` wins, then the `Accept-Language` header (`de*` → `de`, anything else → `en`), then the hard fallback `de`. - Response: `200 application/pdf` with `Content-Disposition: attachment; filename="healthlog-report-YYYY-MM-DD.pdf"` and `Cache-Control: no-store`. Body is the raw PDF bytes. - Errors: JSON envelope `{ data: null, error: "..." }` for `401` and `429`; `500` if rendering fails. - Audit action: `doctor-report.pdf.generate`. ## Wellness summary (derived scores) Since v1.10.0 the report carries a **Wellness summary** section below the clinical content, holding the server-derived wellness scores (Recovery, Stress, Strain) and the other derived metrics where present. It is kept deliberately separate from the clinical vitals table: - Each entry is labelled **descriptive, not a clinical assessment**, and the scores never enter the vitals table or the clinical aggregations. - The Stress score is shown as an HRV-derived proxy, not a sensor-measured stress value. - In the FHIR export the same scores are emitted as `survey`-category `Observation`s (text-only `code`, no fabricated LOINC) with a per-Observation non-clinical note — so a downstream system never mistakes them for a vital sign or a diagnosis. Each derived metric states its inputs, method, and the cited standard it is grounded in (the same provenance the in-app explainer shows). ## Implementation notes - `src/lib/doctor-report-pdf-core.ts` contains the isomorphic renderer. jsPDF runs unchanged in Node — `doc.output("arraybuffer")` returns a valid `%PDF-` byte stream in both environments, so we deliberately avoid headless Chromium (Puppeteer/Playwright) to keep the runtime dependency surface low. - `src/lib/doctor-report-pdf.ts` is a thin client façade that returns the raw `jsPDF` instance so the existing settings-page download flow (which calls `doc.save(...)`) keeps working unchanged. - Server-side translations come from `messages/{de,en}.json` via `getServerTranslator()` (`src/lib/i18n/server-translator.ts`).