A Condition records a clinical problem, diagnosis, or symptom against a patient. It maps to the FHIR Condition resource and shows up in the Care product UI as Symptoms.
Source:
A Condition lives across two layers, and the split matters when you read the code:
- The Django model (
care/emr/models/condition.py) is storage. Its coded and timing fields (code, body_site, onset, abatement) are opaque JSONFields — the model says nothing about their shape.
- The Pydantic resource specs (
care/emr/resources/condition/spec.py) are the API. They define the enums, the structure inside each JSON field, field validation, the value-set binding for code, and the separate read and write schemas.
Models
| Model | Purpose |
|---|
Condition | A clinical condition, problem, diagnosis, or symptom recorded for a patient |
Condition extends EMRBaseModel, the shared Care EMR base that provides external_id, created_date/modified_date, soft-delete via deleted, created_by/updated_by, and history/meta JSON.
Condition fields
Status & classification
| Field | Type | Notes |
|---|
clinical_status | CharField(100), nullable | Clinical state. Spec binds to ClinicalStatusChoices; optional on write. |
verification_status | CharField(100), nullable | Level of certainty. Spec binds to VerificationStatusChoices; required on write (ConditionSpec/ConditionUpdateSpec). |
category | CharField(100), nullable | Classification. Spec binds to CategoryChoices; required on create (ConditionSpec). |
severity | CharField(100), nullable | Subjective severity. Spec binds to SeverityChoices; optional on write. |
Coded concepts
| Field | Type | Notes |
|---|
code | JSONField (default=dict, not null/blank) | The condition itself. On write, a single Coding bound to the CARE_CODITION_CODE_VALUESET value set (SNOMED CT clinical findings). On read, serialized as a plain Coding. |
body_site | JSONField (default=dict, not null/blank) | Anatomical location(s). Exists on the model but no current spec exposes it — never written or read through the standard Condition API. |
Timing
| Field | Type | Notes |
|---|
onset | JSONField (default=dict) | Choice-of-type onset, shaped by ConditionOnSetSpec. Defaults to {}. |
abatement | JSONField (default=dict) | When the condition resolved or went into remission. Shaped by ConditionAbatementSpec. Defaults to {}. |
recorded_date | DateTimeField, nullable | When the condition was first recorded. Not exposed by the current specs. |
Context & notes
| Field | Type | Notes |
|---|
patient | FK → Patient, on_delete=CASCADE | Subject of the condition. Derived server-side from the encounter on create, never read from the client (__exclude__). |
encounter | FK → Encounter, nullable, on_delete=CASCADE | Encounter the condition was recorded in. Set server-side on create (__exclude__); the client supplies a UUID in the spec, which is validated to exist. |
note | TextField, nullable | Free-text clinical note. |
Enums
Every enum is a str, Enum in care/emr/resources/condition/spec.py. The value stored and serialized is the string in the table.
ClinicalStatusChoices values
| Value |
|---|
active |
recurrence |
relapse |
inactive |
remission |
resolved |
unknown |
VerificationStatusChoices values
| Value |
|---|
unconfirmed |
provisional |
differential |
confirmed |
refuted |
entered_in_error |
CategoryChoices values
| Value |
|---|
problem_list_item |
encounter_diagnosis |
chronic_condition |
SeverityChoices values
Nested JSON shapes
These spec classes (all extend EMRResource) are the real structure behind the model's JSON fields.
ConditionOnSetSpec (onset shape)
| Field | Type | Default | Notes |
|---|
onset_datetime | datetime | None | None | Coerced to timezone-aware (make_aware) if naive. Cannot be in the future — a value > care_now() is rejected. |
onset_age | int | None | None | Age at onset. |
onset_string | str | None | None | Free-text onset description. |
note | str | None | None | Note about the onset. |
ConditionAbatementSpec (abatement shape)
| Field | Type | Default | Notes |
|---|
abatement_datetime | datetime | None | None | Unlike onset_datetime, no future-date check. |
abatement_age | int | None | None | Age at abatement. |
abatement_string | str | None | None | Free-text abatement description. |
note | str | None | None | Note about the abatement. |
Coding shape
code is a single Coding, not a CodeableConcept. extra="forbid".
| Field | Type | Notes |
|---|
system | str | None | Code system URI (e.g. http://snomed.info/sct). |
version | str | None | Code system version. |
code | str | Required. The code value. |
display | str | None | Human-readable label. |
code value-set binding
On write, code is typed ValueSetBoundCoding[CARE_CODITION_CODE_VALUESET.slug] — a Coding checked against the Condition code value set (care/emr/resources/condition/valueset.py, slug system-condition-code), which covers SNOMED CT concepts that are is-a 404684003 (Clinical finding). Codes outside it are rejected on ConditionSpec, ConditionUpdateSpec, and ChronicConditionUpdateSpec. The read spec falls back to a plain Coding and skips value-set validation, so reads stay cheap.
Resource specs (API schema)
Every spec extends BaseConditionSpec → EMRResource and round-trips through serialize (DB → Pydantic) and de_serialize (Pydantic → DB). BaseConditionSpec sets __model__ = Condition, sets __exclude__ = ["patient", "encounter"] (both server-maintained, never trusted from the client), and exposes id: UUID4.
| Spec class | Role | Exposes / behaviour |
|---|
BaseConditionSpec | shared base | id; excludes patient and encounter from direct mapping. |
ConditionSpec | write · create | clinical_status?, verification_status (required), severity?, code (value-set bound, required), encounter (UUID4, required), onset (={}), abatement (={}), note?, category (required). Validates that the encounter exists; on create sets obj.encounter and obj.patient = encounter.patient. |
ConditionUpdateSpec | write · update | clinical_status?, verification_status (required), severity?, code (value-set bound, required), onset (={}), abatement (={}), note?. Does not accept encounter, category, or patient. |
ChronicConditionUpdateSpec | write · update (chronic) | Extends ConditionUpdateSpec and adds encounter (UUID4). On deserialize, if encounter is set, resolves it (get_object_or_404) and assigns obj.encounter. |
ConditionReadSpec | read · detail/list | clinical_status, verification_status, category, criticality, severity (all plain str), code (plain Coding), encounter (UUID4), onset, abatement, created_by?, updated_by?, note?, created_date, modified_date. |
Validation & server-side behaviour
- Encounter is required and verified on create.
ConditionSpec.validate_encounter_exists rejects unknown encounter UUIDs; perform_extra_deserialization (create only) loads the encounter and derives patient from it. Clients never set patient or encounter directly.
verification_status is mandatory on both ConditionSpec and ConditionUpdateSpec; category is mandatory only on create (ConditionSpec).
code must belong to the bound value set (SNOMED CT clinical findings) on every write spec.
onset_datetime cannot be in the future and is forced timezone-aware; abatement_datetime carries no such constraint.
ConditionReadSpec exposes criticality, a string in the read schema with no backing column on the Condition model. The standard serializer never populates it, so it comes back unset.
- Read serialization (
perform_extra_serialization) maps id = external_id, replaces encounter with its external_id, and expands created_by/updated_by via serialize_audit_users (cached UserSpec).
body_site and recorded_date exist on the model but no current spec reads or writes them — storage-only, outside the standard Condition API surface.
Condition links to two clinical records:
patient → FK Patient (CASCADE, server-derived from encounter)
encounter → FK Encounter (CASCADE, nullable; required on create)
Deleting a Patient or Encounter cascades to its Condition rows. The column allows a null encounter, but the standard create flow always supplies one and derives patient from it.
Methods & save behaviour
serialize(obj) and de_serialize(obj) (from EMRResource) convert between the Condition model and the spec. Field mapping uses the model's non-FK column names; __exclude__ (patient, encounter) and id/external_id are skipped during deserialization.
ConditionSpec.perform_extra_deserialization(is_update=False, obj) — create path: resolves the encounter and sets obj.patient = obj.encounter.patient.
ChronicConditionUpdateSpec.perform_extra_deserialization — resolves and assigns encounter when provided.
ConditionReadSpec.perform_extra_serialization — sets id/encounter external ids and serializes audit users.
external_id, audit fields, meta/history, and soft-delete (deleted) are platform-maintained via EMRBaseModel.
API integration notes
- Send
code as a structured Coding ({system, code, display}) drawn from the SNOMED CT clinical-finding value set, not as free text. Always include verification_status; add category and encounter when creating.
onset and abatement are structured choice-of-type objects (*_datetime, *_age, *_string, note), not arbitrary JSON. onset_datetime must be timezone-aware and not in the future.
- Don't send
patient, external_id, audit fields, or deleted from the client — the server owns them.