Skip to main content
Version: 3.0

Product Knowledge

ProductKnowledge is the reusable definition of a product — its coding, base presentation unit, ingredients and nutrients, drug characteristics, and storage rules. Define it once and every concrete Product points back to it instead of carrying duplicate data. A definition is either instance-wide or scoped to a single facility.

The Django model is only the storage layer: code, names, storage_guidelines, definitional, and base_unit are opaque JSONFields whose real structure lives in the Pydantic resource specs. Every enum, nested JSON shape, validation rule, and read/write difference below comes from those specs.

Source:

Models

ModelPurpose
ProductKnowledgeFoundational, reusable definition of a product (medication, nutritional product, or consumable)

ProductKnowledge extends SlugBaseModel, which extends EMRBaseModel (the shared base providing external_id, created_date/modified_date, soft-delete via deleted, created_by/updated_by, and history/meta JSON). SlugBaseModel sets FACILITY_SCOPED = True and adds the slug helpers calculate_slug and parse_slug, so a record is addressable by an instance slug (i-<slug>) or a facility slug (f-<facility_external_id>-<slug>).

ProductKnowledge fields

Identity & scope

FieldTypeNotes
facilityFK → facility.FacilityPROTECT, nullable. Null means an instance-level definition; set means facility-scoped. Excluded from the spec base (__exclude__ = ["facility"]); set on write via a facility UUID, surfaced on read as is_instance_level
slugCharField(255)Slug value; combined with facility to form the addressable slug. Set server-side from slug_value on write
alternate_identifierCharField(255)Nullable; secondary external identifier
statusCharField(255)Lifecycle status. Spec-constrained to ProductKnowledgeStatusOptions (required)
product_typeCharField(255)Product category. Spec-constrained to ProductTypeOptions (required)
categoryFK → emr.ResourceCategoryCASCADE, nullable; classifies the product into a resource category. Write it as a category slug string, resolved via ResourceCategory.objects.get(slug=...); read returns it as a nested object

Naming

FieldTypeNotes
nameCharField(255)Primary display name (required in spec)
namesJSONField (list)Nullable; list of ProductName { name_type: ProductNameTypes, name: str }. name_type is bound to the ProductNameTypes enum, not a FHIR value set
names_cacheCharField(2048)Nullable; denormalized space-joined string of name plus every names entry, rebuilt on each save() to power search. Platform-maintained, absent from every spec — never set it from clients

Coding & definition (JSON fields)

The model stores these as bare JSONFields; the spec enforces the shapes below.

FieldModel typeSpec type / shapeNotes
codeJSONField (dict)Coding | NoneNullable; product code as a Coding, not value-set bound at the spec layer
base_unitJSONField (dict)ValueSetBoundCoding[CARE_UCUM_UNITS]Required. A Coding bound to the UCUM units value set (system-ucum-units)
namesJSONField (list)list[ProductName] | NoneSee ProductName
storage_guidelinesJSONField (list)list[StorageGuideline] | NoneSee StorageGuideline
definitionalJSONField (dict)ProductDefinitionSpec | NoneType-specific definition payload. See ProductDefinitionSpec

Enums

ProductTypeOptions (product_type)

Value
medication
nutritional_product
consumable

ProductKnowledgeStatusOptions (status)

Value
draft
active
retired
unknown

ProductNameTypes (names[].name_type)

Value
trade_name
alias
original_name
preferred

DrugCharacteristicCode (definitional.drug_characteristic[].code)

Value
imprint_code
size
shape
color
coating
scoring
logo
image

Nested spec shapes

These are plain Pydantic BaseModels in spec.py, not value-set bound unless noted.

ProductName

names[] — alternate names for the product.

FieldTypeRequiredNotes
name_typeProductNameTypesyesEnum, see above
namestryes

StorageGuideline

storage_guidelines[] — a storage note paired with a stability duration.

FieldTypeRequiredNotes
notestryes
stability_durationDurationSpec { value: int, unit: Coding }yesvalue is an integer count; unit is a free Coding

ProductDefinitionSpec

definitional — type-specific definition payload.

FieldTypeDefaultNotes
dosage_formValueSetBoundCoding[MEDICATION_FORM_CODES] | Nonerequired (nullable)Coding bound to medication form codes (system-medication-form-codes, SNOMED is-a 736542009)
intended_routeslist[Coding][]Free Codings, not value-set bound
ingredientslist[ProductIngredient][]See ProductIngredient
nutrientslist[ProductNutrient][]See ProductNutrient
drug_characteristiclist[DrugCharacteristic][]See DrugCharacteristic

ProductIngredient

definitional.ingredients[].

FieldTypeRequiredNotes
is_activeboolyesActive vs. inactive ingredient
substanceValueSetBoundCoding[CARE_SUBSTANCE_VALUSET]yesCoding bound to the substance value set (system-substance, SNOMED is-a 105590001)
strengthProductStrengthyesSee ProductStrength

ProductNutrient

definitional.nutrients[].

FieldTypeRequiredNotes
itemValueSetBoundCoding[CARE_NUTRIENTS_VALUESET]yesCoding bound to the nutrients value set (system-nutrients, SNOMED is-a 226355009)
amountProductStrengthyesSee ProductStrength

ProductStrength

Shared by ProductIngredient.strength and ProductNutrient.amount.

FieldTypeRequiredNotes
ratioRatio { numerator: Quantity, denominator: Quantity }yes
quantityQuantityyesSee Quantity

DrugCharacteristic

definitional.drug_characteristic[].

FieldTypeRequiredNotes
codeDrugCharacteristicCodeyesEnum, see above
valuestryes

Shared common types

Coding

From care/emr/resources/common/coding.py (extra="forbid").

FieldTypeRequired
systemstr | Noneno
versionstr | Noneno
codestryes
displaystr | Noneno

A ValueSetBoundCoding[<slug>] is a Coding subclass that additionally validates code against the named value set on deserialization.

Quantity

From care/emr/resources/common/quantity.py (extra="forbid").

FieldTypeNotes
valueDecimal | Nonemax 20 digits, 6 decimal places
unitCoding | Nonehuman-readable unit
codeCoding | Nonemachine-processable unit
metadict | None

Ratio { numerator: Quantity, denominator: Quantity } — both required.

Resource specs (API schema)

Every spec builds on EMRResource (care/emr/resources/base.py), which provides serialize (DB → Pydantic), de_serialize (Pydantic → DB), and the perform_extra_serialization / perform_extra_deserialization hooks. EMRResource.serialize copies only non-FK database fields that are declared spec fields and not in __exclude__.

Spec classRoleAdds / behaviour
BaseProductKnowledgeSpecshared base__exclude__ = ["facility"]. Fields: id, alternate_identifier, status, product_type, code, base_unit (required, UCUM-bound), name, names, storage_guidelines, definitional
ProductKnowledgeUpdateSpecwrite · updateAdds category: str | None (resource-category slug) and slug_value: SlugType (required). On deserialize, resolves category via ResourceCategory.objects.get(slug=...) and sets obj.slug = self.slug_value
ProductKnowledgeWriteSpecwrite · createExtends the update spec; adds facility: UUID4 | None. On deserialize, runs the update-spec logic, then resolves facility via Facility.objects.get(external_id=...) when supplied
ProductKnowledgeReadSpecread · detail/listAdds is_instance_level: bool, category: dict | None, slug_config: dict, slug: str. On serialize, sets id = external_id, is_instance_level = not facility_id, serializes category via ResourceCategoryReadSpec, and sets slug_config = parse_slug(slug)

Validation & bound value sets

  • statusProductKnowledgeStatusOptions and product_typeProductTypeOptions, both required.
  • base_unit is required and bound to CARE_UCUM_UNITS (system-ucum-units, system http://unitsofmeasure.org).
  • definitional.dosage_form is bound to MEDICATION_FORM_CODES; ingredients[].substance to CARE_SUBSTANCE_VALUSET; nutrients[].item to CARE_NUTRIENTS_VALUESET. code and intended_routes stay free Codings.
  • slug_value uses SlugType: 5–50 chars, URL-safe (^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$), starting and ending alphanumeric.
  • category on write is a slug string; an unknown slug raises DoesNotExist from .get(...).

Server-maintained behaviour

  • facility never flows through the generic field loop (it sits in __exclude__); only ProductKnowledgeWriteSpec (create) sets it, from the facility UUID.
  • slug is set server-side from slug_value; names_cache is rebuilt in the model's save() (see below).
  • On read, is_instance_level, slug_config, and the serialized category are computed in perform_extra_serialization — none are stored columns.

Methods & save behaviour

save() side effects

save() rebuilds names_cache before persisting:

  1. names_cache is reset to "<name> ".
  2. For each entry in names, the entry's name (dict key or attribute) is appended, space-separated.
  3. super().save() persists the record, including the slug logic inherited from SlugBaseModel.

names_cache is platform-maintained — write to name and names instead of setting it directly.

API integration notes

  • Exposed through Care's REST API; aligns with the FHIR MedicationKnowledge and NutritionProduct concepts.
  • product_type decides what definitional means (medication, nutritional_product, or consumable).
  • facility controls scope: omit it for instance-wide, set it for facility-local. Address records by slug (i-<slug> or f-<facility_external_id>-<slug>); the parsed parts come back as slug_config.
  • code, names, storage_guidelines, definitional, and base_unit carry their structure and value-set binding in the spec, not the model. base_unit is required.