Repository navigation
19 Share With Doctor Architecture
Share With Doctor lets a guardian hand a clinician exactly the records they choose and nothing else (internal code name: BabyCare). The guardian can share in three ways: download a ZIP package, download a clinician PDF summary, or create an expiring, revocable doctor link that may be PIN-protected. The doctor opens the link in a public page (web/doctor.html) that needs no account. That page can reach only the documents in the package, addressed by their position in the package, never by database id.
- Resolve a selection (preset, condition, categories, dates, manual list or complete) using documented links only, and show the exact document list before anything is generated.
- Build packages whose file names contain only date, title and bundle reference: no database ids, storage names or tokens.
- Label every summary statement with its provenance.
- Issue links with a random token (only its SHA-256 is stored), an optional PBKDF2-hashed PIN, a fixed expiry and revocation.
- Log every access without clinical content.
| Endpoint | Caller | Purpose |
|---|---|---|
GET /api/share/presets |
guardian | presets, categories, expiry choices |
POST /api/patients/{pid}/share/preview |
guardian | document list, record counts, unverified-row count, warnings |
POST or GET …/share/package.zip
|
guardian | ZIP package (GET takes a base64url spec for mobile share sheets) |
POST or GET …/share/summary.pdf
|
guardian | clinician summary PDF |
POST …/share/links |
primary guardian | create a link. Returns the token once and the path /doctor.html#t=<token>
|
GET …/share/links, GET /api/share/links/{sid}/log
|
guardian | link list with status, access log |
DELETE /api/share/links/{sid} |
primary guardian | revoke |
GET /api/share/public/{token} |
doctor | package metadata and document list |
GET /api/share/public/{token}/document/{n} |
doctor | the n-th package document |
GET /api/share/public/{token}/summary.pdf, …/package.zip
|
doctor | summary, or ZIP when downloads are allowed |
Scope (ShareSpec): mode is one of complete, condition, categories, dates, manual or preset; presets are ecoli, latest_hospitalization, antibiotics, latest_labs, vaccination and complete. The spec also accepts episode_ids, categories (validated against CATEGORY_CHOICES), date_from/date_to, explicit document_ids and exclude_document_ids, include_pending and daily_care_days (≤ 90). Link policy (LinkIn): expiry of 1h, 24h, 3d or 7d; a pin of 4–12 digits; view_only; allow_download.
flowchart TD
O["Owner<br/>primary guardian"] --> S["Select scope<br/>preset, condition, categories, dates, manual"]
S --> PV["Preview<br/>exact document list"]
PV --> G["Generate package<br/>resolved selection stored encrypted"]
G --> T["Expiring token<br/>256-bit, only SHA-256 stored"]
T --> P{"Optional PIN"}
P -->|"set"| PH["PBKDF2 hash with salt"]
P -->|"not set"| L["Link sent<br/>doctor.html with token in fragment"]
PH --> L
L --> D["Doctor opens link"]
D --> CHK{"Token valid, not revoked,<br/>not expired, PIN correct"}
CHK -->|"no"| DEN["404 or 410 or 401 or 429<br/>logged as denied, expired_hit, pin_failed"]
CHK -->|"yes"| SC["Scoped access<br/>documents by package index only"]
SC --> AU["Audit<br/>access log and counter"]
AU --> R["Revoke<br/>revoked_at set, later hits get 410"]
O --> R
-
secrets.token_urlsafe(32)makes the token.SharePackage.token_hashholdssha256(token), and the plaintext is returned once in the create response. The link list never includes it ('token' not in listing[0]). - The doctor page reads the token from the URL fragment (
#t=), which browsers do not send with the page request. The page then calls/api/share/public/{token}, so the token does appear in API request paths. - The PIN is hashed with
security.hash_password(PBKDF2-HMAC-SHA256, 390,000 iterations) and a 16-byte random salt. It is compared withhmac.compare_digestand sent in theX-Share-Pinheader, never in the URL. - After 8
pin_failedevents within 15 minutes, the link answers429until the window passes. - Tokens shorter than 20 or longer than 120 characters return
404without a database lookup.
expires_at = creation time + 1, 24, 72 or 168 hours. _open_package checks revocation first (410, logged denied), then expiry (410, logged expired_hit), then the PIN. package_json reports active, expired or revoked. Only the primary guardian (patient.user_id == user.id) can create or revoke links; caregivers with shared access cannot.
entries_for() numbers the selected documents 1..N and names each one Folder/<record_date>_<title-slug>[_<bundle ref>].<ext>, with folders Labs, Imaging, Prescriptions, Hospital, Vaccinations and Other. The public endpoint /document/{n} looks up that index inside the stored selection. An index outside the package returns 404 Not in this package. The public JSON has no id field and no storage names, and the tests check this explicitly. A doctor's token gives no access to the authenticated /api/patients/... or /api/documents/... routes (401).
build_zip() writes the following under HealthBook_Share_<date>/:
| Entry | Content |
|---|---|
<Folder>/<file> |
decrypted originals of the selected documents only |
Doctor_Summary.pdf |
clinician summary (when include_summary) |
INDEX.html |
table of files with a noindex,nofollow meta tag |
manifest.csv |
file, folder, date, title, type, bundle ref, pages, SHA-256 of each original |
build_summary_pdf() ("Medical Summary for Clinician") opens with a legend, and every line carries one of these labels plus the package file it comes from:
| Label | Meaning |
|---|---|
DOCUMENTED |
on a clinician document, or verified against the original page |
PARENT REPORTED |
entered by the caregiver |
OCR UNVERIFIED |
transcribed, not yet checked |
PRESCRIBED |
a prescription or discharge plan; not proof of administration |
CONFIRMED ADMINISTERED |
a dated administration record exists |
UNKNOWN |
administration not documented either way |
PENDING VERIFICATION |
a lifetime-health row awaiting the caregiver check |
REFERENCE ONLY, NOT RECEIVED (culture panel entry)
|
reference schedules, and susceptibility entries that are not medicines |
The daily-care section comes from care.care_export, and each of its lines is labelled PARENT REPORTED. The summary PDF is drawn with PyMuPDF base fonts, and its text is passed through _latin(), which replaces characters outside Latin-1.
ShareAccessLog events: created, opened, document, package, summary, pin_failed, revoked, expired_hit, denied. Each row stores the time, a SHA-256 hash of the client IP, and a short policy-only detail (for example "2 documents; expiry 24h; pin yes"). Tests confirm that no profile name or condition ever appears in the log. Successful opens increment access_count and set last_accessed_at. Guardian actions are also written to the main Audit table.
Public responses set X-Robots-Tag: noindex, nofollow, noarchive, Cache-Control: no-store and Referrer-Policy: no-referrer. In view_only mode (or when downloads are off), documents are served inline and package.zip returns 403. scope_json, label and reason are stored with EncryptedText.
web/share.js and mobile/ShareScreen.tsx provide scope selection, preview, link creation, the link list with status and access log, and revoke. The mobile app copies the link with expo-clipboard or sends it through the native share sheet. web/doctor.js renders the doctor page: it asks for the PIN on 401, shows the error text on 404/410, and fetches files as blobs so the PIN header can be attached.
api/share.py, api/share_models.py, api/security.py (hash_password), api/export_model.py (shares resolve), web/share.js, web/doctor.html, web/doctor.js, mobile/ShareScreen.tsx, tests/test_share.py.
PDF Export Architecture · Security and Privacy Model · Authentication and Session Security · Encryption and Private Storage · API Design · Limitations
Overview
Platform
Identity and access
Medical records
- 10 Document Ingestion
- 11 OCR and Human Verification
- 13 Medicines and Administration
- 14 Vaccination Model
- 15 Labs, Growth, Longitudinal
- 16 Illness and Lifetime Health
Daily care
Output and sharing
Security
Engineering
- 26 Testing and Validation
- 27 Deployment and CI/CD
- 28 Technology Stack
- 29 Design Decisions
- 30 Limitations
- 31 Roadmap