Skip to main content
Version: 3.0

Location

FacilityLocation is a node in a facility's building/ward/room/bed tree — where resources sit and where care happens. You work with it when you place an encounter somewhere, scope organization access to part of a facility, or model a site's physical layout.

Source:

Nodes nest without limit: every node may have a parent and children, so one tree can run from a campus down to a single bed. Encounters and organizations attach to nodes, and access granted at a node cascades to its descendants asynchronously rather than resolving on each request. The model maps to the FHIR Location resource.

The Django model is only the storage layer. location_type, metadata, and cached_parent_json are opaque JSONFields; their real shape lives in the Pydantic resource specs (care/emr/resources/location/spec.py), which also define every enum and the read/write schema split. See Resource specs (API schema).

Models

ModelPurpose
FacilityLocationA node in a facility's location tree (building, ward, room, bed, …)
FacilityLocationOrganizationGrants a FacilityOrganization access to a location
FacilityLocationEncounterRecords how/when an encounter occupied a location (e.g. a bed)

All three extend EMRBaseModel, which supplies external_id, audit fields, and soft-delete semantics.

FacilityLocation fields

Descriptive

FieldTypeSpec values / shapeNotes
nameCharField(255)str, requiredMust be unique among siblings at the same level under the same root (see validation)
descriptionCharField(255)str, requiredFree-text
statusCharField(255)StatusChoices enumOne of StatusChoices
operational_statusCharField(255)FacilityLocationOperationalStatusChoices enumFHIR operational status code; one of FacilityLocationOperationalStatusChoices
system_availability_statusCharField(255)str (read-only)Server-maintained, derived from encounter association. Resolves to one of LocationAvailabilityStatusChoices (available / reserved)
modeCharField(255)FacilityLocationModeChoices enumkind (a type, e.g. a building/ward) or instance (a single location, e.g. Bed 1). Set on create, then frozen. See FacilityLocationModeChoices
location_typeJSONField (default=dict, nullable)Coding | None (default None)A single FHIR Coding, not a CodeableConcept. Unbound — any coding is accepted
formCharField(255)FacilityLocationFormChoices enumFHIR physical form; one of FacilityLocationFormChoices

Hierarchy & tree caches

These fields denormalize the tree so descendants are queryable without recursive joins. save() and the cascade task own them; never set them from a client.

FieldTypeNotes
parentFK → FacilityLocationSET_NULL, nullable. Immediate parent. Accepted on write as a UUID (parent); on read it's dropped in favour of the serialized parent JSON
root_locationFK → selfCASCADE, related_name="root", nullable. Top of this node's tree. Excluded from specs
has_childrenBooleanFielddefault=False. Set True on the parent when a child is created. Read-only in list/retrieve specs
level_cacheIntegerFielddefault=0. Depth in the tree (parent.level_cache + 1)
parent_cacheArrayField[int]Ordered ancestor IDs (parent.parent_cache + [parent.id])
cached_parent_jsonJSONField (default=dict)Serialized parent chain (FacilityLocationListSpec) plus a cache_expiry ISO timestamp. Rebuilt lazily by get_parent_json(), invalidated by the cascade task. cache_expiry_days = 15
sort_indexIntegerFielddefault=0. In specs int | None, default 0, constrained 0 ≤ sort_index ≤ 10000 (MIN_SORT_INDEX/MAX_SORT_INDEX). Auto-assigned as max(sibling sort_index) + 1 when left unset

Access & encounter

FieldTypeNotes
facilityFK → facility.FacilityPROTECT. Owning facility. Excluded from specs — resolved from route/context
facility_organization_cacheArrayField[int]default=list. Org IDs that can reach this location; rebuilt by sync_organization_cache()
current_encounterFK → EncounterSET_NULL, nullable, default=None. Populated from FacilityLocationEncounter. Excluded from the base spec; surfaced as serialized JSON in list/retrieve specs
metadataJSONFielddefault=dict. Open extension bag for deployment-specific data

Enum values

Every enum is a str Enum in care/emr/resources/location/spec.py. Values below are character-for-character from source.

StatusChoices values

ValueMeaning
activeLocation is active
inactiveLocation is inactive
unknownStatus unknown

FacilityLocationOperationalStatusChoices values

FHIR HL7 v2 bed/location operational status codes.

CodeMeaning
CClosed
HHousekeeping
OOccupied
UUnoccupied
KContaminated
IIsolated

FacilityLocationModeChoices values

ValueMeaning
instanceA single, concrete location (e.g. Bed 1). Instances cannot have children
kindA class/type of location (e.g. a ward or building)

FacilityLocationFormChoices values

FHIR location-physical-type codes.

CodeMeaning
siSite
buBuilding
wiWing
waWard
lvlLevel
coCorridor
roRoom
bdBed
veVehicle
hoHouse
caCabinet
rdRoad
areaArea
jdnJurisdiction
viVirtual

LocationEncounterAvailabilityStatusChoices values

Used by FacilityLocationEncounter.status — how an encounter occupies a location.

ValueMeaning
plannedOccupancy planned
activeCurrently occupied
reservedReserved
completedOccupancy ended

LocationAvailabilityStatusChoices values

The server-derived values that system_availability_status resolves to.

ValueMeaning
availableNot actively tied to an encounter
reservedTied to an encounter

Coding shape

location_type is a single Coding object (extra="forbid"):

Coding {
system: str | None = None
version: str | None = None
code: str # required
display: str | None = None
}

FacilityLocationOrganization

Links an organization to a location. Users under that organization inherit access to the location and its encounters through whatever role they hold on the organization.

location → FK FacilityLocation (CASCADE)
organization → FK FacilityOrganization (CASCADE)

Saving one of these rows calls location.save() (rebuilding facility_organization_cache), then location.cascade_changes() to propagate the grant down to descendants asynchronously. On write, the organization set comes from FacilityLocationWriteSpec.organizations, a list of UUIDs.

FacilityLocationEncounter

A bed assignment, in effect: it records how an encounter occupied a location over a time window.

FieldTypeSpecNotes
statusCharField(25)LocationEncounterAvailabilityStatusChoicesOne of planned / active / reserved / completed
locationFK → FacilityLocationexcluded / nestedCASCADE. Surfaced as JSON in ...ListSpecWithLocation
encounterFK → EncounterUUID on write, JSON on readCASCADE. Validated to exist on create
start_datetimeDateTimeFielddatetime (required)Occupancy start
end_datetimeDateTimeField (nullable)datetime | NoneOccupancy end; open-ended when null

FacilityLocation.current_encounter is populated from these rows.

Methods & save behaviour

save() side effects

On insert (no id yet):

  1. If parent is set, level_cache, root_location, and parent_cache are derived from the parent, and the parent's has_children is flipped to True if needed.
  2. If sort_index is unset, it's assigned max(sibling sort_index) + 1.

On update, cached_parent_json is cleared so it rebuilds lazily. After every super().save(), sync_organization_cache() runs and persists facility_organization_cache through a second save(update_fields=[...]), so each write touches the row twice.

Caches & cascade

  • sync_organization_cache() — unions the parent's org cache, the org chains of every linked FacilityLocationOrganization, and the facility's default_internal_organization_id, then writes facility_organization_cache.
  • get_parent_json() — returns the cached parent chain, refreshing cached_parent_json (via FacilityLocationListSpec.serialize(parent)) once it expires (cache_expiry_days = 15).
  • cascade_changes() / handle_cascade(base_location) — a Celery task that walks every descendant and re-saves it to invalidate each node's cached_parent_json. It fires when a location's organizations change, so propagation is eventually consistent, not immediate.

validate_uniqueness()

Classmethod that enforces name uniqueness among siblings at the same level_cache within the same root_location — or among root-level locations when there's no root. For a new instance with a parent, level_cache and root_location are computed from that parent before the check runs.

Resource specs (API schema)

The Pydantic specs in care/emr/resources/location/spec.py define the request/response shapes. All extend EMRResource, which provides serialize/de_serialize plus the perform_extra_serialization/perform_extra_deserialization hooks (see base model and base.py).

Location specs

Spec classRoleExposes / notes
FacilityLocationBaseSpecshared base__model__ = FacilityLocation; id: UUID4 | None. __exclude__ = [parent, facility, organizations, root_location, current_encounter]
FacilityLocationSpecshared fieldsstatus, operational_status, name, description, location_type (Coding | None), form, sort_index (0..10000, default 0)
FacilityLocationWriteSpecwrite · createAdds parent: UUID4 | None, organizations: list[UUID4] (required), mode: FacilityLocationModeChoices. Validator: a set parent must exist and must not be mode = instance ("Instances cannot have children"). perform_extra_deserialization resolves the parent UUID to an FK
FacilityLocationUpdateSpecwrite · updateSame fields as FacilityLocationSpec. Drops parent/organizations/mode — hierarchy and mode are fixed after creation
FacilityLocationMinimalListSpecread · minimalAdds parent: dict, mode: str, has_children: bool, system_availability_status: str. perform_extra_serialization sets id = external_id and parent = obj.get_parent_json()
FacilityLocationListSpecread · listExtends minimal; adds current_encounter: dict | None, serialized via EncounterListSpec when present
FacilityLocationRetrieveSpecread · detailExtends list; adds created_by/updated_by (via serialize_audit_users)

Encounter-association specs

Spec classRoleExposes / notes
FacilityLocationEncounterBaseSpecshared base__model__ = FacilityLocationEncounter; id: UUID4 | None. __exclude__ = [encounter, location]
FacilityLocationEncounterCreateSpecwrite · createstatus (LocationEncounterAvailabilityStatusChoices), encounter: UUID4, start_datetime, end_datetime: datetime | None. Validator: encounter must exist. perform_extra_deserialization resolves the encounter UUID to an FK
FacilityLocationEncounterUpdateSpecwrite · updatestatus, start_datetime, end_datetime. Encounter is not re-assignable
FacilityLocationEncounterListSpecread · listencounter: UUID4, start_datetime, end_datetime, status: str; id = external_id
FacilityLocationEncounterListSpecWithLocationread · listExtends list; adds location: dict (serialized via FacilityLocationListSpec)
FacilityLocationEncounterReadSpecread · detailencounter: dict (serialized via EncounterRetrieveSpec), start_datetime, end_datetime, status, created_by/updated_by

Server-maintained behaviour

  • These are platform-maintained — don't write them from a client: system_availability_status, has_children, level_cache, parent_cache, cached_parent_json, facility_organization_cache, current_encounter, and sort_index (when left unset).
  • On read, parent comes back as the serialized parent chain (get_parent_json()), never the raw FK.
  • Access granted through organizations / FacilityLocationOrganization lands asynchronously; the org cache and descendant caches settle only after the cascade task runs.
  • location_type takes a free Coding (no value-set slug) and metadata is an open bag, so both add coded or extension data without a schema migration.

API integration notes

  • The API is modelled on the FHIR Location resource, so payload field names may differ from these Django names.
  • mode separates kind (a location type) from instance (a concrete location such as a bed). Beds are FacilityLocation instances, not a separate model, and instance locations cannot have children.
  • parent, organizations, and mode are settable only at creation (FacilityLocationWriteSpec). Updates go through FacilityLocationUpdateSpec, which omits all three.
  • For fields you can't write, see Server-maintained behaviour.