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.
Source:
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 | CASCADE. Spec field is UUID4 | 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 |
| Field | Storage type | Spec shape / notes |
|---|
data_entered_by | FK → users.User | CASCADE, nullable. related_name="observations_entered". Write specs accept data_entered_by_id: int; read specs serialize a nested UserSpec |
performer | JSONField (default {}) | Spec: optional Performer { type: PerformerType (related_person|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 |
note | TextField | Spec: str | None. Free-text note |
Clinical qualifiers
| Field | Storage type | Spec shape / notes |
|---|
body_site | JSONField (default {}) | Spec: optional Coding bound to CARE_BODY_SITE_VALUESET (slug system-body-site-observation, SNOMED CT). FHIR Observation.bodySite |
method | JSONField (default {}) | Spec: optional Coding bound to CARE_OBSERVATION_COLLECTION_METHOD (slug system-collection-method, SNOMED CT). FHIR Observation.method |
reference_range | JSONField (default []) | 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 | Nullable. Spec: UUID4 | 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 | CASCADE, nullable. Spec field is UUID4 | 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 |
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.
| Field | Value set | Slug | System(s) |
|---|
main_code | CARE_OBSERVATION_VALUSET | system-observation | http://loinc.org |
body_site | CARE_BODY_SITE_VALUESET | system-body-site-observation | http://snomed.info/sct (curated concept list + descendants of 442083009) |
method | CARE_OBSERVATION_COLLECTION_METHOD | system-collection-method | http://snomed.info/sct (descendants of 272394005, 129264002, 386053000) |
These interpretation value sets are registered for the frontend and observation definitions, but are not enforced on the interpretation dict:
| Value set | Slug | Concepts (system http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation) |
|---|
| Normal | system-normal-coded-valueset | N Normal, ND Not detected, NEG Negative |
| Abnormal | system-abnormal-coded-valueset | A Abnormal, AA Critically abnormal, H High, HH Critically high, L Low, LL Critically low, POS Positive, DET Detected |
| Critical | system-critical-coded-valueset | C Critical, CC Critical high, CL Critical low, CV Critical value |
| UCUM units | system-ucum-units | http://unitsofmeasure.org (all UCUM units) |
Nested JSON shapes (from the spec)
The structures behind the model's JSONFields.
Coding
Coding {
system: str | null # e.g. http://loinc.org
version: str | null
code: str # required
display: str | null
} # extra keys forbidden
CodeableConcept (alternate_coding)
CodeableConcept {
id: str | null
coding: Coding[] | null
text: str | null # field present, required key
} # extra keys forbidden
QuestionnaireSubmitResultValue (value)
QuestionnaireSubmitResultValue {
value: str | null # raw/string value
unit: Coding | null # for quantity values
coding: Coding | null # for coded values
}
Performer {
type: PerformerType # related_person | user
id: str
}
ReferenceRange (reference_range[])
ReferenceRange {
min: float | null
max: float | null
unit: str | null
interpretation: str # required
value: str | null
}
Component (component[])
Component {
value: QuestionnaireSubmitResultValue # required
interpretation: str | dict = {}
reference_range: ReferenceRange[] = []
code: Coding | null = null
note: str = ""
}
Resource specs (API schema)
The Pydantic specs build on EMRResource, which provides serialize / de_serialize and the perform_extra_serialization / perform_extra_deserialization hooks. Every concrete spec inherits from BaseObservationSpec.
| Spec | Role | Notes |
|---|
BaseObservationSpec | shared base | Defines the common fields: id, status, category, main_code, alternate_coding, subject_type, encounter, effective_datetime, performer, value_type, value, note, body_site (bound), method (bound), reference_range, interpretation, parent, questionnaire_response, component |
ObservationSpec | write · create | 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_by UserSpec 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.
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.