Medication Request
A MedicationRequest records a prescriber's intent to supply or administer a medication to a patient — the FHIR MedicationRequest resource. You create one whenever a drug is ordered during an encounter; it carries the medication, dosage instructions, timing, and route as structured JSON, grouped under a prescription.
Source:
- Model:
care/emr/models/medication_request.py - Spec:
resources/medication/request/spec.py - Spec:
resources/medication/request_prescription/spec.py - Value sets:
resources/medication/valueset/
Several fields are opaque JSONFields whose structure the Django model never declares. The contract lives in the Pydantic resource specs under care/emr/resources/medication/request/: the enums, the nested shape of those JSON fields, validation rules, the read/write schema split, and the server-side side effects. The tables below merge both layers.
Models
| Model | Purpose |
|---|---|
MedicationRequestPrescription | Groups medication requests authored together for an encounter (a prescription) |
MedicationRequest | A single medication order/request for a patient within an encounter |
Both extend EMRBaseModel, which supplies external_id, created_date/modified_date, soft-delete via deleted, created_by/updated_by, and history/meta JSON.
MedicationRequest fields
Order status & intent
| Field | Type | Required | Notes |
|---|---|---|---|
status | CharField(100) → MedicationRequestStatus | Yes (spec) | Nullable on the model, required by the spec; defaults to active. See status values |
status_reason | CharField(100) → StatusReason | None | No | Why the request was discontinued or changed. See status reason values |
intent | CharField(100) → MedicationRequestIntent | Yes (spec) | Nullable on the model, required by the spec. See intent values |
category | CharField(100) → MedicationRequestCategory | Yes (spec) | Nullable on the model, required by the spec. See category values |
priority | CharField(100) → MedicationRequestPriority | Yes (spec) | Nullable on the model, required by the spec. See priority values |
do_not_perform | BooleanField → bool | Yes | Set True to record that the medication must explicitly not be given |
dispense_status | CharField(100) → MedicationRequestDispenseStatus | None | No | Defaults to None. Tracks dispense progress. See dispense status values |
Medication & dosage
| Field | Type | Required | Notes |
|---|---|---|---|
medication | JSONField → ValueSetBoundCoding[system-medication] | None | Conditional | Model default {}. A single Coding { system, code, display } bound to the Medication value set (SNOMED CT medicinal products). Set exactly one of medication / requested_product |
method | JSONField | n/a | Model default {}. Stored but not exposed by any current spec — administration method travels per dosage line via dosage_instruction[].method |
dosage_instruction | JSONField → list[DosageInstruction] | Yes | Model default []. The structured dosage lines. See DosageInstruction shape |
note | TextField → str | None | No | Free-text note |
Subject & provenance
| Field | Type | Required | Notes |
|---|---|---|---|
patient | FK → Patient | server-set | CASCADE. Ignored if sent by the client — derived from encounter.patient |
encounter | FK → Encounter | Yes (create) | CASCADE. Write spec takes encounter as a UUID and validates that it exists |
authored_on | DateTimeField → datetime | Yes (spec) | Must be tz-aware. Model default timezone.now when omitted |
requester | FK → users.User | No | SET_NULL. The prescriber; write spec takes a requester UUID |
requested_product | FK → ProductKnowledge | Conditional | SET_NULL, default None. Write spec takes a requested_product UUID. Set exactly one of medication / requested_product |
prescription | FK → MedicationRequestPrescription | No | SET_NULL, default None. Set via a prescription UUID or create_prescription (see validation) |
Enum values
Spec enums use underscored member values (on_hold, entered_in_error, original_order), not the hyphenated FHIR spellings. These are the exact strings the API accepts and returns.
MedicationRequestStatus values
| Value |
|---|
active |
on_hold |
ended |
stopped |
completed |
cancelled |
entered_in_error |
draft |
unknown |
StatusReason values
| Value | Meaning (FHIR) |
|---|---|
altchoice | Alternative choice |
clarif | Prescription requires clarification |
drughigh | Drug level too high |
hospadm | Admission to hospital |
labint | Lab interference issues |
non_avail | Patient not available |
preg | Patient is pregnant or breastfeeding |
salg | Allergy |
sddi | Drug interaction |
sdupther | Duplicate therapy |
sintol | Suspected intolerance |
surg | Patient scheduled for surgery |
washout | Washout |
MedicationRequestIntent values
| Value |
|---|
proposal |
plan |
order |
original_order |
reflex_order |
filler_order |
instance_order |
MedicationRequestPriority values
| Value |
|---|
routine |
urgent |
asap |
stat |
MedicationRequestCategory values
| Value |
|---|
inpatient |
outpatient |
community |
discharge |
MedicationRequestDispenseStatus values
| Value |
|---|
complete |
partial |
incomplete |
declined |
TimingUnit values
UCUM time units used by Timing.repeat.period_unit and bounds_duration.unit.
| Value | Unit |
|---|---|
s | second |
min | minute |
h | hour |
d | day |
wk | week |
mo | month |
a | year |
DoseType values
Used by DoseAndRate.type.
| Value |
|---|
ordered |
calculated |
DosageInstruction nested spec
Each entry in the dosage_instruction JSON list deserializes to a DosageInstruction (Pydantic, snake_case keys) — the structure hiding behind the opaque JSONField.
| Field | Type | Required | Notes |
|---|---|---|---|
sequence | int | None | No | Order of the instruction |
text | str | None | No | Free-text dosage instruction (SIG) |
additional_instruction | list[Coding] | None | No | Bound to the Additional Instruction value set |
patient_instruction | str | None | No | Patient-facing instructions |
timing | Timing | None | No | See Timing below |
as_needed_boolean | bool | Yes | Set True for PRN orders |
as_needed_for | Coding | None | No | Reason for PRN; bound to the As Needed value set |
site | Coding | None | No | Bound to the Body Site value set |
route | Coding | None | No | Bound to the Route value set |
method | Coding | None | No | Bound to the Administration Method value set |
dose_and_rate | DoseAndRate | None | No | See DoseAndRate below |
max_dose_per_period | DoseRange | None | No | Upper dose limit per period |
Sub-types under DosageInstruction
Timing {
repeat: TimingRepeat # required
code: Coding | None # e.g. BID/TID/QID timing abbreviation
}
TimingRepeat {
frequency: int # required — repetitions per period
period: Decimal(20,0) # required — duration the frequency applies to
period_unit: TimingUnit # required — s|min|h|d|wk|mo|a
bounds_duration: TimingQuantity # required — total span
}
TimingQuantity { # integer-valued time amount
value: Decimal(20,0) # required
unit: TimingUnit # required
}
DoseAndRate {
type: DoseType # required — ordered | calculated
dose_range: DoseRange | None # for titrated doses
dose_quantity: DosageQuantity | None # for regular doses
}
DoseRange {
low: DosageQuantity # required
high: DosageQuantity # required
}
DosageQuantity { # measured dose amount
value: Decimal(20,6) # required
unit: Coding # required
}
The Pydantic specs define these JSON keys as snake_case (as_needed_boolean, period_unit, dose_and_rate, max_dose_per_period, bounds_duration), so that is how they sit in storage — not the camelCase FHIR wire format.
Bound value sets
Coded fields bind to Care value sets through ValueSetBoundCoding[<slug>]. On write, the supplied Coding.code is validated against the value set; a code outside it is rejected. All draw from SNOMED CT (http://snomed.info/sct).
| Field | Value set slug | SNOMED CT scope |
|---|---|---|
medication | system-medication | << 763158003 Medicinal product |
dosage_instruction[].route | system-route | is-a 284009009 Route of administration |
dosage_instruction[].site | system-body-site | is-a 91723000 Anatomical structure |
dosage_instruction[].method | system-administration-method | is-a 736665006 |
dosage_instruction[].as_needed_for | system-as-needed-reason | is-a 404684003 Clinical finding |
dosage_instruction[].additional_instruction | system-additional-instruction | is-a 419492006 |
system-medication-not-given lives in this directory too, but it binds on Medication Administration, not on the request.
Resource specs (API schema)
Every spec extends EMRResource, which provides serialize / de_serialize plus the perform_extra_serialization / perform_extra_deserialization hooks. MedicationRequestResource sets __exclude__ = [patient, encounter, requester, requested_product, prescription], so those relations are wired up by hand instead of auto-mapped.
| Spec class | Role | Notes |
|---|---|---|
MedicationRequestAbstractSpec | shared (fields) | Plain BaseModel holding the clinical fields: status, status_reason, intent, category, priority, do_not_perform, medication, dosage_instruction, authored_on, note, dispense_status |
MedicationRequestResource | shared (binding) | Binds the spec to the MedicationRequest model and declares __exclude__ |
BaseMedicationRequestSpec | shared | MedicationRequestResource + MedicationRequestAbstractSpec + id: UUID4 |
MedicationRequestSpec | write · create | Adds encounter (UUID, required), requester, requested_product, prescription (UUIDs), and create_prescription. Carries the validators and perform_extra_deserialization |
MedicationRequestUpdateSpec | write · update | Narrow — only status, note, dispense_status are mutable after create |
MedicationRequestReadWithoutPrescriptionSpec | read · embedded | Full read minus prescription; expands requested_product (ProductKnowledgeReadSpec), requester/created_by/updated_by (UserSpec), plus created_date/modified_date |
MedicationRequestReadSpec | read · detail/list | Adds expanded prescription (MedicationRequestPrescriptionReadSpec) and the encounter external id |
CreatePrescription | write · nested | { name?, note?, alternate_identifier (required) } — inline prescription creation |
Validation (create — MedicationRequestSpec)
- Medication XOR product: you cannot set both
medicationandrequested_product, and one is required. - Prescription XOR create: you cannot set both
prescriptionandcreate_prescription. - Encounter existence: the
encounterUUID must match an existingEncounter(field validator). authored_on, and any nestedPeriodSpec/timing datetimes, must be timezone-aware.
Server-side side effects (perform_extra_deserialization)
encounteris resolved from its UUID, thenpatientis copied fromencounter.patient— the client never setspatientdirectly.requester/requested_productare resolved byexternal_id. Ifrequested_product.facilityis set and differs fromencounter.facility, the write raises"Product not found in facility".prescriptionis resolved byexternal_id, scoped to the sameencounter.create_prescriptionlooks up an existing prescription by(alternate_identifier, encounter). A match that is notactiveraises"Prescription is not active"; no match creates a newMedicationRequestPrescription(status=active, copyingname/note,prescribed_by=requester) and links the request to it.
Serialization (read)
perform_extra_serialization sets id = external_id and expands requested_product, requester, and the audit users (created_by/updated_by) from cache. MedicationRequestReadSpec goes further, expanding prescription and emitting encounter as its external id.
Related models
MedicationRequestPrescription
The container that groups every medication request authored together for an encounter into one prescription. Each request points back via MedicationRequest.prescription.
encounter → FK Encounter (CASCADE)
patient → FK Patient (CASCADE)
name → CharField(100), nullable
note → TextField, nullable
prescribed_by → FK users.User (CASCADE, nullable)
status → CharField(100), nullable → MedicationRequestPrescriptionStatus
approval_status → CharField(100), nullable
alternate_identifier → CharField(100), nullable
tags → ArrayField[int], default []
A UniqueConstraint (unique_alternate_identifier_encounter) keeps alternate_identifier unique per encounter.
MedicationRequestPrescriptionStatus values
active, on_hold, ended, stopped, completed, cancelled, entered_in_error, draft.
A pharmacist may only move a prescription between active, on_hold, completed (MEDICATION_PRESCRIPTION_PHARMACIST_ALLOWED_STATUS).
Prescription specs
| Spec class | Role | Notes |
|---|---|---|
BaseMedicationRequestPrescriptionSpec | shared | id, status (MedicationRequestPrescriptionStatus), note, name |
MedicationRequestPrescriptionWriteSpec | write · create | Adds encounter (UUID), prescribed_by (UUID); resolves encounter→patient and prescribed_by server-side |
MedicationRequestPrescriptionUpdateSpec | write · update | Same shared fields, no new FKs |
MedicationRequestPrescriptionReadSpec | read · list | Adds created_date, modified_date, expanded prescribed_by (UserSpec), tags (rendered via SingleFacilityTagManager) |
MedicationRequestPrescriptionRetrieveSpec | read · detail | Adds audit created_by/updated_by |
MedicationRequestPrescriptionRetrieveMedicationsSpec | read · detail+children | Adds medications (each via MedicationRequestReadWithoutPrescriptionSpec) and full encounter (EncounterRetrieveSpec) |
MedicationRequestPrescriptionRetrieveDetailedSpec | read · detail | Adds encounter via EncounterListSpec |
Methods & save behaviour
- Read and write run through the inherited
EMRResource.serialize/de_serialize.de_serializedumps withexclude_defaults=True, maps the remaining fields onto the model, then callsperform_extra_deserializationfor the relation and prescription handling above. patientalways comes fromencounter; the client never writes it.- Inline prescription creation (
create_prescription) is idempotent on(alternate_identifier, encounter)and proceeds only when an existing match isactive. authored_onfalls back totimezone.nowat the model layer when omitted.
API integration notes
- Coded fields take a single
Codingobject{ system, code, display }validated against bound SNOMED CT value sets — send acodefrom the right value set or the write fails. - Enum members are underscored (
on_hold,entered_in_error,original_order); send those literals, not the FHIR hyphenated forms. dosage_instructioncarries SIG text, timing/frequency, PRN flags, dose and rate, and max-dose limits — write the full nested shape (snake_case keys) rather than free text alone.- On create, send either
medicationorrequested_product(not both) and at most one ofprescription/create_prescription. - Updates reach only
status,note, anddispense_status. requester,requested_product, andprescriptionuseSET_NULL, so a request outlives the user, product, or prescription it referenced.
Related
- Reference: Medication Administration
- Reference: Medication Dispense
- Reference: Medication Statement
- Reference: Product Knowledge
- Reference: Encounter
- Reference: Patient
- Reference: Base Model
- Source: medication_request.py · request/spec.py · request_prescription/spec.py