Diagnostic Report
A DiagnosticReport records the results and interpretation of a diagnostic test, tied to the ServiceRequest that ordered it. You create one to report on an investigation, then read it back with its observations, encounter, and requester embedded.
The Django model (care/emr/models/diagnostic_report.py) imposes no shape: it persists category and code as opaque JSONFields and status as a free CharField. Shape and validation come from the Pydantic resource specs (care/emr/resources/diagnostic_report/) — the status enum, the coded-concept structure, the bound value sets, and the separate read/write API schemas. The specs build on EMRResource and serialize between the API and the model.
Source:
- Model:
care/emr/models/diagnostic_report.py - Spec:
care/emr/resources/diagnostic_report/spec.py - Valueset:
care/emr/resources/diagnostic_report/valueset.py
Models
| Model | Purpose |
|---|---|
DiagnosticReport | Results and interpretation of a diagnostic test or investigation, tied to a ServiceRequest |
DiagnosticReport 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.
DiagnosticReport fields
Classification
| Field | Model type | Spec shape | Notes |
|---|---|---|---|
status | CharField(255) | DiagnosticReportStatusChoices (enum, required) | Lifecycle status. Stored as free text but constrained to the enum below on write. Optional on update. |
category | JSONField (null) | Coding { system, version?, code, display? }, required | Service section the report belongs to. code is validated against the Diagnostic Service Sections value set. |
code | JSONField (null) | Coding { system, version?, code, display? }, optional | The test or panel performed. code is validated against the Observation value set (LOINC). Nullable. |
The Coding shape (from care/emr/resources/common/coding.py, extra="forbid"):
| Field | Type | Required | Notes |
|---|---|---|---|
system | str | optional | Code system URI (e.g. http://terminology.hl7.org/CodeSystem/v2-0074, http://loinc.org) |
version | str | optional | Code system version |
code | str | required | The code; validated against the bound value set |
display | str | optional | Human-readable label |
Narrative
| Field | Model type | Spec shape | Notes |
|---|---|---|---|
note | TextField (null) | str | None | Free-text comments. Not parsed by the platform. |
conclusion | TextField (null) | str | None | Clinical interpretation/summary of the results. Not parsed by the platform. |
Relationships
| Field | Type | Notes |
|---|---|---|
facility | FK → facility.Facility | PROTECT, nullable. Facility the report is scoped to. Set server-side; not a writable spec field. |
patient | FK → emr.Patient | CASCADE. Subject of the report. Set server-side; not a writable spec field. |
encounter | FK → emr.Encounter | CASCADE. Encounter the report was produced under. Set server-side; not a writable spec field. |
service_request | FK → emr.ServiceRequest | CASCADE. The order this report fulfils. Write-only on create (resolved from external_id); excluded from base serialization via __exclude__. |
Enum values
DiagnosticReportStatusChoices
From spec.py. (modified is defined in source but commented out and is not an active value.)
| Value | Meaning |
|---|---|
registered | Report exists / has been registered |
partial | Some results available, report incomplete |
preliminary | Provisional results pending verification |
final | Verified, complete report |
Bound value sets
Coded fields validate their code against a registered Care value set (ValueSetBoundCoding). A code outside the bound set is rejected at deserialization.
| Field | Value set | Slug | Included system(s) |
|---|---|---|---|
category | Diagnostic Service Sections | system-diagnostic-service-sections-code | http://terminology.hl7.org/CodeSystem/v2-0074 |
code | Observation | system-observation | http://loinc.org |
Resource specs (API schema)
Every spec extends EMRResource (care/emr/resources/base.py) through DiagnosticReportSpecBase. serialize builds the read payload from the model (mapping external_id → id); de_serialize writes spec fields back to the model. The perform_extra_serialization/perform_extra_deserialization hooks handle server-side behaviour — resolving foreign keys on write, embedding related resources on read.
| Spec class | Role | Fields / behaviour |
|---|---|---|
DiagnosticReportSpecBase | shared base | id?, status (enum, required), category (bound Coding, required), code? (bound Coding), note?, conclusion?. __model__ = DiagnosticReport, __exclude__ = ["service_request"]. |
DiagnosticReportCreateSpec | write · create | Base + service_request: UUID4 (required, the request's external_id). perform_extra_deserialization resolves it via get_object_or_404(ServiceRequest, external_id=...) and sets obj.service_request. |
DiagnosticReportUpdateSpec | write · update | Base, but status is optional (DiagnosticReportStatusChoices | None). service_request is not writable on update. |
DiagnosticReportListSpec | read · list | Base + created_date, modified_date, service_request: dict | None. perform_extra_serialization sets id = external_id and embeds the linked request via BaseServiceRequestSpec.serialize(...). |
DiagnosticReportRetrieveSpec | read · detail | Extends ListSpec + observations: list[dict], encounter: dict, created_by?, updated_by?, requester?. See serialization below. |
DiagnosticReportRetrieveSpec.perform_extra_serialization (server-side, read)
The detail read enriches the list payload so a viewer gets the report, its observations, and its context in one call:
- Calls the
ListSpechook (setsid, embedsservice_request). serialize_audit_users→ populatescreated_by/updated_byfrom cache (UserSpec).observations→ allObservationrows wherediagnostic_report == obj, each viaObservationRetrieveSpec.serialize(...).requester→ ifservice_request.requester_idis set, loaded from cache viaUserSpec.encounter→EncounterListSpec.serialize(obj.encounter), withencounter["patient"]set toPatientRetrieveSpec.serialize(obj.encounter.patient, facility=obj.facility).
These embedded fields (service_request, observations, encounter, requester, audit users) are read-only and are not accepted on create/update.
Related models
DiagnosticReport is the only model defined in this file. It sits downstream of a ServiceRequest and is anchored to a patient and encounter:
diagnostic_report
facility → FK facility.Facility (PROTECT, nullable)
patient → FK emr.Patient (CASCADE)
encounter → FK emr.Encounter (CASCADE)
service_request → FK emr.ServiceRequest (CASCADE)
Deleting a Patient, Encounter, or ServiceRequest cascades to its diagnostic reports; the facility link is PROTECTed. Routine deletes are soft, via EMRBaseModel. On retrieve, related Observation records are pulled in through the reverse FK (Observation.diagnostic_report).
Methods & save behaviour
- Create (
DiagnosticReportCreateSpec.de_serialize): writesstatus,category,code,note,conclusiononto the model;perform_extra_deserializationresolvesservice_requestfrom itsexternal_id(404 if missing).facility,patient, andencountercome from the request context in the view layer, not the payload. - Update (
DiagnosticReportUpdateSpec): same base fields, butstatusis optional, so a partial status change is allowed.service_requestcannot be reassigned. - Read (
List/Retrieve): no model writes; all enrichment happens inperform_extra_serialization(see above). User and service-request lookups go through the serializer cache (model_from_cache). - Coded fields are validated at deserialization:
category.codemust be in the Diagnostic Service Sections value set;code.code(when present) must be in the Observation value set.statusmust be aDiagnosticReportStatusChoicesvalue.
API integration notes
- Diagnostic reports are exposed through Care's REST API and align with the FHIR
DiagnosticReportresource; payload field names follow the spec classes above. categoryandcodeare coded concepts (Coding:system/code/display), not free strings. Populatecodefrom the bound value set — anything outside it is rejected, andCodingforbids extra keys.statusis stored as a freeCharField, but the spec restricts writes to theDiagnosticReportStatusChoicesenum (registered,partial,preliminary,final).- A report fulfils exactly one
service_request. Create the Service Request first, then pass itsexternal_idasservice_requeston create. noteandconclusioncarry human-readable narrative and are not parsed by the platform.- The detail (
Retrieve) payload embeds the linked service request, its requester, the encounter (with patient), audit users, and all child observations — none of which are writable.
Related
- Reference: Service Request
- Reference: Observation
- Reference: Specimen
- Reference: Encounter
- Reference: Patient
- Reference: Base Model
- Source: diagnostic_report.py on GitHub
- Source: resources/diagnostic_report/spec.py on GitHub
- Source: resources/diagnostic_report/valueset.py on GitHub