← Knowledge library

Clinical care

Base models & conventions

Every MANSOQ EMR resource inherits a shared base that supplies opaque IDs, audit fields, soft-delete, history, slugs, and feature flags. A resource author writes domain fields and validation; the base handles the rest. It spans two layers: -…

3.1 · versioned documentation · 13 min read

Every MANSOQ EMR resource inherits a shared base that supplies opaque IDs, audit fields, soft-delete, history, slugs, and feature flags. A resource author writes domain fields and validation; the base handles the rest. It spans two layers:

Come here for the rules a resource gets for free: the soft-delete contract, what serialize/de_serialize do, how write and read schemas diverge, and the common spec types behind the JSON fields. Concrete resources such as Patient inherit a storage base and define their own specs on top of EMRResource.

Source: care/emr/models/base.py, care/utils/models/base.py, care/emr/resources/base.py, care/emr/resources/common/

Models

Model Layer Purpose
BaseModel storage Lowest-level abstract base — opaque external_id, timestamps, and soft-delete
BaseManager storage Default manager that hides soft-deleted rows
BaseFlag storage Abstract base for feature-flag tables with cache-backed lookups
EMRBaseModel storage Standard base for EMR resources — adds audit created_by/updated_by, history, and meta
SlugBaseModel storage EMR base that adds facility-scoped or instance-scoped slug helpers
EMRResource API Pydantic base for all resource specs — serialize / de_serialize between DB objects and API schemas

All five storage models are abstract = True — none owns a database table. A concrete resource model inherits one and contributes its own columns.

The storage models inherit in one chain:

models.Model
  └─ BaseModel            (care/utils/models/base.py)
       ├─ BaseFlag        (care/utils/models/base.py)
       └─ EMRBaseModel    (care/emr/models/base.py)
            └─ SlugBaseModel

The API specs form a parallel hierarchy under pydantic.BaseModel:

pydantic.BaseModel
  └─ EMRResource          (care/emr/resources/base.py)
       └─ <Resource>SpecBase / <Resource>CreateSpec / ...ReadSpec / ...RetrieveSpec

Storage layer

BaseModel fields

The root abstract model. Every other base — and so every persisted MANSOQ resource — carries these four columns.

Field Type Required Default Notes
external_id UUIDField yes uuid4 unique, db_index. The opaque public identifier used in URLs and API payloads — never expose the integer pk
created_date DateTimeField no auto_now_add nullable, db_index. Set once on insert
modified_date DateTimeField no auto_now nullable, db_index. Bumped on every save
deleted BooleanField yes False db_index. Soft-delete marker

objects is overridden to a BaseManager, so the default queryset returns only live rows.

Soft delete

BaseModel.delete() issues no SQL DELETE. It flips the flag and persists only that column:

def delete(self, *args):
    self.deleted = True
    self.save(update_fields=["deleted"])

Rows stay put for audit and referential integrity. To reach deleted rows, bypass the default manager — for example via Model._base_manager or an unfiltered queryset.

EMRBaseModel fields

The standard base for EMR resource models. On top of the BaseModel columns it adds audit, history, and metadata:

Field Type Required Default Notes
history JSONField no dict Flattened JSON array of every version of the resource with its performer. Not returned by regular endpoints — served on request through a dedicated history API for audit and point-in-time reconstruction
meta JSONField no dict Open metadata bag. EMRResource writes spec fields here when __store_metadata__ = True (see below)
created_by FK → users.User no None on_delete=SET_NULL, nullable. related_name="%(app_label)s_%(class)s_created_by"
updated_by FK → users.User no None on_delete=SET_NULL, nullable. related_name="%(app_label)s_%(class)s_updated_by"

The %(app_label)s_%(class)s_… related_name template lets every concrete subclass reuse the same FK definitions without reverse-accessor collisions.

SlugBaseModel helpers

Extends EMRBaseModel for resources that expose a human-readable, scoped slug — value sets and definitions, for instance. It adds no columns of its own; concrete subclasses supply their own slug and, when facility-scoped, facility fields. It sets the class flag FACILITY_SCOPED = True.

Method Behaviour
calculate_slug_from_facility(facility_external_id, slug) Class helper → f-<facility_external_id>-<slug>
calculate_slug_from_instance(slug) Class helper → i-<slug>
calculate_slug() Instance helper — facility-scoped form when FACILITY_SCOPED and facility are set, otherwise instance-scoped
parse_slug(slug) Reverses the encoding; returns {facility, slug_value} for f- slugs or {slug_value} for i- slugs; raises ValueError on invalid input

Slug encoding:

facility-scoped:  f-<facility external_id (36-char UUID)>-<slug>
instance-scoped:  i-<slug>

parse_slug reads the facility segment as slug[2:38] (36 chars), validates it as a UUID, takes slug[39:] as the slug value, and rejects any slug of length ≤ 2.


Related models

BaseManager

The default manager set as objects on BaseModel. Every query through Model.objects is implicitly scoped to deleted=False:

def get_queryset(self):
    return super().get_queryset().filter(deleted=False)

BaseFlag

Abstract base for per-entity feature-flag tables (facility or organization flags, for example). It extends BaseModel, stores one validated flag name, and serves all lookups from cache.

Field Type Notes
flag CharField(max_length=1024) Flag name, validated against FlagRegistry for the subclass's flag_type

Subclasses configure these class attributes:

Attribute Purpose
cache_key_template Cache key for a single (entity_id, flag_name) pair
all_flags_cache_key_template Cache key for all flags of one entity
flag_type Flag category validated against FlagRegistry
entity_field_name Name of the FK field pointing at the owning entity

Helper properties and classmethods:

Member Behaviour
entity Resolves the related entity via entity_field_name
entity_id Resolves <entity_field_name>_id without a join
validate_flag(flag_name) Validates the name against FlagRegistry for flag_type
check_entity_has_flag(entity_id, flag_name) Cached exists() lookup (TTL 1 day)
get_all_flags(entity_id) Cached tuple of all flag names for an entity (TTL 1 day)

API layer

EMRResource

The Pydantic base (care/emr/resources/base.py) that every resource spec extends. It moves data both ways between Django model instances and the API schema, and holds the serialization rules in one place.

Class attribute Default Role
__model__ None The Django model the spec serializes to/from
__exclude__ [] Field names skipped in both serialize and de_serialize
__store_metadata__ False When True, spec fields that are not DB columns are read from / written to the model's meta JSON bag
__version__ 0.1 Stamped onto every serialized object as version
meta {} Open metadata dict carried on the spec itself
Method Direction Behaviour
serialize(obj, user=None) DB → API Builds a spec via model_construct from the DB object's columns; copies meta fields back out when __store_metadata__; calls perform_extra_serialization (always sets mapping["id"] = obj.external_id) and, when a user is passed, perform_extra_user_serialization; stamps version
de_serialize(obj=None, partial=False) API → DB Dumps the spec (exclude_defaults=True), writes mapped fields onto a new or existing model instance (skipping __exclude__, id, external_id), routes non-column fields into meta when __store_metadata__, then calls perform_extra_deserialization(is_update, obj). is_update is True when an existing obj is supplied
perform_extra_serialization(mapping, obj) DB → API Hook for resolving derived/nested read fields; base sets id
perform_extra_deserialization(is_update, obj) API → DB Hook for server-side side effects on write (e.g. appending to status_history, resolving FKs from external_id, validating coded values against a value set)
serialize_audit_users(mapping, obj) DB → API Helper that fills created_by / updated_by from a cached UserSpec
to_json() — model_dump(mode="json", exclude=["meta"])
get_database_mapping() — Lists the model's non-FK column names, used to decide which spec fields map to columns

Each concrete resource exposes a small set of specs built on EMRResource, named by convention:

Spec class Kind Role
<X>SpecBase shared Common fields shared by the write/read specs
<X>CreateSpec write · create Request body for POST; de_serialize builds a new model instance
<X>UpdateSpec write · update Request body for PUT/PATCH; de_serialize updates an existing instance (is_update=True)
<X>ListSpec read · list Lightweight response for list endpoints
<X>ReadSpec / <X>RetrieveSpec read · detail Full response for the detail endpoint (often resolving nested/coded fields in perform_extra_serialization)

Coded fields commonly bind to a value set — a system/code allow-list. The resource spec declares the binding (e.g. via json_schema_extra={"slug": "<valueset-slug>"} on the field) and validates the submitted Coding against it in perform_extra_deserialization. Status fields commonly maintain a server-side status_history appended on create/update. Each resource page documents its exact bindings and side effects.

PeriodSpec

Defined alongside EMRResource in base.py. The validated period type used by write specs — distinct from the looser Period read type in common/.

Field Type Required Default Validation
start datetime no None must be timezone-aware
end datetime no None must be timezone-aware

When both are set, start must be ≤ end. Naive datetimes are rejected with "Start/End Date must be timezone aware".

PhoneNumber

An Annotated type exported from base.py: a phone string validated by PhoneNumberValidator with number_format="E164", no default region, and no region restriction. Use it wherever a spec field accepts a phone number.


Shared common specs

care/emr/resources/common/ holds the structured types reused across resources — the actual shapes behind many of the opaque JSONFields in the storage layer.

Coding

A single code from a code system. model_config = extra="forbid".

Field Type Required Default Notes
system str no None URI of the code system
version str no None Code-system version
code str yes — The code value
display str no None Human-readable label

CodeableConcept

A concept expressed as one or more codings plus free text. extra="forbid".

Field Type Required Default Notes
id str no None
coding list[Coding] no None One or more Coding entries
text str | None yes (field present) — Free-text rendering; declared without a default, so the key must be supplied (may be null)

Period

The read/storage period shape — looser than PeriodSpec, with no timezone or ordering validation. extra="forbid".

Field Type Required Default
id str no None
start datetime no None
end datetime no None

Quantity

A measured amount, optionally with coded units. extra="forbid".

Field Type Required Default Notes
value Decimal no None max_digits=20, decimal_places=6
unit Coding no None Human-readable unit
meta dict no None
code Coding no None Machine-processable unit

Ratio (same file) wraps two required Quantity values: numerator and denominator.

ContactPoint

A means of contact (phone, email, etc.). All three fields are required.

Field Type Required Notes
system ContactPointSystemChoices yes enum below
value str yes The contact value
use ContactPointUseChoices yes enum below

ContactPointSystemChoices values

Value
phone
fax
email
pager
url
sms
other

ContactPointUseChoices values

Value
home
work
temp
old
mobile

MonetaryComponent and pricing types

Pricing line components used by billing resources (see Charge Item Definition, Charge Item).

MonetaryComponent:

Field Type Required Default Notes
monetary_component_type MonetaryComponentType yes — enum below
code Coding no None
factor Decimal no None max_digits=20, decimal_places=6
amount Decimal no None max_digits=20, decimal_places=6
tax_included_amount Decimal no None max_digits=20, decimal_places=6; only allowed when type is base
global_component bool no False
conditions list[EvaluatorConditionSpec] no [] Must be empty for base components

Model validators enforce:

MonetaryComponentType values

Value
base
surcharge
discount
tax
informational

MonetaryComponentsWithoutBase (a RootModel over list[MonetaryComponent]) adds two list-level rules: no duplicate code.code values across the list; and, when a base component declares tax_included_amount, the sum of tax-component amounts (or base.amount * factor) plus tax_included_amount must equal the base amount. MonetaryComponents extends it with one more: at most one base component.

MonetaryComponentDefinition extends MonetaryComponent for definition-time use: it adds a required title: str, disables the duplicate-code / amount-or-factor checks, and forbids a base component entirely.

DiscountConfiguration:

Field Type Notes
max_applicable int ge=0
applicability_order DiscountApplicability enum below

DiscountApplicability values

Value
total_asc
total_desc

EvaluatorConditionSpec

A single condition evaluated against a registered metric — used inside MonetaryComponent.conditions.

Field Type Required Notes
metric str yes Must resolve to a registered evaluator in EvaluatorMetricsRegistry, else "Invalid metric"
operation str yes Validated by the resolved evaluator's validate_rule
value dict | str yes Validated by the resolved evaluator's validate_rule

ValueSet definition types

The shapes used to define a value set (the FHIR compose structure), used by ValueSet.

All ValueSet types use extra="forbid".

MailTypeChoices

Defined in common/mail_type.py.

Name Value
create create_password
reset reset_password

Methods & save behaviour

API integration notes

Related

Source evidence

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