References/Observation Definition
More languages
More actions
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 [https://build.fhir.org/observationdefinition.html 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: [https://github.com/ohcnetwork/care/blob/develop/care/emr/models/observation_definition.py
care/emr/models/observation_definition.py] - Resource spec: [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation_definition/spec.py
care/emr/resources/observation_definition/spec.py] - Helper: [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation_definition/observation.py
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
{{Field|model=ObservationDefinition|section=Identity & scope|name=slug|type=CharField(255)|notes=Set server-side from |
title |
CharField(1024) |
Required. Display name shown when authoring questionnaires; not the observation code
{{Field|model=ObservationDefinition|section=Identity & scope|name=status|type=CharField(255)|notes=Required. Lifecycle code from |
description |
TextField |
Required. Natural-language (markdown) description |
derived_from_uri |
TextField |
External URI this definition was derived from. Optional (str
|
Coding & measurement
| Field | Type | Notes
{{Field|model=ObservationDefinition|section=Coding & measurement|name=category|type=CharField(255)|notes=Nullable in the model, but required in the API as |
|---|---|---|
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
{{Field|model=ObservationDefinition|section=Components & ranges|name=component|type=JSONField|notes=Nullable. |
|---|
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 | 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 | 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: References/Base models & conventions