Skip to main content
Version: 3.0

Condition

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

ModelPurpose
ConditionA 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

FieldTypeNotes
clinical_statusCharField(100), nullableClinical state. Spec binds to ClinicalStatusChoices; optional on write.
verification_statusCharField(100), nullableLevel of certainty. Spec binds to VerificationStatusChoices; required on write (ConditionSpec/ConditionUpdateSpec).
categoryCharField(100), nullableClassification. Spec binds to CategoryChoices; required on create (ConditionSpec).
severityCharField(100), nullableSubjective severity. Spec binds to SeverityChoices; optional on write.

Coded concepts

FieldTypeNotes
codeJSONField (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_siteJSONField (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

FieldTypeNotes
onsetJSONField (default=dict)Choice-of-type onset, shaped by ConditionOnSetSpec. Defaults to {}.
abatementJSONField (default=dict)When the condition resolved or went into remission. Shaped by ConditionAbatementSpec. Defaults to {}.
recorded_dateDateTimeField, nullableWhen the condition was first recorded. Not exposed by the current specs.

Context & notes

FieldTypeNotes
patientFK → Patient, on_delete=CASCADESubject of the condition. Derived server-side from the encounter on create, never read from the client (__exclude__).
encounterFK → Encounter, nullable, on_delete=CASCADEEncounter the condition was recorded in. Set server-side on create (__exclude__); the client supplies a UUID in the spec, which is validated to exist.
noteTextField, nullableFree-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

Value
mild
moderate
severe

Nested JSON shapes

These spec classes (all extend EMRResource) are the real structure behind the model's JSON fields.

ConditionOnSetSpec (onset shape)

FieldTypeDefaultNotes
onset_datetimedatetime | NoneNoneCoerced to timezone-aware (make_aware) if naive. Cannot be in the future — a value > care_now() is rejected.
onset_ageint | NoneNoneAge at onset.
onset_stringstr | NoneNoneFree-text onset description.
notestr | NoneNoneNote about the onset.

ConditionAbatementSpec (abatement shape)

FieldTypeDefaultNotes
abatement_datetimedatetime | NoneNoneUnlike onset_datetime, no future-date check.
abatement_ageint | NoneNoneAge at abatement.
abatement_stringstr | NoneNoneFree-text abatement description.
notestr | NoneNoneNote about the abatement.

Coding shape

code is a single Coding, not a CodeableConcept. extra="forbid".

FieldTypeNotes
systemstr | NoneCode system URI (e.g. http://snomed.info/sct).
versionstr | NoneCode system version.
codestrRequired. The code value.
displaystr | NoneHuman-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 BaseConditionSpecEMRResource 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 classRoleExposes / behaviour
BaseConditionSpecshared baseid; excludes patient and encounter from direct mapping.
ConditionSpecwrite · createclinical_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.
ConditionUpdateSpecwrite · updateclinical_status?, verification_status (required), severity?, code (value-set bound, required), onset (={}), abatement (={}), note?. Does not accept encounter, category, or patient.
ChronicConditionUpdateSpecwrite · update (chronic)Extends ConditionUpdateSpec and adds encounter (UUID4). On deserialize, if encounter is set, resolves it (get_object_or_404) and assigns obj.encounter.
ConditionReadSpecread · detail/listclinical_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.