Medication Statement
A MedicationStatement records a medication the patient is taking, has taken, or intends to take, as reported by the patient, a relative, or a clinician rather than actively prescribed or dispensed. Reach for it when you're capturing a medication history rather than driving an order. It aligns with the FHIR MedicationStatement resource.
The Django model stores medication and effective_period as opaque JSONFields. Their real structure comes from the Pydantic resource specs in care/emr/resources/medication/statement/, which define the enums, nested JSON shapes, validation, the value-set binding on medication, and the read/write API schemas. This page covers both layers.
Source:
- Model:
care/emr/models/medication_statement.py - Spec:
care/emr/resources/medication/statement/spec.py - Value set:
care/emr/resources/medication/valueset/medication.py
Models
| Model | Purpose |
|---|---|
MedicationStatement | A record of a medication the patient is taking, has taken, or intends to take — as reported rather than actively prescribed |
MedicationStatement extends EMRBaseModel, the shared Care EMR base that supplies external_id, audit fields created_by/updated_by, created_date/modified_date, history/meta JSON, and soft-delete semantics.
MedicationStatement fields
Type is the Django storage type. Spec shape is the structured shape the Pydantic resource spec enforces when reading and writing over the API.
Statement details
| Field | Type | Spec shape | Notes |
|---|---|---|---|
status | CharField(100) | MedicationStatementStatus enum | Required. See Status values. |
reason | CharField(100) | str | None | Optional. Why the medication is being taken. |
medication | JSONField (default=dict) | Coding bound to the system-medication value set | Required on create. A single SNOMED CT Coding, not a full CodeableConcept. See Coding shape and Bound value set. |
information_source | CharField(100) | MedicationStatementInformationSourceType enum | None | Optional. Who supplied the statement. See Information-source values. |
effective_period | JSONField (default=dict) | PeriodSpec | None | Optional. The period the medication is or was taken. See PeriodSpec shape. |
dosage_text | TextField | str | None | Optional, free-text dosage instructions. The spec flags that a structured Dosage may replace this later. |
note | TextField | str | None | Optional, free-text annotation. |
Relationships
| Field | Type | Notes |
|---|---|---|
patient | FK → Patient | on_delete=CASCADE. The patient the statement is about. Derived server-side from the encounter — never accepted from clients. |
encounter | FK → Encounter | on_delete=CASCADE. The encounter the statement was recorded in. Supplied as a UUID on create. |
patient → FK Patient (CASCADE)
encounter → FK Encounter (CASCADE)
Deleting a patient or encounter deletes its medication statements.
Enum values
MedicationStatementStatus values
MedicationStatementStatus(str, Enum) in spec.py, bound to status.
| Value | Meaning |
|---|---|
active | The medication is being taken |
on_hold | Taking has been paused |
completed | The course has finished |
stopped | The medication was stopped |
unknown | Status is not known |
entered_in_error | The record was entered in error |
not_taken | The patient is not taking the medication |
intended | The patient intends to take the medication |
MedicationStatementInformationSourceType values
MedicationStatementInformationSourceType(str, Enum) in spec.py, bound to information_source.
| Value | Meaning |
|---|---|
related_person | A relative or related person reported it |
practitioner | A clinician reported it |
patient | The patient self-reported it |
Nested JSON shapes
Coding shape
medication is stored as JSON but typed as a single Coding (from care/emr/resources/common/coding.py), not a CodeableConcept. The spec sets extra="forbid", so unrecognized keys are rejected.
| Field | Type | Notes |
|---|---|---|
system | str | None | Code system URI (e.g. http://snomed.info/sct) |
version | str | None | Code-system version |
code | str | Required. Validated against the bound value set |
display | str | None | Human-readable label |
Bound value set
medication is declared as ValueSetBoundCoding[CARE_MEDICATION_VALUESET.slug]. On write, code is validated against the system-medication value set (medication.py), which includes SNOMED CT concepts under the constraint << 763158003 |Medicinal product|. Any code outside the set is rejected.
The
care/emr/resources/medication/valueset/directory holds further value sets for other medication resources (route, body site, administration method, additional instruction, as-needed reason, medication-not-given reason).MedicationStatementbinds onlymedication→system-medication.
PeriodSpec shape
effective_period is stored as JSON but typed as PeriodSpec (from care/emr/resources/base.py).
| Field | Type | Notes |
|---|---|---|
start | datetime | None | Must be timezone-aware if present; naive datetimes are rejected |
end | datetime | None | Must be timezone-aware if present |
validate_period (mode after) rejects a naive start/end and requires start to be no greater than end.
Resource specs (API schema)
Every spec subclasses EMRResource (care/emr/resources/base.py), which provides serialize (DB object → pydantic) and de_serialize (pydantic → DB object). __model__ = MedicationStatement, and __exclude__ = ["patient", "encounter"] holds those two fields out of the generic field copy so the extra hooks set them instead.
| Spec class | Role | Fields exposed |
|---|---|---|
BaseMedicationStatementSpec | shared base | id, status, reason, medication, dosage_text, effective_period, encounter, information_source, note |
MedicationStatementSpec | write · create | Inherits BaseMedicationStatementSpec; adds encounter existence validation + extra deserialization |
MedicationStatementUpdateSpec | write · update | status, effective_period, note only |
MedicationStatementReadSpec | read · list & detail | Inherits base; adds created_by, updated_by (UserSpec), created_date, modified_date |
What the specs validate and maintain on your behalf:
encountermust exist —MedicationStatementSpec.validate_encounter_exists, afield_validatoronencounter, raises"Encounter not found"unless anEncounterwith thatexternal_idexists.patientis derived server-side —MedicationStatementSpec.perform_extra_deserializationruns only on create (is_updatefalse): it resolves theEncounterfrom the supplied UUID and setsobj.patient = obj.encounter.patient. Clients never sendpatient.- Updates are narrow —
MedicationStatementUpdateSpecaccepts onlystatus,effective_period, andnote.medication,reason,dosage_text,information_source, andencountercan't be changed through it. - Read serialization —
MedicationStatementReadSpec.perform_extra_serializationsetsid = obj.external_id,encounter = obj.encounter.external_id, and serializes audit users viaserialize_audit_users(created_by/updated_byasUserSpec). medicationvalue-set check — runs at validation time againstCoding.code(see Bound value set).effective_periodtz-awareness and ordering — enforced byPeriodSpec(seePeriodSpecshape).
There is no status_history. status is a single current value, not an appended log.
Methods & save behaviour
- From
EMRBaseModel:external_id(UUID), audit fields, soft delete. Deletes are soft — a removed statement stays in the table withdeleted=Trueand is hidden by the default manager. - Create flow: client sends
MedicationStatementSpec→de_serializecopies scalar/JSON fields →perform_extra_deserializationresolvesencounterand back-fillspatient→ model saved. - Read flow:
MedicationStatementReadSpec.serialize(obj)copies fields, thenperform_extra_serializationinjectsid,encounter(as UUIDs), and audit users.
API integration notes
- Exposed through Care's REST API, aligned with FHIR
MedicationStatement. Use the spec field names above for payloads. medicationis a single SNOMED CTCodingobject whosecodemust be in thesystem-medicationvalue set — not a free CodeableConcept, and not a scalar string.effective_perioddatetimes must be timezone-aware and ordered (start ≤ end).encountergoes in as a UUID;patientis derived from it server-side and must not be sent.- Updates flow through
MedicationStatementUpdateSpecand can touch onlystatus,effective_period, andnote. information_sourceis what separates a self-reported or third-party statement from an actively managed prescription (MedicationRequest).external_id(UUID),created_by/updated_by,created_date/modified_date,history, andmetaare platform-maintained — don't set them from clients.
Related
- Reference: Medication Request
- Reference: Medication Administration
- Reference: Medication Dispense
- Reference: Patient
- Reference: Encounter
- Reference: Base models & conventions
- Source (model): medication_statement.py
- Source (spec): statement/spec.py
- Source (value set): valueset/medication.py