Skip to content

18 PDF Export Architecture

Anubha Parashar edited this page Oct 10, 2026 · 1 revision

The record export produces a designed A4 medical dossier for a selected scope. It can come as a PDF summary only, as a PDF with every original report appended, or as a ZIP archive with the PDF, the originals and a manifest (internal code name: BabyCare). Every format is rendered from a single structured MedicalExportModel, so the PDF, the plain-text rendering and the ZIP manifest can never disagree. Statuses are copied from the records, never inferred. Generation happens in memory for each request, the response is sent with no-store, and each export is audited.

Responsibilities

  • Translate a preset or custom selection into a record scope, using the same resolver as Share With Doctor (share.resolve).
  • Arrange the selected records into presentation sections with provenance labels and Source Rnn · p.N references.
  • Render a ReportLab body (cover, executive summary, contents with page numbers, sections, vector charts), then append the original documents unchanged with PyMuPDF.
  • Link every source reference to its appendix page, link every appendix page back to the summary, add bookmarks and a "Page X of Y" footer.
  • Stream the result once. Nothing is written to disk.

Inputs and outputs

Endpoint Purpose
GET /api/export/presets Presets, sections and the three modes
POST /api/patients/{pid}/export/preview Title, file name, date range, sections, counts, estimated pages, source list
POST /api/patients/{pid}/export/download PDF (summary, with_sources) or ZIP (archive)
GET /api/patients/{pid}/export/download?… Same request as query parameters, for the mobile download-and-share flow
GET /api/patients/{pid}/export/debug.txt Plain-text rendering of the same model (support use)

Presets (EXPORT_TYPES): complete, ecoli (condition-specific history resolved from documented culture and episode links), latest_hospitalization, antibiotics, labs, vaccination, daily_care_7d and custom. A custom export takes sections (a subset of summary, timeline, illnesses, hospitalizations, medicines, antibiotics, labs, cultures, vaccinations, growth, daily_care, sources, plus prescriptions and stool_photos), date_from/date_to (validated to be in order), include_sources, include_photos and daily_care_days (1–365). Unknown sections return 422.

Pipeline

flowchart TD
  A["Selected record scope<br/>preset or custom sections and dates"] --> B["export_model.spec_for<br/>to share.resolve"]
  B --> C["Export model<br/>MedicalExportModel"]
  C --> D["Clinician summary<br/>cover, executive summary, contents"]
  C --> E["Structured sections<br/>timeline, medicines, labs, vaccinations, daily care"]
  E --> F["Source references<br/>Source Rnn and page"]
  C --> G["Original-report appendix<br/>divider page plus unchanged pages"]
  D --> H["ReportLab body<br/>two or three pagination passes"]
  E --> H
  F --> H
  H --> I["PyMuPDF finish<br/>GOTO links, back links, bookmarks, Page X of Y"]
  G --> I
  I --> J["Private temporary PDF<br/>in memory, no-store"]
  J --> K["User download<br/>web blob or mobile cache file"]
  J --> L["Doctor share<br/>ZIP archive or share package"]
Loading

The export model

build_model() returns a JSON-serialisable dict (schema: babycare-export-1) with patient, meta, sources, summary, timeline, illnesses, hospitalizations, medicines, antibiotics, labs, trends, same_day_conflicts, cultures, susceptibility_matrix, vaccinations, growth, daily_care, vitals and counts.

  • Sources are the documents in the resolved selection, ordered as in the package. Each gets an appendix letter (A, B, … AA) and a reference R{document id}.
  • Labs are grouped by category (haematology, inflammation, stool, liver, kidney/electrolytes, nutrition, thyroid, immunology, microbiology, other). Each row keeps its own printed reference interval, and values outside it are marked "outside printed interval". Trends need at least two numeric points with compatible units. Same-day duplicate values are listed as conflicts.
  • Daily care comes from care.analytics and care.care_export (see Daily Health).
  • Pending items lists every unverified lab, culture, medicine, vaccine or fever row, in its own section "Items pending verification".

Status labels per row

Domain Labels Rule
Medicines PRESCRIBED, PRESCRIBED (unverified transcription), SCHEDULED, CONFIRMED GIVEN, STOPPED CONFIRMED GIVEN only when a given medication event exists. A prescription is never shown as given
Antibiotic courses PRESCRIBED, SCHEDULED, CONFIRMED GIVEN, CONFIRMED GIVEN (hospital statement, no dated chart), UNKNOWN, NOT RECEIVED copied from administration_status
Vaccinations status from vaccine_status a due date is never shown as a given date (test_vaccination_due_date_is_not_a_given_date)
Cultures S / R / I matrix with a "named in a prescription or course" column that column does not mean given. Susceptibility is never a medicine

Labels appear as coloured chips (STATUS_COLOURS). The Medications section opens with a status legend, and every body page carries the footer "statuses are copied from source documents, never inferred".

Sections

Cover → Executive clinical summary → Contents → Medical timeline → Hospitalizations → Illness episodes → Medications → Antibiotic history (with a course Gantt: solid = administration documented, dashed = prescribed or scheduled only) → Laboratory results (with trend charts) → Microbiology → Vaccinations → Growth → Daily Health journal (with a care chart) → Items pending verification → Original source documents (index) → Appendix.

Rendering details

  • Fonts: DejaVu Sans and DejaVu Serif (regular, bold, oblique) are bundled in api/assets/fonts and registered as BCSans / BCSerif, so non-Latin-1 text renders correctly. If the files are missing, the code falls back to the base-14 Helvetica/Times fonts (Latin-1 only). The appendix bands use the same TTF through PyMuPDF.
  • Pagination: the body is built once to learn its page numbers, then up to three more times until the contents page numbers and appendix page map stop changing.
  • Appendix: each source gets a divider page (APPENDIX X, title, reference, date, category, pages). PDFs are inserted as vector pages, never rasterised, and images at their original resolution. Header and footer bands sit outside the document area. Unreadable files get a placeholder page that points to the archive export.
  • Links: internal bcsrc: links become LINK_GOTO targets. Every appendix page gets "‹ Back to summary".
  • Determinism: invariant=1, fixed metadata dates and no_new_id make the output byte-identical for identical input (test_generation_is_in_memory_and_deterministic).
  • Archive mode: the ZIP contains Medical_Record.pdf, Original_Reports/<date_title_ref>.<ext>, manifest.csv (file, appendix, reference, date, category, title, pages, mime, appendix page) and Record_Summary.txt, all with a fixed ZIP timestamp. File names carry no database ids.

Clients

  • Web (web/exports.js): a "Download / Share" page with preset tiles, section checkboxes, mode choice and preview. The download is fetched as a blob, saved through an object URL, and the URL is revoked after 60 s.
  • Mobile (mobile/ExportScreen.tsx): FileSystem.downloadAsync into the app cache directory with the bearer header, then the native share sheet (expo-sharing). The cached file is deleted afterwards.

Security boundaries

Only a guardian of the profile can preview or export (own_patient). Other families get 404, and anonymous requests get 401. Each download writes two audit rows: the mode, and a detail row with preset, page count, appended sources and a 12-character SHA-256 prefix of the output. Responses send Cache-Control: no-store, private and nosniff. Stool photographs are included only on explicit request. Storage names and session tokens never appear in the output.

Clinical safety rules

Prescribed ≠ scheduled ≠ given. Culture susceptibility ≠ antibiotic received. Vaccination due date ≠ administered. OCR-extracted rows stay labelled until verified, and every statement keeps a reference to its original document.

Implementation

api/export_api.py, api/export_model.py, api/export_pdf.py, api/assets/fonts/, api/share.py (resolve, entries_for), api/care.py, web/exports.js, mobile/ExportScreen.tsx, tests/test_export.py.

Related pages

Share With Doctor Architecture · Medicines and Administration Model · Vaccination Model · Labs, Growth and Longitudinal Health · OCR and Human Verification Workflow · Limitations

Clone this wiki locally