Specimen
A Specimen is a physical sample — blood, tissue, swab — collected from a patient for laboratory analysis. You create one to fulfill a lab order, then update it as the specimen moves through collection, receipt, and processing.
Several of the model's fields (care/emr/models/specimen.py) are opaque JSONFields whose structure lives only in the Pydantic resource specs (care/emr/resources/specimen/). The specs define the enums, nested JSON shapes, value-set bindings, validation, and the read/write schemas, so you need both layers to work with this resource.
Source:
- Model:
care/emr/models/specimen.py - Resource specs:
care/emr/resources/specimen/spec.py,valueset.py - Viewset:
care/emr/api/viewsets/specimen.py
Models
Specimen extends EMRBaseModel, the shared Care EMR base providing external_id, audit fields, soft-delete via deleted, and history/meta JSON.
Specimen fields
Identity & status
| Field | Model type | Spec shape / values | Notes |
|---|---|---|---|
accession_identifier | CharField(255), db_index=True | str, default "" | Lab accession/barcode identifier; indexed for lookup. Defaults to "", so it's optional on write. |
status | CharField(20) | SpecimenStatusOptions enum | Required on base/create, optional on update. See status values. |
specimen_type | JSONField | Coding bound to value set system-specimen_type-code | Kind of material collected, as a single coding. Required on base/create, optional on update. |
Clinical links
| Field | Model type | Notes |
|---|---|---|
facility | FK → Facility | PROTECT, nullable; the facility the specimen belongs to. Excluded from all specs (__exclude__). |
patient | FK → Patient | CASCADE; the patient the specimen was collected from. Set on create via subject_patient (UUID). |
encounter | FK → Encounter | CASCADE, nullable; the encounter during which collection occurred. Set on create via subject_encounter (UUID). |
service_request | FK → ServiceRequest | CASCADE, nullable; the order this specimen fulfills. Set on create via request (UUID); excluded from base/read field copy. |
specimen_definition | FK → SpecimenDefinition | CASCADE, nullable; the definition/template this specimen was derived from. |
Collection & processing
| Field | Model type | Spec shape | Notes |
|---|---|---|---|
collection | JSONField default=dict | CollectionSpec | None, default None | A single collection event: collector, method, body site, quantity, fasting. See CollectionSpec. |
received_time | DateTimeField, nullable | str | None, default None | When the lab received the specimen; ISO 8601 string in the spec. |
condition | JSONField default=list | list[Coding] bound to system-specimen-condition-code, default [] | Coded conditions/state of the specimen on receipt. |
processing | JSONField default=list | list[ProcessingSpec], default [] | Processing steps applied. See ProcessingSpec. |
note | TextField, nullable | str | None, default None | Free-text annotation. |
Enums
SpecimenStatusOptions values
The status enum (str). Two things to watch: entered_in_error uses an underscore rather than the FHIR hyphen, and draft is a Care addition with no FHIR equivalent.
| Value | Meaning |
|---|---|
draft | Care-specific; not from FHIR |
available | Specimen available for testing |
unavailable | Specimen no longer available |
unsatisfactory | Specimen unsuitable for testing |
entered_in_error | Recorded in error |
Nested JSON specs
These define the real structure behind the model's JSONFields.
CollectionSpec
Shape of the collection JSON field — a single collection event.
| Field | Type | Required | Notes |
|---|---|---|---|
collector | UUID4 | None | optional | External ID of the collecting User. Validated to exist (User.objects.filter(external_id=...)), else rejected with "Collector user not found". Serialized output adds collector_object, the resolved UserSpec from cache. |
collected_date_time | datetime | None | optional | When the specimen was collected. |
quantity | QuantitySpec | None | optional | Amount collected. See QuantitySpec. |
method | Coding | None bound to system-collection-method-code | optional | Collection method (SNOMED). See collection method values. |
procedure | UUID4 | None | optional | Reference to a procedure. |
body_site | Coding | None bound to system-body-site-observation | optional | Anatomical site collected from. |
fasting_status_codeable_concept | Coding | None bound to system-fasting-status-code | optional | Patient fasting status (HL7 v2-0916). |
fasting_status_duration | DurationSpec | None | optional | Fasting duration. See DurationSpec. |
ProcessingSpec
One element of the processing list.
| Field | Type | Required | Notes |
|---|---|---|---|
description | str | required | Description of the processing step. |
method | Coding | None bound to system-specimen-processing-method-code | optional | Processing method (SNOMED, descendants of 9265001). |
performer | UUID4 | None | optional | External ID of the performing User. Validated to exist, else rejected with "Performer user not found". Serialized output adds performer_object, the resolved UserSpec from cache. |
time_date_time | str | required | When the step was performed. |
QuantitySpec
| Field | Type | Notes |
|---|---|---|
value | Decimal | max_digits=20, decimal_places=0, so integer-valued. |
unit | Coding | Required unit coding. |
This is a specimen-local quantity spec, distinct from the shared Quantity common type — which allows decimal_places=6, optional value/unit, plus code/meta.
DurationSpec
| Field | Type | Notes |
|---|---|---|
value | int | Duration magnitude. |
unit | Coding | Required unit coding. |
Coding (shared common type)
Every coded field above uses Coding (extra="forbid"):
| Field | Type | Required |
|---|---|---|
system | str | None | optional |
version | str | None | optional |
code | str | required |
display | str | None | optional |
Bound value sets
Coded fields bind to Care value sets via ValueSetBoundCoding[<slug>], which validates the submitted code against the value set at write time.
| Field | Value set | Slug | Source system |
|---|---|---|---|
specimen_type | Specimen Type Code | system-specimen_type-code | HL7 v2-0487 |
condition[] | Specimen Condition | system-specimen-condition-code | HL7 v2-0493 (v2.0.0) |
collection.method | Collection Method | system-collection-method-code | SNOMED CT (enumerated, see below) |
collection.body_site | Body Site | system-body-site-observation | (observation body-site value set) |
collection.fasting_status_codeable_concept | Fasting Status | system-fasting-status-code | HL7 v2-0916 (v2.0.0) |
processing[].method | Specimen Processing Method | system-specimen-processing-method-code | SNOMED CT (< 9265001) |
Collection Method value set
The system-collection-method-code value set enumerates these SNOMED CT codes:
| Code | Display |
|---|---|
129316008 | Aspiration - action |
129314006 | Biopsy - action |
129300006 | Puncture - action |
129304002 | Excision - action |
129323009 | Scraping - action |
73416001 | Urine specimen collection, clean catch |
225113003 | Timed urine collection |
70777001 | Urine specimen collection, catheterized |
386089008 | Collection of coughed sputum |
278450005 | Finger-prick sampling |
The other coded fields aren't enumerated. Fasting Status (http://terminology.hl7.org/CodeSystem/v2-0916), Specimen Condition (v2-0493), Specimen Type (v2-0487), Body Site, and Specimen Processing Method (SNOMED < 9265001) are pulled in by code-system reference or filter rather than a fixed concept list.
Resource specs (API schema)
Every spec extends EMRResource (care/emr/resources/base.py), which provides serialize (DB object → pydantic, via get_database_mapping) and de_serialize (pydantic → DB object), plus the perform_extra_serialization / perform_extra_deserialization hooks. __exclude__ = ["facility", "request"] on the base keeps those fields from being copied field-for-field.
| Spec class | Role | Key fields / behaviour |
|---|---|---|
BaseSpecimenSpec | shared base | id, accession_identifier, status (required), specimen_type (required), received_time, collection, processing, condition, note. __model__ = Specimen, __exclude__ = ["facility", "request"]. |
SpecimenCreateSpec | write · create | Adds subject_patient: UUID4 (required), subject_encounter: UUID4 (required), request: UUID4 | None. Wires up the FK links on creation. |
SpecimenUpdateSpec | write · update | Relaxes status and specimen_type to optional (| None) for partial updates. |
SpecimenReadSpec | read · list | Adds specimen_definition: dict | None. perform_extra_serialization sets id = external_id and, when present, embeds the linked specimen definition via SpecimenDefinitionReadSpec.serialize(...).to_json(). |
SpecimenRetrieveSpec | read · detail | Extends SpecimenReadSpec, adds service_request: dict | None; serialization additionally embeds the linked service request via ServiceRequestReadSpec.serialize(...).to_json(). |
Nested specs (defined in the same module): CollectionSpec, ProcessingSpec, QuantitySpec, DurationSpec — see Nested JSON specs.
Validation & server behaviour
collection.collectorandprocessing[].performermust reference existing users byexternal_id; serialization then injects the resolvedcollector_object/performer_object(cachedUserSpec).- Coded fields are validated against their value sets on write (
ValueSetBoundCoding); an unbound or invalidcodeis rejected. - Read specs embed related resources — specimen definition on list and detail, service request on detail — as nested JSON rather than bare IDs.
- The base spec doesn't override
perform_extra_deserialization, so nostatus_history-style server-side history is appended on write, unlike some other clinical resources.
API integration notes
- Aligned with the FHIR
Specimenresource, though some Care field names differ. On create,subject_patient/subject_encounter/requestmap to thepatient/encounter/service_requestFKs. - The viewset (
SpecimenViewSet) mixes in retrieve and update only (EMRRetrieveMixin,EMRUpdateMixin) — so you get read/detail/list and update, withpydantic_model = BaseSpecimenSpec,pydantic_update_model = SpecimenUpdateSpec,pydantic_read_model = SpecimenReadSpec,pydantic_retrieve_model = SpecimenRetrieveSpec. - Filtering:
accession_identifier(icontains); ordering oncreated_date/modified_date. - The custom action
POST retrieve_by_accession_identifierlooks up a specimen byaccession_identifier(must be ≥ 5 chars), returning aSpecimenRetrieveSpec. It raises a validation error on no match or multiple matches. - Authorization keys off the linked
service_request: read viacan_read_specimen, write viacan_write_specimen. One service request may own multiple specimens.
Related models
facility → FK Facility (PROTECT, nullable) [excluded from specs]
patient → FK Patient (CASCADE) [set via subject_patient]
encounter → FK Encounter (CASCADE, nullable) [set via subject_encounter]
service_request → FK ServiceRequest (CASCADE, nullable) [set via request]
specimen_definition → FK SpecimenDefinition (CASCADE, nullable)
A Specimen is created to fulfill a ServiceRequest (a lab order) and may be shaped by a SpecimenDefinition describing the expected sample. Results derived from a specimen surface through a DiagnosticReport.
Related
- Reference: Specimen Definition
- Reference: Service Request
- Reference: Diagnostic Report
- Reference: Patient
- Reference: Base Model
- Source (model): specimen.py on GitHub
- Source (spec): specimen/spec.py on GitHub
- Source (value sets): specimen/valueset.py on GitHub