Observation Definition
An ObservationDefinition is master data describing how a particular observation is captured — its code, permitted data type, unit, method, body site, and (interpretation) reference ranges. You create one when you need a measurement collected consistently across forms; questionnaires reference it so the same observation (blood pressure, say) is recorded the same way everywhere, whether instance-wide or within a single facility. One observation code can back several definitions. The model loosely follows the FHIR ObservationDefinition resource.
The Django model is only storage: code, body_site, method, permitted_unit, component, and qualified_ranges are opaque JSONFields. The shape you write against — enums, value-set bindings, read/write contracts — lives in the Pydantic resource specs (care/emr/resources/observation_definition/spec.py), built on the EMRResource base.
Source:
- Model:
care/emr/models/observation_definition.py - Resource spec:
care/emr/resources/observation_definition/spec.py - Helper:
care/emr/resources/observation_definition/observation.py
Models
| Model | Purpose |
|---|---|
ObservationDefinition | Master-data definition of how an observation is coded, typed, and measured |
ObservationDefinition extends SlugBaseModel, which adds facility-scoped slug helpers on top of EMRBaseModel. From EMRBaseModel it inherits external_id, created_date/modified_date, soft-delete via deleted, created_by/updated_by, and the history/meta JSON fields.
ObservationDefinition fields
Identity & scope
| Field | Type | Notes |
|---|---|---|
facility | FK → Facility (PROTECT) | Nullable, default=None. Set means the definition is scoped to that facility; null means instance-wide. Excluded from the base spec (__exclude__ = ["facility"]) and set server-side from the facility UUID on create |
version | IntegerField | Defaults to 1. Read-only — appears in ...ReadSpec only, never accepted on write |
slug | CharField(255) | Set server-side from slug_value, then namespaced. See slug behaviour |
title | CharField(1024) | Required. Display name shown when authoring questionnaires; not the observation code |
status | CharField(255) | Required. Lifecycle code from ObservationStatusChoices — see Status values. Definitions move to retired rather than being destroyed |
description | TextField | Required. Natural-language (markdown) description |
derived_from_uri | TextField | External URI this definition was derived from. Optional (str | None, default None) |
Coding & measurement
| Field | Type | Notes |
|---|---|---|
category | CharField(255) | Nullable in the model, but required in the API as ObservationCategoryChoices — see Category values |
code | JSONField | Required. What is being observed: a Coding bound to the system-observation value set (LOINC) — see Coding shape |
permitted_data_type | CharField(100) | Required. QuestionType enum (questionnaire data types) — see Permitted data type values. group, display, and url are rejected |
body_site | JSONField | Nullable. Coding bound to the system-body-site-observation value set (SNOMED CT) |
method | JSONField | Nullable. Coding bound to the system-collection-method value set (SNOMED CT) |
permitted_unit | JSONField | Nullable. Coding bound to the system-ucum-units value set (UCUM). Care does not convert between units when displaying data |
Components & ranges
| Field | Type | Notes |
|---|---|---|
component | JSONField | Nullable. list[ObservationDefinitionComponentSpec] | None. Several measured values under one definition (systolic + diastolic, for example), each with its own code, permitted_data_type, permitted_unit, and qualified_ranges — see ObservationDefinitionComponentSpec |
qualified_ranges | JSONField | Model default list. Required in the API as list[QualifiedRangeSpec], though it may be empty. Condition-dependent reference ranges / interpretations — see QualifiedRangeSpec |
Enum values
Status values
ObservationStatusChoices — bound to the status field.
| Value |
|---|
draft |
active |
retired |
unknown |
Category values
ObservationCategoryChoices — bound to the category field.
| Value |
|---|
social_history |
vital_signs |
imaging |
laboratory |
procedure |
survey |
exam |
therapy |
activity |
Permitted data type values
QuestionType (from questionnaire/spec.py) — bound to permitted_data_type on both the definition and each component. validate_question_type rejects group, display, and url with "Cannot create a definition with this type".
| Value | Allowed for a definition |
|---|---|
group | No (rejected) |
boolean | Yes |
decimal | Yes |
integer | Yes |
string | Yes |
text | Yes |
display | No (rejected) |
date | Yes |
dateTime | Yes |
time | Yes |
choice | Yes |
url | No (rejected) |
quantity | Yes |
structured | Yes |
JSON field shapes (nested specs)
Coding shape
The coded fields (code, body_site, method, permitted_unit) store a Coding object. The spec binds each to a value set via ValueSetBoundCoding[<slug>], so the submitted code is validated against that value set on write.
Coding {
system: str | null
version: str | null
code: str # required
display: str | null
} # extra="forbid" — unknown keys rejected
| Field | Bound value set (slug) | Code system |
|---|---|---|
code | system-observation | LOINC (http://loinc.org) |
body_site | system-body-site-observation | SNOMED CT (http://snomed.info/sct) |
method | system-collection-method | SNOMED CT |
permitted_unit | system-ucum-units | UCUM (http://unitsofmeasure.org) |
ObservationDefinitionComponentSpec
Element of the component list — one per measured value when a definition carries several.
ObservationDefinitionComponentSpec {
code: ValueSetBoundCoding[system-observation] # required
permitted_data_type: QuestionType # required; group/display/url rejected
permitted_unit: ValueSetBoundCoding[system-ucum-units] | null = null
qualified_ranges: list[QualifiedRangeSpec] # required
}
QualifiedRangeSpec
Element of qualified_ranges (on the definition and on each component). A qualified range is either numeric (ranges) or categorical (coded value sets) — never both.
QualifiedRangeSpec {
title: str | null = null
conditions: list[EvaluatorConditionSpec] = []
ranges: list[NumericRangeSpec] = []
default_interpretation: InterpretationSpec | null = null
normal_coded_value_set: str | null = ""
critical_coded_value_set: str | null = ""
abnormal_coded_value_set: str | null = ""
valueset_interpretation: list[ValueSetInterpretationSpec] = []
}
validate_categorical_or_numeric enforces:
- Either
ranges(numeric) or at least one coded value set /valueset_interpretation(categorical) — one is required. - Numeric
rangesand coded value sets cannot both appear. - Numeric
rangesmust not overlap (sorted bymin, treating missingmin/maxas -∞/+∞). - Coded value-set slugs (
normal_coded_value_set,critical_coded_value_set,abnormal_coded_value_set, and eachvalueset_interpretation.valuset) must be unique — duplicates are rejected.
NumericRangeSpec
NumericRangeSpec {
interpretation: InterpretationSpec # required
min: Decimal(max_digits=20, decimal_places=6) | null = null
max: Decimal(max_digits=20, decimal_places=6) | null = null
}
At least one of min/max must be provided (validate_range).
InterpretationSpec
InterpretationSpec {
display: str # required
icon: str | null = ""
color: str | null = ""
highlight: bool | null = false
code: Coding | null = {}
}
ValueSetInterpretationSpec
ValueSetInterpretationSpec {
interpretation: InterpretationSpec # required
valuset: str # value-set slug (note: spelling "valuset")
}
EvaluatorConditionSpec
Element of QualifiedRangeSpec.conditions. Validated against EvaluatorMetricsRegistry: an unknown metric raises, and the registered evaluator validates operation/value.
EvaluatorConditionSpec {
metric: str # must resolve in EvaluatorMetricsRegistry
operation: str
value: dict | str
}
Resource specs (API schema)
All specs subclass BaseObservationDefinitionSpec (itself a subclass of EMRResource, with __model__ = ObservationDefinition and __exclude__ = ["facility"]). EMRResource provides serialize/de_serialize plus the perform_extra_serialization / perform_extra_deserialization hooks.
| Spec class | Role | Notable fields / behaviour |
|---|---|---|
BaseObservationDefinitionSpec | Shared base | id, title, status, description, category, code, permitted_data_type, component, body_site, method, permitted_unit, derived_from_uri, qualified_ranges. Runs validate_data_type and validate_qualified_ranges_consistency |
ObservationDefinitionCreateSpec | Write · create | Adds facility: UUID4 | null and slug_value: SlugType. Validates the facility exists; perform_extra_deserialization resolves the facility and sets obj.slug = slug_value |
ObservationDefinitionUpdateSpec | Write · update | Adds slug_value: SlugType; perform_extra_deserialization sets obj.slug = slug_value. The facility is not re-bound on update |
ObservationDefinitionReadSpec | Read · list & detail | Adds version: int | null, facility: dict | null, slug_config: dict, slug: str. perform_extra_serialization sets id = external_id, serializes facility via FacilityBareMinimumSpec, and sets slug_config = parse_slug(slug) |
Notes:
- Value-set bindings —
code,body_site,method,permitted_unit(and component equivalents) areValueSetBoundCoding[...]; the submitted coding is validated against the bound value set (system-observation,system-body-site-observation,system-collection-method,system-ucum-units). - Data-type guard —
permitted_data_type(definition and component) rejectsgroup,display,url. - Range consistency —
validate_qualified_ranges_consistencyrequires everyqualified_rangesentry on a definition to be the same kind: all numericrangesor all coded value sets. Mixing raises. slug_value—SlugType: 5–50 chars, URL-safe ([a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]), starting and ending alphanumeric. Stored namespaced asobj.slug(see below).- No metadata store —
__store_metadata__isFalse, so spec fields map directly to model columns rather than intometa.
Methods & save behaviour
ObservationDefinition overrides nothing of its own. Slug handling comes from SlugBaseModel, and the create/update side effects live in the API specs.
Slug behaviour
SlugBaseModel sets FACILITY_SCOPED = True, so the stored slug is namespaced:
facility-scoped: f-<facility.external_id>-<slug_value>
instance-scoped: i-<slug_value>
calculate_slug()returns thef-…form whenfacilityis set, otherwise thei-…form.parse_slug(slug)validates the prefix and returns a dict:{"facility": <uuid>, "slug_value": <bare slug>}forf-slugs, or{"slug_value": <bare slug>}fori-slugs.ObservationDefinitionReadSpecexposes this asslug_config.
The create/update specs receive the bare slug_value and store it on obj.slug; the model's slug helpers apply the namespacing.
API integration notes
- The facility binding is write-once: the create spec resolves the
facilityUUID to an FK; the update spec only re-setsslug_valueand never re-binds the facility. - Retire definitions via
status(retired) instead of deleting. The inheriteddeletedflag gives you ORM-level soft-delete if you need it. permitted_unitcodings follow UCUM, but Care never converts between units at display time — store data in the unit you intend to read.convert_od_to_observation(observation.py) builds anObservationfrom a definition for a given encounter, carrying overcategoryandcodeand defaultingstatustofinal.
Related
- Reference: Observation
- Reference: Service Request
- Reference: Questionnaire
- Reference: Value Set
- Reference: Base model
- Source: observation_definition.py (model)
- Source: observation_definition/spec.py (resource spec)
- Source: observation/valueset.py (bound value sets)