Condition
Technical reference for the Condition module in Care EMR. For the plain-language view, read the Condition concept.
Source:
- Model:
care/emr/models/condition.py - Resource spec:
care/emr/resources/condition/spec.py - Value set:
care/emr/resources/condition/valueset.py - Viewsets:
care/emr/api/viewsets/condition.py
Condition has two layers, and the split matters when you read the code:
- The Django model is storage. Its coded and timing fields (
code,body_site,onset,abatement) are opaqueJSONFields. The model says nothing about their shape. - The Pydantic resource specs are the API. They define the enums, the structure inside each JSON field, the validation, the value-set binding for
code, and the separate read and write schemas.
One model backs two API surfaces. SymptomViewSet serves symptom, and DiagnosisViewSet serves diagnosis. The category field separates them.
Models
| Model | Purpose |
|---|---|
Condition | A clinical problem, symptom, or diagnosis recorded for a patient |
Condition extends EMRBaseModel, which provides external_id, created_date/modified_date, created_by/updated_by, soft delete through deleted, and the history/meta JSON fields.
Condition fields
Status and classification
| Field | Type | Notes |
|---|---|---|
clinical_status | CharField(100), nullable | Course of the condition. Bound to ClinicalStatusChoices by the specs; optional on write. |
verification_status | CharField(100), nullable | Certainty. Bound to VerificationStatusChoices; required on ConditionSpec and ConditionUpdateSpec. |
category | CharField(100), nullable | Bound to CategoryChoices; required on create. SymptomViewSet overwrites it with problem_list_item in perform_create. |
severity | CharField(100), nullable | Bound 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 condition code value set. On read, a plain Coding. |
body_site | JSONField (default=dict, not null/blank) | Anatomical site. No current spec exposes it, so no client reads or writes it. |
Timing
| Field | Type | Notes |
|---|---|---|
onset | JSONField (default=dict) | Shaped by ConditionOnSetSpec. |
abatement | JSONField (default=dict) | Shaped by ConditionAbatementSpec. |
recorded_date | DateTimeField, nullable | Not exposed by the current specs. |
Context and notes
| Field | Type | Notes |
|---|---|---|
patient | FK → Patient, on_delete=CASCADE | Derived server-side from the encounter on create. Listed in __exclude__, so no client sets it. |
encounter | FK → Encounter, nullable, on_delete=CASCADE | Set server-side on create from the UUID in the write spec. Listed in __exclude__. |
note | TextField, nullable | Free-text note. |
Enums
Every enum is a str, Enum in care/emr/resources/condition/spec.py. The stored and serialized value is the string in the table.
ClinicalStatusChoices values
| Value |
|---|
active |
recurrence |
relapse |
inactive |
remission |
resolved |
unknown |
The frontend offers every value except unknown.
VerificationStatusChoices values
| Value |
|---|
unconfirmed |
provisional |
differential |
confirmed |
refuted |
entered_in_error |
CategoryChoices values
| Value | Used by |
|---|---|
problem_list_item | SymptomViewSet, which forces this value on create and filters its queryset by it |
encounter_diagnosis | DiagnosisViewSet, the value the diagnosis form sends |
chronic_condition | DiagnosisViewSet, for long-term diagnoses |
SeverityChoices values
| Value |
|---|
mild |
moderate |
severe |
Nested JSON shapes
These spec classes extend EMRResource and define the real structure behind the JSON fields.
ConditionOnSetSpec (onset shape)
| Field | Type | Default | Notes |
|---|---|---|---|
onset_datetime | datetime | None | None | Made timezone-aware when naive. A value after care_now() is rejected. |
onset_age | int | None | None | Age at onset. |
onset_string | str | None | None | Free-text onset. |
note | str | None | None | Note about the onset. |
ConditionAbatementSpec (abatement shape)
| Field | Type | Default | Notes |
|---|---|---|---|
abatement_datetime | datetime | None | None | No future-date check. |
abatement_age | int | None | None | Age at abatement. |
abatement_string | str | None | None | Free-text abatement. |
note | str | None | None | Note about the abatement. |
Coding shape
code is a single Coding, not a CodeableConcept.
| Field | Type | Notes |
|---|---|---|
system | str | None | Code system URI, such as http://snomed.info/sct. |
version | str | None | Code system version. |
code | str | Required. The code value. |
display | str | None | Label for the code. |
code value-set binding
On write, code is typed ValueSetBoundCoding[CARE_CODITION_CODE_VALUESET.slug]. The value set has the slug system-condition-code and includes the SNOMED CT concepts that are is-a 404684003 (Clinical finding). Care rejects codes outside the value set on ConditionSpec, ConditionUpdateSpec, and ChronicConditionUpdateSpec. The read spec uses a plain Coding and skips the check, which keeps reads cheap.
Resource specs (API schema)
Every spec extends BaseConditionSpec → EMRResource. BaseConditionSpec sets __model__ = Condition, sets __exclude__ = ["patient", "encounter"], 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 (required, value-set bound), 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 (required, value-set bound), onset, abatement, note?. Accepts neither encounter nor category. |
ChronicConditionUpdateSpec | write · update | Extends ConditionUpdateSpec and adds encounter (UUID4). On deserialize, resolves the encounter with get_object_or_404 and assigns it. |
ConditionReadSpec | read · list/detail | clinical_status, verification_status, category, severity (plain str), code (plain Coding), encounter (UUID4), onset, abatement, created_by?, updated_by?, note?, created_date, modified_date. |
Validation and server-side behaviour
ConditionSpec.validate_encounter_existsrejects an unknown encounter UUID.perform_extra_deserializationruns on create only, loads the encounter, and derivespatientfrom it.verification_statusis mandatory on both write specs.categoryis mandatory on create only.codemust belong to the bound value set on every write spec.onset_datetimecannot be in the future, and Care makes it timezone-aware.abatement_datetimehas no such rule.ConditionReadSpec.perform_extra_serializationmapsidtoexternal_id, replacesencounterwith itsexternal_id, and expandscreated_by/updated_by.body_siteandrecorded_dateare storage only. No spec reads or writes them.
Viewsets
Both viewsets extend EMRModelViewSet, EncounterBasedAuthorizationBase, EMRQuestionnaireResponseMixin, and the local ValidateEncounterMixin. They are registered under the patient-nested router.
| Viewset | Route | Queryset | Notes |
|---|---|---|---|
SymptomViewSet | patient/<patient_id>/symptom/ | Conditions of the patient with category = problem_list_item | perform_create forces category to problem_list_item. Registered as the symptom system questionnaire. |
DiagnosisViewSet | patient/<patient_id>/diagnosis/ | Every condition of the patient | Registered as the diagnosis system questionnaire. Overrides authorize_update for chronic conditions. |
ValidateEncounterMixin.validate_data rejects the request when the encounter belongs to a different patient than the one in the URL.
Both viewsets expose the shared upsert action, which the frontend uses to send several conditions in one atomic request.
Filters
ConditionFilters applies to both viewsets: encounter, clinical_status, exclude_clinical_status, verification_status, exclude_verification_status, severity (case-insensitive exact), name (matches code__display), and category. The status and category filters accept several comma-separated values.
Authorization
| Action | Check | Permission |
|---|---|---|
| List, retrieve | authorize_read_encounter in get_queryset | can_view_clinical_data on the patient, or can_read_encounter_clinical_data on the encounter in the encounter query parameter |
| Create, update, destroy | can_update_encounter_clinical_data | can_write_encounter_clinical_data on the encounter |
| Update a chronic condition | DiagnosisViewSet.authorize_update | can_view_clinical_data on the patient |
can_update_encounter_clinical_data returns False when the encounter status is Completed, Cancelled, Entered in Error, or Discontinued. No user writes a condition to a closed encounter.
Related models
patient → FK Patient (CASCADE, derived from the encounter)
encounter → FK Encounter (CASCADE, nullable in the column, required on create)
Deletion of a Patient or an Encounter cascades to its Condition rows.
API integration notes
- Send
codeas aCodingfrom the condition code value set, never as free text. Always sendverification_status. Sendcategoryandencounteron create. onsetandabatementare structured objects, not arbitrary JSON.- Do not send
patient,external_id, the audit fields, ordeleted. The server owns them. - To remove a saved condition from the lists, set
verification_statustoentered_in_error. The frontend lists exclude that value.
Related
- Concept: Condition
- Reference: Patient
- Reference: Encounter
- Reference: Base model