Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

References/Resource Category: Difference between revisions

From OHC Network Wiki
Content deleted Content added
OHC identity seed
 
Add Navbox platform (via update-page on MediaWiki MCP Server)
 
Line 267: Line 267:
* Monetary-component propagation runs through Celery, so descendants' <code>calculated_monetary_components</code> is eventually consistent after a write.
* Monetary-component propagation runs through Celery, so descendants' <code>calculated_monetary_components</code> is eventually consistent after a write.
* Monetary-component fields serialize only for <code>resource_type == &quot;charge_item_definition&quot;</code>; during merges, component identity rests on the nested <code>code.system</code> + <code>code.code</code> pair.
* Monetary-component fields serialize only for <code>resource_type == &quot;charge_item_definition&quot;</code>; during merges, component identity rests on the nested <code>code.system</code> + <code>code.code</code> pair.

{{Navbox platform}}


{{Related}}
{{Related}}

Latest revision as of 09:54, 6 July 2026

referenceplatformCARE 3.0+

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
resource_type CharField(255) yes
resource_sub_type CharField(255) yes
title CharField(255) yes
slug CharField(255) yes
description TextField no

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

{{Field|model=ResourceCategory|section=Tree structure|name=has_children|type=BooleanField|notes=Default False. Flips to True on the parent when a child is created (see [[#methods--save-behaviour}}

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] Components configured directly on this category

{{Field|model=ResourceCategory|section=Charge item monetary components|name=calculated_monetary_components|type=JSONField → list[MonetaryComponent] | None|notes=Effective components after merging the parent's calculated components with this node's configured components (see [[#methods--save-behaviour}}

Enums

ResourceCategoryResourceTypeOptions values

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

Value Meaning
product_knowledge Category classifies References/Product Knowledge entries
activity_definition Category classifies [[References/Activity Definition
charge_item_definition Category classifies [[References/Charge Item Definition

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 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/*RetrieveSpecResourceCategoryReadSpec serves both list and detail.

ResourceCategoryReadSpec serialization (perform_extra_serialization)

  • id = obj.external_id
  • parent = obj.get_parent_json() — the nested ancestor snapshot (shape above)
  • slug_config = obj.parse_slug(obj.slug) — a facility-scoped slug returns {"facility": <uuid str>, "slug_value": <str>}; an instance slug returns {"slug_value": <str>}
  • calculated_monetary_components / configured_monetary_components — set only when resource_type == "charge_item_definition"; otherwise these default-None fields are omitted

SlugType validation

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

  • length 5–50
  • regex ^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$ — URL-safe (letters, digits, -, _), starting and ending alphanumeric

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
code Coding None
factor Decimal None
amount Decimal None
tax_included_amount Decimal None
global_component bool False
conditions list[EvaluatorConditionSpec] []

MonetaryComponent model validators:

  • tax_included_amount only allowed when type is base
  • base must have no conditions
  • base must have an amount
  • amount and factor are mutually exclusive (not both)
  • either amount or factor must be present (unless global_component and code)

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()

  • On create (self.id not yet set): runs super().save(), then set_organization_cache(), then enqueues monetary-component summarisation via summarise_monetary_components(self).
  • On update: a plain super().save(*args, **kwargs), no cache rebuild.

set_organization_cache()

Runs once on creation when a parent is present:

  • Sets parent_cache = [*parent.parent_cache, parent.id] and level_cache = parent.level_cache + 1.
  • Sets root_org to the parent's root_org, or to the parent itself if the parent is a root.
  • If the parent's has_children was False, flips it to True and persists it with save(update_fields=["has_children"]).
  • Persists the node via super().save().

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:

  • For a root (no parent), calculated_monetary_components = configured_monetary_components.
  • Otherwise it merges the parent's calculated_monetary_components with this node's configured_monetary_components via merge_monetary_components(), then persists with save(update_fields=["calculated_monetary_components"]).
  • It re-dispatches itself (.delay(component.id)) for every child, propagating changes through the tree.

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

  • Categories are facility-scoped. The (slug, facility) index and SlugBaseModel encoding (f-<facility>-<slug>) keep slugs unique per facility. Clients send slug_value (5–50, URL-safe); the server encodes it and slug_config decodes it.
  • Write parent by slug (ResourceCategoryWriteSpec.parentResourceCategory.objects.get(slug=...)), not by id. On read it comes back as the nested get_parent_json() snapshot.
  • parent_cache, level_cache, root_org, has_children, cached_parent_json, and calculated_monetary_components are platform-maintained — don't set them. Write parent and configured_monetary_components; save() and the Celery task derive the rest.
  • Cache rebuilds (set_organization_cache, monetary-component summarisation) run only on create. Changing an existing node's parent or configured components does not rebuild the caches.
  • Monetary-component propagation runs through Celery, so descendants' calculated_monetary_components is eventually consistent after a write.
  • Monetary-component fields serialize only for resource_type == "charge_item_definition"; during merges, component identity rests on the nested code.system + code.code pair.