Repository navigation
13 Medicines and Administration Model
HealthBook AI (internal code name: BabyCare) stores three different facts about a medicine in three different places: what a clinician wrote, what the app was told to remind about, and what a person actually gave. The data model, API and UI are built so that one can never stand in for another. The app records medication history. It never prescribes, suggests doses or changes treatment.
| Concept | Stored as | Created by | What it proves | What it does not prove |
|---|---|---|---|---|
| Prescribed |
Medicine course (medicines) |
caregiver entry or a confirmed document review | what was written, by whom, on which page | that any dose was given |
| Scheduled |
schedule_times + start_date/end_date on a verified course, expanded into reminders and daily due slots |
caregiver verification against the prescription | that a reminder or due slot exists | that the dose was given; a delivered notification is not a dose |
| Given (home) |
MedicationEvent (given/skipped/stopped) or Daily Health MedicationAdministration
|
an explicit caregiver action | that the caregiver reported this outcome at this time | clinical correctness |
| Given (hospital) |
TreatmentAdministration linked to a hospital visit |
caregiver copying a hospital administration record, with the document attached | that the hospital record documents administration | n/a |
Related rules: appointment/attendance ≠ treatment administered, and a notification/reminder is never proof a dose was given.
Diagram: Three separate medicine concepts and their links
flowchart LR
subgraph P["Prescribed"]
DOC["Source document and page"] --> MED["Medicine course, draft"]
MED -->|"guardian verifies against prescription"| MV["Medicine course, verified"]
MV -->|"any revision"| MED
end
subgraph S["Scheduled"]
MV -->|"schedule times plus start and end"| REM["Reminders and due slots"]
REM --> NTF["Notification delivered"]
end
subgraph G["Given"]
EV["MedicationEvent given or skipped or stopped"]
DC["Daily Health administration record"]
TA["Hospital TreatmentAdministration"]
end
MV -->|"caregiver logs outcome"| EV
REM -.->|"slot matched, never auto-filled"| DC
VIS["Hospital visit attended"] -->|"plus attached chart"| TA
NTF -.->|"is not proof of a dose"| EV
POST /api/patients/{pid}/medicines always creates a course with status='draft'. Fields separate the written plan from actual dates:
| Group | Fields |
|---|---|
| Written |
name, generic_name, brand_name, dosage, dose_unit, route, frequency, instructions, prescribing_doctor, indication, prescription_date
|
| Planned |
start_date, end_date, schedule_times (up to 8 unique HH:MM times, sorted) |
| Actual |
actual_start_date, actual_stop_date (never in the future) |
| Provenance |
source_document_id, source_page (checked by safe_doc) |
course_type |
prescription, discharge_prescription, inpatient_treatment, historical_prescription
|
Verification (POST /api/medicines/{id}/confirm) requires checked_against_prescription: true, non-empty instructions, a linked source document, and exact start and end dates if reminder times are set. It sets status='verified' and verified_at.
Revision (PATCH /api/medicines/{id}) re-validates the merged course, stores a MedicineRevision snapshot, increments version, and resets the course to draft with verified_at=null. A revised course stops generating reminders until it is re-verified. Reminder keys include the version (med-{id}-v{version}-...), so old reminders cannot survive a change. Changing the profile's timezone also returns every verified course to draft.
Stop (POST /api/medicines/{id}/stop) records the actual stop date and a reason, writes a stopped event, and sets status='discontinued'.
POST /api/medicines/{id}/events:
- refuses with 409 if the course is not
verified("Cannot record administration against an unverified or revised draft"); - is idempotent:
(medicine_id, idempotency_key)is unique, and a repeated key returns the original event; - accepts only naive local clock times in the profile timezone and rejects times more than 5 minutes in the future;
- with
source='copied_from_hospital_document', requires the hospital record to be attached; - stores
medicine_versionso the event is tied to the exact prescription version.
PATCH /api/hospital-visits/{id} changes only the appointment status (planned, attended, postponed, cancelled). In code, attended means visited the facility, not that any medication was infused. A documented dose is a separate TreatmentAdministration (POST /api/hospital-visits/{vid}/administrations). It requires the source document, checked_administration_record: true, a past time after birth, and a globally unique idempotency key.
The Daily Health journal (api/care.py, api/care_api.py) keeps MedicationAdministration rows with status given, skipped, missed, later or unknown. A row may reference a course or carry only a typed name.
-
Due slots are computed only from verified courses that have
schedule_times, within the planned dates and before any actual stop. -
medicine_day()matches a record to a slot byscheduled_for(within 60 s) or by time (within 3 h). A slot with no record showsno_record, never "given". - The schedule card's GIVEN / SKIP / LATER buttons (and MISSED via the API) call
POST /api/patients/{pid}/care/medicine/quick. The API docstring states it is "never called automatically".client_keymakes repeated taps idempotent. - Counts and adherence tables report "a given record exists for N of M scheduled slots". These are observational statements, not adherence judgements.
GET /api/patients/{pid}/medication-history lists every course with its events, a display_period (prescribed, planned and actual dates) and missing-information flags:
| Flag | Condition |
|---|---|
| prescription date not documented | no prescription or start date |
| actual start date unknown / actual end date unknown | actual dates absent (end ignored if discontinued) |
| no administration events recorded | no given event (non-inpatient courses) |
| inpatient dates and times not in supplied records | course_type='inpatient_treatment' |
| prescription not yet verified | status='draft' |
| no source document attached | no source_document_id
|
antibiotic_list_match is true when the name text contains a word from a fixed list. The response states this "is not a clinical classification". Courses are grouped by normalised generic name. Antibiotic exposure metrics are a separate model with their own administration_status, described in Illness History and Lifetime Health.
- Prescribed ≠ Scheduled ≠ Given/Administered.
- Unverified or revised drafts generate no reminders and accept no administration events.
- No dose, frequency or schedule is derived from handwriting (
dose_and_frequency: not_parsed). - AI never prescribes or changes doses.
api/main.py (MedicineIn, add/confirm/revise/events, visits), api/workflows.py (stop, administrations, timezone reconciliation), api/reminders.py, api/care.py (due_slots, medicine_day), api/care_api.py (quick_dose), api/care_models.py, api/history_api.py (medication_history, missing_info), web/care.js.
Daily Health and Care Calendar · Notifications and Reminder Architecture · OCR and Human Verification Workflow · Illness History and Lifetime Health · Design Decisions
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