An Observation is one named, coded data point about a subject — almost always a patient: a blood pressure reading, a temperature, a coded answer captured through a questionnaire. main_code records what was observed (usually bound to LOINC) and value holds the result. You rarely create one by hand; most observations are materialized when a questionnaire is submitted.
The model is just storage; several fields are opaque JSONFields. Their real structure lives in the Pydantic resource specs under care/emr/resources/observation/, which own the enums, nested JSON shapes, validation, and the read/write schema split. Each field row below gives the storage column first, then the shape the spec enforces on top of it.
Models
Model
Purpose
Observation
A single coded measurement or finding about a patient, captured within an encounter
Observation extends EMRBaseModel, the shared Care EMR base that supplies external_id, created_date/modified_date, soft-delete via deleted, created_by/updated_by, and history/meta JSON.
Observation fields
Classification & coding
Field
Storage type
Spec shape / notes
status
CharField(255)
Enum ObservationStatus — final, amended, entered_in_error. Required in specs. Questionnaire-sourced observations land as final
is_group
BooleanField
Default False. True marks a grouping/panel observation that holds component/child observations instead of a single value. Not exposed in the resource specs
category
JSONField (default {})
Spec: optional Coding { system?, version?, code, display? }. FHIR observation category, derived from the questionnaire
main_code
JSONField (default {})
Spec: optional Coding. What is being observed, typically LOINC (CARE_OBSERVATION_VALUSET, system http://loinc.org)
alternate_coding
JSONField (default [])
Spec: optional CodeableConcept { id?, coding?: Coding[], text }. Same concept expressed in other code systems
Subject & context
Field
Storage type
Spec shape / notes
subject_type
CharField(255)
Enum SubjectType — patient, encounter. Spec defaults to encounter
subject_id
UUIDField
The subject entity's id. Set server-side; not in the spec body
patient
FK → Patient
CASCADE. Set server-side, not in the spec body
encounter
FK → Encounter
None. Where the observation was recorded
effective_datetime
DateTimeField
When the observation was effective. Nullable in storage, but the spec requires a tz-aware datetime on create; optional on update
user), id: str }. Use it when the performer differs from the data-entry user
Value
Field
Storage type
Spec shape / notes
value_type
CharField(255)
Enum QuestionType (from the questionnaire module). Tells the client how to read value — see the table below. Required in specs
value
JSONField
Spec: QuestionnaireSubmitResultValue { value?: str, unit?: Coding, coding?: Coding }. Required on create, optional on update. value holds the raw/string result, unit the quantity unit, coding the coded answer
Spec: ReferenceRange[] (shape below), default []. Reference ranges for the value, usually carried over from the observation definition
interpretation
JSONField (default {})
Spec: free dict, default {}. Coded interpretation against the reference range (high/low/normal). Normal/abnormal/critical value sets exist (below) but are not enforced on this dict
interpretation_old
CharField(255)
Nullable. Legacy free-text interpretation, kept for backward compatibility. Not in the specs
Structure & relationships
Field
Storage type
Spec shape / notes
parent
UUIDField
None — the external_id of a parent observation, for nesting related observations
component
JSONField (default [])
Spec: Component[] (shape below), default []. Sub-observations sharing this context but carrying their own codes and values (FHIR Observation.component)
questionnaire_response
FK → QuestionnaireResponse
None. The submission that produced this observation
diagnostic_report
FK → DiagnosticReport
CASCADE, nullable. The report this observation belongs to, if any. Not in the write specs
observation_definition
FK → ObservationDefinition
CASCADE, nullable. The definition this observation templates from. Serialized as BaseObservationDefinitionSpec only in ObservationRetrieveSpec
Enum & value-set reference
ObservationStatus values
Value
Meaning
final
Recorded, complete and verified. The default for questionnaire-sourced observations
amended
Was final, then corrected
entered_in_error
Recorded in error; disregard it
PerformerType values (performer.type)
Value
related_person
user
SubjectType values (subject_type)
Value
patient
encounter (spec default)
QuestionType values (value_type)
From the questionnaire module. The value tells the client which key of value to read.
Value
Value sourced from QuestionnaireSubmitResultValue
group
grouping node (no direct value)
boolean
value ("true"/"false")
decimal
value
integer
value
string
value
text
value
display
display-only
date
value
dateTime
value
time
value
choice
coding
url
value
quantity
value + unit
structured
structured payload
Bound value sets
Coded fields validate against registered Care value sets. A body_site or method coding outside its value set is rejected during deserialization.
Adds data_entered_by_id, created_by_id, updated_by_id (all int). Used internally when materializing observations from a questionnaire submission
ObservationUpdateSpec
write · update
The base, but effective_datetime and value relax to optional (None) for partial updates
ObservationReadSpec
read · list
Swaps the user IDs for nested created_by / updated_by / data_entered_byUserSpec objects
ObservationRetrieveSpec
read · detail
Extends ObservationReadSpec and additionally serializes observation_definition via BaseObservationDefinitionSpec when it is set
Server-side behaviour
ObservationSpec.perform_extra_deserialization (create/update): sets obj.external_id = self.id, copies data_entered_by_id, created_by_id, updated_by_id onto the model, drops data_entered_by_id from meta, and on create (is_update is false) clears obj.id so a new row is inserted.
ObservationReadSpec.perform_extra_serialization: maps id from external_id; deliberately blanks encounter, patient, and questionnaire_response to None in the output to avoid extra DB queries; resolves the audit users (created_by, updated_by) and data_entered_by from cache (model_from_cache).
ObservationRetrieveSpec.perform_extra_serialization: calls the parent, then inlines the full observation_definition when the FK is set.
body_site / method are ValueSetBoundCoding[...]: the supplied Coding is validated against its bound value set during deserialization and rejected if the code is not a member.
de_serialize only writes fields present in the model's column mapping. Non-default values are persisted (model_dump(exclude_defaults=True)), and unknown spec-only fields would land in meta when __store_metadata__ is set — which, by default, it is not for observations.
Related models
Observation is self-contained; it expresses relationships through foreign keys rather than secondary tables.
patient → FK Patient (CASCADE)
encounter → FK Encounter (CASCADE)
data_entered_by → FK users.User (CASCADE, nullable)
questionnaire_response → FK QuestionnaireResponse (CASCADE, nullable)
diagnostic_report → FK DiagnosticReport (CASCADE, nullable)
observation_definition → FK ObservationDefinition (CASCADE, nullable)
parent is the exception: a soft, UUID-based self-reference to another observation's external_id, not a Django FK. It lets observations nest without a database constraint.
API integration notes
Observations are exposed through Care's REST API and align with the FHIR Observation resource. FHIR field names (effectiveDateTime, bodySite, and so on) differ from the Django model names; the resource specs above are the authoritative request/response schemas.
Most observations are created as a side effect of submitting a questionnaire. value and value_type mirror the submission types (QuestionnaireSubmitResultValue / QuestionType), and questionnaire_response records provenance.
effective_datetime must be timezone-aware. Required on create, optional on update.
status is one of final / amended / entered_in_error; questionnaire-sourced observations are final.
body_site and method are validated against their bound value sets, and main_code is expected to bind to LOINC via CARE_OBSERVATION_VALUSET.
On read, the list and detail specs blank out encounter, patient, and questionnaire_response, so don't rely on those being populated in observation payloads.
Prefer structured observations. Allergy and medication checks read coded values, not free text, so unstructured data leaves them blind.