3.6 KiB
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/jsonwith 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 (
401otherwise). - Rate-limit: 10/h per user, shared with the JSON endpoint (
429on excess). - Body:
{ days?: number; locale?: "de" | "en" }. Body is optional — empty bodies are accepted. - Locale resolution: explicit
body.localewins, then theAccept-Languageheader (de*→de, anything else →en), then the hard fallbackde. - Response:
200 application/pdfwithContent-Disposition: attachment; filename="healthlog-report-YYYY-MM-DD.pdf"andCache-Control: no-store. Body is the raw PDF bytes. - Errors: JSON envelope
{ data: null, error: "..." }for401and429;500if 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-categoryObservations (text-onlycode, 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.tscontains 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.tsis a thin client façade that returns the rawjsPDFinstance so the existing settings-page download flow (which callsdoc.save(...)) keeps working unchanged.- Server-side translations come from
messages/{de,en}.jsonviagetServerTranslator()(src/lib/i18n/server-translator.ts).