Facility administration
Device
A Device tracks a physical or logical piece of equipment within a facility — from its lifecycle status to where it's placed and which encounter it's attached to. The Django model is only the storage layer; it types fields loosely — CharField…
3.1 · versioned documentation · 8 min read
A Device tracks a physical or logical piece of equipment within a facility — from its lifecycle status to where it's placed and which encounter it's attached to.
The Django model is only the storage layer; it types fields loosely — CharField for status enums, JSONField for contact/metadata. The contract lives in the Pydantic resource specs under care/emr/resources/device/, which constrain those CharFields to enums, structure the JSON fields, and define validation, read/write schemas, and server-maintained side effects.
Source:
- Model:
care/emr/models/device.py - Spec:
care/emr/resources/device/spec.py - History spec:
care/emr/resources/device/history_spec.py - Viewset:
care/emr/api/viewsets/device.py - Device-type registry:
care/emr/registries/device_type/device_registry.py
Models
| Model | Purpose |
|---|---|
Device |
A physical or logical device tracked within a facility |
DeviceEncounterHistory |
Records each period a device was associated with an Encounter |
DeviceLocationHistory |
Records each period a device was placed at a FacilityLocation |
DeviceServiceHistory |
Records servicing/maintenance events for a device |
All models extend EMRBaseModel (shared MANSOQ EMR base with external_id, audit fields, and soft-delete semantics).
Device fields
Device data
| Field | Type | Spec constraint | Notes |
|---|---|---|---|
identifier |
CharField(1024) |
str | None |
Free-form device identifier. Filterable via ?identifier= (case-insensitive exact match) |
status |
CharField(16) |
DeviceStatusChoices (required) |
Lifecycle status — see enum |
availability_status |
CharField(14) |
DeviceAvailabilityStatusChoices (required) |
Physical availability — see enum |
manufacturer |
CharField(1024) |
str | None |
Optional in the spec, though the model column itself is non-null |
manufacture_date |
DateTimeField |
datetime | None |
Optional, tz-aware |
expiration_date |
DateTimeField |
datetime | None |
Optional, tz-aware |
lot_number |
CharField(1024) |
str | None |
Optional |
serial_number |
CharField(1024) |
str | None |
Optional |
registered_name |
CharField(1024) |
str (required) |
Formal/registered device name; required by every write spec. Searchable via ?search= |
user_friendly_name |
CharField(1024) |
str | None |
Display name. Searchable via ?search= |
model_number |
CharField(1024) |
str | None |
Optional |
part_number |
CharField(1024) |
str | None |
Optional |
contact |
JSONField (default {}) |
list[ContactPoint] (default []) |
A list of structured contact points, not a free-form dict. See ContactPoint shape |
care_type |
CharField(1024) (default None) |
str | None (create only) |
Discriminator selecting a registered device-type plugin. Validated against DeviceTypeRegistry on create — must be a registered key (e.g. "camera") or null. Filterable via ?care_type= |
metadata |
JSONField (default {}) |
not exposed directly | Excluded from all specs. Written server-side by the care_type plugin's handle_create/handle_update. Read it back through the virtual mansoq_metadata field (below) |
Virtual / spec-only field
| Field | Spec type | Notes |
|---|---|---|
mansoq_metadata |
dict (default {}) |
Not a DB column. On read it's populated by the care_type plugin — DeviceListSpec calls the plugin's list(obj), DeviceRetrieveSpec calls retrieve(obj). On write it carries plugin-specific data for handle_create/handle_update, which typically persist into the model's metadata column. Falls back to {} when there's no care_type or the plugin lookup fails |
Relations
| Field | Type | Notes |
|---|---|---|
facility |
FK → Facility |
CASCADE; owning facility (required). Set server-side from the URL (facility_external_id), never from the request body |
managing_organization |
FK → FacilityOrganization |
SET_NULL, nullable; org responsible for the device. Excluded from the create/update body — managed via dedicated endpoints (see API integration notes) |
current_location |
FK → FacilityLocation |
SET_NULL, nullable; where the device is currently placed. Excluded from the create/update body — managed via associate_location |
current_encounter |
FK → Encounter |
SET_NULL, nullable; encounter the device is currently attached to. Excluded from the create/update body — managed via associate_encounter |
Access cache
| Field | Type | Notes |
|---|---|---|
facility_organization_cache |
ArrayField[int] |
Denormalized cache of facility-organization IDs, rebuilt on every save(). Don't set it directly. get_queryset reads it to scope which devices a user may see |
Enum values
DeviceStatusChoices values
status field. Defined in spec.py.
| Value | Meaning |
|---|---|
active |
Device is in service |
inactive |
Device is not currently in service |
entered_in_error |
Record created in error |
DeviceAvailabilityStatusChoices values
availability_status field. Defined in spec.py.
| Value | Meaning |
|---|---|
lost |
Device cannot be located |
damaged |
Device is damaged |
destroyed |
Device is destroyed |
available |
Device is available for use |
ContactPoint shape
contact is list[ContactPoint]. Each item (from care/emr/resources/common/contact_point.py):
ContactPoint {
system: ContactPointSystemChoices # required
value: str # required
use: ContactPointUseChoices # required
}
system values |
use values |
|---|---|
phone, fax, email, pager, url, sms, other |
home, work, temp, old, mobile |
Related models
DeviceEncounterHistory
Audit trail of device-to-encounter associations. Attaching a device to an encounter creates a new row; detaching it stamps end. Read-only via the API (EMRModelReadOnlyViewSet), ordered by -end.
device → FK Device (CASCADE)
encounter → FK Encounter (CASCADE)
start → DateTimeField
end → DateTimeField (nullable)
DeviceLocationHistory
Audit trail of device placements. Moving a device to a location creates a new row; leaving it stamps end. Read-only via the API, ordered by -end.
device → FK Device (CASCADE)
location → FK FacilityLocation (CASCADE)
start → DateTimeField
end → DateTimeField (nullable)
DeviceServiceHistory
Servicing/maintenance log for a device. Writable via the API (create + update + list + retrieve).
device → FK Device (PROTECT, required)
serviced_on → DateTimeField (nullable, default None)
note → TextField (default "")
edit_history → JSONField (default [])
device uses PROTECT, so a Device with service-history rows can't be hard-deleted while those rows exist. edit_history is an append-only audit list the server maintains on every update (see Methods & save behaviour).
Resource specs (API schema)
The viewset wires these specs as create = DeviceCreateSpec, update = DeviceUpdateSpec, list = DeviceListSpec, retrieve = DeviceRetrieveSpec. All extend EMRResource (serialize / de_serialize, with perform_extra_serialization / perform_extra_deserialization hooks).
Device specs
| Spec class | Role | Adds / overrides | Excludes |
|---|---|---|---|
DeviceSpecBase |
shared base | id, all device-data fields, status/availability_status enums, contact: list[ContactPoint], registered_name required |
facility, managing_organization, current_location, current_encounter, mansoq_metadata |
DeviceCreateSpec |
write · create | care_type (validated against DeviceTypeRegistry), mansoq_metadata: dict |
(inherits base excludes) |
DeviceUpdateSpec |
write · update | mansoq_metadata: dict — no care_type, which makes care_type immutable after create |
(inherits base excludes) |
DeviceListSpec |
read · list | id = external_id; mansoq_metadata from plugin list(obj) |
(inherits) |
DeviceRetrieveSpec |
read · detail | full nested objects: current_location (FacilityLocationListSpec), current_encounter (EncounterListSpec), managing_organization (FacilityOrganizationReadSpec), created_by/updated_by (audit users); mansoq_metadata from plugin retrieve(obj) |
(inherits) |
Device history specs
| Spec class | Role | Fields | Excludes |
|---|---|---|---|
DeviceLocationHistoryListSpec |
read · list | id, location (nested FacilityLocationListSpec), created_by, start, end? |
device, location (raw FK) |
DeviceEncounterHistoryListSpec |
read · list | id, encounter (nested EncounterListSpec), created_by, start, end? |
device, encounter (raw FK) |
DeviceServiceHistorySpecBase |
shared base | id |
device, edit_history |
DeviceServiceHistoryWriteSpec |
write | serviced_on: datetime (required), note: str (required) |
(inherits) |
DeviceServiceHistoryListSpec |
read · list | adds created_date, modified_date; id = external_id |
(inherits) |
DeviceServiceHistoryRetrieveSpec |
read · detail | adds edit_history: list[dict] (each entry's updated_by hydrated to a UserSpec from cache), created_by, updated_by |
(inherits) |
Validation rules
statusandavailability_statusmust each match one of their enum values, or the request is rejected.registered_nameis required on every write.care_type(create only) must be a registered device-type key ornull.DeviceCreateSpec.validate_care_typecallsDeviceTypeRegistry.get_care_device_class(value), which raises for unknown types.care_typecan't change on update —DeviceUpdateSpecdoesn't expose it.DeviceServiceHistory.serviced_onandnoteare required on write; updates are blocked onceedit_historyreaches 50 entries ("Cannot Edit instance anymore").ContactPoint.system,value, anduseare all required for each contact entry.
Methods & save behaviour
Device.save() — organization cache
Every save rebuilds facility_organization_cache:
- Look up the
FacilityOrganizationwithorg_type="root"for the device'sfacility; its ID seeds the cache. - If
managing_organizationis set, add itsidand itsparent_cache(the full ancestor chain). - Persist the resulting set as
facility_organization_cache.
This lets the list endpoint filter by organization hierarchy without traversing joins. Treat the cache as platform-maintained; managing_organization and facility are the writable inputs that drive it.
care_type plugin hooks
When care_type is set, the matching DeviceTypeBase subclass from DeviceTypeRegistry runs inside the create/update/delete transaction:
| Stage | Hook | Effect |
|---|---|---|
| create | handle_create(request_data, obj) |
May mutate obj / obj.metadata and persist (e.g. the bundled camera plugin stores request_data["some_data"] into metadata) |
| update | handle_update(request_data, obj) |
Same, on update |
| destroy | handle_delete(obj) |
Validation/cleanup before the device is soft-deleted (deleted=True) |
| list | list(obj) |
Supplies mansoq_metadata for DeviceListSpec |
| retrieve | retrieve(obj) |
Supplies mansoq_metadata for DeviceRetrieveSpec |
DeviceServiceHistory edit history
On perform_update, the viewset snapshots the current DB row and appends {serviced_on, note, updated_by} to edit_history before writing the new values — a server-maintained, append-only trail of previous states. Updates are refused once it holds 50 entries.
API integration notes
facilitycomes from the URL (facility_external_id) on create, not from the body.current_location,current_encounter, andmanaging_organizationaren't writable through the device create/update body. Dedicated detail actions mutate them, and each one closes the open history row (stampsend) before opening a new one:POST associate_encounter—{ encounter: uuid | null }; closes the openDeviceEncounterHistory, setscurrent_encounter, and opens (and returns) a new history row.nulldetaches.POST associate_location—{ location: uuid | null }; same pattern againstDeviceLocationHistory.POST add_managing_organization—{ managing_organization: uuid }.POST remove_managing_organization— clearsmanaging_organization.
- When an encounter reaches a completed status,
disassociate_device_from_encounterclearscurrent_encounteron all attached devices and stampsendon their openDeviceEncounterHistoryrows. metadata(the model column) is plugin-owned; clients read deployment-specific data back through themansoq_metadatavirtual field, notmetadata.facility_organization_cacheis platform-maintained — don't set it directly. The list endpoint scopes results to devices whose cache overlaps the requesting user's facility-organization memberships, or, when?location=is supplied, by location permission /parent_cachewith?include_children=.
Related
- Reference: Facility
- Reference: Organization
- Reference: Location
- Reference: Encounter
- Source: device.py model
- Source: device spec
- Source: device history spec
- Source: ContactPoint
Source evidence
References are pinned to the private MANSOQ source repository. Repository access is required to open these links.