← Knowledge library

Clinical care

Resource Category

ResourceCategory is a facility-scoped, self-nesting category tree that classifies resources — product_knowledge, activity_definition, and charge_item_definition — and carries charge-item monetary components that inherit down the tree. The…

3.1 · versioned documentation · 8 min read

ResourceCategory is a facility-scoped, self-nesting category tree that classifies resources — product_knowledge, activity_definition, and charge_item_definition — and carries charge-item monetary components that inherit down the tree.

The Django model is just storage. The behaviour you code against lives in the Pydantic resource specs in care/emr/resources/resource_category/: the resource_type enum, the MonetaryComponent shape behind the *_monetary_components JSON fields, slug validation, and the split between read and write schemas. Several model fields are opaque JSONFields whose real structure exists only in the specs — see Resource specs (API schema).

Source:

Models

Model Purpose
ResourceCategory A facility-scoped, self-nesting category tree node that classifies resources (product_knowledge, activity_definition, charge_item_definition) and carries inheritable charge-item monetary components

ResourceCategory extends SlugBaseModel with FACILITY_SCOPED = True, the facility-scoped slug variant of the base. It inherits the EMR base fields — external_id, audit fields, soft-delete, history/meta JSON — and adds slug handling on top.

ResourceCategory fields

Core

Field Type Required Notes
facility FK → facility.Facility (PROTECT) yes Owning facility; PROTECT blocks deletion while categories reference it. Set server-side and excluded from spec write/read payloads
resource_type CharField(255) yes Top-level classification. The spec constrains it to the ResourceCategoryResourceTypeOptions enum — see values
resource_sub_type CharField(255) yes Secondary classification; a free string in the spec (resource_sub_type: str)
title CharField(255) yes Display name (title: str)
slug CharField(255) yes Stored encoded as f-<facility_external_id>-<slug_value>. Clients send the unencoded slug_value (validated SlugType); calculate_slug() encodes it
description TextField no Nullable, blank-able (description: str | None = None)

Meta declares a composite index on (slug, facility) for slug lookups within a facility.

Tree structure

Field Type Notes
parent FK → emr.ResourceCategory (CASCADE) Self-reference to the parent node; nullable. related_name="children". __exclude__d from spec serialization — written via slug lookup, read via get_parent_json()
is_child BooleanField Default False. Exposed on write (ResourceCategoryWriteSpec) and read
has_children BooleanField Default False. Flips to True on the parent when a child is created (see save behaviour). Read-only in specs
root_org FK → emr.ResourceCategory (CASCADE) Self-reference to the root of this node's tree; nullable. related_name="root". Not exposed in specs

Tree caches

set_organization_cache() and get_parent_json() denormalize the ancestor chain into these fields, so reads resolve it without recursive joins. They are platform-maintained; clients never write them.

Field Type Rebuilt by
parent_cache ArrayField[int] set_organization_cache() — the parent's parent_cache plus the parent's id
level_cache IntegerField set_organization_cache() — parent's level_cache + 1 (default 0 for roots). Exposed read-only (level_cache: int = 0)
cached_parent_json JSONField get_parent_json() — nested snapshot (shape below) with a cache_expiry timestamp; refreshed after cache_expiry_days (15)

get_parent_json() builds cached_parent_json in this shape, which is also the shape of the parent field in ResourceCategoryReadSpec:

{
  "id": str,            # parent.external_id (UUID)
  "slug": str,          # parent.slug (encoded)
  "title": str,
  "description": str | null,
  "parent": { ... },    # recursively the grandparent's cached_parent_json ({} at root)
  "cache_expiry": str   # ISO datetime; now + 15 days
}

Root nodes (no parent_id) serialize parent as {}.

Charge item monetary components

Both are JSONField(default=list) in the model; the spec types each element as a MonetaryComponent (see shape). They serialize only when resource_type == charge_item_definition.

Field Type Notes
configured_monetary_components JSONField → list[MonetaryComponent] | None Components configured directly on this category
calculated_monetary_components JSONField → list[MonetaryComponent] | None Effective components after merging the parent's calculated components with this node's configured components (see Methods). Server-maintained, read-only

Enums

ResourceCategoryResourceTypeOptions values

care/emr/resources/resource_category/spec.py — bound to resource_type.

Value Meaning
product_knowledge Category classifies Product Knowledge entries
activity_definition Category classifies Activity Definitions
charge_item_definition Category classifies Charge Item Definitions; enables the monetary-component fields

MonetaryComponentType values

care/emr/resources/common/monetary_component.py — bound to MonetaryComponent.monetary_component_type.

Value
base
surcharge
discount
tax
informational

Resource specs (API schema)

All specs extend EMRResource (serialize/de_serialize; reads run perform_extra_serialization, writes run perform_extra_deserialization). With __model__ = ResourceCategory and __exclude__ = ["parent"], the parent FK is never auto-mapped — every spec resolves it explicitly.

Spec class Role Exposes / behaviour
ResourceCategoryBaseSpec shared base id (UUID, read), title, description, resource_type (enum), resource_sub_type
ResourceCategoryWriteSpec write · create Base + parent: str | None (parent slug), is_child: bool = False, slug_value: SlugType. When parent is set, perform_extra_deserialization resolves obj.parent = ResourceCategory.objects.get(slug=self.parent), and sets obj.slug = self.slug_value
ResourceCategoryUpdateSpec write · update Base + slug_value: SlugType. perform_extra_deserialization sets obj.slug = self.slug_value; update never reassigns parent
ResourceCategoryReadSpec read · list & detail Base + parent: dict, has_children: bool, level_cache: int = 0, is_child: bool, slug: str, slug_config: dict, calculated_monetary_components, configured_monetary_components. See serialization below

Docstrings label these "ChargeItemDefinition Category", but the model is ResourceCategory. There is no separate *ListSpec/*RetrieveSpec — ResourceCategoryReadSpec serves both list and detail.

ResourceCategoryReadSpec serialization (perform_extra_serialization)

SlugType validation

slug_value is Annotated[str, Field(min_length=5, max_length=50), AfterValidator(slug_validator)]:

The stored slug is the encoded form f-<facility_external_id>-<slug_value> (via SlugBaseModel.calculate_slug()); slug_config round-trips it back to {facility, slug_value}.

MonetaryComponent (nested shape)

Element type of configured_monetary_components / calculated_monetary_components (common/monetary_component.py).

Field Type Default Notes
monetary_component_type MonetaryComponentType enum — required; see values
code Coding | None None Coding { system?: str, version?: str, code: str, display?: str } (extra="forbid")
factor Decimal | None None max_digits=20, decimal_places=6
amount Decimal | None None max_digits=20, decimal_places=6
tax_included_amount Decimal | None None max_digits=20, decimal_places=6; allowed only on base
global_component bool False
conditions list[EvaluatorConditionSpec] [] each { metric: str, operation: str, value: dict | str }, validated against the evaluator-metrics registry

MonetaryComponent model validators:

The category stores and validates these per element as MonetaryComponent. The stricter collection validators — MonetaryComponents/MonetaryComponentsWithoutBase (single base, no duplicate codes, tax-sum balance) — live with the charge-item resources, not the category list.

Methods & save behaviour

ResourceCategory overrides save() to populate the tree caches on create. A handful of helper methods plus a Celery task maintain those caches and the inherited monetary components.

save()

set_organization_cache()

Runs once on creation when a parent is present:

get_parent_json()

Returns cached_parent_json, rebuilding it lazily. A snapshot still within its cache_expiry is returned as-is. Past expiry, the method walks the parent chain, builds a fresh nested snapshot with a new expiry (now + cache_expiry_days), persists it via save(update_fields=["cached_parent_json"]), and returns it. Root nodes (no parent_id) return {}.

summarise_monetary_components(category) — Celery task

A @shared_task that recomputes calculated_monetary_components down the subtree:

merge_monetary_components(parent, child) keys components by code.system + code.code; child components override parent components sharing a key, and components without a code are appended unmerged.

API integration notes

Related

Source evidence

References are pinned to the private MANSOQ source repository. Repository access is required to open these links.