Skip to main content
Version: 3.1

Tagging

A TagConfig defines one tag: its label, category, scope, and place in a tag tree. Tags are hierarchical labels you attach to resources — patients, encounters, charge items — to classify and filter them. You manage the definitions through TagConfig; the applied tags live as integer ID arrays on the tagged resources.

Two layers back the tag. The Django model (care/emr/models/tag_config.py) is storage — it holds opaque JSONFields (metadata, cached_parent_json) whose structure it never describes. The Pydantic resource specs (care/emr/resources/tag/config_spec.py) are the API and implementation layer: they pin down the enums, give those JSON fields a real shape, run field validation, and split read from write.

Source:

Models

ModelPurpose
TagConfigDefinition of a single tag — its display, category, scope, status, metadata, and position in the tag tree

TagConfig extends EMRBaseModel, the shared Care EMR base providing external_id, the audit fields created_by/updated_by and created_date/modified_date, soft-delete via deleted, and history/meta JSON. Applied tag IDs live on the tagged resource itself — for example Patient.instance_tags / Patient.facility_tags and Encounter.tags.

TagConfig fields

Scope

A tag is scoped to at most one owner — an instance-wide organization, a facility-scoped organization, or a facility.

FieldTypeNotes
facilityFK → FacilityPROTECT, nullable, default=None. Facility that owns the tag
facility_organizationFK → FacilityOrganizationCASCADE, nullable. Owning facility organization. Not allowed on instance-level (no-facility) tags
organizationFK → OrganizationCASCADE, nullable. Owning instance-wide organization
resourceCharField(255)The resource type the tag applies to. Constrained by TagResource in the write spec (see enum)

Display & classification

FieldTypeNotes
statusCharField(255)Lifecycle status. Constrained by TagStatus in the spec — active / archived (see enum)
displayCharField(255)Human-readable label. Required in the spec
descriptionTextFieldNullable, blank-able. Required key in the base spec but accepts null
categoryCharField(255)Grouping category. Constrained by TagCategoryChoices in the spec (see enum)
priorityIntegerFielddefault=100. Ordering weight
metadataJSONFielddefault=None, nullable. Shape defined by TagConfigMetadata: { color: str?, icon: str? } (see nested spec)

Tree structure & caches

TagConfig is self-referential — tags form a tree, and denormalized caches are rebuilt on insert to avoid recursive joins (the same pattern as Organization). These fields are platform-maintained and never writable through the create/update specs.

FieldTypeNotes
parentFK → selfCASCADE, nullable. related_name="children". Null parent means a root tag
root_tag_configFK → selfCASCADE, nullable. related_name="root". Top of the tree, derived on save
has_childrenBooleanFielddefault=False. Flipped to True on the parent when a child is created
level_cacheIntegerFielddefault=0. Depth in the tree (parent.level_cache + 1)
parent_cacheArrayField[int]default=list. Full ancestor id chain (parent.parent_cache + [parent.id])
cached_parent_jsonJSONFielddefault=dict. Materialized parent record with a cache_expiry; rebuilt after cache_expiry_days (15). Shape: { id, display, description, category, parent (nested same shape), level_cache, cache_expiry }

Enums

TagCategoryChoices values

category binds to this enum in every spec (str values).

Value
diet
drug
lab
admin
contact
clinical
behavioral
research
advance_directive
safety

TagResource values

resource binds to this enum in TagConfigWriteSpec (set only on create; the read specs surface it as a plain str).

Value
encounter
activity_definition
service_request
charge_item
charge_item_definition
patient
token_booking
medication_request_prescription
supply_request_order
supply_delivery_order
account

TagStatus values

status binds to this enum in every spec (str values).

ValueMeaning
activeTag is available for use
archivedTag retired; retained for historical references

Nested specs (shape of JSON fields)

TagConfigMetadata (shape of metadata)

Gives the opaque metadata JSONField a real structure. A plain Pydantic BaseModel, not an EMRResource.

FieldTypeRequiredDefaultNotes
colorstr | NonenoNoneDisplay color hint
iconstr | NonenoNoneDisplay icon hint

Resource specs (API schema)

Every spec extends EMRResource (care/emr/resources/base.py), which supplies serialize (DB object → spec) and de_serialize (spec → DB object) plus the perform_extra_serialization / perform_extra_deserialization hooks. __exclude__ on the base spec keeps the FK fields (facility, facility_organization, organization, parent) out of the generic field copy so each spec can resolve them explicitly.

Spec classRoleNotes
TagConfigBaseSpecshared__model__ = TagConfig; __exclude__ = [facility, facility_organization, organization, parent]. Fields: id (UUID4?), display, category (TagCategoryChoices), description (str | None), priority (int = 100), status (TagStatus), metadata (TagConfigMetadata?)
TagConfigWriteSpecwrite · createBase + facility?, facility_organization?, organization?, parent? (all UUID4), resource (TagResource, required). Runs two model_validator(after) checks and resolves FKs in perform_extra_deserialization
TagConfigUpdateSpecwrite · updateBase + facility_organization?, organization? (UUID4). Cannot change facility, parent, or resource. Resolves organization/facility_organization in perform_extra_deserialization (facility organization scoped to obj.facility)
TagConfigReadSpecread · list@cacheable. Base + level_cache (int = 0), system_generated (bool), has_children (bool), parent (dict | None), resource (str), facility (dict | None). Inlines parent via get_parent_json() and a FacilityBareMinimumSpec for facility
TagConfigRetrieveSpecread · detailExtends read spec + created_by (dict), updated_by (dict), facility_organization (dict | None), organization (dict | None). Adds audit users and full org/facility-org serialization

Write-spec validation (TagConfigWriteSpec)

Two @model_validator(mode="after") checks run before deserialization:

  • validate_exists:
    • If facility is set, it must exist; if facility_organization is also set it must belong to that facility, else ValidationError("Facility Organization not found").
    • If organization is set, it must exist, else ValidationError("Organization not found").
    • If parent is set, a TagConfig with that external_id and the same resource must exist (and, when facility is set, belong to that facility), else ValueError("Parent tag config not found").
  • validate_organizations: a facility_organization without a facility is rejected — ValueError("Facility Organization not allowed in instance level tag configs").

Server-maintained behaviour

  • TagConfigWriteSpec.perform_extra_deserialization resolves parent, organization, and facility (plus facility_organization scoped to the resolved facility) from their external_ids onto the model instance; parent is explicitly set to None when absent.
  • TagConfigReadSpec.perform_extra_serialization sets id = external_id, inlines the parent record via obj.get_parent_json() (only when non-empty), and serializes facility with FacilityBareMinimumSpec ({ id, name }).
  • TagConfigRetrieveSpec.perform_extra_serialization calls the read-spec hook, then serialize_audit_users (populates created_by/updated_by from cache) and serializes facility_organization / organization with their full read specs.
  • Tree caches (level_cache, parent_cache, root_tag_config, has_children) are populated by the model's set_tag_config_cache() on insert — never accepted from clients.
  • system_generated is declared on TagConfigReadSpec but is not a field on the current TagConfig model (nor on EMRBaseModel). The generic serialize only copies model fields, so it is not populated from storage unless something else provides it. Treat it as a read-only flag and verify against the live model before relying on it.

Cache invalidation

A post_save signal connects invalidate_tag_config_cache to TagConfig. Every save does three things.

  1. Invalidates the tag's own TagConfigReadSpec cache and resets cached_parent_json = {}.
  2. Invalidates all descendants (rows whose parent_cache overlaps the tag id) — they embed parent data via get_parent_json().
  3. Invalidates the parent's cache (its has_children may have changed).

Methods & save behaviour

set_tag_config_cache()

When a parent is set, recomputes parent_cache (parent.parent_cache + [parent.id]) and level_cache (parent.level_cache + 1), then derives root_tag_config from the parent — the parent itself if the parent is a root, otherwise the parent's root_tag_config. If the parent had no children, it flips parent.has_children = True, persisting only that field. Finishes with super().save().

get_parent_json()

Returns cached_parent_json when it is present and its cache_expiry is still in the future. Otherwise it recursively rebuilds the nested parent record (id = parent external_id, display, description, category, nested parent, level_cache), stamps a fresh expiry (cache_expiry_days = 15), persists cached_parent_json, and returns it. Returns {} for root tags.

save() side effects

On insert (no id yet), the base save() runs first, then set_tag_config_cache() populates the tree caches in a second write pass. On update, save() persists normally without recomputing caches. Both paths trigger the post_save cache-invalidation signal described above.

API integration notes

The REST API exposes only the definitions. Applied tags are stored as integer ID arrays on each tagged resource (e.g. Patient.instance_tags/facility_tags, Encounter.tags), not as join rows.

Each operation maps to one spec:

  • Create — TagConfigWriteSpec. Sets resource, the scope FKs, and parent. resource must match a TagResource value.
  • Update — TagConfigUpdateSpec. Reassigns org/facility-org only, plus the shared display/category/status/metadata fields; facility, parent, and resource are fixed.
  • List — TagConfigReadSpec. Detail — TagConfigRetrieveSpec.

metadata carries display hints (color, icon) without a schema migration, shaped by TagConfigMetadata.