diff --git a/versioned_docs/version-3.0/references/access/_category_.json b/versioned_docs/version-3.0/references/access/_category_.json
new file mode 100644
index 0000000..1befab6
--- /dev/null
+++ b/versioned_docs/version-3.0/references/access/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Access & Identity",
+ "position": 9,
+ "key": "access-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Access & Identity",
+ "description": "Users and skills, roles, and the permission model that governs who can do what across organizations and facilities."
+ }
+}
diff --git a/versioned_docs/version-3.0/references/access/permission-association.mdx b/versioned_docs/version-3.0/references/access/permission-association.mdx
new file mode 100644
index 0000000..93e870b
--- /dev/null
+++ b/versioned_docs/version-3.0/references/access/permission-association.mdx
@@ -0,0 +1,199 @@
+---
+sidebar_position: 4
+---
+
+# Permission Association
+
+Technical reference for the `RoleAssociation` module in Care EMR.
+
+`RoleAssociation` is the binding that grants a [`RoleModel`](./role.mdx) to a [`User`](./user.mdx) within a specific context (for example, an organization or facility). A role is just a named collection of [permissions](./permission.mdx); this association is what actually scopes that collection to a user inside a context. A user can hold multiple roles across different contexts.
+
+The Django model (`care/security/models/permission_association.py`) is the **storage** layer: the `user`/`role` foreign keys, the generic `context`/`context_id` pair, and `expiry`. `RoleAssociation` has **no resource spec of its own** — it is a platform-internal authorization join, not a client-writable EMR resource. The Pydantic **resource specs** that matter here govern the [`role`](./role.mdx) it points at (`care/emr/resources/role/spec.py`) and the permission-resolution mixins (`care/emr/resources/permissions.py`) that *read* these rows to compute a user's effective permissions in a context.
+
+**Source:**
+- Model: [`care/security/models/permission_association.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/models/permission_association.py)
+- Role spec (the granted role): [`care/emr/resources/role/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py)
+- Permission-resolution mixins: [`care/emr/resources/permissions.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py)
+- Role definitions / `RoleContext`: [`care/security/roles/role.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/roles/role.py)
+- Permission contexts: [`care/security/permissions/constants.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/constants.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `RoleAssociation` | Grants a `RoleModel` to a `User` within a named context, with optional expiry |
+
+`RoleAssociation` extends `BaseModel` — the lightweight Care base providing `external_id` (UUID), `created_date`, `modified_date`, and soft-delete via `deleted` (the overridden `delete()` sets `deleted=True` rather than removing the row). See [Base model](../foundation/base-model.mdx).
+
+It does **not** extend `EMRBaseModel`, so it has no `created_by` / `updated_by` audit fields, no `history`/`meta` JSON, and no `external_id`-routed EMR API. There is no `RoleAssociationCreateSpec`/`...ReadSpec` — rows are created and removed through Care's access-control flows, not via a Pydantic serializer.
+
+## `RoleAssociation` fields
+
+### Subject and role
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `user` | `FK → User` | yes | — | Subject receiving the role. `on_delete=CASCADE`, `null=False`, `blank=False` — deleting the user removes the association |
+| `role` | `FK → RoleModel` | yes | — | Role (set of permissions) being granted. `on_delete=CASCADE`, `null=False`, `blank=False`. Read/write through the role specs — see [Role](./role.mdx) |
+
+### Context
+
+The context identifies *where* the role applies. It is stored as a type/id pair rather than a typed foreign key, so a single association table can scope roles to any kind of context (organization, facility, etc.). There is **no DB-level referential integrity** on this pair.
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `context` | `CharField(1024)` | yes | — | Context type — the name of the entity the role is scoped to. Free-form string at the model layer; not bound to an enum |
+| `context_id` | `BigIntegerField` | yes | — | Integer primary key of the context entity (not a UUID/`external_id`) |
+
+### Lifecycle
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `expiry` | `DateTimeField` | no | `null` | `null=True`, `blank=True`. When set, the role allocation is *intended* to lapse after this time. It is a single timestamp, **not** a `PeriodSpec`/start–end range, and the model does not enforce it |
+
+## Related models
+
+The role granted by an association resolves to permissions through these models. None of them are part of `RoleAssociation` itself, but every effective-permission lookup walks through them.
+
+### `RoleModel`
+
+The granted role ([Role](./role.mdx)). A named, flat set of permissions; `is_system` roles are platform-seeded and cannot be edited via the API.
+
+```text
+name → CharField(1024) # unique among non-deleted rows
+description → TextField (default "")
+is_system → BooleanField (default False)
+is_archived → BooleanField (default False)
+temp_deleted→ BooleanField (default False)
+contexts → ArrayField[CharField(24)] (default []) # bound to RoleContext in specs
+```
+
+### `RolePermission`
+
+Join table linking a `RoleModel` to a single `PermissionModel`. A role accumulates permissions through many `RolePermission` rows; lookups exclude `temp_deleted=True`.
+
+```text
+role → FK RoleModel (CASCADE, required)
+permission → FK PermissionModel (CASCADE, required)
+temp_deleted → BooleanField (default False)
+```
+
+### `PermissionModel`
+
+The atomic permission a role grants ([Permission](./permission.mdx)).
+
+```text
+slug → CharField(1024) unique, indexed
+name → CharField(1024)
+description → TextField (default "")
+context → CharField(1024) # one of the PermissionContext values
+temp_deleted → BooleanField (default False)
+```
+
+## Enum / value tables
+
+`RoleAssociation` stores plain strings/integers, but the role it points at and the permissions that role resolves to are constrained by these enums.
+
+### `RoleContext` values
+
+Each element of the granted role's `contexts` array is validated against this enum (`care/security/roles/role.py`). These are the **organizational boundary** types a role can apply to — distinct from `PermissionContext`. The free-form `RoleAssociation.context` column typically names an entity of one of these boundary types.
+
+| Value | Meaning |
+| --- | --- |
+| `FACILITY` | Role applies within a facility |
+| `GOVT_ORG` | Role applies within a government organization |
+| `ROLE_ORG` | Role applies within a role (user-group) organization |
+
+### `PermissionContext` values
+
+`PermissionModel.context` / `Permission.context` is one of these (`care/security/permissions/constants.py`). The permission-resolution mixins (below) filter a user's grants by these contexts when computing effective permissions from `RoleAssociation` rows.
+
+| Value |
+| --- |
+| `GENERIC` |
+| `FACILITY` |
+| `PATIENT` |
+| `QUESTIONNAIRE` |
+| `ORGANIZATION` |
+| `FACILITY_ORGANIZATION` |
+| `ENCOUNTER` |
+
+### `PermissionEnum` (role write field)
+
+When a role is written via `RoleCreateSpec`, its `permissions` field is typed against `PermissionController.get_enum()` — a `str` enum built dynamically at runtime from every registered permission `name` across all permission handlers (e.g. `can_create_patient`, `can_write_patient`, `can_list_patients`, `can_view_clinical_data`). The set is deployment-dependent (internal handlers + any plugin-registered ones), not a fixed list.
+
+### Built-in (`is_system`) roles
+
+Seeded by `RoleController` (`care/security/roles/role.py`). Associations frequently bind a user to one of these. They cannot be created, updated, or deleted through the API.
+
+| Role | Contexts |
+| --- | --- |
+| `Doctor` | `FACILITY`, `GOVT_ORG` |
+| `Nurse` | `FACILITY`, `GOVT_ORG` |
+| `Staff` | `FACILITY`, `GOVT_ORG` |
+| `Volunteer` | `FACILITY`, `GOVT_ORG` |
+| `Pharmacist` | `FACILITY` |
+| `Administrator` | `FACILITY`, `GOVT_ORG` |
+| `Facility Admin` | `FACILITY` |
+| `Admin` | `FACILITY`, `GOVT_ORG` |
+| `Admin` (role org) | `ROLE_ORG` |
+| `Manager` (role org) | `ROLE_ORG` |
+| `Member` (role org) | `ROLE_ORG` |
+
+## Resource specs (API schema)
+
+`RoleAssociation` has **no dedicated `...CreateSpec`/`...UpdateSpec`/`...ListSpec`/`...RetrieveSpec`** — it is not exposed as an EMR resource. The Pydantic surface relevant to associations is twofold: the specs that define the **role** an association grants, and the mixins that **consume** associations to expose a user's effective permissions on another resource. All extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB → Pydantic via `perform_extra_serialization`) and `de_serialize` (Pydantic → DB via `perform_extra_deserialization`).
+
+### Specs for the granted role
+
+| Spec class | Role | Fields |
+| --- | --- | --- |
+| `RoleBaseSpec` | shared base (`__exclude__ = ["permissions"]`) | `id`, `name`, `description`, `is_system`, `is_archived`, `contexts` |
+| `RoleCreateSpec` | write · create & update | base fields + `permissions: list[PermissionEnum]` (≥ 1, de-duplicated) |
+| `RoleReadSpec` | read · detail/list | base fields + `permissions: list[PermissionSpec]` |
+| `RoleReadMinimalSpec` | read · minimal/embedded | base fields only |
+| `PermissionSpec` | nested (read) | `name`, `description`, `slug` (`SlugType`), `context` |
+
+`RoleBaseSpec.contexts` is `list[RoleContext]` — bound to the `RoleContext` enum at the API layer even though the model column is an open string array. `PermissionSpec.slug` is a `SlugType`: `str`, length 5–50, pattern `^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$`. Key role validation (`RoleCreateSpec.validate_role`, mode="after"): non-blank unique name (`name__iexact`), reject `is_system=True`, reject updating a system role, require ≥ 1 permission. See [Role](./role.mdx) for the full spec breakdown.
+
+### Permission-resolution mixins (`care/emr/resources/permissions.py`)
+
+These mixins are inherited by *other* resource specs (patient, encounter, facility). When `serialize` is called with an authenticated `user`, `perform_extra_user_serialization` walks the user's `RoleAssociation` rows for that object, resolves the granted roles to permissions, and writes them into `mapping["permissions"]`. This is the read path that turns stored associations into a permission list on the wire.
+
+| Mixin | Resolves (from the user's associations) | Context filter | Extra fields |
+| --- | --- | --- | --- |
+| `PermissionsMixin` | base — adds `permissions: list[str]` | — | — |
+| `PatientPermissionsMixin` | roles the user holds on a patient | `permission__context in ("PATIENT", "FACILITY")` | — |
+| `EncounterPermissionsMixin` | roles the user holds on an encounter | `permission__context in ("ENCOUNTER", "PATIENT")` | — |
+| `FacilityPermissionsMixin` | roles on facility root + sub-orgs | none (root) / child set excludes `can_update_facility` | `root_org_permissions`, `child_org_permissions` |
+
+Permission slugs are looked up via `RolePermission.objects.filter(role_id__in=roles, …).values_list("permission__slug", flat=True)`, so only active (non-`temp_deleted`) grants on the resolved roles count.
+
+## Methods & save behaviour
+
+`RoleAssociation` defines no model methods, validators, or `save()`/`delete()` overrides of its own. It inherits soft-delete from `BaseModel`: calling `delete()` sets `deleted=True` and persists with `save(update_fields=["deleted"])`, and the default manager filters out soft-deleted rows.
+
+A source `TODO` notes that a composite index on `user`, `context`, and `context_id` is planned but not yet present; lookups by those fields are not index-backed today.
+
+`expiry` is a stored timestamp only — the model does not enforce it. Expiry-based revocation is the responsibility of the authorization layer that reads these associations.
+
+There is no `perform_extra_serialization` / `perform_extra_deserialization` on `RoleAssociation` (it has no spec). The serialization hooks that matter live on the role specs and the permission mixins described above.
+
+## API integration notes
+
+- `RoleAssociation` is a platform-maintained authorization record, not a client-writable EMR resource. It is created and removed through Care's access-control flows, not via FHIR, a Pydantic spec, or direct REST writes.
+- The `context` / `context_id` pair is a generic (non-FK) reference. `context` is a free-form string (typically naming an entity in one of the `RoleContext` boundary types) and `context_id` is the entity's **integer PK** — not its `external_id` UUID. Integrators must pair the right type string with the matching PK; there is no database-level referential integrity on the context.
+- A user may have many `RoleAssociation` rows — one per (role, context) grant. Effective permissions in a given context are computed by the `permissions` mixins, which union the active permission slugs of all resolved roles, filtered by `PermissionContext`.
+- `expiry` is advisory at the model layer; do not assume the row is automatically deactivated when it passes.
+- The granted `role` is written/read through the role specs (`RoleCreateSpec` / `RoleReadSpec`), and each role's `contexts` is bound to the `RoleContext` enum — not an open string at the API layer.
+
+## Related
+
+- Reference: [Role](./role.mdx)
+- Reference: [Permission](./permission.mdx)
+- Reference: [User](./user.mdx)
+- Reference: [Organization](../facility/organization.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [permission_association.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/security/models/permission_association.py)
+- Source: [role/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py)
+- Source: [permissions.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py)
diff --git a/versioned_docs/version-3.0/references/access/permission.mdx b/versioned_docs/version-3.0/references/access/permission.mdx
new file mode 100644
index 0000000..27d9646
--- /dev/null
+++ b/versioned_docs/version-3.0/references/access/permission.mdx
@@ -0,0 +1,163 @@
+---
+sidebar_position: 3
+---
+
+# Permission
+
+Technical reference for the `Permission` module in Care EMR.
+
+A permission is the ability to perform a single action in a given context — usually expressed as "Action on Resource" (for example, "Can Create Patient" in the `PATIENT` context). Permissions are the atomic building blocks of Care's security system: they are declared in code, synced into the database, grouped into [roles](./role.mdx), and bound to resources via [permission associations](./permission-association.mdx).
+
+The Django `PermissionModel` is the **storage** layer. The real implementation lives in two other places: the **permission registry** (`care/security/permissions/`), which declares every permission as a Python `enum` member with a `name`, `description`, `context`, and the default `roles` that hold it; and the Pydantic **resource specs** (`care/emr/resources/role/spec.py`), which define the read/write API schema. This doc covers all three.
+
+**Source:**
+
+- Model: [`care/security/models/permission.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/models/permission.py)
+- Spec: [`care/emr/resources/role/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py) (`PermissionSpec`)
+- Computed-permission mixins: [`care/emr/resources/permissions.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py)
+- Registry: [`care/security/permissions/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/base.py), [`care/security/permissions/constants.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/constants.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `PermissionModel` | A single, named permission (action) that can be granted to a user in a context |
+
+`PermissionModel` extends [`BaseModel`](../foundation/base-model.mdx) — the lowest-level Care base. It does **not** use `EMRBaseModel`, so it has no `created_by`/`updated_by`, `history`, or `meta` columns. From `BaseModel` it inherits:
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `external_id` | `UUIDField` | `default=uuid4`, `unique`, indexed. Opaque public identifier — never expose the integer `pk` |
+| `created_date` | `DateTimeField` | `auto_now_add`; nullable, indexed |
+| `modified_date` | `DateTimeField` | `auto_now`; nullable, indexed |
+| `deleted` | `BooleanField` | `default=False`, indexed. Soft-delete flag; the default manager hides `deleted=True` rows |
+
+## `PermissionModel` fields
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `slug` | `CharField(1024)` | yes | — | `unique`, indexed. Stable machine identifier for the permission (the `enum` member name, e.g. `can_create_patient`). The slug — not `external_id` — is how code, specs, and the API reference a permission. On the API it is the `lookup_field` |
+| `name` | `CharField(1024)` | yes | — | Human-readable display name (e.g. `"Can Create Patient"`). Sourced from the registry `Permission.name` |
+| `description` | `TextField` | no | `""` | Free-text explanation. Sourced from the registry `Permission.description`; many declared permissions ship with an empty description |
+| `context` | `CharField(1024)` | yes | — | The resource context the permission applies to. No DB-level `choices`, but values are always one of the `PermissionContext` enum strings below (set by the sync command from `Permission.context.value`) |
+| `temp_deleted` | `BooleanField` | no | `False` | Staging flag used by the sync command to mark permissions no longer present in the declared set before they are hard-deleted; distinct from the inherited `deleted` soft-delete flag |
+
+### `context` values (`PermissionContext`)
+
+`context` is a free-form `CharField` at the DB level, but the registry only ever writes one of these enum values ([`constants.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/constants.py)):
+
+| Value | Meaning |
+| --- | --- |
+| `GENERIC` | Not scoped to a specific resource type |
+| `FACILITY` | Scoped to a facility |
+| `PATIENT` | Scoped to a patient |
+| `QUESTIONNAIRE` | Scoped to a questionnaire |
+| `ORGANIZATION` | Scoped to a (govt/role) organization |
+| `FACILITY_ORGANIZATION` | Scoped to a facility organization |
+| `ENCOUNTER` | Scoped to an encounter |
+
+The context drives which permissions are considered when resolving access for a serialized resource — see the mixins below, which filter on `permission__context__in=[...]`.
+
+## Related models
+
+### Registry declaration — `Permission` dataclass
+
+Permissions are not authored in the database. Each is a member of a `*Permissions` `enum` (one per resource area, e.g. `PatientPermissions`, `EncounterPermissions`), where the member **name** becomes the `slug` and the member **value** is a `Permission` dataclass ([`constants.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/constants.py)):
+
+| Field | Type | Maps to `PermissionModel` | Notes |
+| --- | --- | --- | --- |
+| `name` | `str` | `name` | Display name |
+| `description` | `str` | `description` | Free text |
+| `context` | `PermissionContext` | `context` (`.value`) | One of the enum values above |
+| `roles` | `list[Role]` | — (drives `RolePermission`) | Default system roles that receive this permission on sync |
+
+Example (`care/security/permissions/patient.py`):
+
+```python
+can_create_patient = Permission(
+ "Can Create Patient", "", PermissionContext.PATIENT,
+ [STAFF_ROLE, DOCTOR_ROLE, NURSE_ROLE, ADMINISTRATOR, ADMIN_ROLE, FACILITY_ADMIN_ROLE],
+)
+```
+
+`PermissionController` ([`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/base.py)) aggregates every handler enum:
+
+- `get_permissions() → dict[slug, Permission]` — the full declared registry (cached).
+- `get_enum() → Enum` — a dynamically built `str` enum of all permission slugs, used by [`RoleCreateSpec`](./role.mdx) to constrain the `permissions` write field to known slugs.
+
+### Sync command (`sync_permissions_roles`)
+
+The management command [`sync_permissions_roles`](https://github.com/ohcnetwork/care/blob/develop/care/security/management/commands/sync_permissions_roles.py) is the only writer of this table. It is idempotent and Redis-locked, and runs in a single transaction:
+
+1. Mark every `PermissionModel` `temp_deleted=True`.
+2. For each declared permission, upsert by `slug`, setting `name`, `description`, `context` (from `Permission.context.value`) and clearing `temp_deleted`.
+3. Hard-delete any row still `temp_deleted=True` (i.e. no longer declared).
+4. Upsert system roles, then rebuild `RolePermission` rows from each `Permission.roles` list (same temp-delete-then-prune pattern).
+
+### `RolePermission`
+
+Permissions reach users only through roles. `RolePermission` is the join table (`role` FK, `permission` FK, `temp_deleted`) connecting a [`RoleModel`](./role.mdx) to a `PermissionModel`. Effective access is the union of permissions across a user's role bindings, filtered to active grants (`rolepermission__temp_deleted=False`).
+
+## Resource specs (API schema)
+
+The Pydantic specs build on `EMRResource` ([`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)), whose `serialize` (DB → spec) and `de_serialize` (spec → DB) drive read/write, with `perform_extra_serialization`/`perform_extra_deserialization` hooks for side effects.
+
+| Spec class | Role | File | Notes |
+| --- | --- | --- | --- |
+| `PermissionSpec` | read · list + detail | `role/spec.py` | The only permission-facing spec. Served read-only by `PermissionViewSet` |
+| `PermissionsMixin` | read augmentation | `permissions.py` | Adds a computed `permissions` list to other resources' serialized output for the requesting user |
+
+There is **no** `PermissionCreateSpec`/`PermissionUpdateSpec` — permissions are reference data and never client-writable. (Write specs exist for roles instead; see [Role](./role.mdx).)
+
+### `PermissionSpec`
+
+`__model__ = PermissionModel`. A flat read schema with no extra serialization hooks (it inherits the base, which sets `id = external_id`):
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `str` | From `PermissionModel.name` |
+| `description` | `str` | From `PermissionModel.description` |
+| `slug` | `SlugType` | `str`, `min_length=5`, `max_length=50`, must match `^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$` (URL-safe; starts and ends alphanumeric). See [`slug_type.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/utils/slug_type.py) |
+| `context` | `str` | One of the `PermissionContext` values above |
+| `id` | `UUID4` | Added by `serialize` as `external_id` (inherited base behaviour) |
+
+Served by [`PermissionViewSet`](https://github.com/ohcnetwork/care/blob/develop/care/security/api/viewsets/permissions.py) — an `EMRModelReadOnlyViewSet` (list + retrieve only), `lookup_field="slug"`, filterable by `name` (`icontains`). There is no create/update/delete endpoint.
+
+### Computed `permissions` on other resources (`PermissionsMixin`)
+
+[`permissions.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py) defines mixins that other read specs inherit so a serialized resource carries the **current user's** effective permission slugs for that object. `PermissionsMixin.perform_extra_user_serialization` calls `add_permissions(mapping, user, obj)` for authenticated users, populating a `permissions: list[str]` field:
+
+| Mixin | Output fields | Resolution |
+| --- | --- | --- |
+| `PatientPermissionsMixin` | `permissions` | Roles on the patient; permission slugs where `context in ["PATIENT", "FACILITY"]` |
+| `EncounterPermissionsMixin` | `permissions` | Roles on the encounter; permission slugs where `context in ["ENCOUNTER", "PATIENT"]` |
+| `FacilityPermissionsMixin` | `permissions`, `root_org_permissions`, `child_org_permissions` | Union of root-org and sub-org role permissions; `child_org_permissions` excludes the `can_update_facility` slug |
+
+These lists let the frontend gate UI by capability without a separate authorization round-trip.
+
+## Methods & save behaviour
+
+`PermissionModel` defines no custom `save()`, `delete()` override, validators, or signals beyond what it inherits from `BaseModel`. Soft-delete behaviour (`delete()` setting `deleted=True`) and the soft-delete-filtering default manager come from the base. The lifecycle is driven externally by the `sync_permissions_roles` command (see above), which uses `temp_deleted` as a staging flag and **hard-deletes** undeclared rows.
+
+`PermissionSpec` performs no extra serialization or deserialization; serialization is the base `serialize` flow plus the inherited `id = external_id` mapping.
+
+## API integration notes
+
+- Permissions are **platform-maintained reference data**, declared in the permission registry and synced into this table by `sync_permissions_roles` — they are read-only over the API (`PermissionViewSet` exposes list + retrieve only).
+- Reference a permission by its `slug`, which is stable, `unique`, and the API `lookup_field`; treat `external_id` as the opaque public ID and never expose the integer `pk`.
+- Permissions are not granted directly to users. They are collected into a [role](./role.mdx) (via `RolePermission`), and the role is bound to a resource (organization, patient, encounter, …) through a [permission association](./permission-association.mdx). Effective access is the union of active permissions across a user's role bindings.
+- `context` is a `CharField` with no DB `choices`, but is always one of the `PermissionContext` enum strings; the mixins filter resource permissions by context, so the value is load-bearing.
+- When writing a role, the `permissions` field is constrained to known slugs via `PermissionController.get_enum()` — submitting an unknown slug fails validation. See [Role](./role.mdx).
+
+## Related
+
+- Reference: [Role](./role.mdx)
+- Reference: [Permission association](./permission-association.mdx)
+- Reference: [User](./user.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Source: [permission.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/security/models/permission.py)
+- Source: [role/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py) (`PermissionSpec`)
+- Source: [resources/permissions.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py)
diff --git a/versioned_docs/version-3.0/references/access/role.mdx b/versioned_docs/version-3.0/references/access/role.mdx
new file mode 100644
index 0000000..d5ca011
--- /dev/null
+++ b/versioned_docs/version-3.0/references/access/role.mdx
@@ -0,0 +1,220 @@
+---
+sidebar_position: 2
+---
+
+# Role
+
+Technical reference for the `Role` module in Care EMR.
+
+A role is an arbitrary, flat collection of permissions that can be assigned to a user within a given organizational boundary (it is **not** a job title or a type — "Doctor", "Doctor Read Only" and "Doctor Scheduler" are all just distinct permission sets). A user may hold multiple roles across different organizations, but only one role per organization chain. Roles are resolved into effective permissions through `RolePermission` join rows.
+
+The Django model (`care/security/models/role.py`) is the **storage** layer: `name`, `description`, flags, and the `contexts` array. The Pydantic **resource specs** (`care/emr/resources/role/spec.py`, `care/emr/resources/permissions.py`) define the API read/write schemas, the `contexts` enum binding, the permission enum, and all create/update validation.
+
+**Source:**
+- Model: [`care/security/models/role.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/models/role.py)
+- Spec: [`care/emr/resources/role/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py)
+- Permissions mixins: [`care/emr/resources/permissions.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py)
+- Role definitions: [`care/security/roles/role.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/roles/role.py)
+- Permission registry: [`care/security/permissions/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/base.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `RoleModel` | A named role grouping a set of permissions |
+| `RolePermission` | Join table linking a `RoleModel` to a `PermissionModel` |
+
+Both models extend `BaseModel` (see [Base model](../foundation/base-model.mdx)) — they get `external_id`, `created_date`/`modified_date`, and soft-delete via the `deleted` flag, but **not** the richer `created_by`/`updated_by`/history/meta fields of `EMRBaseModel`.
+
+## `RoleModel` fields
+
+A role comprises multiple permissions. Roles can be created on the fly: system roles (`is_system=True`) cannot be deleted, and the API also blocks creating or updating them; user-created roles can be removed by users with the appropriate permission.
+
+### Identity & description
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `name` | `CharField(1024)` | yes | — | Role name. Unique across non-deleted rows (see Meta). Spec rejects empty/blank on create and rejects a name that already exists case-insensitively |
+| `description` | `TextField` | no | `""` | Free text |
+| `contexts` | `ArrayField[CharField(24)]` | yes | `[]` | The boundary types this role applies to. The model stores opaque strings; the spec constrains each element to the **`RoleContext`** enum (see [RoleContext values](#rolecontext-values)) |
+
+### Status flags
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `is_system` | `BooleanField` | no | `False` | `True` when created by the platform. Such roles cannot be deleted; the create/update spec rejects any request with `is_system=True` and rejects updates to existing system roles |
+| `temp_deleted` | `BooleanField` | no | `False` | Soft-marks a role for staged removal independent of `deleted` |
+| `is_archived` | `BooleanField` | no | `False` | Hides the role without deleting it. Exposed in the API specs |
+
+### Meta / constraints
+
+A partial unique constraint enforces a unique `name` only among non-deleted rows:
+
+```text
+UniqueConstraint(fields=["name"], condition=Q(deleted=False), name="unique_name_if_not_deleted")
+```
+
+The `RoleCreateSpec` validator additionally enforces uniqueness case-insensitively (`name__iexact`) before the DB constraint is reached.
+
+## Related models
+
+### `RolePermission`
+
+Join table connecting a role to a single permission. A role accumulates permissions through many `RolePermission` rows.
+
+```text
+role → FK RoleModel (CASCADE, required)
+permission → FK PermissionModel (CASCADE, required)
+temp_deleted → BooleanField (default False)
+```
+
+`temp_deleted` lets a permission grant be excluded from a role without removing the row — permission lookups filter on `rolepermission__temp_deleted=False`.
+
+### `PermissionModel`
+
+The permission a `RolePermission` points at ([Permission](../access/permission.mdx)).
+
+```text
+slug → CharField(1024) unique, indexed
+name → CharField(1024)
+description → TextField (default "")
+context → CharField(1024) # one of the PermissionContext values
+temp_deleted → BooleanField (default False)
+```
+
+The available permissions are not stored as a fixed list in the DB; they are registered in code via `PermissionController` (`care/security/permissions/base.py`), which aggregates per-domain `enum.Enum` handlers (e.g. `PatientPermissions`, `FacilityPermissions`). Each member's value is a `Permission(name, description, context, roles)` dataclass.
+
+## Enum / value tables
+
+### `RoleContext` values
+
+Each element of `contexts` is validated against this enum (`care/security/roles/role.py`). These are the **organizational boundary** types a role can apply to — distinct from `PermissionContext`.
+
+| Value | Meaning |
+| --- | --- |
+| `FACILITY` | Role applies within a facility |
+| `GOVT_ORG` | Role applies within a government organization |
+| `ROLE_ORG` | Role applies within a role (user-group) organization |
+
+### `PermissionContext` values
+
+`PermissionModel.context` / `Permission.context` is one of these (`care/security/permissions/constants.py`). The `permissions` mixins (below) filter grants by these contexts when resolving a user's effective permissions.
+
+| Value |
+| --- |
+| `GENERIC` |
+| `FACILITY` |
+| `PATIENT` |
+| `QUESTIONNAIRE` |
+| `ORGANIZATION` |
+| `FACILITY_ORGANIZATION` |
+| `ENCOUNTER` |
+
+### `PermissionEnum` (write field for `permissions`)
+
+`RoleCreateSpec.permissions` is typed against `PermissionController.get_enum()` — a `str` enum built dynamically at runtime from every registered permission `name` across all permission handlers. Values are the permission identifiers (slugs), e.g. `can_create_patient`, `can_write_patient`, `can_list_patients`, `can_view_clinical_data`. The full set is the union of all handlers listed in `PermissionController.internal_permission_handlers` plus any registered via plugins, so the enum is deployment-dependent rather than fixed.
+
+### Built-in (`is_system`) roles
+
+Seeded by `RoleController` (`care/security/roles/role.py`). These are created with `is_system=True` and cannot be created, updated, or deleted through the API.
+
+| Role | Contexts |
+| --- | --- |
+| `Doctor` | `FACILITY`, `GOVT_ORG` |
+| `Nurse` | `FACILITY`, `GOVT_ORG` |
+| `Staff` | `FACILITY`, `GOVT_ORG` |
+| `Volunteer` | `FACILITY`, `GOVT_ORG` |
+| `Pharmacist` | `FACILITY` |
+| `Administrator` | `FACILITY`, `GOVT_ORG` |
+| `Facility Admin` | `FACILITY` |
+| `Admin` | `FACILITY`, `GOVT_ORG` |
+| `Admin` (role org) | `ROLE_ORG` |
+| `Manager` (role org) | `ROLE_ORG` |
+| `Member` (role org) | `ROLE_ORG` |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB → Pydantic, via `perform_extra_serialization`) and `de_serialize` (Pydantic → DB, via `perform_extra_deserialization`). `RoleBaseSpec` sets `__exclude__ = ["permissions"]` so the `permissions` field is handled explicitly by each subclass rather than copied field-by-field.
+
+| Spec class | Role | Fields |
+| --- | --- | --- |
+| `RoleBaseSpec` | shared base | `id`, `name`, `description`, `is_system`, `is_archived`, `contexts` |
+| `RoleCreateSpec` | write · create & update | base fields + `permissions: list[PermissionEnum]` |
+| `RoleReadSpec` | read · detail/list | base fields + `permissions: list[PermissionSpec]` |
+| `RoleReadMinimalSpec` | read · minimal/embedded | base fields only |
+| `PermissionSpec` | nested (read) | `name`, `description`, `slug` (`SlugType`), `context` |
+
+### Field shapes & bindings
+
+| Field | Spec type | Notes |
+| --- | --- | --- |
+| `id` | `UUID4 \| None` | The role's `external_id` on read (set in `perform_extra_serialization`); ignored on write |
+| `name` | `str \| None` | Required & non-blank on create; case-insensitive uniqueness enforced |
+| `description` | `str \| None` | Optional |
+| `is_system` | `bool \| None` (default `False`) | Must be falsy on write — `True` is rejected |
+| `is_archived` | `bool \| None` (default `False`) | — |
+| `contexts` | `list[RoleContext]` | Bound to the `RoleContext` enum |
+| `permissions` (write) | `list[PermissionEnum]` (default `[]`) | Dynamic `str` enum of every registered permission name; must contain ≥ 1 entry |
+| `permissions` (read) | `list[PermissionSpec]` | Resolved server-side from the role's active grants |
+| `PermissionSpec.slug` | `SlugType` | `str`, length 5–50, pattern `^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$` |
+
+### Validation (`RoleCreateSpec.validate_role`, mode="after")
+
+Runs on both create and update (the `is_update` flag and target `object` are read from the serializer context):
+
+- On create, `name` must be present and non-blank (`"Role name cannot be empty"`).
+- `name` must be unique case-insensitively (`name__iexact`); on update the current row is excluded. Duplicate → `"Role with this name already exists"`.
+- Updating a role whose `is_system=True` → `"Cannot update system roles"`.
+- `is_system=True` in the payload → `"Cannot create system roles"`.
+- `permissions` must be non-empty → `"At least one permission must be assigned to the role"`.
+- `permissions` is de-duplicated (`list(set(...))`).
+
+### Server-side serialization behaviour
+
+- **Write** (`RoleCreateSpec.perform_extra_deserialization`): the validated `permissions` list is attached to the model instance as `obj.permissions` (a transient attribute, since `permissions` is in `__exclude__` and is not a model column). The view/service layer materializes these into `RolePermission` rows.
+- **Read detail** (`RoleReadSpec.perform_extra_serialization`): sets `id = obj.external_id` and fills `permissions` from `obj.get_permissions_for_role()` — the cached list of `{name, slug, context, description}` for every active (non-`temp_deleted`) grant.
+- **Read minimal** (`RoleReadMinimalSpec.perform_extra_serialization`): sets `id = obj.external_id` only; omits `permissions` entirely.
+
+### Permission resolution mixins
+
+`care/emr/resources/permissions.py` defines mixins that other resource specs inherit to expose the **calling user's** effective permissions on an object (not the role's own list). They populate `mapping["permissions"]` in `perform_extra_user_serialization` only when an authenticated `user` is passed to `serialize`:
+
+| Mixin | Resolves | Context filter |
+| --- | --- | --- |
+| `PermissionsMixin` | base — adds `permissions: list[str]` | — |
+| `PatientPermissionsMixin` | roles the user holds on a patient | `permission__context in ("PATIENT", "FACILITY")` |
+| `EncounterPermissionsMixin` | roles the user holds on an encounter | `permission__context in ("ENCOUNTER", "PATIENT")` |
+| `FacilityPermissionsMixin` | roles on facility root + sub-orgs; also exposes `root_org_permissions` and `child_org_permissions` (child set excludes `can_update_facility`) | none (root) / excludes `can_update_facility` (child) |
+
+## Methods & save behaviour
+
+`RoleModel` resolves its effective permissions through two cached helpers backed by the Django cache (Redis), with a 7-day TTL:
+
+- `get_permission_sk_for_role() → list[str]` — returns the `slug` of every active permission on the role. Cache key `role_permissions_cache:{id}`.
+- `get_permissions_for_role() → list[dict]` — returns full permission detail (`name`, `slug`, `context`, `description`) for every active permission. Cache key `role_permissions:{id}`. Used by `RoleReadSpec`.
+
+Both queries join through `RolePermission` and exclude `temp_deleted=True` grants.
+
+### Cache invalidation signal
+
+A `@receiver([post_save, post_delete], sender=RolePermission)` handler (`invalidate_role_permissions_cache`) clears both cache keys for the affected `role_id` whenever a `RolePermission` is created, updated, or deleted. Integrators mutating role-permission links through the ORM get automatic cache invalidation; raw SQL writes bypass the signal and leave stale caches.
+
+## API integration notes
+
+- Roles are referenced from access-control models (for example organization memberships and patient/encounter associations) to resolve a user's effective permissions in a given context.
+- `is_system` roles are platform-maintained: the spec rejects creating one (`is_system=True`) and rejects any update to an existing system role.
+- Writes go through `RoleCreateSpec` (create and update). `permissions` is required (≥ 1) and is de-duplicated; the resolved set is attached to the model as `obj.permissions` for the service layer to translate into `RolePermission` rows.
+- Reads use `RoleReadSpec` (full, with nested `PermissionSpec[]`) or `RoleReadMinimalSpec` (no permissions) depending on the endpoint.
+- Each element of `contexts` is bound to the `RoleContext` enum (`FACILITY`, `GOVT_ORG`, `ROLE_ORG`) — not an open string at the API layer, even though the model column is.
+- Permission membership is managed through `RolePermission` rows, not by editing `RoleModel` directly — prefer ORM writes so the cache-invalidation signal fires.
+
+## Related
+
+- Reference: [Permission](../access/permission.mdx)
+- Reference: [Permission association](../access/permission-association.mdx)
+- Reference: [User](../access/user.mdx)
+- Reference: [Organization](../facility/organization.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [role.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/security/models/role.py)
+- Source: [role/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py)
+- Source: [permissions.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py)
diff --git a/versioned_docs/version-3.0/references/access/user.mdx b/versioned_docs/version-3.0/references/access/user.mdx
new file mode 100644
index 0000000..e8d4ab9
--- /dev/null
+++ b/versioned_docs/version-3.0/references/access/user.mdx
@@ -0,0 +1,268 @@
+---
+sidebar_position: 1
+---
+
+# User & Skills
+
+Technical reference for the `User` module in Care EMR. The Django model is the **storage** layer; the Pydantic resource specs in `care/emr/resources/user/` and `care/emr/resources/mfa/` are the **API/implementation** layer that define enums, validation, the shape of opaque `JSONField`s, and the read/write request/response schemas.
+
+**Source:** [`care/users/models.py`](https://github.com/ohcnetwork/care/blob/develop/care/users/models.py), [`care/facility/models/patient.py`](https://github.com/ohcnetwork/care/blob/develop/care/facility/models/patient.py), [`care/emr/resources/user/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/user/spec.py), [`care/emr/resources/mfa/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/mfa/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `User` | Care account: authentication, profile, clinician credentials, and notification/MFA data |
+| `Skill` | Instance-wide skill/competency definition |
+| `UserSkill` | Through model linking a `User` to a `Skill` |
+| `UserFlag` | Feature flag scoped to a single `User` |
+| `PlugConfig` | Slug-keyed JSON configuration for installed plugs |
+| `MobileOTP` | One-time password issued against a phone number |
+
+Base classes vary by model:
+
+- `User` extends Django's `AbstractUser` (adds `external_id` and a soft-delete `deleted` flag of its own; not an `EMRBaseModel`).
+- `Skill`, `UserSkill`, and `MobileOTP` extend [`BaseModel`](../foundation/base-model.mdx) (`external_id`, `created_date`, `modified_date`, soft-delete via `deleted`).
+- `UserFlag` extends `BaseFlag` (a `BaseModel` subclass that adds a `flag` field plus cache-invalidating `save()`).
+- `PlugConfig` extends plain `django.db.models.Model` (no audit or soft-delete fields).
+
+## `User` fields
+
+`User` extends `AbstractUser`, so it inherits `password`, `email`, `first_name`, `last_name`, `is_staff`, `is_active`, `is_superuser`, `last_login`, and `date_joined` from Django. The fields below are Care additions or overrides. Queried through `CustomUserManager`, which filters out `deleted=True` rows by default (`get_entire_queryset()` bypasses the filter).
+
+### Identity & profile
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `external_id` | `UUIDField` | Unique, indexed; stable public identifier. Surfaced as `id` in every read spec |
+| `username` | `CharField(150)` | Unique; model-validated by `UsernameValidator`. API additionally enforces `^[a-zA-Z0-9_-]{3,}$` and global uniqueness (incl. deleted users) on create |
+| `user_type` | `CharField(100)` | Nullable; free-text role label (e.g. `administrator`). Not exposed by any user spec — set internally / by `create_superuser` |
+| `prefix` | `CharField(10)` | Name prefix (e.g. Dr.); spec caps length at 10. Optional in specs |
+| `suffix` | `CharField(50)` | Name suffix; spec caps length at 50. Optional in specs |
+| `gender` | `CharField(100)` | Nullable in DB. Write specs constrain it to `GenderChoices` (required); read specs return it as a plain string. See [GenderChoices values](#genderchoices-values) |
+| `old_gender` | `IntegerField` | Legacy `GENDER_CHOICES` (`1` Male, `2` Female, `3` Non-binary); nullable. Not exposed by any user spec |
+| `date_of_birth` | `DateField` | Nullable. Read-only in `CurrentUserRetrieveSpec` (string, nullable) |
+| `profile_picture_url` | `CharField(500)` | Storage key. Read specs return the **resolved** URL via `read_profile_picture_url()`, not the raw key |
+| `created_by` | `FK → User (self, SET_NULL)` | Account creator; `related_name="users_created"`. Serialized in `UserRetrieveSpec` as a cached nested `UserSpec` dict (nullable) |
+| `deleted` | `BooleanField` | Soft-delete flag (default `False`); exposed read-only in `UserSpec` |
+| `verified` | `BooleanField` | Default `False`; read-only in `CurrentUserRetrieveSpec` |
+| `is_service_account` | `BooleanField` | Default `False`; marks machine/integration accounts. Settable on create, read in `UserRetrieveSpec` |
+
+### Contact
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `phone_number` | `CharField(14)` | Model: `mobile_or_landline_number_validator`. Specs cap at 14 chars and required; on create, must be globally unique (rejects existing) |
+| `alt_phone_number` | `CharField(14)` | Nullable; model `mobile_validator`. Read-only string in `CurrentUserRetrieveSpec` |
+| `video_connect_link` | `URLField` | Nullable; tele-consult link. Not exposed by any user spec |
+
+### Clinician credentials
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `qualification` | `TextField` | Nullable. Read-only (nullable string) in `CurrentUserRetrieveSpec` |
+| `doctor_experience_commenced_on` | `DateField` | Nullable; used to derive experience. Read-only (nullable string) in `CurrentUserRetrieveSpec` |
+| `doctor_medical_council_registration` | `CharField(255)` | Nullable; council registration number. Read-only (nullable string) in `CurrentUserRetrieveSpec` |
+| `weekly_working_hours` | `IntegerField` | Nullable; model-validated `0`–`168`. Read-only (nullable string) in `CurrentUserRetrieveSpec` |
+
+### Organization & facility
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `geo_organization` | `FK → emr.Organization (SET_NULL)` | Geographic/administrative org. Write specs accept it as a `UUID4` and resolve to an `Organization` with `org_type="govt"` (404 otherwise). Read in `UserRetrieveSpec` as a nested `OrganizationReadSpec` dict. Listed in `UserBaseSpec.__exclude__`, so it never round-trips through the generic field copy |
+| `home_facility` | `FK → facility.Facility (PROTECT)` | Primary facility. Not exposed by any user spec |
+| `skills` | `ManyToManyField → Skill` | Through `UserSkill` |
+| `cached_role_orgs` | `JSONField` | Nullable; cached role/organization map. Lazily populated by `get_cached_role_orgs()`; surfaced as `role_orgs` in read specs. Platform-maintained — do not write directly |
+
+### Notifications, MFA & preferences
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `pf_endpoint` | `TextField` | Web-push endpoint; nullable. Read-only in `CurrentUserRetrieveSpec` |
+| `pf_p256dh` | `TextField` | Web-push key; nullable. Read-only in `CurrentUserRetrieveSpec` |
+| `pf_auth` | `TextField` | Web-push auth secret; nullable. Read-only in `CurrentUserRetrieveSpec` |
+| `totp_secret` | `TextField` | Nullable; TOTP seed. Never serialized; written via the MFA setup/verify flow |
+| `mfa_settings` | `JSONField` | Default `{}`. Shape: `{ "totp": { "enabled": bool, ... } }`. `is_mfa_enabled()` reads `mfa_settings["totp"]["enabled"]`; surfaced as the boolean `mfa_enabled` in `UserSpec`. Not written directly by clients |
+| `preferences` | `JSONField` | Default `{}`; open per-user UI/app preferences bag. Read-only `dict` in `CurrentUserRetrieveSpec` |
+
+`REQUIRED_FIELDS = ["email"]` and the model is managed by `CustomUserManager`.
+
+## Enum values
+
+### GenderChoices values
+
+Bound to `gender` on every **write** spec (`UserUpdateSpec`, `UserCreateSpec`). Defined in [`care/emr/resources/patient/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/patient/spec.py) and reused here (read specs return `gender` as a free string).
+
+| Value |
+| --- |
+| `male` |
+| `female` |
+| `non_binary` |
+| `transgender` |
+
+Note: `create_superuser()` sets `gender="non_binary"`, which matches this enum. The legacy integer `GENDER_CHOICES` on the model (`1` Male, `2` Female, `3` Non-binary) is separate and unrelated to the API.
+
+### LoginMethod values
+
+Used by `MFALoginRequest.method` in [`care/emr/resources/mfa/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/mfa/spec.py).
+
+| Value | Meaning |
+| --- | --- |
+| `totp` | Authenticator app one-time code |
+| `backup` | Single-use backup recovery code |
+
+## Resource specs (API schema)
+
+All user specs build on `EMRResource` ([`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)), which provides `serialize` (DB → pydantic) and `de_serialize` (pydantic → DB) plus the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `UserBaseSpec` sets `__model__ = User` and `__exclude__ = ["geo_organization"]`.
+
+| Spec class | Role | Fields / behaviour |
+| --- | --- | --- |
+| `UserBaseSpec` | shared base | `id`, `first_name`, `last_name`, `phone_number` (max 14), `prefix` (max 10, optional), `suffix` (max 50, optional) |
+| `UserUpdateSpec` | write · update | Base + `gender` (`GenderChoices`, required), `phone_number` (max 14), `geo_organization` (`UUID4`, optional). On de-serialize, resolves `geo_organization` to an `Organization` with `org_type="govt"` (404 if missing) |
+| `UserCreateSpec` | write · create | Extends `UserUpdateSpec` + `password` (optional), `username`, `email`, `is_service_account` (default `False`), `role_orgs: list[UserRoleOrgCreateSpec]`. Validates username pattern/uniqueness, phone uniqueness, email format/uniqueness, password strength. On de-serialize, stashes `role_orgs` on the instance and calls `set_password()` |
+| `UserRoleOrgCreateSpec` | write · nested | `organization: UUID4`, `role: UUID4` — a single role-in-organization assignment supplied at user creation |
+| `UserSpec` | read · list/summary | Base + `last_login`, `profile_picture_url`, `gender` (string), `username`, `mfa_enabled` (default `False`), `phone_number`, `deleted` (default `False`), `role_orgs: dict`. `@cacheable(use_base_manager=True)` (cached, incl. deleted users). Sets `id` from `external_id`, resolves picture URL, computes `mfa_enabled`, loads `role_orgs` |
+| `UserRetrieveSpec` | read · detail | Extends `UserSpec` + `geo_organization: dict`, `created_by: dict \| None`, `email`, `flags: list[str]`, `is_service_account`. Serializes `created_by` (cached `UserSpec`), `geo_organization` (`OrganizationReadSpec`), and `flags` (`get_all_flags()`) |
+| `CurrentUserRetrieveSpec` | read · self (`/users/getcurrentuser`-style) | Extends `UserRetrieveSpec` + `is_superuser`, `qualification`, `doctor_experience_commenced_on`, `doctor_medical_council_registration`, `weekly_working_hours`, `alt_phone_number`, `date_of_birth` (all nullable strings), `verified`, `pf_endpoint`/`pf_p256dh`/`pf_auth`, `organizations: list[dict]`, `facilities: list[dict]`, `permissions: list[str]`, `preferences: dict`. Computes the caller's organizations, facilities (excluding deleted), and resolved permission slugs |
+| `PublicUserReadSpec` | read · public | Base + `last_login`, `profile_picture_url`, `gender`, `username`, `role_orgs: list[dict]`. Minimal unauthenticated projection |
+
+Plain-`BaseModel` request/response DTOs (not `EMRResource`, no model binding) for the password-reset flow, all in `user/spec.py`:
+
+| DTO | Shape |
+| --- | --- |
+| `ResetPasswordCheckRequest` | `{ token: str }` |
+| `ResetPasswordConfirmRequest` | `{ token: str, password: str }` |
+| `ResetPasswordResponse` | `{ detail: str }` |
+| `ResetPasswordRequestTokenRequest` | `{ username: str }` |
+
+### MFA specs
+
+Plain-`BaseModel` DTOs in [`care/emr/resources/mfa/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/mfa/spec.py) (no model binding; drive the TOTP/backup-code flow that writes `totp_secret` and `mfa_settings`):
+
+| DTO | Direction | Shape |
+| --- | --- | --- |
+| `TOTPSetupResponse` | response | `{ uri: str, secret_key: str }` |
+| `TOTPVerifyRequest` | request | `{ code: str }` |
+| `TOTPVerifyResponse` | response | `{ backup_codes: list[str] }` |
+| `PasswordVerifyRequest` | request | `{ password: str }` |
+| `MFALoginRequest` | request | `{ method: LoginMethod, code: str, temp_token: str }` |
+| `MFALoginResponse` | response | `{ access: str, refresh: str }` (JWT pair) |
+
+### Validation rules (write specs)
+
+| Field | Rule | Source |
+| --- | --- | --- |
+| `username` | `^[a-zA-Z0-9_-]{3,}$`; must not already exist (checked against the **entire** queryset, incl. deleted) | `UserCreateSpec.validate_username` |
+| `phone_number` | Must not already exist on any user | `UserCreateSpec.validate_phone_number` |
+| `email` | Must not already exist; must pass Django `validate_email` | `UserCreateSpec.validate_user_email` |
+| `password` | If provided, must pass Django `validate_password` (else "Password is too weak"); `None` is allowed | `UserCreateSpec.validate_password` |
+| `gender` | Must be a `GenderChoices` member | type annotation |
+| `geo_organization` | Must reference an existing `Organization` with `org_type="govt"` | `UserUpdateSpec.perform_extra_deserialization` |
+
+### Server-maintained behaviour
+
+- **`role_orgs` on create** — `UserCreateSpec.perform_extra_deserialization` copies `role_orgs` onto `obj._role_orgs` for the view to apply role/organization assignments after the user is saved; it is **not** a DB column.
+- **`set_password`** — create hashes the supplied password (or sets an unusable password when `None`) via `obj.set_password(self.password)`.
+- **Cache** — `UserSpec` is `@cacheable(use_base_manager=True)`; saving a `User` invalidates the cache (via `post_save`), and `created_by` is read through `model_from_cache(UserSpec, ...)`. `use_base_manager=True` lets cached lookups resolve soft-deleted users.
+- **Computed read fields** — `id` (from `external_id`), `profile_picture_url` (resolved), `mfa_enabled`, `role_orgs`, `flags`, `geo_organization`, `created_by`, and the current-user `organizations`/`facilities`/`permissions` are all populated in `perform_extra_serialization`, never copied straight from columns.
+
+## Shared spec types
+
+Referenced by the resource layer (defined under `care/emr/resources/base.py` and `care/emr/resources/common/`). User specs do not bind any coded fields, but the base period/contact types are part of the shared vocabulary:
+
+| Type | Shape | Source |
+| --- | --- | --- |
+| `PeriodSpec` | `{ start: datetime \| None, end: datetime \| None }` — both must be **timezone-aware**; `start ≤ end` enforced | [`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py) |
+| `Period` | `{ id: str?, start: datetime?, end: datetime? }`, `extra="forbid"` | [`common/period.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/period.py) |
+| `ContactPoint` | `{ system: ContactPointSystemChoices, value: str, use: ContactPointUseChoices }` | [`common/contact_point.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/contact_point.py) |
+| `PhoneNumber` | E.164 string (`PhoneNumberValidator`) | [`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py) |
+
+`ContactPointSystemChoices`: `phone`, `fax`, `email`, `pager`, `url`, `sms`, `other`. `ContactPointUseChoices`: `home`, `work`, `temp`, `old`, `mobile`.
+
+## Related models
+
+### `Skill`
+
+Instance-wide competency definition. Extends [`BaseModel`](../foundation/base-model.mdx).
+
+```text
+name → CharField(255), unique
+description → TextField (nullable, default "")
+```
+
+### `UserSkill`
+
+Through model joining `User` and `Skill`. Extends [`BaseModel`](../foundation/base-model.mdx).
+
+```text
+user → FK User (CASCADE, nullable)
+skill → FK Skill (CASCADE, nullable)
+```
+
+A unique constraint (`unique_user_skill`) prevents duplicate `(skill, user)` pairs among non-deleted rows.
+
+### `UserFlag`
+
+Feature flag attached to a single user. Extends `BaseFlag` (a [`BaseModel`](../foundation/base-model.mdx) subclass with a `flag` field).
+
+```text
+user → FK User (CASCADE)
+flag → CharField(1024) (inherited from BaseFlag)
+```
+
+`flag_type = FlagType.USER`. A unique constraint (`unique_user_flag`) prevents duplicate `(user, flag)` pairs among non-deleted rows. Flags are read through `UserFlag.check_user_has_flag(user_id, flag_name)` and `get_all_flags(user_id)`, both cache-backed (TTL 1 day). `UserRetrieveSpec.flags` surfaces these names as a `list[str]`.
+
+### `PlugConfig`
+
+Slug-keyed configuration for installed plugs. Extends plain `models.Model` (no audit/soft-delete fields).
+
+```text
+slug → CharField(255), unique
+meta → JSONField (default {})
+```
+
+### `MobileOTP`
+
+Defined in `care/facility/models/patient.py`. One-time password issued against a phone number for verification flows. Extends [`BaseModel`](../foundation/base-model.mdx).
+
+```text
+is_used → BooleanField (default False)
+phone_number → CharField(14) (mobile_or_landline_number_validator)
+otp → CharField(10)
+```
+
+## Methods & save behaviour
+
+`User` overrides no `save()`/`delete()` but exposes notable helpers:
+
+- `get_cached_role_orgs()` — returns `cached_role_orgs` if set; otherwise loads from `OrganizationUser.get_cached_role_orgs(self.id)` and persists it via `save(update_fields=["cached_role_orgs"])`. Read specs surface the result as `role_orgs`.
+- `read_profile_picture_url()` — resolves `profile_picture_url` against `FACILITY_CDN` or the S3 bucket endpoint; returns `None` when unset. Used by every read spec's `profile_picture_url`.
+- `is_mfa_enabled()` — `True` when `mfa_settings["totp"]["enabled"]` is set; surfaced as `mfa_enabled`.
+- `full_name` (property) — joins `prefix`, `get_full_name()`, and `suffix`.
+- `check_username_exists(username)` (static) — checks across the entire (including deleted) queryset; used by `UserCreateSpec.validate_username`.
+- `get_all_flags()` — delegates to `UserFlag.get_all_flags(self.id)`; surfaced as `flags` in `UserRetrieveSpec`.
+
+`CustomUserManager` adds:
+
+- `get_queryset()` filters `deleted=False`; `get_entire_queryset()` returns all rows.
+- `create_superuser()` forces `phone_number="+919696969696"`, `gender="non_binary"`, and `user_type="administrator"`.
+- `make_random_password()` generates a secure password guaranteeing lower/upper/digit composition.
+
+`UserFlag.save()` (via `BaseFlag`) validates the flag name against the registry and invalidates the per-flag and all-flags cache keys on every write.
+
+## API integration notes
+
+- `User` is exposed through Care's REST API; `external_id` (returned as `id`) is the public identifier, never the integer PK or `username` alone.
+- `deleted` is honoured by `CustomUserManager` — soft-deleted users are excluded from normal queries, but cached `UserSpec` lookups use the base manager (`use_base_manager=True`) so they can still resolve deleted users (e.g. as `created_by`).
+- Authorization is not stored on `User`. Access is resolved through the roles/permissions/organization system; `cached_role_orgs` is a platform-maintained cache — do not write it directly from clients. New users supply `role_orgs` (organization + role UUIDs) at create time.
+- `mfa_settings`, `preferences`, and `PlugConfig.meta` are open JSON bags for evolving config without migrations. `mfa_settings` is mutated by the MFA flow (`mfa/spec.py`), not by user create/update.
+- `is_service_account` distinguishes machine/integration accounts from human users.
+- Write paths use `UserCreateSpec` / `UserUpdateSpec`; read paths use `UserSpec` (list), `UserRetrieveSpec` (detail), `CurrentUserRetrieveSpec` (self), and `PublicUserReadSpec` (public). Picture URLs, MFA status, roles, flags, and nested org/creator objects are computed server-side.
+
+## Related
+
+- Reference: [Role](./role.mdx), [Permission](./permission.mdx), [Permission association](./permission-association.mdx)
+- Reference: [Organization](../facility/organization.mdx), [Facility](../facility/facility.mdx)
+- Concept: [Patient](../../concepts/clinical/patient.mdx) (`GenderChoices` is shared with the patient resource)
+- Base: [Base model](../foundation/base-model.mdx)
+- Source: [users/models.py](https://github.com/ohcnetwork/care/blob/develop/care/users/models.py), [facility/models/patient.py](https://github.com/ohcnetwork/care/blob/develop/care/facility/models/patient.py), [emr/resources/user/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/user/spec.py), [emr/resources/mfa/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/mfa/spec.py), [emr/resources/base.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
diff --git a/versioned_docs/version-3.0/references/billing/_category_.json b/versioned_docs/version-3.0/references/billing/_category_.json
new file mode 100644
index 0000000..512129e
--- /dev/null
+++ b/versioned_docs/version-3.0/references/billing/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Billing",
+ "position": 6,
+ "key": "billing-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Billing",
+ "description": "Patient accounts, charge items and their definitions, invoices, and payment reconciliation."
+ }
+}
diff --git a/versioned_docs/version-3.0/references/billing/account.mdx b/versioned_docs/version-3.0/references/billing/account.mdx
new file mode 100644
index 0000000..894bbf2
--- /dev/null
+++ b/versioned_docs/version-3.0/references/billing/account.mdx
@@ -0,0 +1,218 @@
+---
+sidebar_position: 1
+---
+
+# Account
+
+Technical reference for the `Account` module in Care EMR.
+
+`Account` is a financial bucket aggregating all transactions made against a patient within a facility. The Django model is the **storage layer** — several fields are opaque `JSONField`s whose real structure lives in the Pydantic **resource specs** (`care/emr/resources/account/`). This doc enriches the model fields with the spec-defined enums, JSON shapes, validation, and the API read/write schemas.
+
+**Source:**
+- Model: [`care/emr/models/account.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/account.py)
+- Resource spec: [`care/emr/resources/account/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/spec.py)
+- Default account helper: [`account/default_account.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/default_account.py)
+- Balance sync: [`account/sync_items.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/sync_items.py)
+- Base resource: [`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Account` | A financial bucket for all transactions made against a patient in a facility, aggregating charges and balances across encounters |
+
+`Account` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+An account is perpetual by default: a charge item is placed on the default account for a patient within a facility, and to start with only one active+open account exists per patient per facility (see [`get_default_account`](#default-account)). Accounts can be balanced and closed once a patient is discharged, and invoices are created against accounts or items within them.
+
+## `Account` fields
+
+### Identity & scope
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → facility.Facility` (PROTECT) | yes | Facility where the account is created. Server-set; not exposed on write specs |
+| `patient` | `FK → emr.Patient` (PROTECT) | yes | The entity that incurred the expenses. On create, supplied as a patient `external_id` (UUID) and resolved server-side |
+| `name` | `CharField(255)` | yes | Human-readable label. Default account auto-names as `"{patient.name} {YYYY-MM-DD}"` |
+| `description` | `TextField` | no | Nullable; explanation of purpose/use |
+| `primary_encounter` | `FK → emr.Encounter` (SET_NULL, nullable) | no | Optional link to associate the account with a single encounter for reports, summaries, and insurance paperwork. Settable only via the update spec (supplied as an encounter `external_id`) |
+
+### Status
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(255)` | yes | Account lifecycle status — `AccountStatusOptions` enum (see [values](#accountstatusoptions-values)) |
+| `billing_status` | `CharField(255)` | yes | Billing lifecycle status — `AccountBillingStatusOptions` enum (see [values](#accountbillingstatusoptions-values)) |
+| `service_period` | `JSONField` (default `{}`) | yes (on spec) | Transaction window. Shape = `PeriodSpec { start, end }` — see [service_period shape](#service_period-shape) |
+
+### Balances & totals
+
+These decimal totals are stored with `max_digits=20, decimal_places=6` and default to `0`. They are **platform-maintained aggregates** computed by [`sync_account_items`](#balance-sync), not client-writable. None appear on the write specs.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `total_net` | `DecimalField` | Net total before adjustments. Currently **not populated** by `sync_account_items` (logic commented out); stays at default `0` |
+| `total_gross` | `DecimalField` | Sum of `total_price` over charge items in `paid`/`billed` status |
+| `total_paid` | `DecimalField` | Sum of `active`+`complete` payment reconciliations minus credit-note reconciliations |
+| `total_balance` | `DecimalField` | `total_gross − total_paid` |
+| `total_billable_charge_items` | `DecimalField` | Sum of `total_price` over charge items in `billable` status |
+| `total_price_components` | `JSONField` (default `{}`) | Intended breakdown of price components (taxes, discounts, etc.); shape = list of `MonetaryComponent` (see [below](#monetarycomponent-shape)). Currently **not populated** by `sync_account_items` (logic commented out) |
+| `cached_items` | `JSONField` (default `{}`) | Denormalized snapshot of the account's charge items. Currently **not populated** by `sync_account_items` (logic commented out). Serialized as a `list` on retrieve |
+| `calculated_at` | `DateTimeField` (nullable) | Timestamp the balances were last calculated (set to `care_now()` on each sync) |
+
+### Tags & extensions
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `tags` | `ArrayField[int]` (default `[]`) | Tag IDs applied to the account; rendered via `SingleFacilityTagManager` on read |
+| `extensions` | `JSONField` (default `{}`) | Open extension bag for deployment-specific metadata. Validated by `ExtensionValidator` against `ExtensionResource.account`; surfaced on the retrieve spec |
+
+## Enum values
+
+### `AccountStatusOptions` values
+
+Lifecycle status of the account. Stored as the raw enum value string in `status`.
+
+| Value | Meaning |
+| --- | --- |
+| `active` | Account is open and in use (default-account state) |
+| `inactive` | Account is no longer actively used |
+| `entered_in_error` | Account was created in error |
+| `on_hold` | Account is temporarily paused |
+
+Note: values use underscores (e.g. `entered_in_error`, `on_hold`), not the FHIR-style hyphenated forms.
+
+### `AccountBillingStatusOptions` values
+
+Billing lifecycle status of the account. Stored as the raw enum value string in `billing_status`.
+
+| Value | Meaning |
+| --- | --- |
+| `open` | Billing open / accruing (default-account state) |
+| `carecomplete_notbilled` | Care complete, not yet billed |
+| `billing` | Billing in progress |
+| `closed_baddebt` | Closed as bad debt |
+| `closed_voided` | Closed and voided |
+| `closed_completed` | Closed, billing completed |
+| `closed_combined` | Closed, combined into another account |
+
+## JSON field shapes
+
+### `service_period` shape
+
+`service_period` stores a `PeriodSpec` (from [`resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)):
+
+```text
+PeriodSpec {
+ start: datetime | null # ISO 8601, timezone-aware
+ end: datetime | null # ISO 8601, timezone-aware
+}
+```
+
+Validation (`PeriodSpec.validate_period`):
+- `start`, if present, **must be timezone-aware** (naive datetimes raise `"Start Date must be timezone aware"`).
+- `end`, if present, **must be timezone-aware** (`"End Date must be timezone aware"`).
+- If both present, `start <= end` (`"Start Date cannot be greater than End Date"`).
+
+The default account is created with only `start` set, formatted as `"%Y-%m-%dT%H:%M:%S.%fZ"` (`care_now()`).
+
+The FHIR-style [`Period`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/period.py) common type (`{ id?, start?, end? }`, `extra="forbid"`) is a related shape but `Account` binds `service_period` to `PeriodSpec`, not `Period`.
+
+### `MonetaryComponent` shape
+
+`total_price_components` is intended to hold a list of [`MonetaryComponent`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py) entries (the same shape used across billing):
+
+```text
+MonetaryComponent {
+ monetary_component_type: base | surcharge | discount | tax | informational
+ code: Coding | null
+ factor: Decimal(max_digits=20, decimal_places=6) | null
+ amount: Decimal(max_digits=20, decimal_places=6) | null
+ tax_included_amount: Decimal(max_digits=20, decimal_places=6) | null
+ global_component: bool = false
+ conditions: EvaluatorConditionSpec[] = []
+}
+```
+
+Key `MonetaryComponent` validation: `tax_included_amount` only allowed on a `base` component; a `base` component must have an `amount`, no `factor`, and no `conditions`; `amount` and `factor` are mutually exclusive; exactly one of `amount`/`factor` is required unless it is a `global_component` with a `code`.
+
+## Resource specs (API schema)
+
+The API layer is defined in [`resources/account/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/spec.py). All specs build on `EMRResource` (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). `AccountSpec` sets `__exclude__ = ["patient"]` and binds `___extension_resource_type__ = ExtensionResource.account`.
+
+| Spec class | Role | Fields exposed (beyond base) |
+| --- | --- | --- |
+| `AccountSpec` | shared base | `id`, `status`, `billing_status`, `name`, `service_period` (`PeriodSpec`), `description` |
+| `AccountCreateSpec` | write · create | base + `patient` (UUID, required) |
+| `AccountUpdateSpec` | write · update | base + `primary_encounter` (UUID, optional) |
+| `AccountMinimalReadSpec` | read · minimal | base + `total_gross`, `total_paid`, `total_balance`, `total_billable_charge_items` (all `Decimal` 20/6), `calculated_at`, `created_date`, `modified_date` |
+| `AccountReadSpec` | read · list | minimal + `patient` (serialized `PatientListSpec`), `tags` (rendered tag dicts) |
+| `AccountRetrieveSpec` | read · detail | read + `patient` (serialized `PatientRetrieveSpec`), `primary_encounter` (serialized `EncounterRetrieveSpec` when set), `cached_items` (`list`), `total_price_components` (`dict`), `extensions` (`dict`) |
+
+### Validation & server-side behaviour
+
+- **Create** (`AccountCreateSpec`): mixes in `ExtensionValidator` (validates `extensions`) and requires `patient` as a UUID. `perform_extra_deserialization` resolves it via `get_object_or_404(Patient, external_id=...)` and assigns `obj.patient`. `patient` is otherwise excluded from the base serialize loop.
+- **Update** (`AccountUpdateSpec`): mixes in `ExtensionValidator`; `primary_encounter` is optional. If supplied, `perform_extra_deserialization` resolves it via `get_object_or_404(Encounter, external_id=...)` and assigns `obj.primary_encounter`.
+- **Status / billing_status** are validated against `AccountStatusOptions` / `AccountBillingStatusOptions` — invalid values are rejected by the Pydantic enums.
+- **Read serialization**: `perform_extra_serialization` sets `mapping["id"] = obj.external_id`; `AccountReadSpec` additionally serializes the patient (`PatientListSpec`) and renders tags via `SingleFacilityTagManager`; `AccountRetrieveSpec` serializes the full patient (`PatientRetrieveSpec`, scoped to `obj.facility`) and the primary encounter (`EncounterRetrieveSpec`) when present.
+- **Totals are read-only**: balance/aggregate fields appear only on read specs; they are recomputed by `sync_account_items`, never set from client payloads.
+
+
+### Default account
+
+`get_default_account(patient, facility)` ([`default_account.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/default_account.py)) returns the first account for that `patient` + `facility` with `status = active` and `billing_status = open`. If none exists it creates one with:
+
+- `status = active`, `billing_status = open`
+- `service_period = { "start": care_now() }` (start only)
+- `name = "{patient.name} {YYYY-MM-DD}"`
+
+This is how the default account is materialized when the first charge item is added — clients normally do not create accounts directly.
+
+
+### Balance sync
+
+`sync_account_items(account)` ([`sync_items.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/sync_items.py)) recomputes the totals under an `AccountLock`:
+
+- `total_billable_charge_items` = `Σ total_price` of charge items with status `billable`
+- `total_gross` = `Σ total_price` of charge items with status `paid` or `billed`
+- `total_paid` = `Σ amount` of `active` + `complete` non-credit-note payment reconciliations − the same for credit notes
+- `total_balance` = `total_gross − total_paid`
+- `calculated_at` = `care_now()`
+
+All sums use `care_round`. `total_net`, `cached_items`, and `total_price_components` are currently **not** updated (their logic is commented out), so they remain at their defaults. `rebalance_account_task(account_id)` is the Celery task wrapper that runs the sync and saves.
+
+## Related models
+
+The `Account` model is the only class defined in `account.py`. Its relationships are:
+
+```text
+facility → FK facility.Facility (PROTECT)
+patient → FK emr.Patient (PROTECT)
+primary_encounter → FK emr.Encounter (SET_NULL, nullable)
+```
+
+[Charge items](./charge-item.mdx) refer back to the account they belong to and drive its totals; [invoices](./invoice.mdx) and [payment reconciliations](./payment-reconciliation.mdx) are created against the account or its items.
+
+## API integration notes
+
+- Account records are exposed through Care's REST API and align to the FHIR `Account` resource; spec field names may differ from FHIR (e.g. enum values use underscores).
+- The default account is created automatically via `get_default_account` when the first charge item is added for a patient in a facility — clients normally do not create accounts directly.
+- On **create**, supply `patient` as the patient's `external_id` (UUID); `facility` is server-scoped.
+- On **update**, `primary_encounter` accepts an encounter `external_id` (UUID); only the base fields plus `primary_encounter` are mutable.
+- Balance/aggregate fields (`total_net`, `total_gross`, `total_paid`, `total_balance`, `total_billable_charge_items`, `total_price_components`, `cached_items`, `calculated_at`) are platform-maintained and appear only on read specs — never set them from clients.
+- `service_period` requires **timezone-aware** datetimes with `start <= end`.
+- `extensions` is the supported place for custom key-value data without schema migrations; it is validated by `ExtensionValidator` and surfaced only on the retrieve spec.
+- Account data should be gated behind permissions that restrict access to users with rights to financial information.
+
+## Related
+
+- Reference: [Charge item](./charge-item.mdx)
+- Reference: [Charge item definition](./charge-item-definition.mdx)
+- Reference: [Invoice](./invoice.mdx)
+- Reference: [Payment reconciliation](./payment-reconciliation.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Patient concept: [Patient](../../concepts/clinical/patient.mdx)
+- Source: [account.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/account.py)
+- Spec: [account/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/spec.py)
diff --git a/versioned_docs/version-3.0/references/billing/charge-item-definition.mdx b/versioned_docs/version-3.0/references/billing/charge-item-definition.mdx
new file mode 100644
index 0000000..5ad9739
--- /dev/null
+++ b/versioned_docs/version-3.0/references/billing/charge-item-definition.mdx
@@ -0,0 +1,212 @@
+---
+sidebar_position: 3
+---
+
+# Charge Item Definition
+
+Technical reference for the `ChargeItemDefinition` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/charge_item_definition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/charge_item_definition.py)
+- Resource spec: [`care/emr/resources/charge_item_definition/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/charge_item_definition/spec.py)
+- Shared spec: [`care/emr/resources/common/monetary_component.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
+
+A charge item definition is the billing template that answers "how much does resource X cost". It binds a billable resource to a set of price components — base rate, surcharges, discounts, and taxes — that are evaluated when a [charge item](../billing/charge-item.mdx) is generated during data entry.
+
+Two layers describe this resource:
+
+- The **Django model** (`care/emr/models/charge_item_definition.py`) is the storage layer. `price_components` and `discount_configuration` are opaque `JSONField`s — their real structure is not visible in the model.
+- The **Pydantic resource specs** (`care/emr/resources/charge_item_definition/`) are the API layer. They define the enum for `status`, the structured shape of those JSON fields (via `MonetaryComponent` / `DiscountConfiguration`), validation rules, and the read/write schemas. See [Resource specs (API schema)](#resource-specs-api-schema).
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ChargeItemDefinition` | Facility-scoped pricing template defining the price components applied to a billable resource |
+
+`ChargeItemDefinition` extends [`SlugBaseModel`](../foundation/base-model.mdx) — the slug-aware Care EMR base. `SlugBaseModel` extends `EMRBaseModel`, so the model inherits `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and the `history`/`meta` JSON fields, and adds facility-scoped slug helpers (see [Methods & save behaviour](#methods--save-behaviour)).
+
+## `ChargeItemDefinition` fields
+
+### Identity & versioning
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → facility.Facility` | yes | `PROTECT`. The facility that owns this definition |
+| `version` | `IntegerField` | — | Default `1`. Revision number; surfaced read-only by the spec |
+| `status` | `CharField(255)` | yes | Lifecycle status — constrained by `ChargeItemDefinitionStatusOptions` (`draft`, `active`, `retired`). See [Status values](#status-values) |
+| `title` | `CharField(255)` | yes | Human-readable name for the definition |
+| `slug` | `CharField(255)` | yes | Stored slug (see [slug behaviour](#methods--save-behaviour)). On write the client sends `slug_value`; the server prefixes it |
+| `derived_from_uri` | `TextField` | no | Nullable, default `None`. URI of an upstream definition this was derived from |
+
+### Descriptive
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `description` | `TextField` | no | Nullable, default `None`. Natural-language description |
+| `purpose` | `TextField` | no | Nullable, default `None`. Why the definition exists |
+
+### Pricing & rules
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `price_components` | `JSONField` | yes | Default `[]`. List of `MonetaryComponent` — see [Price components shape](#price-components-shape) |
+| `discount_configuration` | `JSONField` | no | Nullable, default `None`. Shaped as `DiscountConfiguration` — see [Discount configuration shape](#discount-configuration-shape) |
+| `can_edit_charge_item` | `BooleanField` | yes | Default `True`. Whether a charge item generated from this definition may be edited after creation |
+| `category` | `FK → emr.ResourceCategory` | no | `CASCADE`, nullable. Categorizes the definition. On write supplied as an `ExtendedSlugType` slug; resolved server-side |
+| `tags` | `ArrayField[int]` | — | Default `[]`. Tag IDs; rendered to objects on read |
+
+## Enum values
+
+### Status values
+
+`ChargeItemDefinitionStatusOptions` (`spec.py`) — `status` is constrained to these values. Note the model `CharField` is unconstrained at the DB level; the spec is the source of truth.
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Not yet active; excluded from live billing |
+| `active` | In use; only these should drive new charge items |
+| `retired` | Withdrawn; excluded from live billing |
+
+### Monetary component type values
+
+`MonetaryComponentType` (`common/monetary_component.py`) — the `monetary_component_type` of each price component.
+
+| Value | Meaning |
+| --- | --- |
+| `base` | The base rate. Exactly one allowed; must carry an `amount`; may not have `conditions` or a `factor` |
+| `surcharge` | An additive charge layered on the base |
+| `discount` | A reduction layered on the base |
+| `tax` | A tax component |
+| `informational` | Non-priced informational component |
+
+### Discount applicability values
+
+`DiscountApplicability` (`common/monetary_component.py`) — `discount_configuration.applicability_order`.
+
+| Value | Meaning |
+| --- | --- |
+| `total_asc` | Apply discounts in ascending order of total |
+| `total_desc` | Apply discounts in descending order of total |
+
+## JSON field shapes
+
+### Price components shape
+
+`price_components` is a `list[MonetaryComponent]`. Each entry (`common/monetary_component.py`):
+
+```text
+MonetaryComponent {
+ monetary_component_type : MonetaryComponentType # required; see enum above
+ code : Coding | null # billing code (system/version/code/display); code required if present
+ factor : Decimal | null # max_digits=20, decimal_places=6
+ amount : Decimal | null # max_digits=20, decimal_places=6
+ tax_included_amount : Decimal | null # max_digits=20, decimal_places=6; base-only
+ global_component : bool # default false
+ conditions : list[EvaluatorConditionSpec] # default []
+}
+```
+
+`Coding` (`common/coding.py`, `extra="forbid"`): `{ system: str|null, version: str|null, code: str (required), display: str|null }`.
+
+`EvaluatorConditionSpec` (`common/condition_evaluator.py`): `{ metric: str, operation: str, value: dict|str }` — `metric` is validated against `EvaluatorMetricsRegistry`, and `operation`/`value` are validated by that metric's evaluator.
+
+**Per-component validation (`MonetaryComponent`):**
+
+| Rule | Detail |
+| --- | --- |
+| `tax_included_amount` base-only | Allowed only when `monetary_component_type == base` |
+| base no conditions | A `base` component must have no `conditions` |
+| base requires amount | A `base` component must have an `amount` |
+| amount xor factor | `amount` and `factor` cannot both be present |
+| amount or factor required | One of `amount`/`factor` must be present — except when `global_component` is true and a `code` is set |
+
+### Discount configuration shape
+
+`discount_configuration` is a `DiscountConfiguration | null` (`common/monetary_component.py`):
+
+```text
+DiscountConfiguration {
+ max_applicable : int # required; >= 0
+ applicability_order : DiscountApplicability # required; total_asc | total_desc
+}
+```
+
+## Resource specs (API schema)
+
+The API does not serialize the Django model directly; it goes through Pydantic specs built on `EMRResource` (`resources/base.py`), which provide `serialize` (DB → API) and `de_serialize` (API → DB).
+
+| Spec class | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `ChargeItemDefinitionSpec` | shared base | `id`, `status`, `title`, `derived_from_uri`, `description`, `purpose`, `price_components`, `can_edit_charge_item`, `discount_configuration`. `__exclude__ = []` |
+| `ChargeItemDefinitionWriteSpec` | write · create + update | Adds `slug_value: SlugType` and `category: ExtendedSlugType | null`. Validates `price_components`; deserialization side effects below |
+| `ChargeItemDefinitionReadSpec` | read · list + detail | Adds read-only `version`, `category: dict`, `slug_config: dict`, `tags: list[dict]`, `slug: str`, `created_by`, `updated_by`, `created_date`, `updated_date` |
+
+Nested specs used by these: `MonetaryComponent` (price component), `DiscountConfiguration` (discount rules), `Coding` (component code), `EvaluatorConditionSpec` (component conditions).
+
+### Write-spec validation & side effects
+
+- **Duplicate (code, type) check** (`check_components_with_duplicate_codes` field validator): no two `price_components` may share the same `(code.code, monetary_component_type)` pair (only components that carry a `code` are checked).
+- **`perform_extra_deserialization`** (`ChargeItemDefinitionWriteSpec`):
+ - If `category` (slug) is supplied, resolves it via `ResourceCategory.objects.get(slug=category)` and sets `obj.category`.
+ - Sets `obj.slug = self.slug_value` (the unprefixed value; the model's slug helpers prefix it — see below).
+
+### Read-spec serialization
+
+- **`perform_extra_serialization`** (`ChargeItemDefinitionReadSpec`):
+ - `id` ← `obj.external_id`.
+ - `category` ← `ResourceCategoryReadSpec.serialize(obj.category)` when set.
+ - `slug_config` ← `obj.parse_slug(obj.slug)` (decomposes the stored slug into `{facility, slug_value}` or `{slug_value}`).
+ - `tags` ← `SingleFacilityTagManager().render_tags(obj)`.
+ - `created_by` / `updated_by` ← serialized via `serialize_audit_users`.
+
+### Slug type constraints
+
+| Type | Constraints |
+| --- | --- |
+| `SlugType` (`slug_value`) | `min_length=5`, `max_length=50`; pattern `^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$` (URL-safe; alphanumeric start/end) |
+| `ExtendedSlugType` (`category`) | `min_length=7`, `max_length=88`; same pattern, and must start with `f-` or `i-` |
+
+## Related models
+
+`ChargeItemDefinition` references two other models by foreign key:
+
+```text
+facility → FK facility.Facility (PROTECT)
+category → FK emr.ResourceCategory (CASCADE, nullable)
+```
+
+- `facility` is protected: a facility cannot be deleted while definitions reference it.
+- `category` cascades: deleting the [resource category](../platform/resource-category.mdx) removes the link.
+
+## Methods & save behaviour
+
+`ChargeItemDefinition` adds no overrides of its own, but inherits slug behaviour from `SlugBaseModel` (`FACILITY_SCOPED = True`):
+
+| Method | Behaviour |
+| --- | --- |
+| `calculate_slug()` | Returns `f-{facility.external_id}-{slug}` when facility-scoped with a facility, otherwise `i-{slug}` |
+| `calculate_slug_from_facility(facility_external_id, slug)` | Classmethod — builds a facility-scoped slug string |
+| `calculate_slug_from_instance(slug)` | Classmethod — builds an instance-scoped slug string |
+| `parse_slug(slug)` | Splits a stored slug back into `{facility, slug_value}` (facility-scoped, `f-` prefix) or `{slug_value}` (instance-scoped, `i-` prefix); validates the embedded facility UUID; raises on length `<= 2` or an unknown prefix |
+
+Because slugs are namespaced by the owning facility's `external_id`, the same human-readable `slug_value` (for example `consultation-fee`) can coexist across facilities without collision.
+
+## API integration notes
+
+- Charge item definitions are FHIR-aligned (`ChargeItemDefinition`); `price_components` mirror the FHIR `MonetaryComponent` shape. API field names differ from Django model names (e.g. the client sends `slug_value`, not `slug`; `category` is a slug string on write but an object on read).
+- `status` is enum-gated by the spec (`draft`/`active`/`retired`); only `active` definitions should drive new charge items.
+- `price_components` and `discount_configuration` are validated by Pydantic on write — well-formedness is enforced server-side, not left to the client.
+- `can_edit_charge_item` is a platform-enforced flag controlling whether a derived charge item is mutable, not the definition itself.
+- `slug_config`, `tags`, `category`, and audit users are computed server-side and appear only on read.
+
+## Related
+
+- Source: [charge_item_definition.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/charge_item_definition.py)
+- Spec: [charge_item_definition/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/charge_item_definition/spec.py)
+- Spec: [common/monetary_component.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
+- Reference: [Charge Item](../billing/charge-item.mdx)
+- Reference: [Invoice](../billing/invoice.mdx)
+- Reference: [Account](../billing/account.mdx)
+- Reference: [Resource Category](../platform/resource-category.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
diff --git a/versioned_docs/version-3.0/references/billing/charge-item.mdx b/versioned_docs/version-3.0/references/billing/charge-item.mdx
new file mode 100644
index 0000000..57aeff7
--- /dev/null
+++ b/versioned_docs/version-3.0/references/billing/charge-item.mdx
@@ -0,0 +1,266 @@
+---
+sidebar_position: 2
+---
+
+# Charge Item
+
+Technical reference for the `ChargeItem` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/charge_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/charge_item.py)
+- Resource specs: [`care/emr/resources/charge_item/`](https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/charge_item)
+ ([`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/charge_item/spec.py),
+ [`sync_charge_item_costs.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/charge_item/sync_charge_item_costs.py),
+ [`apply_charge_item_definition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/charge_item/apply_charge_item_definition.py),
+ [`handle_charge_item_cancel.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/charge_item/handle_charge_item_cancel.py))
+- Viewset: [`care/emr/api/viewsets/charge_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/charge_item.py)
+
+A charge item tracks the financial cost of a service or product supplied to a patient. Charge items are self-descriptive — they record what was charged, the quantity, the unit and total price components, and any applied discounts or override reasons. Every charge item belongs to an [`Account`](./account.mdx) and is created against a patient (optionally an encounter).
+
+The Django model is the **storage** layer: several fields are opaque `JSONField`s whose real structure lives only in the Pydantic resource specs (`care/emr/resources/charge_item/`). Those specs define the enums, the JSON shapes, validation, and the read/write API schemas, and they drive server-side cost calculation and status side effects. This page documents both layers.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ChargeItem` | A single billable line item for a service or product rendered to a patient |
+
+`ChargeItem` extends `EMRBaseModel` (shared Care EMR base with `external_id`, audit fields — `created_by`/`updated_by`/`created_date`/`modified_date` — and soft-delete semantics — see [Base model](../foundation/base-model.mdx)).
+
+## `ChargeItem` fields
+
+### Context & relationships
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` (`PROTECT`) | Facility where the charge is created. Set server-side from the URL, not the request body |
+| `patient` | `FK → emr.Patient` (`CASCADE`) | Patient associated with the charge. Derived from `encounter` when an encounter is supplied |
+| `encounter` | `FK → emr.Encounter` (`CASCADE`) | Nullable; encounter the charge relates to |
+| `charge_item_definition` | `FK → emr.ChargeItemDefinition` (`CASCADE`) | Nullable; template the charge was applied from. Not writable via the standard create/update specs (set only by `apply_charge_item_defs`) |
+| `account` | `FK → emr.Account` (`CASCADE`) | Account this charge is placed on. Defaults to the patient's default account when omitted |
+
+### Description & status
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `title` | `CharField(255)` | Required; display title for the charge |
+| `description` | `TextField` | Nullable; longer description |
+| `status` | `CharField(255)` | Required; lifecycle status — constrained to `ChargeItemStatusOptions`, see [Status values](#status-values) |
+| `code` | `JSONField` | Nullable; a `Coding` that identifies the charge (e.g. a billing code) — shape `Coding { system?, version?, code, display? }`, see [Coding shape](#coding-shape). No bound value set |
+| `note` | `TextField` | Nullable; free-text (markdown) comments about the charge item |
+
+### Pricing
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `quantity` | `DecimalField(20, 6)` | Required on write; quantity serviced. Multiplies the base/per-unit components during cost sync |
+| `unit_price_components` | `JSONField` | Required on write; list of `MonetaryComponent` — see [MonetaryComponent shape](#monetarycomponent-shape). At most one `base` component; duplicate component codes rejected |
+| `total_price_components` | `JSONField` | Nullable; **server-computed** list of `MonetaryComponent` (as dicts) — the resolved breakdown after applying base × quantity, surcharges, discounts, and taxes. Not client-writable |
+| `total_price` | `DecimalField(20, 6)` | Nullable; **server-computed** resolved total amount. Must be ≥ 0 unless the charge is a reversal |
+| `override_reason` | `JSONField` | Nullable; `ChargeItemOverrideReason { text: str, code?: Coding }` explaining a list-price/factor override. No bound value set |
+| `discount_configuration` | `JSONField` | Nullable (default `None`); `DiscountConfiguration { max_applicable: int (≥0), applicability_order: total_asc \| total_desc }`. Populated from the definition/facility config when applied from a definition |
+
+### Service source
+
+These record why the charge was rendered — the originating resource that produced this charge.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `service_resource` | `CharField(255)` | Nullable (default `None`); resource type — constrained to `ChargeItemResourceOptions`, see [Service resource types](#service-resource-types) |
+| `service_resource_id` | `CharField(255)` | Nullable (default `None`); external id of the originating resource. Required when `service_resource` is set |
+| `performer_actor` | `FK → users.User` (`CASCADE`) | Nullable (default `None`); user who performed the service. Must belong to the facility's organization |
+
+### Invoicing & tags
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `paid_invoice` | `FK → emr.Invoice` (`CASCADE`) | Nullable (default `None`); denormalized link to the invoice that settled this charge. Platform-maintained; cleared on cancel |
+| `paid_on` | `DateTimeField` | Nullable (default `None`); timestamp the charge was paid. Platform-maintained; cleared on cancel |
+| `tags` | `ArrayField[int]` | Tag IDs, defaults to `[]`. Managed through the tag mixin (`SingleFacilityTagManager`), serialized as objects on read |
+
+## Enums
+
+### Status values
+
+`status` is a plain `CharField` in storage, but every write goes through `ChargeItemStatusOptions` (`care/emr/resources/charge_item/spec.py`):
+
+| Value | Meaning |
+| --- | --- |
+| `billable` | Ready to be invoiced |
+| `not_billable` | Will not be billed |
+| `aborted` | Charge was cancelled before billing |
+| `billed` | Included on an invoice |
+| `paid` | Settled (links to `paid_invoice` / `paid_on`) |
+| `entered_in_error` | Recorded in error |
+
+(`planned` exists in the source as a commented-out option and is **not** currently selectable.)
+
+`not_billable`, `aborted`, and `entered_in_error` form the cancelled set (`CHARGE_ITEM_CANCELLED_STATUS`). Transitioning into this set on update triggers the cancel side effect (see [Methods & save behaviour](#methods--save-behaviour)). `billed` and `paid` cannot be set manually — the viewset rejects manual changes into them.
+
+### Service resource types
+
+`service_resource` is constrained by `ChargeItemResourceOptions`:
+
+| Value |
+| --- |
+| `service_request` |
+| `medication_dispense` |
+| `appointment` |
+| `bed_association` |
+
+On create the viewset validates the referenced resource: `service_request` must exist (and not be completed) for the patient/encounter, and `bed_association` must reference a `FacilityLocationEncounter` on a non-completed encounter in the facility.
+
+### MonetaryComponentType values
+
+Used by every entry in `unit_price_components` / `total_price_components` (`care/emr/resources/common/monetary_component.py`):
+
+| Value | Role in cost sync |
+| --- | --- |
+| `base` | Per-unit base price. Exactly one allowed; must carry `amount` (not `factor`); no `conditions`. `base.amount × quantity` seeds the running total |
+| `surcharge` | Added on top of base; `amount` or `factor` (% of base) |
+| `discount` | Subtracted from the net price; `amount` or `factor` (% of net). Filtered/limited by `discount_configuration` |
+| `tax` | Added on the post-discount taxable price; `amount` or `factor` (% of taxable) |
+| `informational` | Carried through without affecting the computed total |
+
+### DiscountApplicability values
+
+`discount_configuration.applicability_order` (`care/emr/resources/common/monetary_component.py`):
+
+| Value | Effect |
+| --- | --- |
+| `total_asc` | Sort candidate discounts by amount ascending before applying `max_applicable` |
+| `total_desc` | Sort by amount descending before applying `max_applicable` |
+
+`max_applicable = 0` drops all discounts.
+
+## JSON field shapes
+
+### Coding shape
+
+`code` and the `code` field inside components / `override_reason` (`care/emr/resources/common/coding.py`, `extra="forbid"`):
+
+```text
+Coding {
+ system?: str
+ version?: str
+ code: str # required
+ display?: str
+}
+```
+
+### MonetaryComponent shape
+
+Each entry of `unit_price_components` and `total_price_components` (`care/emr/resources/common/monetary_component.py`):
+
+```text
+MonetaryComponent {
+ monetary_component_type: base | surcharge | discount | tax | informational # required
+ code?: Coding
+ factor?: Decimal(20,6) # percentage; mutually exclusive with amount
+ amount?: Decimal(20,6) # absolute; mutually exclusive with factor
+ tax_included_amount?: Decimal(20,6) # base component only
+ global_component: bool = False # resolved against facility monetary config
+ conditions: [EvaluatorConditionSpec] = [] # not allowed on base
+}
+```
+
+Validation: exactly one of `amount`/`factor` (unless `global_component` with a `code`); `base` must have `amount`, no `factor`, no `conditions`; `tax_included_amount` only on `base`; duplicate component `code`s are rejected.
+
+`EvaluatorConditionSpec { metric: str, operation: str, value: dict | str }` — conditions are evaluated against patient/facility/encounter context when a definition is applied; a component whose conditions fail is dropped.
+
+### ChargeItemOverrideReason shape
+
+`override_reason` (`care/emr/resources/charge_item/spec.py`):
+
+```text
+ChargeItemOverrideReason {
+ text: str # required
+ code?: Coding # no bound value set
+}
+```
+
+### DiscountConfiguration shape
+
+`discount_configuration` (`care/emr/resources/common/monetary_component.py`):
+
+```text
+DiscountConfiguration {
+ max_applicable: int (>= 0) # 0 drops all discounts
+ applicability_order: total_asc | total_desc
+}
+```
+
+## Resource specs (API schema)
+
+Charge items are exposed through `ChargeItemViewSet` (create / retrieve / update / upsert / list / tag actions, plus the `apply_charge_item_defs` and `change_account` custom actions). The Pydantic specs in `care/emr/resources/charge_item/spec.py` build on `EMRResource` (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks).
+
+| Spec | Role | Notes |
+| --- | --- | --- |
+| `ChargeItemSpec` | shared base | `__model__ = ChargeItem`, `__exclude__ = ["encounter", "account"]`. Fields: `id`, `title`, `description`, `status`, `code`, `quantity`, `unit_price_components`, `note`, `override_reason`. Validators: no duplicate component codes, ≤1 `base` component |
+| `ChargeItemWriteSpec` | write · create | Extends base with `encounter`, `patient`, `account`, `service_resource`, `service_resource_id`, `performer_actor` (all `UUID4`/enum, optional). Requires `encounter` **or** `patient`; requires `service_resource_id` when `service_resource` set |
+| `ChargeItemUpdateSpec` | write · update | Base fields + `performer_actor`. Does not re-bind patient/encounter/account |
+| `ChargeItemReadSpec` | read · list & detail | Base fields + server-side: `total_price_components`, `total_price`, `charge_item_definition` (nested), `paid_invoice` (nested), `tags` (objects), `service_resource(_id)`, `created_date`, `modified_date`, `paid_on`, `performer_actor`, `created_by`, `updated_by`, `discount_configuration` |
+
+Notable validation / binding:
+
+- `code`, `override_reason.code`, and component `code`s are FHIR-aligned `Coding`s with **no bound value set**.
+- `ChargeItemWriteSpec.perform_extra_deserialization` resolves `patient`/`encounter`/`account`/`performer_actor` from external ids; supplying `encounter` overrides `patient` with the encounter's patient, and `account` is looked up scoped to that patient.
+- `ChargeItemReadSpec.perform_extra_serialization` nests the full `ChargeItemDefinitionReadSpec` and `InvoiceReadSpec`, renders tags, and resolves `performer_actor` / `created_by` / `updated_by` from the user cache.
+
+### Nested / related specs
+
+| Spec | Used by | Shape |
+| --- | --- | --- |
+| `Coding` | `code`, component & override codes | see [Coding shape](#coding-shape) |
+| `MonetaryComponent` | `unit_price_components`, `total_price_components` | see [MonetaryComponent shape](#monetarycomponent-shape) |
+| `ChargeItemOverrideReason` | `override_reason` | `{ text, code? }` |
+| `DiscountConfiguration` | `discount_configuration` | `{ max_applicable, applicability_order }` |
+| `ChargeItemDefinitionReadSpec` | `charge_item_definition` (read) | full nested definition — see [Charge Item Definition](./charge-item-definition.mdx) |
+| `InvoiceReadSpec` | `paid_invoice` (read) | full nested invoice — see [Invoice](./invoice.mdx) |
+| `UserSpec` | `performer_actor`, audit users (read) | see [User](../access/user.mdx) |
+
+## Related models
+
+`ChargeItem` is a leaf model — it holds foreign keys rather than owning child rows. Its relationships:
+
+```text
+facility → FK facility.Facility (PROTECT)
+patient → FK emr.Patient (CASCADE)
+encounter → FK emr.Encounter (CASCADE, nullable)
+charge_item_definition → FK emr.ChargeItemDefinition (CASCADE, nullable)
+account → FK emr.Account (CASCADE)
+paid_invoice → FK emr.Invoice (CASCADE, nullable)
+performer_actor → FK users.User (CASCADE, nullable)
+```
+
+## Methods & save behaviour
+
+Cost resolution and status side effects are applied in the spec helpers and the viewset, not in the model's `save()`.
+
+- **Cost sync (`sync_charge_item_costs`)** — runs on every create and on update (unless suppressed). Iterates `unit_price_components`: multiplies the `base` amount by `quantity` to seed `total_price`/`base`, adds `surcharge`s, applies `discount`s (filtered by `discount_configuration` via `apply_discount_configuration`), then adds `tax`es on the taxable price. Writes the resolved breakdown to `total_price_components` and the sum to `total_price`. Raises `ValidationError` if `total_price < 0` and the charge is not a reversal.
+- **Apply from definition (`apply_charge_item_definition` / `POST apply_charge_item_defs`)** — builds a `ChargeItem` from a `ChargeItemDefinition`: copies `title`/`description`, merges category + global components, evaluates per-component `conditions` against patient/facility/encounter context (dropping unmet ones), sets `status = billable`, resolves `discount_configuration`, defaults the account to the patient's default account, and runs cost sync. `reverse` negates component amounts to produce a credit.
+- **Cancel (`handle_charge_item_cancel`)** — fires on update when `status` moves into `CHARGE_ITEM_CANCELLED_STATUS` (`not_billable` / `aborted` / `entered_in_error`). If the charge sits on a **draft** invoice it is removed from the invoice, the invoice is rebalanced and saved, and `paid_invoice` / `paid_on` are cleared. Cancelling a charge on a non-draft invoice raises `ValidationError`. Cost sync is skipped for cancellations.
+- **Update guards (`validate_data` / `perform_update`)** — no updates allowed once the charge is in a cancelled status; updates blocked if the linked invoice is `balanced`/`issued`; `status` cannot be manually moved into `billed`/`paid`; if the source definition has `can_edit_charge_item = False`, pricing fields (`unit_price_components`, `total_price_components`, `total_price`, `quantity`) are reset from the previous object and cost sync is skipped. After update, a draft `paid_invoice` is re-synced.
+- **Cancel authorization (`authorize_cancel`)** — cancellation is free within `CHARGE_ITEM_FREE_CANCEL_PERIOD_MINUTES` of creation; afterwards it requires the `can_cancel_charge_item_in_facility` permission.
+- **Change account (`POST change_account`)** — bulk-moves up to 100 `billable` charge items to a target account, then queues `rebalance_account_task` for every affected account.
+
+## API integration notes
+
+- The viewset sets `facility` from the URL and ignores any body value; `patient` is derived from `encounter` when supplied.
+- A charge item must resolve to an `account` — when none is supplied the patient's default account is used. `encounter` and `charge_item_definition` are optional.
+- `total_price`, `total_price_components`, `paid_invoice`, and `paid_on` are server-maintained; do not set them from clients.
+- Create/update enforce that `performer_actor` (and, when applying definitions, the actor) belongs to the facility's organization.
+- `discount_configuration` and `tags` are structured but deployment-tunable: `discount_configuration` follows `DiscountConfiguration`, and tags are resolved via the single-facility tag manager.
+
+## Related
+
+- Reference: [Account](./account.mdx)
+- Reference: [Charge Item Definition](./charge-item-definition.mdx)
+- Reference: [Invoice](./invoice.mdx)
+- Reference: [Payment Reconciliation](./payment-reconciliation.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [User](../access/user.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [charge_item.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/charge_item.py)
+- Source: [charge_item resource specs on GitHub](https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/charge_item)
diff --git a/versioned_docs/version-3.0/references/billing/invoice.mdx b/versioned_docs/version-3.0/references/billing/invoice.mdx
new file mode 100644
index 0000000..580d4c6
--- /dev/null
+++ b/versioned_docs/version-3.0/references/billing/invoice.mdx
@@ -0,0 +1,177 @@
+---
+sidebar_position: 4
+---
+
+# Invoice
+
+Technical reference for the `Invoice` module in Care EMR.
+
+The Django model is the **storage** layer: it holds the columns and several opaque `JSONField`s (`charge_items_copy`, `total_price_components`, `lock_history`) whose real structure lives in the Pydantic **resource specs**. The specs define the API request/response schemas, the `InvoiceStatusOptions` enum, the shape of the JSON fields, validation, and the server-side totalling/snapshot behaviour. Read both layers together.
+
+**Source:**
+- Model: [`care/emr/models/invoice.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/invoice.py)
+- Resource specs: [`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/invoice/spec.py) · [`sync_items.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/invoice/sync_items.py) · [`return_items_invoice.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/invoice/return_items_invoice.py) · [`default_expression_evaluator.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/invoice/default_expression_evaluator.py)
+- Shared types: [`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py) · [`monetary_component.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Invoice` | A billable grouping of charge items for one account, with snapshotted line items, totals, and status |
+
+`Invoice` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+An invoice groups [charge items](../billing/charge-item.mdx) raised against a single [account](../billing/account.mdx). Invoice `status` drives the charge item lifecycle. On issue, the server computes net/gross totals and snapshots each charge item, so the invoice stays stable even if the underlying charge items change. [Payment reconciliation](../billing/payment-reconciliation.mdx) records (payments and credit notes) are reported against the invoice on the retrieve view.
+
+## `Invoice` fields
+
+### References
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` (PROTECT) | Facility where the invoice is created |
+| `patient` | `FK → emr.Patient` (PROTECT) | Entity that incurred the charges; set server-side from `account.patient` on write |
+| `account` | `FK → emr.Account` (PROTECT) | Account being balanced by this invoice |
+
+### Status & metadata
+
+| Field | Type | Spec / values | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(100)` | `InvoiceStatusOptions` enum — required on write | Lifecycle status. See [values](#invoicestatusoptions-values) |
+| `title` | `CharField(1024)` | `str \| None`, default `None` | Optional human-readable invoice title |
+| `cancelled_reason` | `TextField` | `str \| None` | Optional reason recorded when cancelled |
+| `payment_terms` | `TextField` | `str \| None` | Optional payment details (markdown) |
+| `note` | `TextField` | `str \| None` | Optional free-text comments (markdown) |
+| `number` | `CharField(1000)` | `str \| None`, default `None` | Invoice number. Auto-generated for return invoices via the facility's `invoice_number_expression` (see [Methods](#methods--save-behaviour)) |
+| `issue_date` | `DateTimeField` | `datetime \| None` (tz-aware), default `None` | When the invoice was issued |
+| `is_refund` | `BooleanField` | `bool`, default `False` | Flags a refund/return invoice; required for negative totals (see validation) |
+
+### Charge items
+
+| Field | Type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `charge_items` | `ArrayField[int]`, default `[]` | write: `list[UUID4]`; storage: list of integer PKs | Live references to the grouped `ChargeItem` rows. On write the client sends external UUIDs; resolved to PKs server-side |
+| `charge_items_copy` | `JSONField`, default `[]` | `list[dict]` — array of serialized `ChargeItemReadSpec` | Point-in-time snapshot of the line items, written by `sync_invoice_items`. Used as the retrieve `charge_items` for any non-`draft` invoice |
+
+### Totals (server-maintained)
+
+These are computed by `sync_invoice_items` / `calculate_charge_items_summary` — never set them from a client.
+
+| Field | Type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `total_price_components` | `JSONField`, default `{}` | retrieve: `list[dict]` of `MonetaryComponent` | Aggregated monetary components across all charge items (one per type+code). See [MonetaryComponent shape](#monetarycomponent-shape) |
+| `total_net` | `DecimalField(20, 6)`, default `0` | `Decimal(max_digits=20, decimal_places=6)` | Net total = base + surcharge − discount (tax **excluded**). Rounded with `INVOICE_FINAL_AMOUNT_PRECISION` / `..._ROUNDING_METHOD` |
+| `total_gross` | `DecimalField(20, 6)`, default `0` | `Decimal(max_digits=20, decimal_places=6)` | Gross total = net + tax (tax **included**). Same rounding |
+
+On the read specs, if `locked` is `True`, both `total_net` and `total_gross` are serialized as `0`.
+
+### Locking
+
+| Field | Type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `locked` | `BooleanField`, default `False` | `bool` (read-only on read specs) | When set, the invoice is frozen against further edits and totals serialize as `0` |
+| `lock_history` | `JSONField`, default `[]` | `list[dict]` — each entry `{ user, ... }` | Audit trail of lock/unlock events. On retrieve, each entry's `user` id is hydrated into a `UserSpec` dict |
+
+## Enums
+
+### `InvoiceStatusOptions` values
+
+`care/emr/resources/invoice/spec.py`. Note the API value is `entered_in_error` (underscore), not the FHIR-style hyphen.
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | In progress; retrieve serializes live charge items (not the snapshot) |
+| `issued` | Issued to the patient/payer |
+| `balanced` | Fully settled |
+| `cancelled` | Cancelled |
+| `entered_in_error` | Recorded in error |
+
+`INVOICE_CANCELLED_STATUS = ["cancelled", "entered_in_error"]` — the two terminal/void states.
+
+### `MonetaryComponentType` values
+
+`care/emr/resources/common/monetary_component.py`. Drives how each component contributes to net/gross during totalling.
+
+| Value | Effect on totals |
+| --- | --- |
+| `base` | Added to net (one base component per item; must carry an `amount`) |
+| `surcharge` | Added to net |
+| `discount` | Subtracted from net |
+| `tax` | Added to gross only (excluded from net) |
+| `informational` | Carried for display; not summed |
+
+## Resource specs (API schema)
+
+All specs subclass `EMRResource` (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). `BaseInvoiceSpec.__model__ = Invoice` and `__exclude__ = ["account", "charge_items"]` (these are handled by the hooks, not the generic mapper).
+
+| Spec | Role | Fields beyond base | Server behaviour |
+| --- | --- | --- | --- |
+| `BaseInvoiceSpec` | shared | `id`, `title`, `status` (required), `cancelled_reason`, `payment_terms`, `note`, `issue_date`, `number` | Base for all specs |
+| `InvoiceWriteSpec` | write · create + update | `account: UUID4` (required), `charge_items: list[UUID4]` (default `[]`) | `perform_extra_deserialization`: resolves `account` from `external_id`, sets `patient = account.patient`, stages `charge_items` (rewritten in `perform_create`) |
+| `InvoiceReadSpec` | read · list | `total_net`, `total_gross`, `locked`, `created_date`, `modified_date`, `account: dict`, `is_refund` | `perform_extra_serialization`: `id = external_id`; `account` → `AccountMinimalReadSpec`; if `locked`, zero out `total_net`/`total_gross` |
+| `InvoiceRetrieveSpec` | read · detail | (all read fields) + `charge_items: list[dict]`, `total_price_components: list[dict]`, `created_by`, `updated_by`, `payments: list[dict]`, `total_payments`, `credit_notes: list[dict]`, `total_credit_notes`, `lock_history: list[dict]` | See retrieve serialization below |
+
+`InvoiceWriteSpec` is used for both create and update (no separate Update spec). `InvoiceRetrieveSpec` extends `InvoiceReadSpec`.
+
+### `InvoiceRetrieveSpec.perform_extra_serialization`
+
+- `id = external_id`; `account` serialized via `AccountReadSpec` (full account on detail).
+- `charge_items`: if `status == draft`, live `ChargeItemReadSpec.serialize(...)` over `ChargeItem.objects.filter(id__in=charge_items)` (`select_related` `paid_invoice`, `charge_item_definition`); otherwise the stored `charge_items_copy` snapshot.
+- `created_by` / `updated_by`: hydrated via `serialize_audit_users`.
+- Reconciliations against this invoice with `outcome = complete` and `status = active` are split into:
+ - `payments` (+ `total_payments`) where `is_credit_note` is false,
+ - `credit_notes` (+ `total_credit_notes`) where `is_credit_note` is true,
+ - each serialized via `PaymentReconciliationRetrieveSpec`.
+- `lock_history`: each entry's `user` id replaced with a cached `UserSpec`.
+
+### `MonetaryComponent` shape
+
+Stored inside `total_price_components` and `charge_items_copy`. Source: `care/emr/resources/common/monetary_component.py`.
+
+```text
+MonetaryComponent {
+ monetary_component_type: MonetaryComponentType # required (base|surcharge|discount|tax|informational)
+ code: Coding | None # { system?, version?, code, display? }
+ factor: Decimal(20,6) | None
+ amount: Decimal(20,6) | None
+ tax_included_amount: Decimal(20,6) | None # only valid on base
+ global_component: bool = False
+ conditions: list[EvaluatorConditionSpec] = []
+}
+```
+
+Validation (per component): `base` must carry an `amount` and no `conditions`; `tax_included_amount` is allowed only on `base`; `amount` and `factor` are mutually exclusive; at least one of `amount`/`factor` is required (unless `global_component` with a `code`). Collection-level rules (`MonetaryComponents`) forbid duplicate codes, require a single `base`, and reconcile `tax_included_amount` against the sum of tax components.
+
+## Related models
+
+- [`ChargeItem`](../billing/charge-item.mdx) — line items grouped by the invoice; snapshotted into `charge_items_copy` via `ChargeItemReadSpec`.
+- [`Account`](../billing/account.mdx) — the billing account; resolved on write, rebalanced after return-invoice generation/cancellation.
+- [`PaymentReconciliation`](../billing/payment-reconciliation.mdx) — payments and credit notes reported on the retrieve view (filtered to `outcome=complete`, `status=active`).
+- [`ChargeItemDefinition`](../billing/charge-item-definition.mdx) — applied when building return-invoice charge items.
+
+## Methods & save behaviour
+
+- **`sync_invoice_items(invoice)`** (`sync_items.py`) — recomputes `total_net`, `total_gross`, `total_price_components`, and `charge_items_copy` from the linked charge items, then rounds totals using `INVOICE_FINAL_AMOUNT_PRECISION` / `INVOICE_FINAL_AMOUNT_ROUNDING_METHOD`. **Validation:** if `is_refund` is false and either total is negative, raises `ValidationError("A Refund Ivoice is required for negative values")`.
+- **`calculate_charge_items_summary(charge_items)`** — net = Σ base + Σ surcharge − Σ discount; gross = net + Σ tax; components aggregated per `type + code` (key = `system + code`, else `No-Code`).
+- **`evaluate_invoice_identifier_default_expression(facility)`** (`default_expression_evaluator.py`) — generates `number` from the facility's `invoice_number_expression` with context `{ invoice_count, current_year_yyyy, current_year_yy }`; returns `""` if unset. `evaluate_invoice_dummy_expression` previews an expression with sample context.
+- **`generate_return_invoice(delivery_order)`** (`return_items_invoice.py`) — creates a `draft` refund invoice (`is_refund=True`), reverses each completed `SupplyDelivery`'s charge item via `apply_charge_item_definition(reverse=True)`, marks items `billed`, runs `sync_invoice_items`, sets status `issued`, then triggers `rebalance_account_task`. Raises if any delivery is still `in_progress`.
+- **`cancel_return_invoice(delivery_order)`** — sets the invoice `cancelled`, marks its charge items `entered_in_error` (clears `paid_invoice`/`paid_on`), voids the supply deliveries, rebalances the account, and resyncs inventory.
+
+## API integration notes
+
+- `status` is the control surface for the billing lifecycle. Drive transitions through the API rather than editing rows directly; values are `draft`, `issued`, `balanced`, `cancelled`, `entered_in_error`.
+- On write (`InvoiceWriteSpec`): send `account` and `charge_items` as **external UUIDs**. `patient` is derived from the account server-side and must not be sent. `account` and `charge_items` are excluded from the generic field mapper and handled in `perform_extra_deserialization`.
+- `charge_items_copy`, `total_price_components`, `total_net`, and `total_gross` are platform-maintained snapshots derived from the linked charge items — do not set them directly.
+- List view (`InvoiceReadSpec`) returns a minimal `account`; detail view (`InvoiceRetrieveSpec`) returns the full account plus charge items, aggregated price components, payments, credit notes, and lock history.
+- When `locked` is `True`, the read specs serialize `total_net` and `total_gross` as `0`.
+
+## Related
+
+- Reference: [Account](../billing/account.mdx)
+- Reference: [Charge Item](../billing/charge-item.mdx)
+- Reference: [Charge Item Definition](../billing/charge-item-definition.mdx)
+- Reference: [Payment Reconciliation](../billing/payment-reconciliation.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
+- Source: [invoice.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/invoice.py)
+- Source: [invoice resource specs on GitHub](https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/invoice)
diff --git a/versioned_docs/version-3.0/references/billing/payment-reconciliation.mdx b/versioned_docs/version-3.0/references/billing/payment-reconciliation.mdx
new file mode 100644
index 0000000..12e3f30
--- /dev/null
+++ b/versioned_docs/version-3.0/references/billing/payment-reconciliation.mdx
@@ -0,0 +1,200 @@
+---
+sidebar_position: 5
+---
+
+# Payment Reconciliation
+
+Technical reference for the `PaymentReconciliation` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/payment_reconciliation.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/payment_reconciliation.py)
+- Resource spec: [`care/emr/resources/payment_reconciliation/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/payment_reconciliation/spec.py)
+
+`PaymentReconciliation` records a payment made against an `Account` — and optionally a specific `Invoice`. It captures how, when, by whom, and how much was paid, mirroring the FHIR [`PaymentReconciliation`](https://build.fhir.org/paymentreconciliation.html) resource.
+
+The Django **model** is the storage layer: several constrained fields are stored as plain `CharField`s, and `extensions` is an opaque `JSONField`. The real shape — enum values, amount computation, validation, and the read/write API schemas — lives in the Pydantic **resource specs** (`care/emr/resources/payment_reconciliation/spec.py`), built on `EMRResource`. This page documents both layers.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `PaymentReconciliation` | A single payment (or credit note / adjustment) recorded against an account or invoice |
+
+The model extends `EMRBaseModel` (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics — see [Base model](../foundation/base-model.mdx)).
+
+## `PaymentReconciliation` fields
+
+### Relationships
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → Facility` (PROTECT) | Facility where the payment is recorded |
+| `target_invoice` | `FK → Invoice` (PROTECT) | Nullable (`default=None`); the invoice this payment is allocated to. A single allocation is supported. On write, supplied as the invoice `external_id` (UUID) |
+| `account` | `FK → Account` (PROTECT) | The account being paid against. Required even when `target_invoice` is set. On write, supplied as the account `external_id` (UUID) |
+| `location` | `FK → FacilityLocation` (PROTECT) | Nullable; physical location (e.g. counter) where payment was taken. On write, supplied as the location `external_id` (UUID) |
+
+### Classification
+
+These string columns are bound to fixed enums defined in the resource spec. The model stores them as free-form `CharField(100)`; the enum constraint is enforced at the schema/API layer, not by the database. See [enum values](#enum-values) for the full lists.
+
+| Field | Type | Spec enum | Notes |
+| --- | --- | --- | --- |
+| `reconciliation_type` | `CharField(100)` | `PaymentReconciliationTypeOptions` | Required. `payment` \| `adjustment` \| `advance` |
+| `status` | `CharField(100)` | `PaymentReconciliationStatusOptions` | Required. `active` \| `cancelled` \| `draft` \| `entered_in_error` |
+| `kind` | `CharField(100)` | `PaymentReconciliationKindOptions` | Required. `deposit` \| `periodic_payment` \| `online` \| `kiosk` |
+| `issuer_type` | `CharField(100)` | `PaymentReconciliationIssuerTypeOptions` | Required. `patient` \| `insurer` |
+| `outcome` | `CharField(100)` | `PaymentReconciliationOutcomeOptions` | Required. `queued` \| `complete` \| `error` \| `partial` |
+| `method` | `CharField(100)` | `PaymentReconciliationPaymentMethodOptions` | Required. HL7 [v2-0570](https://terminology.hl7.org/6.2.0/ValueSet-v2-0570.html) payment-method codes — `cash` \| `ccca` \| `cchk` \| `cdac` \| `chck` \| `ddpo` \| `debc` |
+
+### Payment details
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `payment_datetime` | `DateTimeField` → `datetime \| None` | Optional (default `None`); when the payment was issued |
+| `reference_number` | `CharField(1024)` → `str \| None` | Optional; cheque number or payment reference |
+| `authorization` | `CharField(1024)` → `str \| None` | Optional; authorization number |
+| `disposition` | `TextField` → `str \| None` | Optional; disposition message describing the outcome |
+| `note` | `TextField` → `str \| None` | Optional; free-text note |
+
+### Amounts
+
+All amounts are `DecimalField(max_digits=20, decimal_places=6)` (spec: `Decimal`, `max_digits=20`, `decimal_places=6`). Six decimal places give sub-currency precision.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `tendered_amount` | `Decimal(20, 6)` | **Required on write.** Amount offered by the issuer |
+| `returned_amount` | `Decimal(20, 6)` | **Required on write.** Amount returned by the receiver (e.g. change). Validated `returned_amount < tendered_amount` |
+| `amount` | `Decimal(20, 6)` | **Server-computed.** `amount = tendered_amount - returned_amount`; any client-supplied value is overwritten by the write validator |
+
+### Flags & extensions
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `is_credit_note` | `BooleanField` → `bool` | Defaults to `False`; marks the record as a credit note rather than an inbound payment |
+| `extensions` | `JSONField` → `dict` | Defaults to `{}`. Open extension bag. On write, each key is validated against the registered extension handler for resource type `payment_reconciliation` (unknown keys are currently dropped); on read (retrieve), rendered through the registered handlers |
+
+## Enum values
+
+All enums are `str, Enum` classes in [`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/payment_reconciliation/spec.py).
+
+### `PaymentReconciliationTypeOptions` (`reconciliation_type`)
+
+| Value |
+| --- |
+| `payment` |
+| `adjustment` |
+| `advance` |
+
+### `PaymentReconciliationStatusOptions` (`status`)
+
+| Value |
+| --- |
+| `active` |
+| `cancelled` |
+| `draft` |
+| `entered_in_error` |
+
+### `PaymentReconciliationKindOptions` (`kind`)
+
+| Value |
+| --- |
+| `deposit` |
+| `periodic_payment` |
+| `online` |
+| `kiosk` |
+
+### `PaymentReconciliationIssuerTypeOptions` (`issuer_type`)
+
+| Value |
+| --- |
+| `patient` |
+| `insurer` |
+
+### `PaymentReconciliationOutcomeOptions` (`outcome`)
+
+| Value |
+| --- |
+| `queued` |
+| `complete` |
+| `error` |
+| `partial` |
+
+### `PaymentReconciliationPaymentMethodOptions` (`method`)
+
+HL7 v2-0570 payment-method codes.
+
+| Value | HL7 meaning |
+| --- | --- |
+| `cash` | Cash |
+| `ccca` | Credit card |
+| `cchk` | Cashier's check |
+| `cdac` | Credit/debit account |
+| `chck` | Check |
+| `ddpo` | Direct deposit |
+| `debc` | Debit card |
+
+## Resource specs (API schema)
+
+Defined in [`care/emr/resources/payment_reconciliation/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/payment_reconciliation/spec.py). All extend `EMRResource` (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). See [Base model](../foundation/base-model.mdx).
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BasePaymentReconciliationSpec` | shared base | Holds `id`, the six enum fields, `disposition`, `payment_datetime`, `method`, `reference_number`, `authorization`, `note`. `__exclude__ = ["target_invoice", "account"]` (FKs handled in hooks). `___extension_resource_type__ = payment_reconciliation` |
+| `PaymentReconciliationWriteSpec` | write · create & update | Adds `target_invoice` (UUID, optional), `account` (UUID, required), `tendered_amount`, `returned_amount`, `amount` (optional), `is_credit_note` (default `False`), `location` (UUID, optional). Mixes in `ExtensionValidator` |
+| `PaymentReconciliationMinimalReadSpec` | read · list | Adds `amount`, `tendered_amount`, `returned_amount`, `is_credit_note`, `created_date`, `modified_date` |
+| `PaymentReconciliationReadSpec` | read · detail (nested) | Extends minimal read; adds `account: dict` (serialized via `AccountReadSpec`) and `target_invoice: dict \| None` (serialized via `InvoiceReadSpec`) |
+| `PaymentReconciliationRetrieveSpec` | read · full retrieve | Extends read; adds `location: dict \| None` (via `FacilityLocationListSpec`), `extensions: dict`, `created_by` / `updated_by`. Also injects `account.patient` via `PatientRetrieveSpec` |
+
+### Write validation & side effects
+
+`PaymentReconciliationWriteSpec`:
+
+- **Amount validator** (`model_validator(mode="after")`, `check_amount_or_factor`): raises `"Returned amount cannot be greater than tendered amount"` when `returned_amount >= tendered_amount`, then sets `amount = tendered_amount - returned_amount`. The `amount` field on the write spec is therefore advisory only — always recomputed.
+- **`perform_extra_deserialization(is_update, obj)`** resolves the FK UUIDs to model instances:
+ - `target_invoice` (if provided) → `Invoice.objects.get(external_id=...)`
+ - `account` (required) → `Account.objects.get(external_id=...)`
+ - `location` (if provided) → `FacilityLocation.objects.get(external_id=...)`
+- **`extensions`** are validated by `ExtensionValidator.validate_extensions` against the `payment_reconciliation` extension registry before save.
+
+### Read serialization side effects
+
+- `PaymentReconciliationMinimalReadSpec.perform_extra_serialization` maps `id` ← `obj.external_id`.
+- `PaymentReconciliationReadSpec` serializes the related `account` (`AccountReadSpec`) and, when present, `target_invoice` (`InvoiceReadSpec`) into nested objects.
+- `PaymentReconciliationRetrieveSpec` additionally nests the account's `patient` (`PatientRetrieveSpec`, scoped to the account's facility), the `location` (`FacilityLocationListSpec`), the raw `extensions`, and audit users (`created_by` / `updated_by` via `serialize_audit_users`).
+
+## Related models
+
+`PaymentReconciliation` is a single, self-contained model. Its foreign keys reach into the broader billing and facility domains:
+
+```text
+facility → FK Facility (PROTECT)
+target_invoice → FK Invoice (PROTECT, nullable)
+account → FK Account (PROTECT)
+location → FK FacilityLocation (PROTECT, nullable)
+```
+
+All foreign keys use `on_delete=PROTECT`, so a payment record blocks deletion of the facility, invoice, account, or location it references.
+
+## Methods & save behaviour
+
+- **No custom model `save()`** — `PaymentReconciliation` inherits `EMRBaseModel` behaviour (audit fields, `external_id`, soft delete).
+- **Amount is derived, not stored as sent**: on every create/update the write spec computes `amount = tendered_amount - returned_amount` and rejects `returned_amount >= tendered_amount`. Clients should send `tendered_amount` and `returned_amount`; `amount` is informational.
+- **FK resolution** happens in `perform_extra_deserialization`: clients pass `external_id` UUIDs for `account`, `target_invoice`, and `location`, which are resolved to instances at de-serialization time.
+- **Extensions lifecycle**: validated on write against the `payment_reconciliation` extension registry; rendered on retrieve through registered handlers.
+
+## API integration notes
+
+- Exposed through Care's REST API. Write requests use `PaymentReconciliationWriteSpec` for both create and update; list responses use `PaymentReconciliationMinimalReadSpec`, detail/retrieve responses use `PaymentReconciliationReadSpec` / `PaymentReconciliationRetrieveSpec`.
+- `account` is always required; `target_invoice` is optional and represents a single allocation of the payment to one invoice. Both, plus `location`, are passed as `external_id` UUIDs.
+- Classification fields (`reconciliation_type`, `status`, `kind`, `issuer_type`, `outcome`, `method`) are constrained to the fixed enums above by the Pydantic schema — invalid values are rejected at the API even though the column is a plain `CharField`.
+- Send `tendered_amount` and `returned_amount` (both required, `Decimal(20, 6)`); do **not** rely on a client-supplied `amount` — the server overwrites it with `tendered_amount - returned_amount`.
+- `extensions` is the supported place for custom key-value data without schema migrations, validated against registered extension handlers.
+
+## Related
+
+- Reference: [Account](../billing/account.mdx)
+- Reference: [Invoice](../billing/invoice.mdx)
+- Reference: [Charge item](../billing/charge-item.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source (model): [payment_reconciliation.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/payment_reconciliation.py)
+- Source (spec): [payment_reconciliation/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/payment_reconciliation/spec.py)
diff --git a/versioned_docs/version-3.0/references/clinical/_category_.json b/versioned_docs/version-3.0/references/clinical/_category_.json
index 61229f3..30a1cc9 100644
--- a/versioned_docs/version-3.0/references/clinical/_category_.json
+++ b/versioned_docs/version-3.0/references/clinical/_category_.json
@@ -1,5 +1,10 @@
{
"label": "Clinical",
- "position": 1,
- "key": "clinical-references"
+ "position": 2,
+ "key": "clinical-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Clinical",
+ "description": "Patient-centred clinical records — encounters, conditions, allergies, observations, diagnostics, specimens, service requests, and consent."
+ }
}
diff --git a/versioned_docs/version-3.0/references/clinical/activity-definition.mdx b/versioned_docs/version-3.0/references/clinical/activity-definition.mdx
new file mode 100644
index 0000000..a46b8f2
--- /dev/null
+++ b/versioned_docs/version-3.0/references/clinical/activity-definition.mdx
@@ -0,0 +1,163 @@
+---
+sidebar_position: 13
+---
+
+# Activity Definition
+
+Technical reference for the `ActivityDefinition` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/activity_definition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/activity_definition.py)
+- Resource specs: [`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/spec.py) · [`valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/valueset.py) · [`service_request.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/service_request.py)
+
+An Activity Definition is a FHIR-aligned, facility-scoped **template** for clinical activities. It captures the defaults needed to instantiate downstream resources (currently a [Service Request](./service-request.mdx)) when the definition is "applied" against an encounter — combining stored defaults with the encounter context.
+
+The Django model is the **storage layer**: several fields are opaque `JSONField`s or integer `ArrayField`s whose real structure lives in the Pydantic **resource specs** (the API/implementation layer). The specs define the enums, the JSON shapes, the validation rules, and the read/write schemas. Read both layers together.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ActivityDefinition` | Versioned, facility-scoped template describing a clinical activity and the defaults used to create the resources it produces |
+
+`ActivityDefinition` extends `SlugBaseModel` (a Care EMR base that adds a facility-scoped `slug` on top of `EMRBaseModel` — which provides `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and the `history`/`meta` JSON fields). See [Base model](../foundation/base-model.mdx).
+
+## `ActivityDefinition` fields
+
+Each field below shows the **storage type** (Django model) and, where the spec constrains it, the **API shape/values** enforced by the Pydantic specs.
+
+### Identity & versioning
+
+| Field | Storage type | API shape / notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` (PROTECT) | Owning facility; definitions are facility-scoped. Spec-`__exclude__`d — set server-side, never accepted in the payload. |
+| `slug` | `CharField(255)` | Facility-scoped identifier. On write it is supplied as `slug_value` (`SlugType`) and assigned to `obj.slug` in `perform_extra_deserialization`. On read it is returned as `slug` plus a parsed `slug_config` dict. |
+| `version` | `IntegerField` | Default `1`. Read-only in specs (`version: int \| None`), serialized on list/retrieve. Models an append-only version chain. |
+| `latest` | `BooleanField` | Default `True`; marks the current version among a slug's version chain. Not exposed in the specs (server-maintained). |
+| `derived_from_uri` | `TextField` | Nullable. URI this definition was derived from. Optional in specs (`str \| None = None`). |
+
+### Descriptive metadata
+
+| Field | Storage type | API shape / notes |
+| --- | --- | --- |
+| `title` | `CharField(1024)` | Required (`str`). Human-friendly name. |
+| `status` | `CharField(255)` | Required enum — `ActivityDefinitionStatusOptions` (`draft` / `active` / `retired` / `unknown`). |
+| `classification` | `CharField(100)` | Required enum — `ActivityDefinitionCategoryOptions` (`laboratory` / `imaging` / `counselling` / `surgical_procedure` / `education`). |
+| `kind` | `CharField(100)` | Required enum — `ActivityDefinitionKindOptions` (only `service_request`). Determines the resource produced when applied. |
+| `description` | `TextField` | Optional in specs; default `""`. |
+| `usage` | `TextField` | Optional in specs; default `""`. |
+
+### Coded clinical attributes (JSON)
+
+Stored as `JSONField`. The spec binds each to a value set via `ValueSetBoundCoding[]`, which validates the submitted code against that value set. Each coding is shaped `{ system?: str, version?: str, code: str (required), display?: str }` (`extra="forbid"`).
+
+| Field | Storage type | API shape / notes |
+| --- | --- | --- |
+| `code` | `JSONField` (nullable) | **Required** `Coding` bound to value set `activity-definition-procedure-code` (SNOMED CT, `concept is-a 71388002` "Procedure"). |
+| `body_site` | `JSONField` (nullable) | Optional `Coding \| None = None` bound to value set `system-body-site-observation` (SNOMED CT body sites). |
+| `diagnostic_report_codes` | `JSONField` (nullable) | `list[Coding]`, default `[]`. Each `Coding` bound to value set `system-observation` (LOINC). |
+
+### Requirement & linkage arrays
+
+Stored as integer-ID arrays (`ArrayField(IntegerField, default=list)`) holding primary keys — not Django FK relations. The specs accept/return them by **external slug or UUID**, not raw integer PKs:
+
+| Field | Storage type | Write shape | Read (retrieve) shape |
+| --- | --- | --- | --- |
+| `specimen_requirements` | `ArrayField[int]` | `list[ExtendedSlugType]` | `list[dict]` — serialized [Specimen Definitions](./specimen-definition.mdx) (`SpecimenDefinitionReadSpec`) |
+| `observation_result_requirements` | `ArrayField[int]` | `list[ExtendedSlugType]` | `list[dict]` — serialized [Observation Definitions](./observation-definition.mdx) (`ObservationDefinitionReadSpec`) |
+| `locations` | `ArrayField[int]` | `list[UUID4]` | `list[dict]` — serialized [Locations](../facility/location.mdx) (`FacilityLocationListSpec`) |
+| `charge_item_definitions` | `ArrayField[int]` | `list[ExtendedSlugType]` | `list[dict]` — serialized [Charge Item Definitions](../billing/charge-item-definition.mdx) (`ChargeItemDefinitionReadSpec`) |
+| `tags` | `ArrayField[int]` | not written via spec | `list[dict]` — rendered via `SingleFacilityTagManager().render_tags(obj)` |
+
+> `ExtendedSlugType`: 7–88 chars, URL-safe, must start with `f-` or `i-`. `SlugType` (used for `slug_value`): 5–50 chars, URL-safe. Referential integrity for the stored integer arrays is the caller's responsibility — retrieve serialization skips IDs that no longer resolve.
+
+### Service relationships
+
+| Field | Storage type | API shape / notes |
+| --- | --- | --- |
+| `healthcare_service` | `FK → emr.HealthcareService` (PROTECT) | Nullable; default `None`. Write: `UUID4 \| None` (resolved by `external_id` in `perform_extra_deserialization`). Retrieve: serialized via `HealthcareServiceReadSpec`. See [Healthcare Service](../facility/healthcare-service.mdx). |
+| `category` | `FK → emr.ResourceCategory` (CASCADE) | Nullable. Write: `ExtendedSlugType \| None` (resolved by `slug`). Read: serialized via `ResourceCategoryReadSpec`. Deleting the [Resource Category](../platform/resource-category.mdx) cascades. |
+
+## Enum values
+
+### `ActivityDefinitionStatusOptions` (`status`)
+
+| Value |
+| --- |
+| `draft` |
+| `active` |
+| `retired` |
+| `unknown` |
+
+### `ActivityDefinitionKindOptions` (`kind`)
+
+| Value | Notes |
+| --- | --- |
+| `service_request` | Only supported kind; applying the definition produces a [Service Request](./service-request.mdx) |
+
+### `ActivityDefinitionCategoryOptions` (`classification`)
+
+| Value |
+| --- |
+| `laboratory` |
+| `imaging` |
+| `counselling` |
+| `surgical_procedure` |
+| `education` |
+
+## Bound value sets
+
+| Field | Value set slug | Source system / filter |
+| --- | --- | --- |
+| `code` | `activity-definition-procedure-code` | SNOMED CT, `concept is-a 71388002` (Procedure) |
+| `body_site` | `system-body-site-observation` | SNOMED CT body-site concepts |
+| `diagnostic_report_codes[]` | `system-observation` | LOINC (`http://loinc.org`) |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (see [Base model](../foundation/base-model.mdx)), which provides `serialize` (DB → Pydantic) and `de_serialize` (Pydantic → DB) plus the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__model__ = ActivityDefinition`, `__exclude__ = ["facility"]`.
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BaseActivityDefinitionSpec` | shared base | `id`, `title`, `derived_from_uri`, `status`, `description`, `usage`, `classification`, `kind`, `code`, `body_site`, `diagnostic_report_codes`. |
+| `ActivityDefinitionWriteSpec` | write · create + update | Adds `locations` (`list[UUID4]`), `specimen_requirements` / `observation_result_requirements` / `charge_item_definitions` (`list[ExtendedSlugType]`), `healthcare_service` (`UUID4 \| None`), `category` (`ExtendedSlugType \| None`), `slug_value` (`SlugType`). |
+| `ActivityDefinitionReadSpec` | read · list | Base fields plus `version`, `tags`, `category` (serialized dict), `slug`, `slug_config`. |
+| `ActivityDefinitionRetrieveSpec` | read · detail | Extends `ReadSpec`; expands `specimen_requirements`, `observation_result_requirements`, `locations`, `healthcare_service`, `charge_item_definitions` from ID arrays into fully serialized nested objects. |
+
+### Server-side behaviour
+
+- **`ActivityDefinitionWriteSpec.perform_extra_deserialization`** (write path):
+ - Resolves `healthcare_service` by `external_id` (or clears it to `None` when absent).
+ - Resolves `category` by `slug` (only when provided).
+ - Assigns `obj.slug = self.slug_value`.
+- **`ActivityDefinitionReadSpec.perform_extra_serialization`** (read path): sets `id = external_id`, renders `tags`, serializes `category` (if present) via `ResourceCategoryReadSpec`, and parses `slug_config = obj.parse_slug(obj.slug)`.
+- **`ActivityDefinitionRetrieveSpec.perform_extra_serialization`**: calls the parent, then resolves each ID array (`specimen_requirements`, `observation_result_requirements`, `locations`, `charge_item_definitions`) against its model — silently dropping IDs that no longer exist — and serializes `healthcare_service`.
+- The base `de_serialize` writes only model-mapped fields (`exclude_defaults=True`), so unset optional fields fall back to model defaults.
+
+## Methods & save behaviour
+
+- **Apply → Service Request** ([`service_request.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/service_request.py)): `convert_ad_to_sr(activity_definition, encounter)` builds a draft [`ServiceRequest`](./service-request.mdx) carrying over `title`, `category`, `code`, `body_site`, `locations`, `healthcare_service`, and binding `patient`/`encounter` from the encounter. The created request defaults to `status=draft`, `intent=proposal`, `priority=routine`, `do_not_perform=False`.
+- **Charge items on apply**: `apply_ad_charge_definitions(activity_definition, encounter, service_request)` loads every linked [Charge Item Definition](../billing/charge-item-definition.mdx), instantiates a Charge Item per definition (via `apply_charge_item_definition`), tags it to the service request (`service_resource = service_request`, `service_resource_id = external_id`), copies the creator, and sets `performer_actor` to the request's `requester` when set.
+- `version` / `latest` model an append-only version chain — clients select the active definition via `latest`, while older versions remain referenceable.
+
+## API integration notes
+
+- Payload field names follow the Pydantic specs and differ from the Django model: write `slug` as `slug_value`; reference `specimen_requirements` / `observation_result_requirements` / `charge_item_definitions` / `category` by **external slug** (`ExtendedSlugType`, `f-`/`i-` prefixed), `locations` / `healthcare_service` by **UUID**.
+- `code` is required and must validate against the `activity-definition-procedure-code` value set; `body_site` and each entry of `diagnostic_report_codes` validate against their bound value sets.
+- The "apply" operation instantiates the target `kind` (currently only `service_request`) by merging stored defaults with the encounter; linked `charge_item_definitions` are instantiated at the same time.
+- Stored requirement/linkage fields are **plain integer ID arrays** — retrieve serialization resolves them to nested objects and skips IDs that no longer resolve.
+
+## Related
+
+- Reference: [Service Request](./service-request.mdx)
+- Reference: [Specimen Definition](./specimen-definition.mdx)
+- Reference: [Observation Definition](./observation-definition.mdx)
+- Reference: [Charge Item Definition](../billing/charge-item-definition.mdx)
+- Reference: [Healthcare Service](../facility/healthcare-service.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Resource Category](../platform/resource-category.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [`activity_definition.py` (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/activity_definition.py)
+- Source: [`spec.py` (resource specs)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/spec.py)
+- Source: [`valueset.py` (procedure-code value set)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/valueset.py)
+- Source: [`service_request.py` (apply logic)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/service_request.py)
diff --git a/versioned_docs/version-3.0/references/clinical/allergy-intolerance.mdx b/versioned_docs/version-3.0/references/clinical/allergy-intolerance.mdx
new file mode 100644
index 0000000..5d29229
--- /dev/null
+++ b/versioned_docs/version-3.0/references/clinical/allergy-intolerance.mdx
@@ -0,0 +1,175 @@
+---
+sidebar_position: 4
+---
+
+# Allergy Intolerance
+
+Technical reference for the `AllergyIntolerance` module in Care EMR. Care aligns this resource to FHIR `AllergyIntolerance`.
+
+The Django model is the **storage layer** — several fields are opaque `JSONField`s whose real structure is defined only in the Pydantic **resource specs** (the API/implementation layer). The specs define the enums, the JSON-field shapes, validation, the bound value set, and the read/write API schemas. This page documents both.
+
+**Source:**
+- Model: [`care/emr/models/allergy_intolerance.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/allergy_intolerance.py)
+- Specs: [`care/emr/resources/allergy_intolerance/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/allergy_intolerance/spec.py) · [`valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/allergy_intolerance/valueset.py)
+- Base resource: [`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `AllergyIntolerance` | Records a patient's allergy or intolerance to a substance, scoped to an encounter |
+
+`AllergyIntolerance` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `AllergyIntolerance` fields
+
+### Clinical classification
+
+| Field | Type (storage) | Spec type / values | Notes |
+| --- | --- | --- | --- |
+| `clinical_status` | `CharField(100)`, null | `ClinicalStatusChoices` | Active / inactive / resolved (FHIR `clinical-status`). Required on write. |
+| `verification_status` | `CharField(100)`, null | `VerificationStatusChoices` | Confirmation level. Required on write. |
+| `category` | `CharField(100)`, null | `CategoryChoices` | Allergy category. Required on **create** only (not exposed on update). |
+| `criticality` | `CharField(100)`, null | `CriticalityChoices` | Potential clinical harm. Required on write. |
+| `allergy_intolerance_type` | `CharField(20)`, default `"allergy"` | `AllergyIntoleranceTypeOptions` | Allergy vs intolerance. Defaults to `allergy`. |
+
+### Substance & coding
+
+| Field | Type (storage) | Spec type / shape | Notes |
+| --- | --- | --- | --- |
+| `code` | `JSONField` (`default=dict`, not null/blank) | `Coding` bound to value set `system-allergy-code` | The allergen/substance. On write it is a `ValueSetBoundCoding` validated against the bound value set; on read it is a plain `Coding`. Required. |
+
+`Coding` shape: `{ system?: str, version?: str, code: str (required), display?: str }` (`extra="forbid"`).
+
+### Timing
+
+| Field | Type (storage) | Spec type / shape | Notes |
+| --- | --- | --- | --- |
+| `onset` | `JSONField` (`default=dict`) | `AllergyIntoleranceOnSetSpec` | Structured onset. Defaults to `{}`. Only exposed on **create** (write). See shape below. |
+| `recorded_date` | `DateTimeField`, null | `datetime \| None` | When first asserted. Optional, write-on-**create** only. |
+| `last_occurrence` | `DateTimeField`, null | `datetime \| None` | Most recent known reaction. Optional on create and update. |
+
+`AllergyIntoleranceOnSetSpec` shape:
+
+| Sub-field | Type | Required | Default |
+| --- | --- | --- | --- |
+| `onset_datetime` | `datetime` | No | `None` |
+| `onset_age` | `int` | No | `None` |
+| `onset_string` | `str` | No | `None` |
+| `note` | `str` | **Yes** | — |
+
+### Context & notes
+
+| Field | Type (storage) | Spec type | Notes |
+| --- | --- | --- | --- |
+| `patient` | `FK → Patient` (`CASCADE`) | excluded from specs | Subject of the allergy. Not client-settable — derived server-side from the encounter on create. |
+| `encounter` | `FK → Encounter` (`CASCADE`) | `UUID4` | Encounter the allergy was recorded in. Sent as `external_id`; required on write; validated to exist. |
+| `note` | `TextField`, null | `str \| None` | Free-text clinical note. Optional. |
+| `copied_from` | `BigIntegerField`, null (`default=None`) | not exposed | Source record ID when this entry is a maintained copy. Platform-maintained — never set from clients. |
+
+## Enum values
+
+### `ClinicalStatusChoices`
+
+| Value |
+| --- |
+| `active` |
+| `inactive` |
+| `resolved` |
+
+### `VerificationStatusChoices`
+
+| Value |
+| --- |
+| `unconfirmed` |
+| `presumed` |
+| `confirmed` |
+| `refuted` |
+| `entered_in_error` |
+
+### `CategoryChoices`
+
+| Value |
+| --- |
+| `food` |
+| `medication` |
+| `environment` |
+| `biologic` |
+
+### `CriticalityChoices`
+
+| Value |
+| --- |
+| `low` |
+| `high` |
+| `unable_to_assess` |
+
+### `AllergyIntoleranceTypeOptions`
+
+| Value |
+| --- |
+| `allergy` (default) |
+| `intolerance` |
+
+## Bound value set
+
+| Value set | Slug | System | Status |
+| --- | --- | --- | --- |
+| `CARE_ALLERGY_CODE_VALUESET` ("Allergy") | `system-allergy-code` | SNOMED CT (`http://snomed.info/sct`) | `active` |
+
+The `code` field on write binds to this value set via `ValueSetBoundCoding[...slug]`, which validates the submitted `Coding` against the value set (`validate_valueset`). The compose includes SNOMED CT concepts that are `is-a` descendants of: `105590001`, `418038007`, `267425008`, `29736007`, `340519003`, `190753003`, `413427002`, `716186003`. The value set is also registered as a system (`register_as_system()`).
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB object → pydantic, via `model_construct`) and `de_serialize` (pydantic → DB object), plus the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `BaseAllergyIntoleranceSpec` sets `__model__ = AllergyIntolerance` and `__exclude__ = ["patient", "encounter"]` (these are not auto-mapped; they are handled by the hooks). `meta` is carried on every spec via the base.
+
+| Spec class | Role | Fields exposed |
+| --- | --- | --- |
+| `BaseAllergyIntoleranceSpec` | shared base | `id: UUID4` |
+| `AllergyIntoleranceWriteSpec` | write · create | `clinical_status`, `verification_status`, `category`, `criticality`, `last_occurrence?`, `recorded_date?`, `encounter` (UUID4), `code` (bound Coding), `onset` (default `{}`), `allergy_intolerance_type` (default `allergy`), `note?` |
+| `AllergyIntoleranceUpdateSpec` | write · update | `clinical_status`, `verification_status`, `criticality`, `last_occurrence?`, `note?`, `encounter` (UUID4), `allergy_intolerance_type` (default `allergy`) |
+| `AllergyIntoleranceReadSpec` | read · list & detail | `id`, `clinical_status`, `verification_status`, `category`, `criticality`, `code` (Coding), `encounter` (UUID4), `onset`, `last_occurrence?`, `recorded_date?`, `note?`, `allergy_intolerance_type`, `created_by?`, `updated_by?`, `created_date`, `modified_date` |
+| `AllergyIntoleranceOnSetSpec` | nested (shape of `onset` JSON) | `onset_datetime?`, `onset_age?`, `onset_string?`, `note` (required) |
+
+Notes on the spec set:
+
+- **Create vs update surface differs.** `category`, `code`, `onset`, and `recorded_date` are write-only on **create** (`AllergyIntoleranceWriteSpec`) and are **not** present on `AllergyIntoleranceUpdateSpec` — they cannot be changed after creation through the update path.
+- **Read uses relaxed types.** `AllergyIntoleranceReadSpec` types the coded fields as plain `str` and `code` as `Coding` (no value-set re-validation on read, for performance — it relies on `model_construct`).
+
+### Validation
+
+- `encounter` (both write specs): `validate_encounter_exists` — rejects with `"Encounter not found"` if no `Encounter` has that `external_id`.
+- `code` (create): validated against value set `system-allergy-code` (must be a permitted SNOMED CT allergy concept).
+- `onset.note` is required whenever an `onset` object is supplied.
+- Any `datetime` fields are stored as-is; tz-aware handling for period-style values is enforced by `PeriodSpec` in the base (start ≤ end, both tz-aware) — `AllergyIntolerance` itself uses scalar datetimes, not a `PeriodSpec`.
+
+### Server-side side effects
+
+- **Create** (`AllergyIntoleranceWriteSpec.perform_extra_deserialization`): resolves `encounter` by `external_id` and sets `obj.encounter`, then sets `obj.patient = obj.encounter.patient`. The client never sends `patient` directly.
+- **Update** (`AllergyIntoleranceUpdateSpec.perform_extra_deserialization`): if `encounter` is present, re-resolves and sets `obj.encounter`.
+- **Read** (`AllergyIntoleranceReadSpec.perform_extra_serialization`): sets `mapping["id"] = obj.external_id`, sets `encounter` to `obj.encounter.external_id`, and serializes audit users (`created_by` / `updated_by`) via `serialize_audit_users`.
+
+## Methods & save behaviour
+
+- `serialize` / `de_serialize` are inherited from `EMRResource`. `de_serialize` maps only DB fields not in `__exclude__` and not `id`/`external_id`, then calls `perform_extra_deserialization` for the encounter/patient wiring described above.
+- `patient` is **always** derived from the encounter on create — there is no path to set it from a client payload.
+- `copied_from` is platform-maintained for propagated/copied records and is not part of any spec.
+- `patient` and `encounter` are both `CASCADE`-deleted, so an allergy record never outlives its patient or encounter.
+
+## API integration notes
+
+- Exposed through Care's REST API and aligned to the FHIR `AllergyIntolerance` resource; API field names follow the spec classes above (e.g. `encounter` is sent/returned as a UUID `external_id`, not a numeric FK).
+- Submit `code` as a `Coding` from the bound value set `system-allergy-code` rather than free text, so allergies can be matched against medications and other business logic.
+- The coded status fields (`clinical_status`, `verification_status`, `category`, `criticality`) accept only their enum values listed above; invalid values are rejected on write.
+- Do not send `patient` or `copied_from`; both are server-maintained.
+
+## Related
+
+- Reference: [Patient](./patient.mdx)
+- Reference: [Encounter](./encounter.mdx)
+- Reference: [Condition](./condition.mdx)
+- Reference: [Observation](./observation.mdx)
+- Base: [EMRBaseModel](../foundation/base-model.mdx)
+- Source (model): [allergy_intolerance.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/allergy_intolerance.py)
+- Source (spec): [resources/allergy_intolerance/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/allergy_intolerance/spec.py)
+- Source (value set): [resources/allergy_intolerance/valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/allergy_intolerance/valueset.py)
diff --git a/versioned_docs/version-3.0/references/clinical/condition.mdx b/versioned_docs/version-3.0/references/clinical/condition.mdx
new file mode 100644
index 0000000..1722574
--- /dev/null
+++ b/versioned_docs/version-3.0/references/clinical/condition.mdx
@@ -0,0 +1,194 @@
+---
+sidebar_position: 3
+---
+
+# Condition
+
+Technical reference for the `Condition` module in Care EMR. A condition captures a clinical problem, diagnosis, or symptom recorded for a patient. In the Care product UI conditions are also surfaced as **Symptoms** (the FHIR `Condition` resource).
+
+**Source:**
+- Model: [`care/emr/models/condition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/condition.py)
+- Resource spec: [`care/emr/resources/condition/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/condition/spec.py)
+- Value set: [`care/emr/resources/condition/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/condition/valueset.py)
+
+Two layers define a `Condition`:
+
+- The **Django model** (`care/emr/models/condition.py`) is the storage layer. Coded/timing fields (`code`, `body_site`, `onset`, `abatement`) are opaque `JSONField`s — their real structure lives in the spec layer, not the model.
+- The **Pydantic resource specs** (`care/emr/resources/condition/spec.py`) are the API layer. They define the enums, the structured shape of the JSON fields, field validation, the value-set binding for `code`, and the read/write schemas.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Condition` | A clinical condition, problem, diagnosis, or symptom recorded for a patient |
+
+`Condition` extends [`EMRBaseModel`](../foundation/base-model.mdx), the shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON.
+
+## `Condition` fields
+
+### Status & classification
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `clinical_status` | `CharField(100)`, nullable | Clinical state. Spec binds to [`ClinicalStatusChoices`](#clinicalstatuschoices-values); optional on write. |
+| `verification_status` | `CharField(100)`, nullable | Level of certainty. Spec binds to [`VerificationStatusChoices`](#verificationstatuschoices-values); **required** on write (`ConditionSpec`/`ConditionUpdateSpec`). |
+| `category` | `CharField(100)`, nullable | Classification. Spec binds to [`CategoryChoices`](#categorychoices-values); **required** on create (`ConditionSpec`). |
+| `severity` | `CharField(100)`, nullable | Subjective severity. Spec binds to [`SeverityChoices`](#severitychoices-values); optional on write. |
+
+### Coded concepts
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `code` | `JSONField` (`default=dict`, not null/blank) | The condition itself. On write a single [`Coding`](#coding-shape) bound to the [`CARE_CODITION_CODE_VALUESET`](#code-value-set-binding) value set (SNOMED CT clinical findings). On read serialized as a plain `Coding`. |
+| `body_site` | `JSONField` (`default=dict`, not null/blank) | Anatomical location(s). Present in the model but **not exposed by any current spec** — not written or read through the standard `Condition` API schemas. |
+
+### Timing
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `onset` | `JSONField` (`default=dict`) | Choice-of-type onset. Shape = [`ConditionOnSetSpec`](#conditiononsetspec-onset-shape). Defaults to `{}`. |
+| `abatement` | `JSONField` (`default=dict`) | When the condition resolved / went into remission. Shape = [`ConditionAbatementSpec`](#conditionabatementspec-abatement-shape). Defaults to `{}`. |
+| `recorded_date` | `DateTimeField`, nullable | When the condition was first recorded. Not exposed by the current specs. |
+
+### Context & notes
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `patient` | `FK → Patient`, `on_delete=CASCADE` | Subject of the condition. Server-set from the encounter on create — never accepted directly from clients (`__exclude__`). |
+| `encounter` | `FK → Encounter`, nullable, `on_delete=CASCADE` | Encounter the condition was recorded in. Server-set on create (`__exclude__`); supplied as a UUID in the spec and validated to exist. |
+| `note` | `TextField`, nullable | Free-text clinical note. |
+
+## Enums
+
+All enum classes are defined in `care/emr/resources/condition/spec.py` as `str, Enum`. The stored/serialized value is the string on the right.
+
+### `ClinicalStatusChoices` values
+
+| Value |
+| --- |
+| `active` |
+| `recurrence` |
+| `relapse` |
+| `inactive` |
+| `remission` |
+| `resolved` |
+| `unknown` |
+
+### `VerificationStatusChoices` values
+
+| Value |
+| --- |
+| `unconfirmed` |
+| `provisional` |
+| `differential` |
+| `confirmed` |
+| `refuted` |
+| `entered_in_error` |
+
+### `CategoryChoices` values
+
+| Value |
+| --- |
+| `problem_list_item` |
+| `encounter_diagnosis` |
+| `chronic_condition` |
+
+### `SeverityChoices` values
+
+| Value |
+| --- |
+| `mild` |
+| `moderate` |
+| `severe` |
+
+## Nested JSON shapes
+
+These spec classes (all extend `EMRResource`) define the real structure of the model's JSON fields.
+
+### `ConditionOnSetSpec` (`onset` shape)
+
+| Field | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `onset_datetime` | `datetime \| None` | `None` | Coerced to timezone-aware (`make_aware`) if naive. Validated: **cannot be in the future** (`> care_now()` raises). |
+| `onset_age` | `int \| None` | `None` | Age at onset. |
+| `onset_string` | `str \| None` | `None` | Free-text onset description. |
+| `note` | `str \| None` | `None` | Note about the onset. |
+
+### `ConditionAbatementSpec` (`abatement` shape)
+
+| Field | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `abatement_datetime` | `datetime \| None` | `None` | No future-date validation (unlike `onset_datetime`). |
+| `abatement_age` | `int \| None` | `None` | Age at abatement. |
+| `abatement_string` | `str \| None` | `None` | Free-text abatement description. |
+| `note` | `str \| None` | `None` | Note about the abatement. |
+
+### `Coding` shape
+
+`code` is a single [`Coding`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py) (not a `CodeableConcept`). `extra="forbid"`.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `system` | `str \| None` | Code system URI (e.g. `http://snomed.info/sct`). |
+| `version` | `str \| None` | Code system version. |
+| `code` | `str` | **Required.** The code value. |
+| `display` | `str \| None` | Human-readable label. |
+
+### `code` value-set binding
+
+On write, `code` is typed `ValueSetBoundCoding[CARE_CODITION_CODE_VALUESET.slug]` — a `Coding` validated against the **Condition code** value set (`care/emr/resources/condition/valueset.py`, slug `system-condition-code`). The value set includes SNOMED CT concepts that are `is-a` `404684003` (*Clinical finding*). Codes outside the value set are rejected on `ConditionSpec` / `ConditionUpdateSpec` / `ChronicConditionUpdateSpec`. The read spec uses a plain `Coding` (no value-set validation, for read performance).
+
+## Resource specs (API schema)
+
+All specs extend `BaseConditionSpec` → `EMRResource` and convert via `serialize` (DB → Pydantic) / `de_serialize` (Pydantic → DB). `BaseConditionSpec` sets `__model__ = Condition`, `__exclude__ = ["patient", "encounter"]` (these are server-maintained, never trusted from the client), and exposes `id: UUID4`.
+
+| Spec class | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `BaseConditionSpec` | shared base | `id`; excludes `patient` and `encounter` from direct mapping. |
+| `ConditionSpec` | write · create | `clinical_status?`, `verification_status` (**required**), `severity?`, `code` (value-set bound, **required**), `encounter` (UUID4, **required**), `onset` (`={}`), `abatement` (`={}`), `note?`, `category` (**required**). Validates the encounter exists; on create sets `obj.encounter` and `obj.patient = encounter.patient`. |
+| `ConditionUpdateSpec` | write · update | `clinical_status?`, `verification_status` (**required**), `severity?`, `code` (value-set bound, **required**), `onset` (`={}`), `abatement` (`={}`), `note?`. Does **not** accept `encounter`, `category`, `patient`. |
+| `ChronicConditionUpdateSpec` | write · update (chronic) | Extends `ConditionUpdateSpec` and adds `encounter` (UUID4). On deserialize, if `encounter` is set, resolves it (`get_object_or_404`) and assigns `obj.encounter`. |
+| `ConditionReadSpec` | read · detail/list | `clinical_status`, `verification_status`, `category`, `criticality`, `severity` (all plain `str`), `code` (plain `Coding`), `encounter` (UUID4), `onset`, `abatement`, `created_by?`, `updated_by?`, `note?`, `created_date`, `modified_date`. |
+
+### Validation & server-side behaviour
+
+- **Encounter required and verified on create.** `ConditionSpec.validate_encounter_exists` rejects unknown encounter UUIDs; `perform_extra_deserialization` (create only) loads the encounter and derives `patient` from it. Clients never set `patient`/`encounter` fields directly.
+- **`verification_status` is mandatory** on both `ConditionSpec` and `ConditionUpdateSpec`; `category` is mandatory only on create (`ConditionSpec`).
+- **`code` must be in the bound value set** (SNOMED CT clinical findings) on all write specs.
+- **`onset_datetime` cannot be in the future** and is forced timezone-aware; `abatement_datetime` has no such constraint.
+- **`ConditionReadSpec` exposes `criticality`**, a string that is present in the read schema but has no backing column on the `Condition` model — it is not populated by the standard serializer and is effectively unset on read.
+- **Read serialization** (`perform_extra_serialization`) maps `id = external_id`, replaces `encounter` with its `external_id`, and expands `created_by`/`updated_by` via `serialize_audit_users` (cached `UserSpec`).
+- The Django model carries `body_site` and `recorded_date`, but **no current spec reads or writes them** — they are storage-only fields not part of the standard `Condition` API surface.
+
+## Related models
+
+`Condition` links to two clinical records:
+
+```text
+patient → FK Patient (CASCADE, server-derived from encounter)
+encounter → FK Encounter (CASCADE, nullable; required on create)
+```
+
+Deleting a `Patient` or `Encounter` cascades to its `Condition` rows. Although the model allows a null `encounter`, the standard create flow always derives `patient` from a supplied encounter.
+
+## Methods & save behaviour
+
+- `serialize(obj)` / `de_serialize(obj)` (from `EMRResource`) convert between the `Condition` model and the spec. Field mapping uses the model's non-FK column names; `__exclude__` (`patient`, `encounter`) and `id`/`external_id` are skipped during deserialization.
+- `ConditionSpec.perform_extra_deserialization(is_update=False, obj)` — create path: resolves the encounter and sets `obj.patient = obj.encounter.patient`.
+- `ChronicConditionUpdateSpec.perform_extra_deserialization` — resolves and assigns `encounter` when provided.
+- `ConditionReadSpec.perform_extra_serialization` — sets `id`/`encounter` external ids and serializes audit users.
+- `external_id`, audit fields, `meta`/`history`, and soft-delete (`deleted`) are platform-maintained via [`EMRBaseModel`](../foundation/base-model.mdx).
+
+## API integration notes
+
+- `Condition` aligns with the FHIR `Condition` resource and is surfaced in the product as **Symptoms**.
+- Write `code` as a structured `Coding` (`{system, code, display}`) drawn from the SNOMED CT clinical-finding value set — not free text. `verification_status` must always be provided; `category` and `encounter` are required when creating.
+- `onset` / `abatement` are structured choice-of-type objects (`*_datetime`, `*_age`, `*_string`, `note`) — not arbitrary JSON. `onset_datetime` must be timezone-aware and not in the future.
+- Do not send `patient`, `external_id`, audit fields, or `deleted` from clients — the server maintains them.
+
+## Related
+
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [condition.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/condition.py) · [spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/condition/spec.py) · [valueset.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/condition/valueset.py)
diff --git a/versioned_docs/version-3.0/references/clinical/consent.mdx b/versioned_docs/version-3.0/references/clinical/consent.mdx
new file mode 100644
index 0000000..0ddb166
--- /dev/null
+++ b/versioned_docs/version-3.0/references/clinical/consent.mdx
@@ -0,0 +1,175 @@
+---
+sidebar_position: 8
+---
+
+# Consent
+
+Technical reference for the `Consent` module in Care EMR.
+
+The Django model is the **storage layer**: it persists `status`, `category`, `decision` as bare `CharField`s and `period` / `verification_details` as opaque `JSONField`s. The real constraints — allowed enum values, the nested shape of those JSON fields, validation, and the read/write API schemas — live in the **Pydantic resource specs** (`care/emr/resources/consent/spec.py`), all built on `EMRResource`.
+
+**Source:**
+
+- Model: [`care/emr/models/consent.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/consent.py)
+- Resource spec: [`care/emr/resources/consent/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/consent/spec.py)
+- Base resource: [`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Consent` | Records a patient's consent decision (permit/deny) for a category of activity, scoped to an encounter |
+
+`Consent` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `Consent` fields
+
+Storage types come from the Django model; the **Spec type** column gives the real shape enforced at the API layer.
+
+| Field | Storage type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(50)` | `ConsentStatusChoices` (enum) | Required. Consent state. See [enum](#consentstatuschoices-values) |
+| `category` | `CharField(50)` | `CategoryChoice` (enum) | Required. Classification of the consent. See [enum](#categorychoice-values) |
+| `date` | `DateTimeField` | `datetime` | Required. When the consent was recorded |
+| `period` | `JSONField` (default `dict`) | `PeriodSpec { start, end }` | Validity window. Both ends tz-aware; `start ≤ end`; and `start ≥ date` (enforced on create). See [PeriodSpec](#periodspec-nested-shape) |
+| `encounter` | `FK → Encounter` (`CASCADE`, `related_name="consents"`) | `UUID4` (`external_id`) | Required. Encounter the consent applies to; deleting the encounter cascades to its consents. Excluded from the auto DB mapping (`__exclude__`) and resolved/assigned manually — see [Methods](#methods--save-behaviour) |
+| `decision` | `CharField(10)` | `DecisionType` (enum) | Required. `permit` / `deny`. See [enum](#decisiontype-values) |
+| `verification_details` | `JSONField` (default `list`) | `list[ConsentVerificationSpec]` | Read-only on the API; list of verification records. See [ConsentVerificationSpec](#consentverificationspec-nested-shape) |
+| `note` | `TextField` (null, blank) | `str \| None` | Optional free-text note |
+
+> Note: `source_attachments` is **not** a model field. It is derived at serialize time from `FileUpload` rows (see [Resource specs](#resource-specs-api-schema)).
+
+## Enums
+
+### `ConsentStatusChoices` values
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Consent is being drafted, not yet in force |
+| `active` | Consent is in force |
+| `inactive` | Consent is no longer in force |
+| `not_done` | Consent activity did not occur |
+| `entered_in_error` | Consent was recorded in error |
+
+### `CategoryChoice` values
+
+| Value | Meaning |
+| --- | --- |
+| `research` | Consent for research participation |
+| `patient_privacy` | Patient privacy / information disclosure consent |
+| `treatment` | Consent to treatment |
+| `dnr` | Do-not-resuscitate directive |
+| `comfort_care` | Comfort / palliative care directive |
+| `acd` | Advance care directive |
+| `adr` | Advance directive (other) |
+
+> A `consent_document` category (LOINC 59284-0) exists in migrations only and is **not** an accepted API value.
+
+### `DecisionType` values
+
+| Value | Meaning |
+| --- | --- |
+| `permit` | Consent is granted |
+| `deny` | Consent is refused |
+
+### `VerificationType` values
+
+Used inside `ConsentVerificationSpec.verification_type`.
+
+| Value | Meaning |
+| --- | --- |
+| `family` | Verified by a family member |
+| `validation` | Verified through validation |
+
+## Nested JSON shapes
+
+### `PeriodSpec` nested shape
+
+The `period` JSON field deserializes to `PeriodSpec` (from `care/emr/resources/base.py`). The Django model default is `dict`, so an absent period stores `{}`.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `start` | `datetime \| None` | Default `None`. Must be **timezone-aware** if set |
+| `end` | `datetime \| None` | Default `None`. Must be **timezone-aware** if set |
+
+Validation (`PeriodSpec.validate_period`, mode `after`):
+
+- `start` must be tz-aware (else `"Start Date must be timezone aware"`).
+- `end` must be tz-aware (else `"End Date must be timezone aware"`).
+- If both set, `start ≤ end` (else `"Start Date cannot be greater than End Date"`).
+
+### `ConsentVerificationSpec` nested shape
+
+Each entry in `verification_details` follows this shape (write side). On read, `verified_by` is expanded — see below.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `verified` | `bool` | Required. Whether the consent was verified |
+| `verified_by` | `UUID4 \| None` | Default `None`. User `external_id` who verified (write). On read, replaced by a full `UserSpec` object |
+| `verification_date` | `datetime \| None` | Default `None` |
+| `verification_type` | `VerificationType` | Required. `family` / `validation` |
+| `note` | `str \| None` | Default `None` |
+
+## Resource specs (API schema)
+
+All specs subclass `EMRResource` via `ConsentBaseSpec`. `serialize` builds the read schema from the model; `de_serialize` writes a model from the payload (skipping `id`/`external_id` and `__exclude__` fields). `ConsentBaseSpec` sets `__exclude__ = ["encounter"]`, so the encounter FK is never round-tripped through the generic mapping and is handled by the deserialization hooks instead.
+
+| Spec class | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `ConsentBaseSpec` | shared base | `id`, `status`, `category`, `date`, `period`, `encounter` (UUID4), `decision`, `note`. `__model__ = Consent`, `__exclude__ = ["encounter"]` |
+| `ConsentCreateSpec` | write · create | Same fields as base (all required except `id`, `note`). Validates `period.start ≥ date`; resolves `encounter` from `external_id` → `Encounter` instance |
+| `ConsentUpdateSpec` | write · update | All fields optional (`status`, `category`, `date`, `period`, `encounter`, `decision`, `note` → `None`). Keeps the existing `encounter` (ignores any supplied value) |
+| `ConsentListSpec` | read · list | Base fields plus derived `source_attachments: list` and expanded `verification_details: list` |
+| `ConsentRetrieveSpec` | read · detail | Identical to `ConsentListSpec` (`pass`) |
+
+Nested specs used by the schema: [`PeriodSpec`](#periodspec-nested-shape) (the `period` field) and [`ConsentVerificationSpec`](#consentverificationspec-nested-shape) (entries of `verification_details`).
+
+### Validation
+
+- **Create only** (`ConsentCreateSpec.validate_period_and_date`, mode `after`): if `period.start` is set and `period.start < date`, raises `"Start of the period cannot be before than the Consent date"`.
+- **All specs** (via `PeriodSpec`): tz-aware `start`/`end`, and `start ≤ end`.
+- Enum fields (`status`, `category`, `decision`) reject any value outside their respective enums; on `ConsentUpdateSpec` they may be omitted (`None`).
+
+### Server-maintained behaviour
+
+- **Encounter resolution (create):** `ConsentCreateSpec.perform_extra_deserialization` sets `obj.encounter = Encounter.objects.get(external_id=self.encounter)` when not an update.
+- **Encounter immutability (update):** `ConsentUpdateSpec.perform_extra_deserialization` copies `self.encounter = obj.encounter` on update, so the encounter cannot be reassigned via the API.
+- **Source attachments (read):** `ConsentListSpec.perform_extra_serialization` sets `source_attachments` to the serialized `FileUploadListSpec` of every `FileUpload` where `associating_id == consent.external_id`, `file_category == consent_attachment`, and `file_type == consent`.
+- **Verifier expansion (read):** each `verification_details[i].verified_by` is replaced with a full `UserSpec` (`User.objects.get(external_id=...)`).
+- **Identifiers (read):** `id` is set to `external_id`; `encounter` is serialized as `obj.encounter.external_id`.
+
+## Related models
+
+`Consent` links to a single `Encounter`; an encounter can carry many consents via the `consents` reverse relation:
+
+```text
+encounter → FK Encounter (CASCADE, related_name="consents")
+```
+
+The owning patient is reached transitively through `encounter`. Coded values (`status`, `category`, `decision`) and structured JSON (`period`, `verification_details`) are validated at the API layer (Pydantic specs), not by database constraints.
+
+Read schemas also reach into `FileUpload` (consent attachments) and `User` (verifiers) at serialize time.
+
+## Methods & save behaviour
+
+- `EMRResource.serialize(obj)` → builds the read object: copies non-FK model fields present on the spec, runs `perform_extra_serialization` (id, encounter `external_id`, `source_attachments`, expanded `verification_details`), and stamps `version`.
+- `EMRResource.de_serialize(obj=None)` → builds/updates the model: dumps the spec (`exclude_defaults=True`), assigns matching DB fields (skipping `id`/`external_id` and `__exclude__`), then runs `perform_extra_deserialization`.
+- `encounter` is in `__exclude__`, so it is never set by the generic loop — create resolves it from `external_id`, update preserves the existing FK.
+- Status is a free field: changing `status` to `entered_in_error` (or any other value) is a plain field write — there is no server-side status-history side effect for consent.
+
+## API integration notes
+
+- `Consent` is exposed through Care's REST API using the spec classes above; payload field names match the spec (`encounter` is a UUID `external_id`, not a numeric PK).
+- The model aligns with the FHIR `Consent` resource — `status`, `category`, `decision`, and `period` map to their FHIR counterparts.
+- `period` accepts `{ start, end }` (tz-aware datetimes); `verification_details` is read-only on the consent payload and returns expanded user objects.
+- Allowed values for `status`, `category`, and `decision` are enforced by the Pydantic enums at the API layer, not by the database column definitions.
+- `note` is optional free text and may be null.
+
+## Related
+
+- Reference: [Encounter](./encounter.mdx)
+- Reference: [Patient](./patient.mdx)
+- Reference: [File upload](../platform/file-upload.mdx)
+- Reference: [User](../access/user.mdx)
+- Source: [consent.py (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/consent.py)
+- Source: [spec.py (resource)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/consent/spec.py)
diff --git a/versioned_docs/version-3.0/references/clinical/diagnostic-report.mdx b/versioned_docs/version-3.0/references/clinical/diagnostic-report.mdx
new file mode 100644
index 0000000..c4ddd47
--- /dev/null
+++ b/versioned_docs/version-3.0/references/clinical/diagnostic-report.mdx
@@ -0,0 +1,143 @@
+---
+sidebar_position: 7
+---
+
+# Diagnostic Report
+
+Technical reference for the `DiagnosticReport` module in Care EMR.
+
+The Django model (`care/emr/models/diagnostic_report.py`) is the **storage** layer: it persists `category` and `code` as opaque `JSONField`s and `status` as a free `CharField`. The real structure of those fields — the status enum, the coded-concept shape, the bound value sets, and the read/write API schemas — lives in the Pydantic **resource specs** (`care/emr/resources/diagnostic_report/`), which build on `EMRResource` and serialize/deserialize between the API and the model.
+
+**Source:**
+- Model: [`care/emr/models/diagnostic_report.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/diagnostic_report.py)
+- Spec: [`care/emr/resources/diagnostic_report/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/diagnostic_report/spec.py)
+- Valueset: [`care/emr/resources/diagnostic_report/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/diagnostic_report/valueset.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `DiagnosticReport` | Results and interpretation of a diagnostic test or investigation, tied to a `ServiceRequest` |
+
+`DiagnosticReport` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `DiagnosticReport` fields
+
+### Classification
+
+| Field | Model type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(255)` | `DiagnosticReportStatusChoices` (enum, required) | Lifecycle status of the report. Constrained to the enum below by the spec. Optional on update. |
+| `category` | `JSONField` (null) | `Coding { system, version?, code, display? }`, **required** | Coded service section the report belongs to. Bound to the **Diagnostic Service Sections** value set (`code` is validated against it). |
+| `code` | `JSONField` (null) | `Coding { system, version?, code, display? }`, optional | Coded name/type of the report (test or panel performed). Bound to the **Observation** value set (LOINC). Nullable. |
+
+The `Coding` shape (from `care/emr/resources/common/coding.py`, `extra="forbid"`):
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `system` | `str` | optional | Code system URI (e.g. `http://terminology.hl7.org/CodeSystem/v2-0074`, `http://loinc.org`) |
+| `version` | `str` | optional | Code system version |
+| `code` | `str` | **required** | The code; validated against the bound value set |
+| `display` | `str` | optional | Human-readable label |
+
+### Narrative
+
+| Field | Model type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `note` | `TextField` (null) | `str \| None` | Free-text comments about the report. Not parsed by the platform. |
+| `conclusion` | `TextField` (null) | `str \| None` | Clinical interpretation/summary of the results. Not parsed by the platform. |
+
+### Relationships
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` | `PROTECT`, nullable. Facility the report is scoped to. Set server-side; not a writable spec field. |
+| `patient` | `FK → emr.Patient` | `CASCADE`. Subject of the report. Set server-side; not a writable spec field. |
+| `encounter` | `FK → emr.Encounter` | `CASCADE`. Encounter the report was produced under. Set server-side; not a writable spec field. |
+| `service_request` | `FK → emr.ServiceRequest` | `CASCADE`. The order/request this report fulfils. Write-only on create (resolved from `external_id`); excluded from base serialization via `__exclude__`. |
+
+## Enum values
+
+### `DiagnosticReportStatusChoices`
+
+From `spec.py`. (`modified` is defined in source but commented out and is **not** an active value.)
+
+| Value | Meaning |
+| --- | --- |
+| `registered` | Report exists / has been registered |
+| `partial` | Some results available, report incomplete |
+| `preliminary` | Provisional results pending verification |
+| `final` | Verified, complete report |
+
+## Bound value sets
+
+The coded fields validate their `code` against a registered Care value set (`ValueSetBoundCoding`).
+
+| Field | Value set | Slug | Included system(s) |
+| --- | --- | --- | --- |
+| `category` | Diagnostic Service Sections | `system-diagnostic-service-sections-code` | `http://terminology.hl7.org/CodeSystem/v2-0074` |
+| `code` | Observation | `system-observation` | `http://loinc.org` |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`) via `DiagnosticReportSpecBase`. `serialize` builds the read payload from the model (mapping `external_id` → `id`); `de_serialize` writes spec fields back to the model, with `perform_extra_serialization`/`perform_extra_deserialization` hooks for server-side behaviour.
+
+| Spec class | Role | Fields / behaviour |
+| --- | --- | --- |
+| `DiagnosticReportSpecBase` | shared base | `id?`, `status` (enum, required), `category` (bound Coding, required), `code?` (bound Coding), `note?`, `conclusion?`. `__model__ = DiagnosticReport`, `__exclude__ = ["service_request"]`. |
+| `DiagnosticReportCreateSpec` | write · create | Base + `service_request: UUID4` (required, the report's `external_id`). `perform_extra_deserialization` resolves it via `get_object_or_404(ServiceRequest, external_id=...)` and sets `obj.service_request`. |
+| `DiagnosticReportUpdateSpec` | write · update | Base, but `status` is optional (`DiagnosticReportStatusChoices \| None`). `service_request` is **not** writable on update. |
+| `DiagnosticReportListSpec` | read · list | Base + `created_date`, `modified_date`, `service_request: dict \| None`. `perform_extra_serialization` sets `id = external_id` and embeds the linked request via `BaseServiceRequestSpec.serialize(...)`. |
+| `DiagnosticReportRetrieveSpec` | read · detail | Extends `ListSpec` + `observations: list[dict]`, `encounter: dict`, `created_by?`, `updated_by?`, `requester?`. See serialization below. |
+
+### `DiagnosticReportRetrieveSpec.perform_extra_serialization` (server-side, read)
+
+- Calls the `ListSpec` hook (sets `id`, embeds `service_request`).
+- `serialize_audit_users` → populates `created_by` / `updated_by` from cache (`UserSpec`).
+- `observations` → all `Observation` rows where `diagnostic_report == obj`, each via `ObservationRetrieveSpec.serialize(...)`.
+- `requester` → if `service_request.requester_id` is set, loaded from cache via `UserSpec`.
+- `encounter` → `EncounterListSpec.serialize(obj.encounter)`, with `encounter["patient"]` set to `PatientRetrieveSpec.serialize(obj.encounter.patient, facility=obj.facility)`.
+
+These embedded fields (`service_request`, `observations`, `encounter`, `requester`, audit users) are **read-only**; they are not accepted on create/update.
+
+## Related models
+
+`DiagnosticReport` is the only model defined in this file. It sits downstream of a `ServiceRequest` and is anchored to a patient and encounter:
+
+```text
+diagnostic_report
+ facility → FK facility.Facility (PROTECT, nullable)
+ patient → FK emr.Patient (CASCADE)
+ encounter → FK emr.Encounter (CASCADE)
+ service_request → FK emr.ServiceRequest (CASCADE)
+```
+
+Deleting a `Patient`, `Encounter`, or `ServiceRequest` cascades to its diagnostic reports; the `facility` link is `PROTECT`ed and only soft-deletes apply through `EMRBaseModel`. On retrieve, related `Observation` records are pulled in via the reverse FK (`Observation.diagnostic_report`).
+
+## Methods & save behaviour
+
+- **Create** (`DiagnosticReportCreateSpec.de_serialize`): writes `status`, `category`, `code`, `note`, `conclusion` onto the model; `perform_extra_deserialization` resolves `service_request` from its `external_id` (404 if missing). `facility`, `patient`, and `encounter` are set by the view layer from the request context, not from the payload.
+- **Update** (`DiagnosticReportUpdateSpec`): same base fields; `status` is optional so a partial status change is allowed. `service_request` cannot be reassigned.
+- **Read** (`List`/`Retrieve`): no model writes; all enrichment happens in `perform_extra_serialization` (see above). User and service-request lookups go through the serializer cache (`model_from_cache`).
+- Coded fields are validated at deserialization: `category.code` must be in the Diagnostic Service Sections value set; `code.code` (when present) must be in the Observation value set. `status` must be a `DiagnosticReportStatusChoices` value.
+
+## API integration notes
+
+- Diagnostic reports are exposed through Care's REST API and align with the FHIR `DiagnosticReport` resource; payload field names follow the spec classes above.
+- `category` and `code` are coded concepts (`Coding`: `system`/`code`/`display`), not free strings — `code` is validated against the bound value set, so populate from the relevant value set. `Coding` forbids extra keys.
+- `status` is stored as a free `CharField` but the spec restricts writes to the `DiagnosticReportStatusChoices` enum (`registered`, `partial`, `preliminary`, `final`).
+- A report fulfils exactly one `service_request`; create the [Service Request](./service-request.mdx) first, then pass its `external_id` as `service_request` on create.
+- `note` and `conclusion` carry human-readable narrative and are not parsed by the platform.
+- The detail (`Retrieve`) payload embeds the linked service request, its requester, the encounter (with patient), audit users, and all child observations — none of which are writable.
+
+## Related
+
+- Reference: [Service Request](./service-request.mdx)
+- Reference: [Observation](./observation.mdx)
+- Reference: [Specimen](./specimen.mdx)
+- Reference: [Encounter](./encounter.mdx)
+- Reference: [Patient](./patient.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
+- Source: [diagnostic_report.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/diagnostic_report.py)
+- Source: [resources/diagnostic_report/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/diagnostic_report/spec.py)
+- Source: [resources/diagnostic_report/valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/diagnostic_report/valueset.py)
diff --git a/versioned_docs/version-3.0/references/clinical/encounter.mdx b/versioned_docs/version-3.0/references/clinical/encounter.mdx
new file mode 100644
index 0000000..53f4acd
--- /dev/null
+++ b/versioned_docs/version-3.0/references/clinical/encounter.mdx
@@ -0,0 +1,267 @@
+---
+sidebar_position: 2
+---
+
+# Encounter
+
+Technical reference for the `Encounter` module in Care EMR. An `Encounter` is a single interaction between a patient and a facility (visit, admission, consultation) and aligns with the FHIR `Encounter` resource.
+
+The Django model is the **storage** layer — several fields are opaque `JSONField`s whose real structure lives in the Pydantic **resource specs**. This page documents both: the model fields and the API request/response schemas (enums, JSON-field shapes, validation, server-side side effects).
+
+**Source:**
+- Model: [`care/emr/models/encounter.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/encounter.py)
+- Specs: [`care/emr/resources/encounter/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/encounter/spec.py) · [`constants.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/encounter/constants.py) · [`valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/encounter/valueset.py) · [`enum_display_names.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/encounter/enum_display_names.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Encounter` | A single interaction between a patient and a facility (visit, admission, consultation) |
+| `EncounterOrganization` | Links an `Encounter` to a `FacilityOrganization` for access control |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+## `Encounter` fields
+
+### Classification & status
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `status` | `CharField(100)` | Nullable in DB; **required** in API. One of `StatusChoices` (see below) |
+| `status_history` | `JSONField` | Default `{}`. Server-maintained. Shape: `{ "history": [{ "status": str, "moved_at": str (ISO datetime) }] }` |
+| `encounter_class` | `CharField(100)` | Nullable in DB; **required** in API. One of `ClassChoices` (see below) |
+| `encounter_class_history` | `JSONField` | Default `{}`. Server-maintained. Shape: `{ "history": [{ "status": str, "moved_at": str (ISO datetime) }] }` |
+| `priority` | `CharField(100)` | Nullable in DB; **required** in API. One of `EncounterPriorityChoices` (see below) |
+| `external_identifier` | `CharField(100)` | Nullable. Identifier from an external/source system |
+
+### Subject & facility
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `patient` | `FK → Patient` | `CASCADE`. The patient this encounter is for. Set on create only |
+| `facility` | `FK → Facility` | `PROTECT`. The facility where the encounter takes place. Set on create only |
+| `appointment` | `FK → TokenBooking` | `SET_NULL`, nullable. Originating [scheduling booking](../scheduling/booking.mdx), if any |
+| `current_location` | `FK → FacilityLocation` | `SET_NULL`, nullable. Cached current [location](../facility/location.mdx) for easier querying |
+
+### Timing & disposition
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `period` | `JSONField` | Default `{}`. `PeriodSpec { start: datetime (tz-aware, optional), end: datetime (tz-aware, optional) }`. If both set, `start ≤ end` |
+| `hospitalization` | `JSONField` | Default `{}`. `HospitalizationSpec` (see shape below). Nullable |
+| `discharge_summary_advice` | `TextField` | Nullable. Free-text discharge advice. Explicitly cleared to `None` on update when omitted |
+
+### Care team
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `care_team` | `JSONField` | Default `{}`. Stored as a list of `{ "user_id": int, "role": Coding }` entries. **Not** writable on create/update (excluded); managed via the dedicated care-team write spec |
+| `care_team_users` | `ArrayField[int]` | Platform-maintained denormalized cache of user IDs derived from `care_team` via `sync_care_team_users_cache()` on every save |
+
+### Organization cache, tags & extensions
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility_organization_cache` | `ArrayField[int]` | Platform-maintained cache of facility-[organization](../facility/organization.mdx) IDs (incl. parent chains). Rebuilt by `sync_organization_cache()` |
+| `tags` | `ArrayField[int]` | Tag IDs applied to the encounter. Serialized via `SingleFacilityTagManager().render_tags()` |
+| `extensions` | `JSONField` | Default `{}`. Open extension bag for deployment-specific metadata. Validated by `ExtensionValidator` against `ExtensionResource.encounter` |
+
+## Nested JSON shapes
+
+### `PeriodSpec` (`period`)
+
+Defined in [`resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py).
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `start` | `datetime \| None` | Must be timezone-aware if provided |
+| `end` | `datetime \| None` | Must be timezone-aware if provided |
+
+Validation (`validate_period`): naive datetimes are rejected; if both `start` and `end` are set, `start` must not be greater than `end`.
+
+### `HospitalizationSpec` (`hospitalization`)
+
+Defined in `spec.py`. All fields optional/nullable.
+
+| Field | Type | Values |
+| --- | --- | --- |
+| `re_admission` | `bool \| None` | — |
+| `admit_source` | `AdmitSourcesChoices \| None` | see [Admit source values](#admit-source-values-admitsourceschoices) |
+| `discharge_disposition` | `DischargeDispositionChoices \| None` | see [Discharge disposition values](#discharge-disposition-values-dischargedispositionchoices) |
+| `diet_preference` | `DietPreferenceChoices \| None` | see [Diet preference values](#diet-preference-values-dietpreferencechoices) |
+
+## Enum values
+
+### Status values (`StatusChoices`)
+
+| Value | |
+| --- | --- |
+| `planned` | |
+| `in_progress` | |
+| `on_hold` | |
+| `discharged` | |
+| `completed` | terminal (in `COMPLETED_CHOICES`) |
+| `cancelled` | terminal (in `COMPLETED_CHOICES`) |
+| `discontinued` | terminal (in `COMPLETED_CHOICES`) |
+| `entered_in_error` | terminal (in `COMPLETED_CHOICES` and `ERROR_CHOICES`) |
+| `unknown` | |
+
+`COMPLETED_CHOICES = [completed, cancelled, entered_in_error, discontinued]`; `ERROR_CHOICES = [entered_in_error]`.
+
+### Class values (`ClassChoices`)
+
+| Value | Meaning |
+| --- | --- |
+| `imp` | inpatient |
+| `amb` | ambulatory |
+| `obsenc` | observation |
+| `emer` | emergency |
+| `vr` | virtual |
+| `hh` | home health |
+
+### Priority values (`EncounterPriorityChoices`)
+
+| Value |
+| --- |
+| `ASAP` |
+| `callback_results` |
+| `callback_for_scheduling` |
+| `elective` |
+| `emergency` |
+| `preop` |
+| `as_needed` |
+| `routine` |
+| `rush_reporting` |
+| `stat` |
+| `timing_critical` |
+| `use_as_directed` |
+| `urgent` |
+
+### Admit source values (`AdmitSourcesChoices`)
+
+| Value | Display (`get_admit_source_display`) |
+| --- | --- |
+| `hosp_trans` | Transferred from other hospital |
+| `emd` | From accident/emergency department |
+| `outp` | From outpatient department |
+| `born` | Born in hospital |
+| `gp` | General Practitioner referral |
+| `mp` | Medical Practitioner/physician referral |
+| `nursing` | From nursing home |
+| `psych` | From psychiatric hospital |
+| `rehab` | From rehabilitation facility |
+| `other` | Other |
+
+### Discharge disposition values (`DischargeDispositionChoices`)
+
+| Value | Display (`get_discharge_disposition_display`) |
+| --- | --- |
+| `home` | Home |
+| `alt_home` | Alternate Home |
+| `other_hcf` | Other Health Care Facility |
+| `hosp` | Hospital |
+| `long` | Long-term Care Facility |
+| `aadvice` | Against Medical Advice |
+| `exp` | Expired |
+| `psy` | Psychiatric Hospital |
+| `rehab` | Rehabilitation Facility |
+| `snf` | Skilled Nursing Facility |
+| `oth` | Other |
+
+### Diet preference values (`DietPreferenceChoices`)
+
+| Value |
+| --- |
+| `vegetarian` |
+| `dairy_free` |
+| `nut_free` |
+| `gluten_free` |
+| `vegan` |
+| `halal` |
+| `kosher` |
+| `none` |
+
+## Resource specs (API schema)
+
+All specs build on `EMRResource` (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). `EncounterSpecBase` sets `__exclude__ = [patient, organizations, facility, appointment, current_location, care_team]` — those fields are handled by hooks or dedicated endpoints rather than direct assignment.
+
+| Spec | Role | Notes |
+| --- | --- | --- |
+| `EncounterSpecBase` | shared | Fields: `id`, `status`, `encounter_class`, `period` (`PeriodSpec`, default `{}`), `hospitalization` (`HospitalizationSpec \| None`, default `{}`), `priority`, `external_identifier`, `discharge_summary_advice`. `__model__ = Encounter` |
+| `EncounterCreateSpec` | write · create | Extends base + `ExtensionValidator`. Adds `patient: UUID4`, `facility: UUID4`, `organizations: list[UUID4] = []`, `appointment: UUID4 \| None`. Resolves FKs by `external_id`; validates appointment belongs to the patient and facility |
+| `EncounterUpdateSpec` | write · update | Extends base + `ExtensionValidator`. Does not accept `patient`/`facility`/`organizations`/`appointment` — only mutable fields. Appends to status/class history on change |
+| `EncounterListSpec` | read · list | Adds serialized `patient` ([`PatientListSpec`](./patient.mdx)), `facility` (`FacilityBareMinimumSpec`), `status_history`, `encounter_class_history`, `created_date`, `modified_date`, `tags`, `current_location` (`FacilityLocationMinimalListSpec \| None`), `care_team` (`[{ member, role }]`) |
+| `EncounterRetrieveSpec` | read · detail | Extends list + `EncounterPermissionsMixin`. Adds `appointment` (`TokenBookingReadSpec`), `created_by`/`updated_by` ([`UserSpec`](../access/user.mdx)), `organizations` (`FacilityOrganizationReadSpec[]`), `location_history` (`FacilityLocationEncounter[]`, newest first), `extensions`; patient via `PatientRetrieveSpec` |
+| `HospitalizationSpec` | nested | Plain `BaseModel`; see shape above |
+| `EncounterCareTeamMemberSpec` | nested (write) | `{ user_id: UUID4, role: Coding }`, `role` bound to the `system-practitioner-role-code` value set |
+| `EncounterCareTeamMemberWriteSpec` | write · care team | `{ members: list[EncounterCareTeamMemberSpec] }`. Dedicated payload for replacing the care team |
+
+### Server-maintained behaviour
+
+- **Status history**: on create, `status_history` is initialized to `{ "history": [{ status, moved_at }] }` with the initial status; on update, a new entry is appended only when `status` changes. Same pattern for `encounter_class_history` / `encounter_class`.
+- **FK resolution (create)**: `patient`, `facility`, and `appointment` are resolved from `external_id` (404 if not found). The appointment must belong to the same patient and to a slot whose resource is in the same facility.
+- **Organizations (create)**: `organizations` (a deduplicated list of UUIDs) is stashed on `obj._organizations`; `EncounterOrganization` rows drive `facility_organization_cache`.
+- **Discharge advice (update)**: when `discharge_summary_advice` is omitted on update, it is explicitly set to `None`.
+- **Care team**: `role` binds to the `PRACTITIONER_ROLE_VALUESET` (slug `system-practitioner-role-code`) — a SNOMED CT value set composed of descendants of `223366009` (healthcare professional) and `224930009` (healthcare-related occupation).
+
+## Related models
+
+### `EncounterOrganization`
+
+Associates an encounter with a facility-scoped [organization](../facility/organization.mdx). Used to drive organization-based access control on encounters.
+
+```text
+encounter → FK Encounter (CASCADE)
+organization → FK FacilityOrganization (CASCADE)
+```
+
+Saving an `EncounterOrganization` calls `encounter.sync_organization_cache()`, which rebuilds the encounter's `facility_organization_cache`.
+
+## Methods & save behaviour
+
+### `sync_care_team_users_cache()`
+
+When `care_team` is a list, populates `care_team_users` with `int(entry["user_id"])` for each entry (defaulting to `-1` when a `user_id` is missing). Provides a flat integer array for fast querying without JSON traversal.
+
+### `sync_organization_cache()`
+
+Recomputes `facility_organization_cache` as the union of:
+
+- every linked `EncounterOrganization` organization plus its `parent_cache` chain, and
+- the facility's `default_internal_organization_id`.
+
+Persists the result with `super().save(update_fields=["facility_organization_cache"])`.
+
+### `save()` side effects
+
+On every save:
+
+1. `sync_care_team_users_cache()` runs, refreshing `care_team_users`.
+2. If the record has no `pk`, it is flagged as newly `created`.
+3. Inside a single `transaction.atomic()` block, the base `save()` runs; for newly created encounters, `evaluate_patient_facility_default_values(patient, facility)` runs to generate facility-scoped patient identifier defaults.
+4. After the transaction, `sync_organization_cache()` runs, which performs a **second write** (`update_fields=["facility_organization_cache"]`).
+
+Integrators should expect **two write passes** when creating or updating encounters through the ORM, plus identifier-default generation on creation.
+
+## API integration notes
+
+- Encounters are exposed through Care's REST API and align with the FHIR `Encounter` resource. Reads use `EncounterListSpec` / `EncounterRetrieveSpec`; writes use `EncounterCreateSpec` / `EncounterUpdateSpec`.
+- `status`, `encounter_class`, and `priority` are **required** on write and validated against their enums even though the DB columns are nullable.
+- `status_history`, `encounter_class_history`, `care_team_users`, and `facility_organization_cache` are **platform-maintained** — do not set them directly from clients. Status/class history is appended server-side on create/update; the caches are derived from `care_team` and `EncounterOrganization`.
+- The care team is **not** writable through create/update; use the dedicated care-team endpoint with `EncounterCareTeamMemberWriteSpec`. Each member's `role` must resolve within the `system-practitioner-role-code` value set.
+- `period`, `hospitalization` carry structured JSON; use the nested-spec shapes above. `period.start`/`end` must be timezone-aware.
+- `current_location` is a cached convenience field; authoritative location history is `location_history` (from `FacilityLocationEncounter`).
+- `extensions` is the supported place for custom key-value data without schema migrations and is validated by `ExtensionValidator`.
+
+## Related
+
+- Reference: [Patient](./patient.mdx)
+- Reference: [Service request](./service-request.mdx)
+- Reference: [Booking](../scheduling/booking.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Organization](../facility/organization.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [User](../access/user.mdx)
+- Concept: [Patient](../../concepts/clinical/patient.mdx)
+- Source (model): [encounter.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/encounter.py)
+- Source (specs): [resources/encounter on GitHub](https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/encounter)
diff --git a/versioned_docs/version-3.0/references/clinical/notes.mdx b/versioned_docs/version-3.0/references/clinical/notes.mdx
new file mode 100644
index 0000000..7e7d74b
--- /dev/null
+++ b/versioned_docs/version-3.0/references/clinical/notes.mdx
@@ -0,0 +1,136 @@
+---
+sidebar_position: 9
+---
+
+# Notes
+
+Technical reference for the `NoteThread` and `NoteMessage` modules in Care EMR.
+
+The Django models (`care/emr/models/notes.py`) are the **storage layer**: `NoteThread`
+groups messages for a patient (optionally scoped to an encounter), and `NoteMessage`
+stores each message body plus an opaque `message_history` `JSONField`. The structure of
+`message_history`, the read/write schemas, and the server-side edit-history behaviour are
+defined by the **Pydantic resource specs** (`care/emr/resources/notes/`), all built on
+`EMRResource`.
+
+**Source:**
+
+- Model: [`care/emr/models/notes.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/notes.py)
+- Specs: [`resources/notes/thread_spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/notes/thread_spec.py), [`resources/notes/notes_spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/notes/notes_spec.py)
+- Base: [`resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `NoteThread` | A discussion thread attached to a patient (optionally scoped to an encounter) |
+| `NoteMessage` | An individual message posted within a `NoteThread` |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `NoteThread` fields
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `title` | `CharField(255)` | Nullable, blank-allowed at the storage layer; the spec layer requires it on write (`title: str`, `max_length=255`) |
+| `patient` | `FK → Patient` | `on_delete=CASCADE`; thread is removed when the [patient](./patient.mdx) is deleted. Excluded from spec serialization (`__exclude__`); set from the URL/viewset context, not the request body |
+| `encounter` | `FK → Encounter` | Nullable, blank-allowed; `on_delete=CASCADE`. Scopes the thread to a specific [encounter](./encounter.mdx) when set. Excluded from spec serialization; on create it is resolved from a UUID in the request body (see specs below) |
+
+A `NoteThread` groups related messages for a patient. Threads with no `encounter` are patient-level; threads with an `encounter` are scoped to that visit. A `TODO` in the source notes that organization-based access restriction is planned but not yet implemented.
+
+## Related models
+
+### `NoteMessage`
+
+An individual message within a thread.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `thread` | `FK → NoteThread` | `on_delete=CASCADE`; messages are removed when the thread is deleted. Excluded from spec serialization (`__exclude__`); set from the URL/viewset context |
+| `message` | `TextField` | Free-text message body. Required on write (`message: str`) |
+| `message_history` | `JSONField` | Defaults to `{}`. Opaque at the model layer; the spec layer gives it the shape below and maintains it server-side on edit. Exposed read-only |
+
+#### `message_history` shape
+
+`message_history` is not a free-form blob — the update spec writes a fixed structure. On
+each update, the **previous** message body is appended to a `history` list:
+
+```jsonc
+{
+ "history": [
+ {
+ "message": "string", // the prior message body (pre-edit)
+ "created_by": {
+ "username": "string",
+ "external_id": "uuid" // editor of the prior version
+ },
+ "edited_at": "datetime str", // timezone.now() at the time of this edit
+ "created_at": "datetime str" // the prior version's modified_date
+ }
+ // ... one entry per edit, oldest first
+ ]
+}
+```
+
+Clients must not set or overwrite `message_history`; it is read-only on the API and
+maintained entirely server-side (see `NoteMessageUpdateSpec` below).
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`resources/base.py`), which provides `serialize`
+(DB object → pydantic, via `perform_extra_serialization`) and `de_serialize`
+(pydantic → DB object, via `perform_extra_deserialization`). Fields listed in
+`__exclude__` are skipped by both directions and handled manually.
+
+### Thread specs
+
+| Spec | Role | Fields exposed | Notes |
+| --- | --- | --- | --- |
+| `NoteThreadSpec` | shared base | `id` (UUID, read), `title` (str, required, ≤255) | `__model__ = NoteThread`; `__exclude__ = ["patient", "encounter"]` |
+| `NoteThreadCreateSpec` | write · create | base + `encounter` (UUID, optional) | Validates `encounter` exists (raises `Encounter not found`); resolves the UUID to an `Encounter` FK in `perform_extra_deserialization` |
+| `NoteThreadUpdateSpec` | write · update | base (`title`) | No extra behaviour; `encounter`/`patient` cannot be reassigned via update |
+| `NoteThreadReadSpec` | read · detail/list | base + `created_by`, `updated_by` (dict, nullable), `created_date`, `modified_date` (datetime) | `perform_extra_serialization` sets `id = external_id` and serializes audit users via `serialize_audit_users` |
+
+Validation / server behaviour:
+
+- `title` is **required** on write at the spec layer (`Field(..., max_length=255)`), even though the column is nullable.
+- `NoteThreadCreateSpec.encounter` is validated with `validate_encounter_exists`: a non-null value must match an existing `Encounter.external_id`, otherwise `ValueError("Encounter not found")`.
+- On create, `perform_extra_deserialization` looks up the `Encounter` by `external_id` and assigns it to the FK. `patient` is not part of any thread spec — it is assigned from the request context.
+
+### Message specs
+
+| Spec | Role | Fields exposed | Notes |
+| --- | --- | --- | --- |
+| `NoteMessageSpec` | shared base | `id` (UUID, read), `message` (str, required) | `__model__ = NoteMessage`; `__exclude__ = ["thread"]` |
+| `NoteMessageCreateSpec` | write · create | base (`message`) | No extra behaviour |
+| `NoteMessageUpdateSpec` | write · update | base (`message`) | `perform_extra_deserialization` appends the pre-edit message to `message_history["history"]` (see below) |
+| `NoteMessageReadSpec` | read · detail/list | base + `message_history` (dict), `created_by`, `updated_by` (dict, nullable), `created_date`, `modified_date` (datetime) | `perform_extra_serialization` sets `id = external_id` and serializes audit users |
+
+Validation / server behaviour:
+
+- `message` is **required** on create and update.
+- `thread` is never in the request body (`__exclude__`); it is taken from the URL/viewset context.
+- **Edit history is maintained server-side.** On update, `NoteMessageUpdateSpec.perform_extra_deserialization` fetches the existing `NoteMessage` by `external_id`, initializes `message_history["history"] = []` if empty, and appends an entry capturing the prior `message`, the prior author (`username` + `external_id`), `edited_at` (`timezone.now()`), and `created_at` (the prior `modified_date`). Clients cannot write `message_history` directly.
+
+## Methods & save behaviour
+
+- `NoteThread` / `NoteMessage` inherit save/audit behaviour from [`EMRBaseModel`](../foundation/base-model.mdx): `external_id`, `created_by`/`updated_by`, `created_date`/`modified_date`, and soft delete via `deleted`.
+- Audit users in read specs are populated by `EMRResource.serialize_audit_users`, which resolves `created_by`/`updated_by` to cached `UserSpec` dicts.
+- `message_history` is mutated only by `NoteMessageUpdateSpec.perform_extra_deserialization` — never by client input.
+- Cascade deletes: deleting a patient removes its threads; deleting a thread removes its messages.
+
+## API integration notes
+
+- Note threads and messages are exposed through Care's REST API; field names in API payloads follow the spec classes above, which may differ from Django model names.
+- Threads are always anchored to a `patient` (set from request context); the `encounter` link is optional and narrows a thread to a single visit. On create, send `encounter` as the encounter's `external_id` (UUID) — it is validated to exist.
+- `title` and `message` are required on write even though the underlying columns are permissive.
+- `message_history` is read-only and platform-maintained: each edit appends the previous body and authorship to `history`. Do not send it from clients.
+- Authorship and timestamps come from the inherited `EMRBaseModel` audit fields (`created_by`, `updated_by`, `created_date`, `modified_date`); soft deletes are handled via `deleted`.
+
+## Related
+
+- Source: [notes.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/notes.py)
+- Specs: [thread_spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/notes/thread_spec.py), [notes_spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/notes/notes_spec.py)
+- Reference: [Patient](./patient.mdx)
+- Reference: [Encounter](./encounter.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
diff --git a/versioned_docs/version-3.0/references/clinical/observation-definition.mdx b/versioned_docs/version-3.0/references/clinical/observation-definition.mdx
new file mode 100644
index 0000000..e4f493d
--- /dev/null
+++ b/versioned_docs/version-3.0/references/clinical/observation-definition.mdx
@@ -0,0 +1,269 @@
+---
+sidebar_position: 6
+---
+
+# Observation Definition
+
+Technical reference for the `ObservationDefinition` module in Care EMR.
+
+An observation definition is master data that describes *how* a particular observation is captured — its code, permitted data type, unit, method, body site, and (interpretation) reference ranges. Questionnaires reference observation definitions so the same observation (e.g. blood pressure) is collected consistently across forms, at either the instance level or a single facility. A single observation code can have multiple definitions. The model is loosely based on the [FHIR ObservationDefinition](https://build.fhir.org/observationdefinition.html) resource.
+
+The Django model is the **storage layer**: `code`, `body_site`, `method`, `permitted_unit`, `component`, and `qualified_ranges` are opaque `JSONField`s. Their real structure, the enums, the value-set bindings, and the read/write contracts live in the **Pydantic resource specs** (`care/emr/resources/observation_definition/spec.py`), all built on the `EMRResource` base.
+
+**Source:**
+
+- Model: [`care/emr/models/observation_definition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/observation_definition.py)
+- Resource spec: [`care/emr/resources/observation_definition/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation_definition/spec.py)
+- Helper: [`care/emr/resources/observation_definition/observation.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation_definition/observation.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ObservationDefinition` | Master-data definition of how an observation is coded, typed, and measured |
+
+`ObservationDefinition` extends [`SlugBaseModel`](../foundation/base-model.mdx), which adds facility-scoped slug helpers on top of `EMRBaseModel`. From `EMRBaseModel` it inherits `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and the `history`/`meta` JSON fields.
+
+## `ObservationDefinition` fields
+
+### Identity & scope
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → Facility` (PROTECT) | Nullable, `default=None`. When set, the definition is scoped to that facility; when `null` it is instance-wide. Excluded from the base spec (`__exclude__ = ["facility"]`) and set server-side from the `facility` UUID on create |
+| `version` | `IntegerField` | Defaults to `1`; assigned version number for the definition. Read-only in the API (`...ReadSpec` only) |
+| `slug` | `CharField(255)` | Set server-side from `slug_value`; namespaced. See [slug behaviour](#slug-behaviour) |
+| `title` | `CharField(1024)` | Required. Human-friendly name for this definition |
+| `status` | `CharField(255)` | Required. Lifecycle code from `ObservationStatusChoices` — see [Status values](#status-values). Definitions are not destroyed — they are moved to `retired` instead |
+| `description` | `TextField` | Required. Natural-language (markdown) description |
+| `derived_from_uri` | `TextField` | External URI this definition was derived from. Optional in the API (`str \| None`, default `None`) |
+
+### Coding & measurement
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `category` | `CharField(255)` | Nullable in the model, but **required** in the API as `ObservationCategoryChoices` — see [Category values](#category-values) |
+| `code` | `JSONField` | Required. `Coding` bound to the `system-observation` value set (LOINC). What is being observed — see [Coding shape](#coding-shape) |
+| `permitted_data_type` | `CharField(100)` | Required. `QuestionType` enum (questionnaire data types) — see [Permitted data type values](#permitted-data-type-values). `group`, `display`, and `url` are rejected |
+| `body_site` | `JSONField` | Nullable. `Coding` bound to the `system-body-site-observation` value set (SNOMED CT) |
+| `method` | `JSONField` | Nullable. `Coding` bound to the `system-collection-method` value set (SNOMED CT) |
+| `permitted_unit` | `JSONField` | Nullable. `Coding` bound to the `system-ucum-units` value set (UCUM). Care does not convert between units when displaying data |
+
+### Components & ranges
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `component` | `JSONField` | Nullable. `list[ObservationDefinitionComponentSpec] \| None`. Multiple measured values for one definition (e.g. systolic + diastolic), each with its own `code`, `permitted_data_type`, `permitted_unit`, and `qualified_ranges` — see [ObservationDefinitionComponentSpec](#observationdefinitioncomponentspec) |
+| `qualified_ranges` | `JSONField` | Model default `list`. **Required** in the API as `list[QualifiedRangeSpec]` (may be empty). Condition-dependent reference ranges / interpretations — see [QualifiedRangeSpec](#qualifiedrangespec) |
+
+## Enum values
+
+### Status values
+
+`ObservationStatusChoices` — bound to the `status` field.
+
+| Value |
+| --- |
+| `draft` |
+| `active` |
+| `retired` |
+| `unknown` |
+
+### Category values
+
+`ObservationCategoryChoices` — bound to the `category` field.
+
+| Value |
+| --- |
+| `social_history` |
+| `vital_signs` |
+| `imaging` |
+| `laboratory` |
+| `procedure` |
+| `survey` |
+| `exam` |
+| `therapy` |
+| `activity` |
+
+### Permitted data type values
+
+`QuestionType` (from `questionnaire/spec.py`) — bound to `permitted_data_type` on both the definition and each component. The values `group`, `display`, and `url` are **rejected** by `validate_question_type` ("Cannot create a definition with this type").
+
+| Value | Allowed for a definition |
+| --- | --- |
+| `group` | No (rejected) |
+| `boolean` | Yes |
+| `decimal` | Yes |
+| `integer` | Yes |
+| `string` | Yes |
+| `text` | Yes |
+| `display` | No (rejected) |
+| `date` | Yes |
+| `dateTime` | Yes |
+| `time` | Yes |
+| `choice` | Yes |
+| `url` | No (rejected) |
+| `quantity` | Yes |
+| `structured` | Yes |
+
+## JSON field shapes (nested specs)
+
+### Coding shape
+
+The coded fields (`code`, `body_site`, `method`, `permitted_unit`) store a `Coding` object. The spec binds each to a value set via `ValueSetBoundCoding[]`, so the submitted `code` is validated against that value set on write.
+
+```text
+Coding {
+ system: str | null
+ version: str | null
+ code: str # required
+ display: str | null
+} # extra="forbid" — unknown keys rejected
+```
+
+| Field | Bound value set (slug) | Code system |
+| --- | --- | --- |
+| `code` | `system-observation` | LOINC (`http://loinc.org`) |
+| `body_site` | `system-body-site-observation` | SNOMED CT (`http://snomed.info/sct`) |
+| `method` | `system-collection-method` | SNOMED CT |
+| `permitted_unit` | `system-ucum-units` | UCUM (`http://unitsofmeasure.org`) |
+
+### ObservationDefinitionComponentSpec
+
+Element of the `component` list. Lets one definition describe several measured values.
+
+```text
+ObservationDefinitionComponentSpec {
+ code: ValueSetBoundCoding[system-observation] # required
+ permitted_data_type: QuestionType # required; group/display/url rejected
+ permitted_unit: ValueSetBoundCoding[system-ucum-units] | null = null
+ qualified_ranges: list[QualifiedRangeSpec] # required
+}
+```
+
+### QualifiedRangeSpec
+
+Element of `qualified_ranges` (on the definition and on each component). A qualified range is either **numeric** (`ranges`) or **categorical** (coded value sets) — never both.
+
+```text
+QualifiedRangeSpec {
+ title: str | null = null
+ conditions: list[EvaluatorConditionSpec] = []
+ ranges: list[NumericRangeSpec] = []
+ default_interpretation: InterpretationSpec | null = null
+ normal_coded_value_set: str | null = ""
+ critical_coded_value_set: str | null = ""
+ abnormal_coded_value_set: str | null = ""
+ valueset_interpretation: list[ValueSetInterpretationSpec] = []
+}
+```
+
+Validation (`validate_categorical_or_numeric`):
+
+- Must provide either `ranges` (numeric) **or** at least one coded value set / `valueset_interpretation` (categorical) — at least one is required.
+- Cannot specify both numeric `ranges` and coded value sets.
+- Numeric `ranges` must not overlap (sorted by `min`, treating missing `min`/`max` as -∞/+∞).
+- Coded value-set slugs (`normal_coded_value_set`, `critical_coded_value_set`, `abnormal_coded_value_set`, and each `valueset_interpretation.valuset`) must be unique — duplicates are rejected.
+
+### NumericRangeSpec
+
+```text
+NumericRangeSpec {
+ interpretation: InterpretationSpec # required
+ min: Decimal(max_digits=20, decimal_places=6) | null = null
+ max: Decimal(max_digits=20, decimal_places=6) | null = null
+}
+```
+
+At least one of `min`/`max` must be provided (`validate_range`).
+
+### InterpretationSpec
+
+```text
+InterpretationSpec {
+ display: str # required
+ icon: str | null = ""
+ color: str | null = ""
+ highlight: bool | null = false
+ code: Coding | null = {}
+}
+```
+
+### ValueSetInterpretationSpec
+
+```text
+ValueSetInterpretationSpec {
+ interpretation: InterpretationSpec # required
+ valuset: str # value-set slug (note: spelling "valuset")
+}
+```
+
+### EvaluatorConditionSpec
+
+Element of `QualifiedRangeSpec.conditions`. Validated against `EvaluatorMetricsRegistry` — an unknown `metric` raises, and the registered evaluator validates `operation`/`value`.
+
+```text
+EvaluatorConditionSpec {
+ metric: str # must resolve in EvaluatorMetricsRegistry
+ operation: str
+ value: dict | str
+}
+```
+
+## Resource specs (API schema)
+
+All specs subclass `BaseObservationDefinitionSpec` (which subclasses `EMRResource`, `__model__ = ObservationDefinition`, `__exclude__ = ["facility"]`). `EMRResource` provides `serialize`/`de_serialize` plus the `perform_extra_serialization` / `perform_extra_deserialization` hooks.
+
+| Spec class | Role | Notable fields / behaviour |
+| --- | --- | --- |
+| `BaseObservationDefinitionSpec` | Shared base | `id`, `title`, `status`, `description`, `category`, `code`, `permitted_data_type`, `component`, `body_site`, `method`, `permitted_unit`, `derived_from_uri`, `qualified_ranges`. Runs `validate_data_type` and `validate_qualified_ranges_consistency` |
+| `ObservationDefinitionCreateSpec` | Write · create | Adds `facility: UUID4 \| null` and `slug_value: SlugType`. Validates the facility exists; `perform_extra_deserialization` resolves the facility and sets `obj.slug = slug_value` |
+| `ObservationDefinitionUpdateSpec` | Write · update | Adds `slug_value: SlugType`; `perform_extra_deserialization` sets `obj.slug = slug_value` (facility is not re-bound on update) |
+| `ObservationDefinitionReadSpec` | Read · list & detail | Adds `version: int \| null`, `facility: dict \| null`, `slug_config: dict`, `slug: str`. `perform_extra_serialization` sets `id = external_id`, serializes `facility` via `FacilityBareMinimumSpec`, and sets `slug_config = parse_slug(slug)` |
+
+Notes:
+
+- **Value-set bindings** — `code`, `body_site`, `method`, `permitted_unit` (and component equivalents) are `ValueSetBoundCoding[...]`; the submitted coding is validated against the bound value set (`system-observation`, `system-body-site-observation`, `system-collection-method`, `system-ucum-units`).
+- **Data-type guard** — `permitted_data_type` (definition and component) rejects `group`, `display`, `url`.
+- **Range consistency** — `validate_qualified_ranges_consistency`: all `qualified_ranges` on a definition must be the same kind (all numeric `ranges` or all coded value sets); mixing raises.
+- **`slug_value`** — `SlugType`: 5–50 chars, URL-safe (`[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]`), must start and end alphanumeric. Stored namespaced as `obj.slug` (see below).
+- **No metadata store** — `__store_metadata__` is `False`, so spec fields map directly to model columns rather than into `meta`.
+
+## Methods & save behaviour
+
+`ObservationDefinition` defines no overrides of its own; slug handling is inherited from `SlugBaseModel`, and the API specs perform the create/update side effects.
+
+### Slug behaviour
+
+`SlugBaseModel` is `FACILITY_SCOPED = True`, so the stored `slug` is namespaced:
+
+```text
+facility-scoped: f--
+instance-scoped: i-
+```
+
+- `calculate_slug()` returns the `f-…` form when `facility` is set, otherwise the `i-…` form.
+- `parse_slug(slug)` validates the prefix and returns a dict: `{"facility": , "slug_value": }` for `f-` slugs, or `{"slug_value": }` for `i-` slugs. `ObservationDefinitionReadSpec` exposes this as `slug_config`.
+
+The create/update specs receive the bare `slug_value` and store it on `obj.slug`; namespacing is applied via the model's slug helpers.
+
+## API integration notes
+
+- Observation definitions are master data: created at the instance level or per-facility, and referenced by questionnaires rather than embedded into them.
+- The facility binding is write-once via the create spec (`facility` UUID → resolved FK); the update spec only re-sets `slug_value`.
+- Definitions are append-only by convention — retire via `status` (`retired`) instead of hard deletion; soft-delete via the inherited `deleted` flag still applies at the ORM level.
+- `code`, `body_site`, `method`, `permitted_unit`, and component codings are FHIR `Coding` objects validated against bound value sets, so the data stays translatable to FHIR.
+- Units follow UCUM principles; Care does not currently convert between units at display time.
+- `qualified_ranges` supports condition-dependent numeric ranges or categorical (coded) interpretations, but a single definition must commit to one kind across all its ranges.
+- `convert_od_to_observation` (`observation.py`) builds an [`Observation`](./observation.mdx) from a definition for a given encounter, carrying over `category` and `code` and defaulting `status` to `final`.
+
+## Related
+
+- Reference: [Observation](./observation.mdx)
+- Reference: [Service Request](./service-request.mdx)
+- Reference: [Questionnaire](../forms/questionnaire.mdx)
+- Reference: [Value Set](../forms/valueset.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [observation_definition.py (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/observation_definition.py)
+- Source: [observation_definition/spec.py (resource spec)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation_definition/spec.py)
+- Source: [observation/valueset.py (bound value sets)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation/valueset.py)
diff --git a/versioned_docs/version-3.0/references/clinical/observation.mdx b/versioned_docs/version-3.0/references/clinical/observation.mdx
new file mode 100644
index 0000000..56d3abe
--- /dev/null
+++ b/versioned_docs/version-3.0/references/clinical/observation.mdx
@@ -0,0 +1,271 @@
+---
+sidebar_position: 5
+---
+
+# Observation
+
+Technical reference for the `Observation` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/observation.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/observation.py)
+- Resource spec: [`care/emr/resources/observation/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation/spec.py)
+- Value sets: [`care/emr/resources/observation/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation/valueset.py)
+
+An observation is a single named, coded data point recorded about a subject — almost always a patient — such as a blood pressure reading, a temperature, or a coded answer captured through a questionnaire. The `main_code` says *what* was observed (typically bound to LOINC), and `value` holds the recorded result.
+
+The Django model is the **storage** layer: several fields are opaque `JSONField`s whose real structure lives in the Pydantic resource specs. The specs (`care/emr/resources/observation/`) are the **API/implementation** layer — they define the enums, the nested shape of those JSON fields, validation, and the read/write schemas. Read both layers together: the table notes below give the storage column, then the spec-enforced shape.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Observation` | A single coded measurement or finding about a patient, captured within an encounter |
+
+`Observation` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `Observation` fields
+
+### Classification & coding
+
+| Field | Storage type | Spec shape / notes |
+| --- | --- | --- |
+| `status` | `CharField(255)` | Enum `ObservationStatus` — `final`, `amended`, `entered_in_error`. Required in specs. Observations created from questionnaires are `final` |
+| `is_group` | `BooleanField` | Default `False`. Marks a grouping/panel observation that holds `component`/child observations rather than a single value. Not exposed in the resource specs |
+| `category` | `JSONField` (default `{}`) | Spec: a single `Coding { system?, version?, code, display? }`, optional. FHIR observation category derived from the questionnaire |
+| `main_code` | `JSONField` (default `{}`) | Spec: `Coding`, optional. Primary code for what is being observed, typically bound to LOINC (`CARE_OBSERVATION_VALUSET`, system `http://loinc.org`) |
+| `alternate_coding` | `JSONField` (default `[]`) | Spec: a single `CodeableConcept { id?, coding?: Coding[], text }`, optional. Additional codings for the same concept across other code systems |
+
+### Subject & context
+
+| Field | Storage type | Spec shape / notes |
+| --- | --- | --- |
+| `subject_type` | `CharField(255)` | Enum `SubjectType` — `patient`, `encounter`. Spec default `encounter` |
+| `subject_id` | `UUIDField` | Identifier of the subject entity. Not exposed in the specs (set server-side) |
+| `patient` | `FK → Patient` | `CASCADE`. The patient the observation belongs to. Set server-side, not in the spec body |
+| `encounter` | `FK → Encounter` | `CASCADE`. Spec field is a `UUID4 \| None`. The encounter in which the observation was recorded |
+| `effective_datetime` | `DateTimeField` | Nullable in storage. Spec: required `datetime` (tz-aware) on create; optional on update. When the observation was effective/recorded |
+
+### Author & performer
+
+| Field | Storage type | Spec shape / notes |
+| --- | --- | --- |
+| `data_entered_by` | `FK → users.User` | `CASCADE`, nullable. `related_name="observations_entered"`. Write specs take `data_entered_by_id: int`; read specs serialize a nested `UserSpec` |
+| `performer` | `JSONField` (default `{}`) | Spec: `Performer { type: PerformerType (related_person\|user), id: str }`, optional. Who performed the observation when not the data-entry user |
+
+### Value
+
+| Field | Storage type | Spec shape / notes |
+| --- | --- | --- |
+| `value_type` | `CharField(255)` | Enum `QuestionType` (from the questionnaire module) — discriminates how `value` is interpreted. See values below. Required in specs |
+| `value` | `JSONField` | Spec: `QuestionnaireSubmitResultValue { value?: str, unit?: Coding, coding?: Coding }`. Required on create, optional on update. `value` carries the raw/string value; `unit` for quantities; `coding` for coded answers |
+| `note` | `TextField` | Spec: `str \| None`. Free-text note about the observation |
+
+### Clinical qualifiers
+
+| Field | Storage type | Spec shape / notes |
+| --- | --- | --- |
+| `body_site` | `JSONField` (default `{}`) | Spec: `Coding` bound to value set `CARE_BODY_SITE_VALUESET` (slug `system-body-site-observation`, SNOMED CT), optional. FHIR `Observation.bodySite` |
+| `method` | `JSONField` (default `{}`) | Spec: `Coding` bound to value set `CARE_OBSERVATION_COLLECTION_METHOD` (slug `system-collection-method`, SNOMED CT), optional. FHIR `Observation.method` |
+| `reference_range` | `JSONField` (default `[]`) | Spec: `ReferenceRange[]` (see shape below), default `[]`. Reference ranges for the value, typically derived from the observation definition |
+| `interpretation` | `JSONField` (default `{}`) | Spec: free `dict`, default `{}`. Coded interpretation relative to the reference range (high/low/normal). Bound value sets exist for normal/abnormal/critical codings (see below) |
+| `interpretation_old` | `CharField(255)` | Nullable. Legacy free-text interpretation retained for backward compatibility. Not in the specs |
+
+### Structure & relationships
+
+| Field | Storage type | Spec shape / notes |
+| --- | --- | --- |
+| `parent` | `UUIDField` | Nullable. Spec: `UUID4 \| None` — `external_id` of a parent observation, used to nest related observations |
+| `component` | `JSONField` (default `[]`) | Spec: `Component[]` (see shape below), default `[]`. Sub-observations that share a context but carry their own codes/values (FHIR `Observation.component`) |
+| `questionnaire_response` | `FK → QuestionnaireResponse` | `CASCADE`, nullable. Spec field is `UUID4 \| None`. The questionnaire submission that produced this observation |
+| `diagnostic_report` | `FK → DiagnosticReport` | `CASCADE`, nullable. The report this observation belongs to, if any. Not in the write specs |
+| `observation_definition` | `FK → ObservationDefinition` | `CASCADE`, nullable. The definition that templates this observation. Serialized (as `BaseObservationDefinitionSpec`) only in `ObservationRetrieveSpec` |
+
+## Enum & value-set reference
+
+### `ObservationStatus` values
+
+| Value | Meaning |
+| --- | --- |
+| `final` | Recorded, complete and verified. Default for questionnaire-sourced observations |
+| `amended` | Previously final, subsequently corrected |
+| `entered_in_error` | Recorded in error; should be disregarded |
+
+### `PerformerType` values (`performer.type`)
+
+| Value |
+| --- |
+| `related_person` |
+| `user` |
+
+### `SubjectType` values (`subject_type`)
+
+| Value |
+| --- |
+| `patient` |
+| `encounter` (spec default) |
+
+### `QuestionType` values (`value_type`)
+
+Discriminator borrowed from the questionnaire module. Tells the client how to read `value`.
+
+| Value | Value sourced from `QuestionnaireSubmitResultValue` |
+| --- | --- |
+| `group` | grouping node (no direct value) |
+| `boolean` | `value` (`"true"`/`"false"`) |
+| `decimal` | `value` |
+| `integer` | `value` |
+| `string` | `value` |
+| `text` | `value` |
+| `display` | display-only |
+| `date` | `value` |
+| `dateTime` | `value` |
+| `time` | `value` |
+| `choice` | `coding` |
+| `url` | `value` |
+| `quantity` | `value` + `unit` |
+| `structured` | structured payload |
+
+### Bound value sets
+
+Coded fields validate against registered Care value sets. A `body_site` or `method` coding that is not a member of its value set is rejected on deserialization.
+
+| Field | Value set | Slug | System(s) |
+| --- | --- | --- | --- |
+| `main_code` | `CARE_OBSERVATION_VALUSET` | `system-observation` | `http://loinc.org` |
+| `body_site` | `CARE_BODY_SITE_VALUESET` | `system-body-site-observation` | `http://snomed.info/sct` (curated concept list + descendants of `442083009`) |
+| `method` | `CARE_OBSERVATION_COLLECTION_METHOD` | `system-collection-method` | `http://snomed.info/sct` (descendants of `272394005`, `129264002`, `386053000`) |
+
+Additional interpretation value sets are registered for use by the FE / observation definitions (not directly enforced on the `interpretation` dict):
+
+| Value set | Slug | Concepts (system `http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation`) |
+| --- | --- | --- |
+| Normal | `system-normal-coded-valueset` | `N` Normal, `ND` Not detected, `NEG` Negative |
+| Abnormal | `system-abnormal-coded-valueset` | `A` Abnormal, `AA` Critically abnormal, `H` High, `HH` Critically high, `L` Low, `LL` Critically low, `POS` Positive, `DET` Detected |
+| Critical | `system-critical-coded-valueset` | `C` Critical, `CC` Critical high, `CL` Critical low, `CV` Critical value |
+| UCUM units | `system-ucum-units` | `http://unitsofmeasure.org` (all UCUM units) |
+
+## Nested JSON shapes (from the spec)
+
+These are the real structures behind the model's `JSONField`s.
+
+### `Coding`
+
+```text
+Coding {
+ system: str | null # e.g. http://loinc.org
+ version: str | null
+ code: str # required
+ display: str | null
+} # extra keys forbidden
+```
+
+### `CodeableConcept` (`alternate_coding`)
+
+```text
+CodeableConcept {
+ id: str | null
+ coding: Coding[] | null
+ text: str | null # field present, required key
+} # extra keys forbidden
+```
+
+### `QuestionnaireSubmitResultValue` (`value`)
+
+```text
+QuestionnaireSubmitResultValue {
+ value: str | null # raw/string value
+ unit: Coding | null # for quantity values
+ coding: Coding | null # for coded values
+}
+```
+
+### `Performer` (`performer`)
+
+```text
+Performer {
+ type: PerformerType # related_person | user
+ id: str
+}
+```
+
+### `ReferenceRange` (`reference_range[]`)
+
+```text
+ReferenceRange {
+ min: float | null
+ max: float | null
+ unit: str | null
+ interpretation: str # required
+ value: str | null
+}
+```
+
+### `Component` (`component[]`)
+
+```text
+Component {
+ value: QuestionnaireSubmitResultValue # required
+ interpretation: str | dict = {}
+ reference_range: ReferenceRange[] = []
+ code: Coding | null = null
+ note: str = ""
+}
+```
+
+## Resource specs (API schema)
+
+The Pydantic specs build on [`EMRResource`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py) (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). All concrete specs inherit from `BaseObservationSpec`.
+
+| Spec | Role | Notes |
+| --- | --- | --- |
+| `BaseObservationSpec` | shared base | Defines all common fields: `id`, `status`, `category`, `main_code`, `alternate_coding`, `subject_type`, `encounter`, `effective_datetime`, `performer`, `value_type`, `value`, `note`, `body_site` (bound), `method` (bound), `reference_range`, `interpretation`, `parent`, `questionnaire_response`, `component` |
+| `ObservationSpec` | write · create | Adds `data_entered_by_id`, `created_by_id`, `updated_by_id` (all `int`). Used internally when materializing observations from a questionnaire submission |
+| `ObservationUpdateSpec` | write · update | Same as base but relaxes `effective_datetime` and `value` to optional (`None`) for partial updates |
+| `ObservationReadSpec` | read · list | Replaces user IDs with nested `created_by` / `updated_by` / `data_entered_by` `UserSpec` objects |
+| `ObservationRetrieveSpec` | read · detail | Extends `ObservationReadSpec`; additionally serializes `observation_definition` via `BaseObservationDefinitionSpec` when present |
+
+### Server-side behaviour
+
+- `ObservationSpec.perform_extra_deserialization` (create/update): sets `obj.external_id = self.id`, copies `data_entered_by_id`, `created_by_id`, `updated_by_id` onto the model, drops `data_entered_by_id` from `meta`, and on **create** (`is_update` is false) clears `obj.id` so a new row is inserted.
+- `ObservationReadSpec.perform_extra_serialization`: maps `id` from `external_id`; deliberately sets `encounter`, `patient`, and `questionnaire_response` to `None` in the serialized output to avoid extra DB queries; resolves audit users (`created_by`, `updated_by`) and `data_entered_by` from cache (`model_from_cache`).
+- `ObservationRetrieveSpec.perform_extra_serialization`: calls the parent, then inlines the full `observation_definition` when the FK is set.
+- `body_site` / `method` are `ValueSetBoundCoding[...]`: the supplied `Coding` is validated against its bound value set during deserialization and rejected if the code is not a member.
+- `de_serialize` only writes fields present in the model's column mapping; non-default values are persisted (`model_dump(exclude_defaults=True)`), and unknown spec-only fields would be stored in `meta` when `__store_metadata__` is set (it is not, by default, for observations).
+
+## Related models
+
+`Observation` is a single self-contained model; its relationships are expressed through foreign keys rather than secondary tables.
+
+```text
+patient → FK Patient (CASCADE)
+encounter → FK Encounter (CASCADE)
+data_entered_by → FK users.User (CASCADE, nullable)
+questionnaire_response → FK QuestionnaireResponse (CASCADE, nullable)
+diagnostic_report → FK DiagnosticReport (CASCADE, nullable)
+observation_definition → FK ObservationDefinition (CASCADE, nullable)
+```
+
+`parent` is a soft, UUID-based self-reference (not a Django FK) to another observation's `external_id`, allowing observations to be nested without enforcing a database constraint.
+
+## API integration notes
+
+- Observations are exposed through Care's REST API and align with the FHIR `Observation` resource. API/FHIR field names (e.g. `effectiveDateTime`, `bodySite`) may differ from the Django model names; the resource specs above are the authoritative request/response schemas.
+- Most observations are created as a side effect of submitting a questionnaire — `value` and `value_type` mirror the questionnaire submission types (`QuestionnaireSubmitResultValue` / `QuestionType`), and the linked `questionnaire_response` records provenance.
+- `effective_datetime` must be timezone-aware. It is required on create and optional on update.
+- `status` is constrained to `final` / `amended` / `entered_in_error`; questionnaire-sourced observations are `final`.
+- Coded fields `body_site` and `method` are validated against their bound value sets; `main_code` is expected to bind to LOINC via `CARE_OBSERVATION_VALUSET`.
+- On read, the list/detail specs blank out `encounter`, `patient`, and `questionnaire_response` to avoid extra queries — clients should not rely on those being populated in observation list payloads.
+- Prefer **structured** observations: storing arbitrary unstructured data is discouraged, since business logic (e.g. allergy/medication checks) relies on coded, structured values rather than free text.
+
+## Related
+
+- Reference: [Observation Definition](../clinical/observation-definition.mdx)
+- Reference: [Diagnostic Report](../clinical/diagnostic-report.mdx)
+- Reference: [Questionnaire](../forms/questionnaire.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
+- Source: [observation.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/observation.py)
+- Source: [observation/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation/spec.py)
+- Source: [observation/valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation/valueset.py)
+- Source: [resources/base.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
diff --git a/versioned_docs/version-3.0/references/clinical/service-request.mdx b/versioned_docs/version-3.0/references/clinical/service-request.mdx
new file mode 100644
index 0000000..5cbde59
--- /dev/null
+++ b/versioned_docs/version-3.0/references/clinical/service-request.mdx
@@ -0,0 +1,199 @@
+---
+sidebar_position: 12
+---
+
+# Service Request
+
+Technical reference for the `ServiceRequest` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/service_request.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/service_request.py)
+- Resource spec: [`care/emr/resources/service_request/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/service_request/spec.py)
+
+A service request is an order or proposal to perform some action — clinical or otherwise — for a patient (FHIR `ServiceRequest`). It can drive the creation of specimens, diagnostic reports, observations, and procedures, all of which link back to the originating request. Care implements a minimal subset of the FHIR spec.
+
+The Django model is the **storage** layer: several columns are opaque `JSONField`s and free-text `CharField`s whose real structure and allowed values live in the **Pydantic resource specs** (`care/emr/resources/service_request/spec.py`). The specs define the enums, the bound value sets, the coded-field shapes, and the read/write API schemas. This page documents both.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ServiceRequest` | An order or proposal to perform an action for a patient |
+
+`ServiceRequest` extends `EMRBaseModel` (shared Care EMR base with `external_id`, audit fields `created_by`/`updated_by`, `created_date`/`modified_date`, `meta`, and soft-delete semantics — see [Base model](../foundation/base-model.mdx)).
+
+## `ServiceRequest` fields
+
+### Request definition
+
+| Field | Model type | Spec type / shape | Notes |
+| --- | --- | --- | --- |
+| `title` | `CharField(1024)` | `str` | Human-readable summary. Required on create; optional on update. |
+| `category` | `CharField(255)` | `ActivityDefinitionCategoryOptions` enum | Coded category. Required on create. See [category values](#category-values). |
+| `status` | `CharField(255)` | `ServiceRequestStatusChoices` enum | Lifecycle status. Required on create. See [status values](#status-values). |
+| `intent` | `CharField(255)` | `ServiceRequestIntentChoices` enum | Required on create. See [intent values](#intent-values). |
+| `priority` | `CharField(255)` | `ServiceRequestPriorityChoices` enum | Required on create. See [priority values](#priority-values). |
+| `do_not_perform` | `BooleanField` | `bool \| None` | `True` if the service/procedure should **not** be performed. Model default `False`; spec default `None` (omitted unless set). |
+| `code` | `JSONField` (nullable) | `Coding` bound to `activity-definition-procedure-code` | The requested service/procedure. **Required** on create (single `Coding`, not `CodeableConcept`). Value validated against the bound value set. |
+| `body_site` | `JSONField` (nullable) | `Coding` bound to `system-body-site-observation`, optional | Coded location on the body. Single `Coding`, validated against the bound value set. |
+
+A bound `Coding` (`ValueSetBoundCoding`) has the shape:
+
+```json
+{
+ "system": "",
+ "version": "",
+ "code": "", // required; must resolve in the bound value set
+ "display": ""
+}
+```
+
+### Clinical detail
+
+| Field | Model type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `note` | `TextField` (nullable) | `str \| None` | Free-text comments. |
+| `patient_instruction` | `TextField` (nullable) | `str \| None` | Instructions surfaced to the patient. |
+| `occurance` | `DateTimeField` (nullable) | `datetime \| None` | When the request should take place. Spelled `occurance` in both model and spec (note the typo). |
+
+### Relationships
+
+| Field | Model type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → facility.Facility` | (not in write spec) | Owning facility. `PROTECT`. Set server-side from request context. |
+| `patient` | `FK → emr.Patient` | (derived) | Subject of the request. `CASCADE`. Not accepted from clients — set server-side from `encounter.patient` on create. |
+| `encounter` | `FK → emr.Encounter` (nullable) | `UUID4` (create only) | Encounter the request was created in. `CASCADE`. Required (UUID) on `Create`; resolved via `get_object_or_404`. Read back as a serialized encounter dict. |
+| `healthcare_service` | `FK → emr.HealthcareService` (nullable) | `UUID4 \| None` | Service fulfilling the request. `PROTECT`. Write accepts `external_id`; resolved server-side. Excluded from base serialize; returned only on `Retrieve`. |
+| `activity_definition` | `FK → emr.ActivityDefinition` (nullable) | (read only) | Template the request was created from. `PROTECT`. Serialized only on `Retrieve`. |
+| `requester` | `FK → users.User` (nullable) | `UUID4 \| None` (write) / `dict \| None` (read) | User who placed the request. `CASCADE`. Write accepts `external_id`; read returns a cached `UserSpec` dict. |
+| `locations` | `ArrayField[int]` | `list[UUID4]` (write) / `list[dict]` (read) | Facility-location IDs the request applies to. Model default `[]`. Write accepts location `external_id`s (stored on `obj._locations` for the manager to persist); serialized as `FacilityLocationListSpec` dicts only on `Retrieve`. |
+| `tags` | `ArrayField[int]` | `list[dict]` (read) | Tag IDs. Model default `[]`. Rendered via `SingleFacilityTagManager().render_tags(obj)` on read; not set directly from the write spec. |
+
+## Enums
+
+### Status values
+
+`ServiceRequestStatusChoices` — note values are **snake_case**, not hyphenated FHIR codes.
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Request is being drafted, not yet actionable |
+| `active` | Request is active |
+| `on_hold` | Request is paused |
+| `entered_in_error` | Request was entered in error |
+| `ended` | Request has ended |
+| `completed` | Request has been fulfilled |
+| `revoked` | Request was revoked/cancelled |
+
+Derived groupings in `spec.py`:
+- `SERVICE_REQUEST_COMPLETED_CHOICES` = `completed`, `revoked`, `ended`, `entered_in_error` (terminal states).
+- `SERVICE_REQUEST_CANCELLED_CHOICES` = `revoked`, `entered_in_error`.
+
+There is **no** `unknown` status.
+
+### Intent values
+
+`ServiceRequestIntentChoices`
+
+| Value |
+| --- |
+| `proposal` |
+| `plan` |
+| `directive` |
+| `order` |
+
+### Priority values
+
+`ServiceRequestPriorityChoices`
+
+| Value |
+| --- |
+| `routine` |
+| `urgent` |
+| `asap` |
+| `stat` |
+
+### Category values
+
+`category` reuses `ActivityDefinitionCategoryOptions` (from `care/emr/resources/activity_definition/spec.py`).
+
+| Value |
+| --- |
+| `laboratory` |
+| `imaging` |
+| `counselling` |
+| `surgical_procedure` |
+| `education` |
+
+## Bound value sets
+
+Coded `Coding` fields validate their `code` against a registered Care value set (`ValueSetBoundCoding[...]`).
+
+| Field | Value set slug | Composition |
+| --- | --- | --- |
+| `code` | `activity-definition-procedure-code` | SNOMED CT `is-a 71388002` (Procedure). See [`activity_definition/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/valueset.py). |
+| `body_site` | `system-body-site-observation` | SNOMED CT — explicit limb/joint concepts plus `is-a 442083009`. See [`observation/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation/valueset.py). |
+
+## Resource specs (API schema)
+
+All specs subclass `EMRResource` (`serialize` / `de_serialize`, `perform_extra_serialization` / `perform_extra_deserialization` hooks — see [Base model](../foundation/base-model.mdx)). `__model__ = ServiceRequest`; `__exclude__ = ["encounter", "healthcare_service", "locations"]` on the base, so those three are handled only by the explicit per-spec hooks.
+
+| Spec class | Role | Adds / overrides |
+| --- | --- | --- |
+| `BaseServiceRequestSpec` | shared | `id`, `title`, `status`, `intent`, `priority`, `category`, `do_not_perform`, `note`, `code` (required), `body_site`, `occurance`, `patient_instruction` |
+| `ServiceRequestWriteSpec` | write · shared | Adds `healthcare_service: UUID4?`, `locations: list[UUID4]`, `requester: UUID4?`. Resolves `healthcare_service` and `requester` from `external_id`; stashes `locations` on `obj._locations`. |
+| `ServiceRequestCreateSpec` | write · create | Adds `encounter: UUID4` (**required**). Resolves the encounter and sets `obj.patient = obj.encounter.patient` server-side. |
+| `ServiceRequestUpdateSpec` | write · update | Makes `title`, `status`, `intent`, `priority`, `category`, `code` all optional (partial update). |
+| `ServiceRequestReadSpec` | read · list | Adds `created_date`, `modified_date`, `encounter` (serialized via `EncounterListSpec`), `tags` (rendered), `requester` (cached `UserSpec`). Sets `id = external_id`. |
+| `ServiceRequestRetrieveSpec` | read · detail | Extends Read with `locations` (`FacilityLocationListSpec`), `healthcare_service` (`HealthcareServiceReadSpec`), `activity_definition` (`ActivityDefinitionReadSpec`), `specimens` (`SpecimenReadSpec`), `diagnostic_reports` (`DiagnosticReportListSpec`), embedded `encounter.patient` (`PatientRetrieveSpec`), and audit `created_by`/`updated_by`. |
+
+### Write-side behaviour (`perform_extra_deserialization`)
+
+- `healthcare_service` (when provided): looked up by `external_id` and assigned.
+- `requester` (when provided): looked up by `external_id` via `get_object_or_404(User, ...)`.
+- `locations`: the list of UUIDs is stored on `obj._locations`; the persisting view/manager translates them to integer IDs for the `locations` array.
+- **Create only:** `encounter` is required and fetched via `get_object_or_404(Encounter, ...)`, then `obj.patient` is forced to `encounter.patient` — clients cannot set `patient` directly.
+
+### Read-side behaviour (`perform_extra_serialization`)
+
+- `id` is set to `external_id`; `encounter` is serialized with `EncounterListSpec`; `tags` come from `SingleFacilityTagManager().render_tags(obj)`; `requester` (if set) is a cached `UserSpec`.
+- **Retrieve only:** queries `FacilityLocation` for `obj.locations`; serializes `healthcare_service` and `activity_definition` if present; fetches related `Specimen` and `DiagnosticReport` rows that reference this request; embeds the patient (`PatientRetrieveSpec`, scoped to `obj.facility`) under `encounter.patient`; and attaches audit users.
+
+## Methods & save behaviour
+
+- `ServiceRequest` inherits `save`, soft-delete, and audit behaviour from `EMRBaseModel` (see [Base model](../foundation/base-model.mdx)). No custom `save` override is defined on the model.
+- All write validation (enums, required `code`, bound value sets, encounter requirement) is enforced by the Pydantic write specs during `de_serialize`, not by Django field choices — the DB columns are plain `CharField`/`JSONField`.
+- `patient` is never accepted from the client; it is derived from `encounter.patient` on create.
+- Related `Specimen` and `DiagnosticReport` records reference the request via their own `service_request` FK; the request's detail view (`Retrieve`) reads them back rather than storing them.
+
+## API integration notes
+
+- `ServiceRequest` is exposed through Care's REST API and aligns with FHIR `ServiceRequest`, but field/enum spellings follow Care conventions: status/intent/priority are **snake_case** enum values and `occurance` keeps its model typo.
+- `code` is **required** and `body_site` optional; both are single `Coding` objects validated against their bound value sets — not `CodeableConcept`.
+- `category`, `status`, `intent`, and `priority` are validated as enums by the spec even though the columns are free `CharField`s.
+- Write `healthcare_service`, `requester`, and `locations` as `external_id`/UUIDs; the server resolves them. `encounter` (UUID) is required on create and determines the patient.
+- `locations` and `tags` store ID arrays for fast filtering — drive them through the location/tag managers rather than setting the raw arrays from clients.
+
+## Related models
+
+- [Activity Definition](./activity-definition.mdx) — template a request can be created from; supplies the `category` enum and `code` value set
+- [Encounter](./encounter.mdx) — required context on create; supplies the patient
+- [Patient](./patient.mdx) — subject, derived from the encounter
+- [Specimen](./specimen.mdx) — specimens collected for a request
+- [Diagnostic Report](./diagnostic-report.mdx) — reports produced for a request
+- [Observation](./observation.mdx) — shares the body-site value set
+- [Healthcare Service](../facility/healthcare-service.mdx) — service fulfilling the request
+- [Location](../facility/location.mdx) — facility locations the request applies to
+- [User](../access/user.mdx) — requester / audit users
+
+## Related
+
+- Reference: [Activity Definition](./activity-definition.mdx)
+- Reference: [Specimen](./specimen.mdx)
+- Reference: [Diagnostic Report](./diagnostic-report.mdx)
+- Reference: [Observation](./observation.mdx)
+- Reference: [Encounter](./encounter.mdx)
+- Reference: [Patient](./patient.mdx)
+- Reference: [Healthcare Service](../facility/healthcare-service.mdx)
+- Source: [service_request.py (model) on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/service_request.py)
+- Source: [service_request/spec.py (resource spec) on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/service_request/spec.py)
diff --git a/versioned_docs/version-3.0/references/clinical/specimen-definition.mdx b/versioned_docs/version-3.0/references/clinical/specimen-definition.mdx
new file mode 100644
index 0000000..4f41293
--- /dev/null
+++ b/versioned_docs/version-3.0/references/clinical/specimen-definition.mdx
@@ -0,0 +1,163 @@
+---
+sidebar_position: 11
+---
+
+# Specimen Definition
+
+Technical reference for the `SpecimenDefinition` module in Care EMR.
+
+A `SpecimenDefinition` is a reusable, facility-scoped template describing a *kind* of specimen — what material to collect, how to prepare the patient, how to collect it, and how it is held in a container for testing. It lets a lab maintain a repository of specimen kinds, and an [Activity Definition](activity-definition.mdx) / [Service Request](service-request.mdx) can reference it so the same container/handling rules are applied consistently. A concrete [Specimen](specimen.mdx) can be instantiated from a definition (the link is preserved, and the specimen keeps a copy of the definition's data for history/integrity).
+
+The Django model is the **storage** layer: `type_collected`, `patient_preparation`, `collection`, and `type_tested` are opaque `JSONField`s. Their real structure, the enums, value-set bindings, and the read/write API schemas live in the **Pydantic resource specs** documented below.
+
+**Source:**
+
+- Model: [`care/emr/models/specimen_definition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/specimen_definition.py)
+- Resource spec: [`care/emr/resources/specimen_definition/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen_definition/spec.py)
+- Value sets: [`care/emr/resources/specimen_definition/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen_definition/valueset.py)
+- Conversion helper: [`care/emr/resources/specimen_definition/specimen.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen_definition/specimen.py)
+- ViewSet: [`care/emr/api/viewsets/specimen_definition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/specimen_definition.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `SpecimenDefinition` | Reusable definition of a kind of specimen to be collected and processed — what to collect, how to prepare the patient, how to collect it, and how it is contained and tested |
+
+`SpecimenDefinition` extends [`SlugBaseModel`](../foundation/base-model.mdx) (the Care EMR base augmented with `FACILITY_SCOPED = True`, a facility-scoped `slug`, plus `external_id`, `history`/`meta` JSON, audit fields, and soft-delete semantics).
+
+## `SpecimenDefinition` fields
+
+### Identity & status
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → facility.Facility` (PROTECT) | model: optional | `null=True`, `blank=True`, `default=None`. In the API it is **never client-supplied** — the ViewSet sets it server-side from the `facility_external_id` URL kwarg, so every API-created definition is facility-scoped. Excluded from both spec schemas (`__exclude__ = ["facility"]`) |
+| `version` | `IntegerField` | default `1` | Definition version. Read-only over the API (exposed by `SpecimenDefinitionReadSpec` only, never accepted on write) |
+| `slug` | `CharField(255)` | yes (server-built) | Stored as the **fully-qualified** slug `f--` (see [Methods & save behaviour](#methods--save-behaviour)). Clients send the bare `slug_value`, not this |
+| `title` | `CharField(1024)` | yes | Human-readable name |
+| `derived_from_uri` | `TextField` | optional | `null=True`, `blank=True`. URI of the canonical/external definition this was derived from |
+| `status` | `CharField(255)` | yes | Lifecycle status. Constrained by `SpecimenDefinitionStatusOptions` — see [enum](#specimendefinitionstatusoptions-values) |
+| `description` | `TextField` | yes | Natural-language description (markdown) |
+
+### Collection & testing detail (JSON fields)
+
+The model stores these as raw JSON; the spec defines their true shape and value-set bindings.
+
+| Field | Model type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `type_collected` | `JSONField` (`null=True`) | `Coding` bound to **Specimen Type Code** value set | Required on write. The kind of material collected. See [value sets](#bound-value-sets) |
+| `patient_preparation` | `JSONField` (`default=list`) | `list[Coding]` bound to **Prepare Patient Prior Specimen Code** value set | Defaults to `[]`. Preparation steps the patient follows before collection |
+| `collection` | `JSONField` (`null=True`) | `Coding` bound to **Specimen Collection Code** value set, optional | The specimen collection procedure |
+| `type_tested` | `JSONField` (`default=dict`) | `TypeTestedSpec` (nested), optional | The container + handling + testing detail. See [`TypeTestedSpec`](#typetestedspec) |
+
+> Each bound coded field accepts a `Coding` object (`{ system, version?, code, display? }`); the `code` is validated against the bound value set at de-serialization time via `ValueSetBoundCoding`.
+
+## Related models
+
+| Type | Where | Shape |
+| --- | --- | --- |
+| `Coding` | [`resources/common/coding.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py) | `{ system: str?, version: str?, code: str (required), display: str? }`, `extra="forbid"` |
+| `QuantitySpec` | spec.py | `{ value: Decimal (max_digits=20, decimal_places=0), unit: Coding }` — note `decimal_places=0`, i.e. integer-valued |
+| `MinimumVolumeSpec` | spec.py | `{ quantity: QuantitySpec?, string: str? }`; validator forbids supplying **both** |
+| `ContainerSpec` | spec.py | `{ description: str?, capacity: QuantitySpec?, minimum_volume: MinimumVolumeSpec?, cap: Coding? (bound to Container Cap), preparation: str? }` |
+| `DurationSpec` | spec.py | `{ value: Decimal (max_digits=20, decimal_places=0), unit: Coding }` (`unit` not yet restricted to datetime units) |
+| `RangeSpec` | spec.py | `{ low: QuantitySpec?, high: QuantitySpec? }` |
+| `HandlingSpec` | spec.py | `{ temperature_qualifier: HandlingConditionOptions?, temperature_range: RangeSpec?, max_duration: DurationSpec?, instruction: str? }` |
+| `TypeTestedSpec` | spec.py | see [`TypeTestedSpec`](#typetestedspec) below |
+
+### `TypeTestedSpec`
+
+The structured shape of the `type_tested` JSON field. Care currently supports **a single container per definition** — for multiple containers per test, repeat the definition in the [Activity Definition](activity-definition.mdx) spec.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `is_derived` | `bool` | yes | Primary (`false`) vs secondary/derived (`true`) specimen |
+| `preference` | `PreferenceOptions` | yes | `preferred` / `alternate` — see [enum](#preferenceoptions-values) |
+| `container` | `ContainerSpec` | optional | The specimen's container (description, capacity, minimum volume, cap, preparation) |
+| `requirement` | `str` | optional | Requirements for delivery / special handling (markdown) |
+| `retention_time` | `DurationSpec` | optional | Usual time this kind of specimen is retained |
+| `single_use` | `bool` | optional | Specimen for single use only |
+| `handling` | `HandlingSpec` | optional | Temperature qualifier/range, max duration, instruction |
+
+## Enums
+
+### `SpecimenDefinitionStatusOptions` values
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Definition is being authored, not yet in use |
+| `active` | Definition is in use |
+| `retired` | Definition is withdrawn from use |
+
+### `PreferenceOptions` values
+
+| Value | Meaning |
+| --- | --- |
+| `preferred` | Preferred specimen for the test |
+| `alternate` | Acceptable alternative specimen |
+
+### `HandlingConditionOptions` values
+
+| Value | Meaning |
+| --- | --- |
+| `room` | Room temperature |
+| `refrigerated` | Refrigerated |
+| `frozen` | Frozen |
+
+## Resource specs (API schema)
+
+All specs build on `EMRResource` (`serialize`/`de_serialize`, `perform_extra_serialization`/`perform_extra_deserialization`). `__exclude__ = ["facility"]` keeps `facility` out of both directions. The ViewSet wires `pydantic_model = SpecimenDefinitionWriteSpec` (create/update) and `pydantic_read_model = SpecimenDefinitionReadSpec` (list/retrieve).
+
+| Spec class | Role | Fields / behaviour |
+| --- | --- | --- |
+| `BaseSpecimenDefinitionSpec` | shared base | `id`, `title`, `derived_from_uri`, `status`, `description`, `type_collected`, `patient_preparation`, `collection`, `type_tested`. Holds the coded-field value-set bindings and the nested `type_tested` shape |
+| `SpecimenDefinitionWriteSpec` | write · create & update | Base fields **plus** `slug_value: SlugType`. `perform_extra_deserialization` copies `slug_value` → `obj.slug` (ViewSet then fully-qualifies it). `facility`, `version`, and the stored `slug` are never client-supplied |
+| `SpecimenDefinitionReadSpec` | read · list & detail | Base fields **plus** `version: int?`, `slug: str`, `slug_config: dict`. `perform_extra_serialization` sets `id = obj.external_id` and `slug_config = obj.parse_slug(obj.slug)` (decomposes the qualified slug into `{ facility, slug_value }`) |
+
+Nested specs used by the schemas: `TypeTestedSpec`, `ContainerSpec`, `MinimumVolumeSpec`, `QuantitySpec`, `DurationSpec`, `RangeSpec`, `HandlingSpec` (see [Related models](#related-models)).
+
+### Validation rules
+
+- `slug_value` (`SlugType`): string, length 5–50, must match `^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$` (URL-safe; starts and ends alphanumeric).
+- `type_collected`, `patient_preparation[*]`, `collection`, `container.cap`: each `Coding.code` is validated against its bound value set by `ValueSetBoundCoding` during de-serialization; an unknown code is rejected.
+- `MinimumVolumeSpec`: `quantity` and `string` are mutually exclusive — supplying both raises `"Only one of quantity or string should be provided"`.
+- `QuantitySpec` / `DurationSpec` `value`: `Decimal`, `max_digits=20`, `decimal_places=0` (integer-valued).
+- Slug uniqueness (ViewSet `validate_data`): the fully-qualified `f--` must be unique within the facility (case-insensitive); otherwise `"Specimen Definition with this slug already exists."` On update, the current record is excluded from the check.
+
+### Bound value sets
+
+| Field | Value set | Slug | Source system(s) |
+| --- | --- | --- | --- |
+| `type_collected` | Specimen Type Code | `system-specimen_type-code` | HL7 v2-0487 (unbounded include) |
+| `patient_preparation[*]` | Prepare Patient Prior Specimen Code | `system-prepare_patient_prior_specimen_code` | SNOMED CT, `is-a 703763000` |
+| `collection` | Specimen Collection Code | `system-specimen_collection_code` | SNOMED CT (curated list: aspiration, biopsy, puncture, excision, scraping, clean-catch/timed/catheterized urine, coughed sputum, finger-prick) |
+| `container.cap` | Container Cap | `system-container_cap-code` | HL7 `container-cap` code system |
+
+## Methods & save behaviour
+
+`SpecimenDefinition` defines no overridden `save()`/`delete()` or custom methods. Slug helpers, audit fields, and soft-delete are inherited from [`SlugBaseModel`](../foundation/base-model.mdx). The side effects below live in the resource spec and the ViewSet, not the model:
+
+- **Slug qualification (write).** Clients send a bare `slug_value`. `SpecimenDefinitionWriteSpec.perform_extra_deserialization` sets `obj.slug = slug_value`, then the ViewSet's `perform_create`/`perform_update` rewrites it to `SpecimenDefinition.calculate_slug_from_facility(facility.external_id, slug)` → `f--`.
+- **Facility binding (create).** `perform_create` sets `instance.facility` from the `facility_external_id` URL kwarg before saving.
+- **Slug decomposition (read).** `SpecimenDefinitionReadSpec.perform_extra_serialization` exposes `slug_config = obj.parse_slug(obj.slug)`, splitting the stored slug back into `{ facility, slug_value }`.
+- **Specimen instantiation.** `convert_sd_to_specimen` ([`specimen.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen_definition/specimen.py)) builds a [`Specimen`](specimen.mdx) from a definition with `status="available"`, `specimen_type = definition.type_collected`, and `specimen_definition = ` — the link and a copy of the type are preserved.
+
+## API integration notes
+
+- **Endpoints are facility-scoped.** The ViewSet supports create, retrieve, update, list, and upsert; `lookup_field = "slug"`. All operations require `facility_external_id` in the URL.
+- **Authorization.** Writes require `can_write_facility_specimen_definition`; list/retrieve require `can_list_facility_specimen_definition` on the facility — otherwise `403 PermissionDenied`. The queryset is filtered to the URL facility.
+- **Filtering / ordering.** `status` (iexact) and `title` (icontains) filters; ordering by `created_date` / `modified_date`.
+- **Write payload** (`SpecimenDefinitionWriteSpec`): send `slug_value`, `title`, `status`, `description`, coded fields (`type_collected`, `patient_preparation`, `collection`) as `Coding` objects, and `type_tested`. Do **not** send `facility`, `version`, or the qualified `slug` — they are server-managed.
+- **Read payload** (`SpecimenDefinitionReadSpec`): adds `id` (= `external_id`), `version`, the qualified `slug`, and `slug_config`.
+- The model maps to the FHIR `SpecimenDefinition` resource. Care's spec is intentionally minimal (single container per definition; `version` bump rather than mutation of published records when behaviour changes).
+
+## Related
+
+- Reference: [Specimen](specimen.mdx)
+- Reference: [Service Request](service-request.mdx)
+- Reference: [Observation Definition](observation-definition.mdx)
+- Reference: [Activity Definition](activity-definition.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Reference: [Valueset](../forms/valueset.mdx)
+- Source: [model on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/specimen_definition.py) · [spec on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen_definition/spec.py) · [value sets on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen_definition/valueset.py)
diff --git a/versioned_docs/version-3.0/references/clinical/specimen.mdx b/versioned_docs/version-3.0/references/clinical/specimen.mdx
new file mode 100644
index 0000000..2664574
--- /dev/null
+++ b/versioned_docs/version-3.0/references/clinical/specimen.mdx
@@ -0,0 +1,209 @@
+---
+sidebar_position: 10
+---
+
+# Specimen
+
+Technical reference for the `Specimen` module in Care EMR.
+
+The Django model (`care/emr/models/specimen.py`) is the **storage layer** — several fields are opaque `JSONField`s whose real structure is defined only in the Pydantic **resource specs** (`care/emr/resources/specimen/`). The specs define the enums, the nested JSON shapes, coded value-set bindings, validation, and the read/write API schemas. This page documents both layers.
+
+**Source:**
+- Model: [`care/emr/models/specimen.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/specimen.py)
+- Resource specs: [`care/emr/resources/specimen/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen/spec.py), [`valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen/valueset.py)
+- Viewset: [`care/emr/api/viewsets/specimen.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/specimen.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Specimen` | A physical sample (blood, tissue, swab, etc.) collected from a patient for laboratory analysis |
+
+`Specimen` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, soft-delete via `deleted`, and `history`/`meta` JSON).
+
+## `Specimen` fields
+
+### Identity & status
+
+| Field | Model type | Spec shape / values | Notes |
+| --- | --- | --- | --- |
+| `accession_identifier` | `CharField(255)`, `db_index=True` | `str`, default `""` | Lab accession/barcode identifier; indexed for lookup. Spec default is empty string (not required on write). |
+| `status` | `CharField(20)` | `SpecimenStatusOptions` enum | Specimen status. Required on base/create; optional on update. See [status values](#specimenstatusoptions-values). |
+| `specimen_type` | `JSONField` | `Coding` bound to value set `system-specimen_type-code` | Kind of material collected (single coding). Required on base/create; optional on update. |
+
+### Clinical links
+
+| Field | Model type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → Facility` | `PROTECT`, nullable; facility the specimen belongs to. Excluded from all specs (`__exclude__`). |
+| `patient` | `FK → Patient` | `CASCADE`; the patient the specimen was collected from. Set on create via `subject_patient` (UUID). |
+| `encounter` | `FK → Encounter` | `CASCADE`, nullable; encounter during collection. Set on create via `subject_encounter` (UUID). |
+| `service_request` | `FK → ServiceRequest` | `CASCADE`, nullable; the order this specimen fulfills. Set on create via `request` (UUID); excluded from base/read field copy. |
+| `specimen_definition` | `FK → SpecimenDefinition` | `CASCADE`, nullable; the definition/template this specimen was derived from. |
+
+### Collection & processing
+
+| Field | Model type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `collection` | `JSONField` `default=dict` | `CollectionSpec \| None`, default `None` | Single collection event (collector, method, body site, quantity, fasting). See [CollectionSpec](#collectionspec). |
+| `received_time` | `DateTimeField`, nullable | `str \| None`, default `None` | When the lab received the specimen (ISO 8601 string in the spec). |
+| `condition` | `JSONField` `default=list` | `list[Coding]` bound to `system-specimen-condition-code`, default `[]` | Coded conditions/state of the specimen on receipt. |
+| `processing` | `JSONField` `default=list` | `list[ProcessingSpec]`, default `[]` | Processing steps applied. See [ProcessingSpec](#processingspec). |
+| `note` | `TextField`, nullable | `str \| None`, default `None` | Free-text annotation. |
+
+## Enums
+
+### `SpecimenStatusOptions` values
+
+`status` enum (`str`). Note: `entered_in_error` uses an underscore (not the FHIR hyphen), and `draft` is a Care-specific addition not in FHIR.
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Care-specific; not from FHIR |
+| `available` | Specimen available for testing |
+| `unavailable` | Specimen no longer available |
+| `unsatisfactory` | Specimen unsuitable for testing |
+| `entered_in_error` | Recorded in error |
+
+## Nested JSON specs
+
+These define the real structure of the model's `JSONField`s.
+
+### `CollectionSpec`
+
+Shape of the `collection` JSON field (a single collection event).
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `collector` | `UUID4 \| None` | optional | External ID of collecting `User`. Validated to exist (`User.objects.filter(external_id=...)`), else `"Collector user not found"`. Serialized output adds `collector_object` (resolved `UserSpec` from cache). |
+| `collected_date_time` | `datetime \| None` | optional | When the specimen was collected. |
+| `quantity` | `QuantitySpec \| None` | optional | Amount collected. See [QuantitySpec](#quantityspec). |
+| `method` | `Coding \| None` bound to `system-collection-method-code` | optional | Collection method (SNOMED). See [collection method values](#collection-method-value-set). |
+| `procedure` | `UUID4 \| None` | optional | Reference to a procedure. |
+| `body_site` | `Coding \| None` bound to `system-body-site-observation` | optional | Anatomical site collected from. |
+| `fasting_status_codeable_concept` | `Coding \| None` bound to `system-fasting-status-code` | optional | Patient fasting status (HL7 v2-0916). |
+| `fasting_status_duration` | `DurationSpec \| None` | optional | Fasting duration. See [DurationSpec](#durationspec). |
+
+### `ProcessingSpec`
+
+One element of the `processing` list.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `description` | `str` | required | Description of the processing step. |
+| `method` | `Coding \| None` bound to `system-specimen-processing-method-code` | optional | Processing method (SNOMED, descendants of `9265001`). |
+| `performer` | `UUID4 \| None` | optional | External ID of performing `User`. Validated to exist, else `"Performer user not found"`. Serialized output adds `performer_object` (resolved `UserSpec` from cache). |
+| `time_date_time` | `str` | required | When the step was performed. |
+
+### `QuantitySpec`
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `value` | `Decimal` | `max_digits=20`, `decimal_places=0` (integer-valued). |
+| `unit` | `Coding` | Required unit coding. |
+
+Note: this is a specimen-local quantity spec, distinct from the shared [`Quantity`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/quantity.py) common type (which allows `decimal_places=6`, optional `value`/`unit`, plus `code`/`meta`).
+
+### `DurationSpec`
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `value` | `int` | Duration magnitude. |
+| `unit` | `Coding` | Required unit coding. |
+
+### `Coding` (shared common type)
+
+All coded fields above use [`Coding`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py) (`extra="forbid"`):
+
+| Field | Type | Required |
+| --- | --- | --- |
+| `system` | `str \| None` | optional |
+| `version` | `str \| None` | optional |
+| `code` | `str` | required |
+| `display` | `str \| None` | optional |
+
+## Bound value sets
+
+Coded fields bind to Care value sets via `ValueSetBoundCoding[]`, which validates the submitted `code` against the value set at write time.
+
+| Field | Value set | Slug | Source system |
+| --- | --- | --- | --- |
+| `specimen_type` | Specimen Type Code | `system-specimen_type-code` | HL7 v2-0487 |
+| `condition[]` | Specimen Condition | `system-specimen-condition-code` | HL7 v2-0493 (v2.0.0) |
+| `collection.method` | Collection Method | `system-collection-method-code` | SNOMED CT (enumerated, see below) |
+| `collection.body_site` | Body Site | `system-body-site-observation` | (observation body-site value set) |
+| `collection.fasting_status_codeable_concept` | Fasting Status | `system-fasting-status-code` | HL7 v2-0916 (v2.0.0) |
+| `processing[].method` | Specimen Processing Method | `system-specimen-processing-method-code` | SNOMED CT (`< 9265001`) |
+
+### Collection Method value set
+
+The `system-collection-method-code` value set enumerates these SNOMED CT codes:
+
+| Code | Display |
+| --- | --- |
+| `129316008` | Aspiration - action |
+| `129314006` | Biopsy - action |
+| `129300006` | Puncture - action |
+| `129304002` | Excision - action |
+| `129323009` | Scraping - action |
+| `73416001` | Urine specimen collection, clean catch |
+| `225113003` | Timed urine collection |
+| `70777001` | Urine specimen collection, catheterized |
+| `386089008` | Collection of coughed sputum |
+| `278450005` | Finger-prick sampling |
+
+Fasting Status (`http://terminology.hl7.org/CodeSystem/v2-0916`), Specimen Condition (`v2-0493`), Specimen Type (`v2-0487`), Body Site, and Specimen Processing Method (SNOMED `< 9265001`) are included by code-system reference/filter rather than an enumerated concept list.
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB object → pydantic, via `get_database_mapping`) and `de_serialize` (pydantic → DB object), plus `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__exclude__ = ["facility", "request"]` on the base means those are never copied field-for-field.
+
+| Spec class | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `BaseSpecimenSpec` | shared base | `id`, `accession_identifier`, `status` (required), `specimen_type` (required), `received_time`, `collection`, `processing`, `condition`, `note`. `__model__ = Specimen`, `__exclude__ = ["facility", "request"]`. |
+| `SpecimenCreateSpec` | write · create | Adds `subject_patient: UUID4` (required), `subject_encounter: UUID4` (required), `request: UUID4 \| None`. Used to wire FK links on creation. |
+| `SpecimenUpdateSpec` | write · update | Relaxes `status` and `specimen_type` to optional (`\| None`) for partial updates. |
+| `SpecimenReadSpec` | read · list | Adds `specimen_definition: dict \| None`. `perform_extra_serialization` sets `id = external_id` and, when present, embeds the linked specimen definition via `SpecimenDefinitionReadSpec.serialize(...).to_json()`. |
+| `SpecimenRetrieveSpec` | read · detail | Extends `SpecimenReadSpec`, adds `service_request: dict \| None`; serialization additionally embeds the linked service request via `ServiceRequestReadSpec.serialize(...).to_json()`. |
+
+Nested specs (defined in the same module): `CollectionSpec`, `ProcessingSpec`, `QuantitySpec`, `DurationSpec` — see [Nested JSON specs](#nested-json-specs).
+
+### Validation & server behaviour
+
+- **User references resolved & validated**: `collection.collector` and `processing[].performer` must reference existing users (by `external_id`); serialization injects fully resolved `collector_object` / `performer_object` (cached `UserSpec`).
+- **Coded fields validated against value sets** at write time (`ValueSetBoundCoding`) — an unbound/invalid `code` is rejected.
+- **Read specs embed related resources** (specimen definition on list/detail; service request on detail) as nested JSON rather than bare IDs.
+- The base spec does **not** override `perform_extra_deserialization`, so no `status_history`-style server-side history is appended on write for this resource (unlike some other clinical resources).
+
+## API integration notes
+
+- Aligned with the FHIR `Specimen` resource; Care API field names differ in places (e.g. `subject_patient` / `subject_encounter` / `request` on create map to the `patient` / `encounter` / `service_request` FKs).
+- The viewset (`SpecimenViewSet`) mixes in **retrieve** and **update** only (`EMRRetrieveMixin`, `EMRUpdateMixin`) — it exposes read/detail/list and update, with `pydantic_model = BaseSpecimenSpec`, `pydantic_update_model = SpecimenUpdateSpec`, `pydantic_read_model = SpecimenReadSpec`, `pydantic_retrieve_model = SpecimenRetrieveSpec`.
+- Filtering: `accession_identifier` (`icontains`); ordering on `created_date` / `modified_date`.
+- Custom action `POST retrieve_by_accession_identifier` looks a specimen up by `accession_identifier` (must be ≥ 5 chars); raises a validation error on multiple/no matches and returns a `SpecimenRetrieveSpec`.
+- Authorization: read via `can_read_specimen`, write via `can_write_specimen`, both keyed off the linked `service_request`.
+- `accession_identifier` is `db_index=True` for fast barcode/accession lookup.
+- A specimen links back to its originating `service_request`; one service request may have multiple specimens.
+
+## Related models
+
+```text
+facility → FK Facility (PROTECT, nullable) [excluded from specs]
+patient → FK Patient (CASCADE) [set via subject_patient]
+encounter → FK Encounter (CASCADE, nullable) [set via subject_encounter]
+service_request → FK ServiceRequest (CASCADE, nullable) [set via request]
+specimen_definition → FK SpecimenDefinition (CASCADE, nullable)
+```
+
+A `Specimen` is typically created to fulfill a [`ServiceRequest`](./service-request.mdx) (a lab order) and may be shaped by a [`SpecimenDefinition`](./specimen-definition.mdx) describing the expected sample. Results derived from a specimen surface through a [`DiagnosticReport`](./diagnostic-report.mdx).
+
+## Related
+
+- Reference: [Specimen Definition](./specimen-definition.mdx)
+- Reference: [Service Request](./service-request.mdx)
+- Reference: [Diagnostic Report](./diagnostic-report.mdx)
+- Reference: [Patient](./patient.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
+- Source (model): [specimen.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/specimen.py)
+- Source (spec): [specimen/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen/spec.py)
+- Source (value sets): [specimen/valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen/valueset.py)
diff --git a/versioned_docs/version-3.0/references/facility/_category_.json b/versioned_docs/version-3.0/references/facility/_category_.json
new file mode 100644
index 0000000..8cb96ba
--- /dev/null
+++ b/versioned_docs/version-3.0/references/facility/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Facility & Administration",
+ "position": 7,
+ "key": "facility-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Facility & Administration",
+ "description": "Facilities, organizations, locations, healthcare services, and devices — the administrative backbone of a deployment."
+ }
+}
diff --git a/versioned_docs/version-3.0/references/facility/device.mdx b/versioned_docs/version-3.0/references/facility/device.mdx
new file mode 100644
index 0000000..1fb808d
--- /dev/null
+++ b/versioned_docs/version-3.0/references/facility/device.mdx
@@ -0,0 +1,231 @@
+---
+sidebar_position: 7
+---
+
+# Device
+
+Technical reference for the `Device` module in Care EMR.
+
+The Django model is the **storage layer** — several fields are typed only loosely (`CharField` for status enums, `JSONField` for `contact`/`metadata`). The **Pydantic resource specs** under `care/emr/resources/device/` are the API/implementation layer: they define the enums those `CharField`s are constrained to, the structured shape of the JSON fields, validation rules, the read/write schemas, and server-maintained side effects.
+
+**Source:**
+- Model: [`care/emr/models/device.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/device.py)
+- Spec: [`care/emr/resources/device/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/device/spec.py)
+- History spec: [`care/emr/resources/device/history_spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/device/history_spec.py)
+- Viewset: [`care/emr/api/viewsets/device.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/device.py)
+- Device-type registry: [`care/emr/registries/device_type/device_registry.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/registries/device_type/device_registry.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Device` | A physical or logical device tracked within a facility |
+| `DeviceEncounterHistory` | Records each period a device was associated with an `Encounter` |
+| `DeviceLocationHistory` | Records each period a device was placed at a `FacilityLocation` |
+| `DeviceServiceHistory` | Records servicing/maintenance events for a device |
+
+All models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+## `Device` fields
+
+### Device data
+
+| Field | Type | Spec constraint | Notes |
+| --- | --- | --- | --- |
+| `identifier` | `CharField(1024)` | `str \| None` | Optional; free-form device identifier. Filterable via `?identifier=` (case-insensitive exact match) |
+| `status` | `CharField(16)` | `DeviceStatusChoices` (**required**) | Lifecycle status — see [enum](#devicestatuschoices-values) |
+| `availability_status` | `CharField(14)` | `DeviceAvailabilityStatusChoices` (**required**) | Physical availability — see [enum](#deviceavailabilitystatuschoices-values) |
+| `manufacturer` | `CharField(1024)` | `str \| None` | Optional in spec (model column itself is non-null) |
+| `manufacture_date` | `DateTimeField` | `datetime \| None` | Optional, tz-aware |
+| `expiration_date` | `DateTimeField` | `datetime \| None` | Optional, tz-aware |
+| `lot_number` | `CharField(1024)` | `str \| None` | Optional |
+| `serial_number` | `CharField(1024)` | `str \| None` | Optional |
+| `registered_name` | `CharField(1024)` | `str` (**required**) | Formal/registered device name; required by all write specs. Searchable via `?search=` |
+| `user_friendly_name` | `CharField(1024)` | `str \| None` | Display name. Searchable via `?search=` |
+| `model_number` | `CharField(1024)` | `str \| None` | Optional |
+| `part_number` | `CharField(1024)` | `str \| None` | Optional |
+| `contact` | `JSONField` (default `{}`) | `list[ContactPoint]` (default `[]`) | NOT a free-form dict — a list of structured contact points. See [`ContactPoint` shape](#contactpoint-shape) |
+| `care_type` | `CharField(1024)` (default `None`) | `str \| None` (create only) | Discriminator selecting a registered device-type plugin. Validated against `DeviceTypeRegistry` on create (must be a registered key, e.g. `"camera"`, or `null`). Filterable via `?care_type=` |
+| `metadata` | `JSONField` (default `{}`) | not exposed directly | Excluded from all specs. Written server-side by the `care_type` plugin's `handle_create`/`handle_update`. Read back through the virtual `care_metadata` field (below) |
+
+#### Virtual / spec-only field
+
+| Field | Spec type | Notes |
+| --- | --- | --- |
+| `care_metadata` | `dict` (default `{}`) | Not a DB column. On read, populated by the `care_type` plugin: `DeviceListSpec` uses the plugin's `list(obj)`, `DeviceRetrieveSpec` uses `retrieve(obj)`. On write it carries plugin-specific data consumed by `handle_create`/`handle_update` (which typically persist into the model's `metadata` column). Empty `{}` when no `care_type` or the plugin lookup fails |
+
+### Relations
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → Facility` | `CASCADE`; owning facility (required). Set server-side from the URL (`facility_external_id`), not from the request body |
+| `managing_organization` | `FK → FacilityOrganization` | `SET_NULL`, nullable; org responsible for the device. Excluded from create/update body — managed via dedicated endpoints (see [API integration notes](#api-integration-notes)) |
+| `current_location` | `FK → FacilityLocation` | `SET_NULL`, nullable; where the device is currently placed. Excluded from create/update body — managed via `associate_location` |
+| `current_encounter` | `FK → Encounter` | `SET_NULL`, nullable; encounter the device is currently attached to. Excluded from create/update body — managed via `associate_encounter` |
+
+### Access cache
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility_organization_cache` | `ArrayField[int]` | **Denormalized cache** of facility-organization IDs, rebuilt on every `save()`. Do not set directly. Used by `get_queryset` to scope which devices a user may see |
+
+## Enum values
+
+### `DeviceStatusChoices` values
+
+`status` field. Defined in `spec.py`.
+
+| Value | Meaning |
+| --- | --- |
+| `active` | Device is in service |
+| `inactive` | Device is not currently in service |
+| `entered_in_error` | Record created in error |
+
+### `DeviceAvailabilityStatusChoices` values
+
+`availability_status` field. Defined in `spec.py`.
+
+| Value | Meaning |
+| --- | --- |
+| `lost` | Device cannot be located |
+| `damaged` | Device is damaged |
+| `destroyed` | Device is destroyed |
+| `available` | Device is available for use |
+
+### `ContactPoint` shape
+
+`contact` is `list[ContactPoint]`. Each item (from `care/emr/resources/common/contact_point.py`):
+
+```text
+ContactPoint {
+ system: ContactPointSystemChoices # required
+ value: str # required
+ use: ContactPointUseChoices # required
+}
+```
+
+| `system` values | `use` values |
+| --- | --- |
+| `phone`, `fax`, `email`, `pager`, `url`, `sms`, `other` | `home`, `work`, `temp`, `old`, `mobile` |
+
+## Related models
+
+### `DeviceEncounterHistory`
+
+Audit trail of device-to-encounter associations. A new row is created when a device is attached to an encounter; `end` is stamped when it detaches. Read-only via the API (`EMRModelReadOnlyViewSet`), ordered by `-end`.
+
+```text
+device → FK Device (CASCADE)
+encounter → FK Encounter (CASCADE)
+start → DateTimeField
+end → DateTimeField (nullable)
+```
+
+### `DeviceLocationHistory`
+
+Audit trail of device placements. A new row is created when a device moves to a location; `end` is stamped when it leaves. Read-only via the API, ordered by `-end`.
+
+```text
+device → FK Device (CASCADE)
+location → FK FacilityLocation (CASCADE)
+start → DateTimeField
+end → DateTimeField (nullable)
+```
+
+### `DeviceServiceHistory`
+
+Servicing/maintenance log for a device. Writable via the API (create + update + list + retrieve).
+
+```text
+device → FK Device (PROTECT, required)
+serviced_on → DateTimeField (nullable, default None)
+note → TextField (default "")
+edit_history → JSONField (default [])
+```
+
+`device` uses `PROTECT`, so a `Device` with service-history rows cannot be hard-deleted while they exist. `edit_history` is an append-only audit list maintained server-side on every update (see [Methods & save behaviour](#deviceservicehistory-edit-history)).
+
+## Resource specs (API schema)
+
+The viewset wires these specs as: create = `DeviceCreateSpec`, update = `DeviceUpdateSpec`, list = `DeviceListSpec`, retrieve = `DeviceRetrieveSpec`. All extend `EMRResource` (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks).
+
+### Device specs
+
+| Spec class | Role | Adds / overrides | Excludes |
+| --- | --- | --- | --- |
+| `DeviceSpecBase` | shared base | `id`, all device-data fields, `status`/`availability_status` enums, `contact: list[ContactPoint]`, `registered_name` required | `facility`, `managing_organization`, `current_location`, `current_encounter`, `care_metadata` |
+| `DeviceCreateSpec` | write · create | `care_type` (validated against `DeviceTypeRegistry`), `care_metadata: dict` | (inherits base excludes) |
+| `DeviceUpdateSpec` | write · update | `care_metadata: dict` — note: **no `care_type`**, so `care_type` is immutable after create | (inherits base excludes) |
+| `DeviceListSpec` | read · list | `id = external_id`; `care_metadata` from plugin `list(obj)` | (inherits) |
+| `DeviceRetrieveSpec` | read · detail | full nested objects: `current_location` (`FacilityLocationListSpec`), `current_encounter` (`EncounterListSpec`), `managing_organization` (`FacilityOrganizationReadSpec`), `created_by`/`updated_by` (audit users); `care_metadata` from plugin `retrieve(obj)` | (inherits) |
+
+### Device history specs
+
+| Spec class | Role | Fields | Excludes |
+| --- | --- | --- | --- |
+| `DeviceLocationHistoryListSpec` | read · list | `id`, `location` (nested `FacilityLocationListSpec`), `created_by`, `start`, `end?` | `device`, `location` (raw FK) |
+| `DeviceEncounterHistoryListSpec` | read · list | `id`, `encounter` (nested `EncounterListSpec`), `created_by`, `start`, `end?` | `device`, `encounter` (raw FK) |
+| `DeviceServiceHistorySpecBase` | shared base | `id` | `device`, `edit_history` |
+| `DeviceServiceHistoryWriteSpec` | write | `serviced_on: datetime` (required), `note: str` (required) | (inherits) |
+| `DeviceServiceHistoryListSpec` | read · list | adds `created_date`, `modified_date`; `id = external_id` | (inherits) |
+| `DeviceServiceHistoryRetrieveSpec` | read · detail | adds `edit_history: list[dict]` (each entry's `updated_by` hydrated to a `UserSpec` from cache), `created_by`, `updated_by` | (inherits) |
+
+### Validation rules
+
+- `status` and `availability_status` must be one of their enum values (request rejected otherwise).
+- `registered_name` is required on every write.
+- `care_type` (create only) must be a registered device-type key or `null`; `DeviceCreateSpec.validate_care_type` calls `DeviceTypeRegistry.get_care_device_class(value)`, which raises for unknown types.
+- `care_type` cannot be changed on update (`DeviceUpdateSpec` does not expose it).
+- `DeviceServiceHistory.serviced_on` and `note` are required on write; updates are blocked once `edit_history` reaches 50 entries (`"Cannot Edit instance anymore"`).
+- `ContactPoint.system`, `value`, and `use` are all required for each contact entry.
+
+## Methods & save behaviour
+
+### `Device.save()` — organization cache
+
+On every save the `facility_organization_cache` is rebuilt:
+
+1. Look up the `FacilityOrganization` with `org_type="root"` for the device's `facility`; its ID seeds the cache.
+2. If `managing_organization` is set, add its `id` and its `parent_cache` (full ancestor chain).
+3. Persist the resulting set as `facility_organization_cache`.
+
+This cache lets the device be filtered by organization hierarchy without traversing joins. Treat it as platform-maintained and rely on `managing_organization` / `facility` as the writable inputs.
+
+### `care_type` plugin hooks
+
+When `care_type` is set, the matching `DeviceTypeBase` subclass from `DeviceTypeRegistry` is invoked inside the create/update/delete transaction:
+
+| Stage | Hook | Effect |
+| --- | --- | --- |
+| create | `handle_create(request_data, obj)` | May mutate `obj` / `obj.metadata` and persist (e.g. the bundled `camera` plugin stores `request_data["some_data"]` into `metadata`) |
+| update | `handle_update(request_data, obj)` | Same, on update |
+| destroy | `handle_delete(obj)` | Validation/cleanup before the device is soft-deleted (`deleted=True`) |
+| list | `list(obj)` | Supplies `care_metadata` for `DeviceListSpec` |
+| retrieve | `retrieve(obj)` | Supplies `care_metadata` for `DeviceRetrieveSpec` |
+
+### `DeviceServiceHistory` edit history
+
+On `perform_update`, the viewset snapshots the **current** DB row and appends `{serviced_on, note, updated_by}` to `edit_history` before saving the new values — so `edit_history` is a server-maintained, append-only trail of previous states. Updates are refused once it holds 50 entries.
+
+## API integration notes
+
+- `facility` is taken from the URL (`facility_external_id`) on create — not from the body.
+- `current_location`, `current_encounter`, and `managing_organization` are **not** writable through the device create/update body. They are mutated through dedicated detail actions, each of which closes the open history row (stamps `end`) and opens a new one:
+ - `POST associate_encounter` — `{ encounter: uuid | null }`; closes the open `DeviceEncounterHistory`, sets `current_encounter`, opens a new history row (returns it). `null` detaches.
+ - `POST associate_location` — `{ location: uuid | null }`; same pattern against `DeviceLocationHistory`.
+ - `POST add_managing_organization` — `{ managing_organization: uuid }`.
+ - `POST remove_managing_organization` — clears `managing_organization`.
+- When an encounter reaches a completed status, `disassociate_device_from_encounter` clears `current_encounter` on all attached devices and stamps `end` on their open `DeviceEncounterHistory` rows.
+- `metadata` (model column) is plugin-owned; clients read deployment-specific data back through the `care_metadata` virtual field, not `metadata`.
+- `facility_organization_cache` is maintained by the platform — do not set it directly. The list endpoint scopes results to devices whose cache overlaps the requesting user's facility-organization memberships (or, when `?location=` is supplied, by location permission / `parent_cache` with `?include_children=`).
+
+## Related
+
+- Reference: [Facility](./facility.mdx)
+- Reference: [Organization](./organization.mdx)
+- Reference: [Location](./location.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Source: [device.py model](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/device.py)
+- Source: [device spec](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/device/spec.py)
+- Source: [device history spec](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/device/history_spec.py)
+- Source: [ContactPoint](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/contact_point.py)
diff --git a/versioned_docs/version-3.0/references/facility/facility-config.mdx b/versioned_docs/version-3.0/references/facility/facility-config.mdx
new file mode 100644
index 0000000..0ea691c
--- /dev/null
+++ b/versioned_docs/version-3.0/references/facility/facility-config.mdx
@@ -0,0 +1,229 @@
+---
+sidebar_position: 2
+---
+
+# Facility Config
+
+Technical reference for the per-facility monetary/billing configuration in Care EMR. The Django model `FacilityMonetoryConfig` is the **storage layer** — several of its fields are opaque `JSONField`s whose real structure lives in the Pydantic **resource specs** (the API/implementation layer). This page documents both: the model fields and the spec-defined shapes, enums, and validation that constrain them.
+
+**Sources:**
+
+- Model: [`care/emr/models/facility_config.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/facility_config.py)
+- Spec: [`care/emr/resources/facility/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py)
+- Monetary types: [`care/emr/resources/common/monetary_component.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
+- Coding: [`care/emr/resources/common/coding.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py)
+- Expression evaluator: [`care/emr/resources/invoice/default_expression_evaluator.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/invoice/default_expression_evaluator.py)
+- API viewset: [`care/emr/api/viewsets/facility.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/facility.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `FacilityMonetoryConfig` | Per-facility billing/monetary configuration: facility-scoped discount codes, discount monetary component definitions, a discount-stacking rule, and the invoice-number expression |
+
+`FacilityMonetoryConfig` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `FacilityMonetoryConfig` fields
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `facility` | `OneToOneField → Facility` | yes | — | `on_delete=CASCADE`, `unique=True`. Exactly one config per facility |
+| `discount_codes` | `JSONField` → `list[Coding]` | no | `[]` | Catalog of facility-defined discount codes. Each entry is a [`Coding`](#coding-shape). Max 100. No bound value set — codes are free-form but must not duplicate or redefine instance `settings.DISCOUNT_CODES` |
+| `discount_monetary_components` | `JSONField` → `list[MonetaryComponentDefinition]` | no | `[]` | Definitions of selectable discount line items. Each entry is a [`MonetaryComponentDefinition`](#monetarycomponentdefinition-shape). Max 100. Every `code` referenced here must resolve to either an instance discount code or a facility `discount_codes` entry |
+| `discount_configuration` | `JSONField` → `DiscountConfiguration \| null` | no | `{}` | Discount-stacking rule: see [`DiscountConfiguration`](#discountconfiguration-shape). `null`/empty is treated as `{}` |
+| `invoice_number_expression` | `CharField(1000)` | no | `None` | Nullable/blank. Expression template used to generate invoice numbers. Validated by dry-running it against a dummy context (see [Invoice expression](#invoice-number-expression)) |
+
+> The Django model declares `discount_codes`, `discount_monetary_components`, and `discount_configuration` as plain `JSONField`s. Their real structure is defined by the Pydantic specs below and is **only enforced at the API layer** — the database does not constrain them.
+
+## JSON field shapes
+
+### `Coding` shape
+
+`discount_codes[]` entries are `Coding` objects (`model_config = extra="forbid"`).
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `system` | `str \| null` | no | Code system URI |
+| `version` | `str \| null` | no | Code system version |
+| `code` | `str` | **yes** | The code value |
+| `display` | `str \| null` | no | Human-readable label |
+
+### `MonetaryComponentDefinition` shape
+
+`discount_monetary_components[]` entries. Subclasses `MonetaryComponent` and adds `title`. Definition-mode validators relax some base rules (duplicate-code and amount-or-factor checks are overridden/disabled), but **a `base`-typed component is rejected** in a definition.
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `title` | `str` | **yes** | — | Display title (added by the definition subclass) |
+| `monetary_component_type` | `MonetaryComponentType` enum | **yes** | — | See [enum values](#monetarycomponenttype-values). `base` is **not allowed** in a definition |
+| `code` | `Coding \| null` | no | `null` | Must resolve against allowed codes (instance + facility) per `FacilityMonetaryCodeSpec` validation |
+| `factor` | `Decimal \| null` | no | `null` | `max_digits=20, decimal_places=6`. Mutually exclusive with `amount` |
+| `amount` | `Decimal \| null` | no | `null` | `max_digits=20, decimal_places=6`. Mutually exclusive with `factor` |
+| `tax_included_amount` | `Decimal \| null` | no | `null` | `max_digits=20, decimal_places=6`. On the base `MonetaryComponent` this is only allowed when type is `base`; irrelevant for definitions since `base` is rejected |
+| `global_component` | `bool` | no | `false` | Whether the component applies globally |
+| `conditions` | `list[EvaluatorConditionSpec]` | no | `[]` | Applicability conditions; each has `metric`, `operation`, `value` validated against the evaluator-metrics registry |
+
+`EvaluatorConditionSpec` (each `conditions[]` entry):
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `metric` | `str` | **yes** | Must be a registered evaluator metric, else "Invalid metric" |
+| `operation` | `str` | **yes** | Validated by the metric's `validate_rule` |
+| `value` | `dict \| str` | **yes** | Validated by the metric's `validate_rule` |
+
+### `DiscountConfiguration` shape
+
+`discount_configuration` object.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `max_applicable` | `int` (`ge=0`) | **yes** | Maximum number of discounts that may stack on a charge |
+| `applicability_order` | `DiscountApplicability` enum | **yes** | Order discounts are applied in; see [enum values](#discountapplicability-values) |
+
+## Enum values
+
+### `MonetaryComponentType` values
+
+From `care/emr/resources/common/monetary_component.py`. String enum.
+
+| Value | Meaning |
+| --- | --- |
+| `base` | Base price (not allowed in a `MonetaryComponentDefinition`) |
+| `surcharge` | Additional charge |
+| `discount` | Discount line item |
+| `tax` | Tax line item |
+| `informational` | Informational-only component |
+
+### `DiscountApplicability` values
+
+From `care/emr/resources/common/monetary_component.py`. String enum.
+
+| Value | Meaning |
+| --- | --- |
+| `total_asc` | Apply discounts in ascending order of total |
+| `total_desc` | Apply discounts in descending order of total |
+
+## Resource specs (API schema)
+
+These Pydantic specs (built on [`EMRResource`](../foundation/base-model.mdx) — `serialize`/`de_serialize`, with `perform_extra_serialization`/`perform_extra_deserialization` hooks, `__model__`, `__exclude__`) define how monetary config is written and read. The monetary config is **not** exposed via the standard facility CRUD; it is written through dedicated facility detail actions and read back inside the facility retrieve payload.
+
+| Spec | Role | Bound to | Notes |
+| --- | --- | --- | --- |
+| `FacilityMonetaryCodeSpec` | write · set monetary config | `FacilityMonetoryConfig` | Validates and persists `discount_codes`, `discount_monetary_components`, `discount_configuration` in one payload. `de_serialize`d onto the get-or-created config row |
+| `FacilityInvoiceExpressionSpec` | write · set invoice expression | (plain `BaseModel`) | Single field `invoice_number_expression`; validated by dry-run before save |
+| `FacilityRetrieveSpec` | read · facility detail | `Facility` | Surfaces the facility's monetary config plus instance-level catalogs in `perform_extra_serialization` |
+| `MonetaryComponentDefinition` | nested (write) | — | Shape of each `discount_monetary_components[]` entry |
+| `DiscountConfiguration` | nested (write) | — | Shape of `discount_configuration` |
+| `Coding` | nested (write) | — | Shape of each `discount_codes[]` entry |
+
+### `FacilityMonetaryCodeSpec`
+
+`__model__ = FacilityMonetoryConfig`, `__exclude__ = []`. Write schema for the `set_monetary_config` action.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `discount_codes` | `list[Coding]` | Facility-defined codes |
+| `discount_monetary_components` | `list[MonetaryComponentDefinition]` | Selectable discount definitions |
+| `discount_configuration` | `DiscountConfiguration \| null` | Stacking rule |
+
+**Validation (model validators):**
+
+- **Count limits** — `discount_codes` and `discount_monetary_components` must each be **fewer than 100** (`DISCOUNT_CODE_COUNT_LIMIT` / `DISCOUNT_MONETARY_COMPONENT_COUNT_LIMIT = 100`); the check uses `>= 100`.
+- **No duplicate codes** — the `code` values in `discount_codes` must be unique.
+- **No redefining system codes** — a facility code may not reuse a `[code, system]` pair already present in instance `settings.DISCOUNT_CODES`.
+- **All component codes must be defined** — every `discount_monetary_components[i].code` (when present) must match a `[code, system]` pair from either `settings.DISCOUNT_CODES` (instance) or the facility's `discount_codes`.
+
+### `FacilityInvoiceExpressionSpec`
+
+Plain `BaseModel` (not an `EMRResource`). Write schema for the `set_invoice_expression` action.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `invoice_number_expression` | `str` | Validated via `evaluate_invoice_dummy_expression(v)`; any exception raises `"Invalid Expression"`. Empty value skips validation |
+
+The dummy context supplies `invoice_count=1234`, `current_year_yyyy=2025`, `current_year_yy=25`; at runtime `evaluate_invoice_identifier_default_expression(facility)` evaluates the stored expression against the live invoice count and current year.
+
+### `FacilityRetrieveSpec` (read path)
+
+`FacilityRetrieveSpec` (extends `FacilityReadSpec` + `FacilityPermissionsMixin`) injects the monetary config into the facility detail response in `perform_extra_serialization`, calling `FacilityMonetoryConfig.get_monetory_config(obj.id)` (get-or-create). Monetary-config-derived fields on the read payload:
+
+| Field | Source |
+| --- | --- |
+| `invoice_number_expression` | config row |
+| `discount_codes` | config row (`list[dict]`) |
+| `discount_monetary_components` | config row (`list[dict]`) |
+| `discount_configuration` | config row (`dict \| null`) |
+| `instance_discount_codes` | `settings.DISCOUNT_CODES` |
+| `instance_discount_monetary_components` | `settings.DISCOUNT_MONETARY_COMPONENT_DEFINITIONS` |
+| `instance_tax_codes` | `settings.TAX_CODES` |
+| `instance_tax_monetary_components` | `settings.TAX_MONETARY_COMPONENT_DEFINITIONS` |
+| `instance_informational_codes` | `settings.INFORMATIONAL_MONETARY_CODES` |
+| `flags` | `obj.get_facility_flags()` |
+| `patient_instance_identifier_configs` | `PatientIdentifierConfigCache.get_instance_config()` |
+| `patient_facility_identifier_configs` | `PatientIdentifierConfigCache.get_facility_config(obj.id)` |
+
+> Facility codes are layered on top of instance-level catalogs: the read payload returns both the facility's own `discount_*` fields and the `instance_*` settings-derived catalogs, while write-time validation forbids facilities from redefining instance codes.
+
+## API integration
+
+Both write operations are detail actions on the facility viewset and require update authorization on the facility.
+
+| Action | Method | Request spec | Behaviour |
+| --- | --- | --- | --- |
+| `set_monetary_config` | `POST` (detail) | `FacilityMonetaryCodeSpec` | `model_validate` with context `{is_update: True, object: }`, then `de_serialize` onto the get-or-created config, set `updated_by`, `save()`. Returns the facility retrieve payload |
+| `set_invoice_expression` | `POST` (detail) | `FacilityInvoiceExpressionSpec` | Sets `invoice_number_expression` on the get-or-created config, set `updated_by`, `save()`. Returns the facility retrieve payload |
+
+## Invoice number expression
+
+`invoice_number_expression` is a string template evaluated by `care.emr.utils.expression_evaluator.evaluate_expression`. Available context variables:
+
+| Variable | Source |
+| --- | --- |
+| `invoice_count` | count of `Invoice` rows for the facility |
+| `current_year_yyyy` | four-digit current year |
+| `current_year_yy` | two-digit current year (`year % 100`) |
+
+A `null`/empty expression evaluates to `""` (no generated number).
+
+## Methods & save behaviour
+
+`FacilityMonetoryConfig` caches derived data in the Django cache and rebuilds it on write.
+
+### Cache keys
+
+| Key | Built by |
+| --- | --- |
+| `facility:{facility_id}:monetory_component` | `get_monetory_component_cache_key(facility_id)` |
+| `facility:{facility_id}:discount_configuration` | `get_discount_configuration_cache_key(facility_id)` |
+
+### Class methods
+
+- `get_monetory_config(facility_id)` — returns the facility's config, **creating an empty one if none exists**. The get-or-create entry point used by both write actions and the retrieve serializer.
+- `get_component_key(component)` — derives a component's lookup key as `code.system + "/" + code.code`.
+- `calculate_monetory_components(components)` — folds a component list into a dict keyed by `get_component_key`.
+- `get_monetory_component(facility_id)` — returns the cached component dict, computing it from `discount_monetary_components` and caching it on a miss.
+- `get_discount_configuration(facility_id)` — returns the cached `discount_configuration` (defaulting to `{}`), caching it on a miss.
+
+### `save()` side effects
+
+`save()` deletes both cache keys for the facility **before** calling `super().save()`, so the next read of `get_monetory_component()` / `get_discount_configuration()` recomputes from the persisted row. Integrators updating monetary config should expect the cache to be invalidated and rebuilt lazily on the next access.
+
+## API integration notes
+
+- One config exists per facility; use `get_monetory_config(facility_id)` to read or auto-create it rather than querying the table directly. The `set_monetary_config` / `set_invoice_expression` actions and `FacilityRetrieveSpec` all rely on this get-or-create.
+- `discount_codes`, `discount_monetary_components`, and `discount_configuration` are opaque `JSONField`s at the DB layer; their real shape and validation come from `FacilityMonetaryCodeSpec` and the nested `Coding` / `MonetaryComponentDefinition` / `DiscountConfiguration` specs.
+- Monetary components are keyed by a FHIR-style `code.system`/`code.code` pair (`get_component_key`); consumers should match on that derived key.
+- `get_monetory_component()` / `get_discount_configuration()` results are cache-backed and platform-maintained; do not write to the cache keys directly — write the model and let `save()` invalidate.
+- `invoice_number_expression` drives invoice number generation and ties this config to the billing domain.
+
+## Related
+
+- Reference: [Facility](./facility.mdx)
+- Reference: [Invoice](../billing/invoice.mdx)
+- Reference: [Charge Item](../billing/charge-item.mdx)
+- Reference: [Charge Item Definition](../billing/charge-item-definition.mdx)
+- Reference: [Payment Reconciliation](../billing/payment-reconciliation.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [facility_config.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/facility_config.py)
+- Source: [facility/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py)
+- Source: [common/monetary_component.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
diff --git a/versioned_docs/version-3.0/references/facility/facility-flag.mdx b/versioned_docs/version-3.0/references/facility/facility-flag.mdx
new file mode 100644
index 0000000..bacc630
--- /dev/null
+++ b/versioned_docs/version-3.0/references/facility/facility-flag.mdx
@@ -0,0 +1,161 @@
+---
+sidebar_position: 3
+---
+
+# Facility Flag
+
+Technical reference for the `FacilityFlag` module in Care EMR.
+
+`FacilityFlag` is a server-side feature-gating primitive: it associates a registered flag name with one `Facility` so behaviour can be toggled per facility. It is **not** a FHIR resource and has **no dedicated Pydantic resource spec** — there is no `FacilityFlag` API endpoint, serializer, or `...CreateSpec`/`...ReadSpec`. The sections below are documented entirely from the Django storage model. Flags reach API clients only as a derived, read-only array on the Facility resource (see [Resource specs](#resource-specs-api-schema)).
+
+**Source:**
+- Model: [`care/facility/models/facility_flag.py`](https://github.com/ohcnetwork/care/blob/develop/care/facility/models/facility_flag.py)
+- Base/flag machinery: [`care/utils/models/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/utils/models/base.py)
+- Flag registry & types: [`care/utils/registries/feature_flag.py`](https://github.com/ohcnetwork/care/blob/develop/care/utils/registries/feature_flag.py)
+- Surfaced via Facility spec: [`care/emr/resources/facility/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `FacilityFlag` | Associates a registered feature flag with a single `Facility`, gating behaviour per facility |
+
+`FacilityFlag` extends `BaseFlag`, which extends `BaseModel` — the lightweight Care EMR base providing `external_id` (UUID), `created_date`/`modified_date` audit timestamps, and soft-delete via `deleted`. Unlike `EMRBaseModel` (see [Base model](../foundation/base-model.mdx)), `BaseModel` does **not** add `created_by`/`updated_by`, `history`, or `meta` JSON. `BaseFlag` adds the `flag` value and the per-entity flag machinery (cache invalidation, validation against the flag registry).
+
+## `FacilityFlag` fields
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `flag` | `CharField(max_length=1024)` | yes | — | Inherited from `BaseFlag`. The registered flag name; validated against the `FACILITY` flag registry on every save (see [validation](#save-inherited-from-baseflag)). Value space is **not** an enum — it is whatever has been registered at runtime via `FlagRegistry`, so it is open-ended but constrained to registered strings |
+| `facility` | `FK → facility.Facility` | yes | — | `on_delete=CASCADE`, `null=False`, `blank=False`. The facility the flag applies to. Drives the `entity` / `entity_id` properties via `entity_field_name="facility"` |
+| `external_id` | `UUIDField` | auto | `uuid4` | Inherited from `BaseModel`; `unique=True`, `db_index=True` |
+| `created_date` | `DateTimeField` | auto | `auto_now_add` | Inherited; `null=True`, `blank=True`, `db_index=True` |
+| `modified_date` | `DateTimeField` | auto | `auto_now` | Inherited; `null=True`, `blank=True`, `db_index=True` |
+| `deleted` | `BooleanField` | no | `False` | Inherited; soft-delete flag, `db_index=True`. The default manager (`BaseManager`) filters `deleted=False` |
+
+There are **no `JSONField`s** on this model, so there is no opaque JSON shape to document. The only constrained value is `flag`, whose allowed values come from the runtime registry rather than a static enum.
+
+### Flag type values
+
+`flag_type` is fixed to `FlagType.FACILITY`. The full `FlagType` enum (from [`feature_flag.py`](https://github.com/ohcnetwork/care/blob/develop/care/utils/registries/feature_flag.py)):
+
+| `FlagType` member | Value | Used by |
+| --- | --- | --- |
+| `USER` | `"USER"` | `UserFlag` ([User](../access/user.mdx)) |
+| `FACILITY` | `"FACILITY"` | `FacilityFlag` (this model) |
+
+`flag` values themselves are not enumerated in source — they are registered dynamically (`FlagRegistry.register(FlagType.FACILITY, "")`). A name not present in the `FACILITY` registry raises `FlagNotFoundError` on save.
+
+### Class-level configuration
+
+These class attributes wire `FacilityFlag` into the generic `BaseFlag` caching and validation logic. They are not database fields.
+
+| Attribute | Value | Notes |
+| --- | --- | --- |
+| `cache_key_template` | `"facility_flag_cache:{entity_id}:{flag_name}"` | Per-flag existence cache key |
+| `all_flags_cache_key_template` | `"facility_all_flags_cache:{entity_id}"` | Cache key for the full flag list of a facility |
+| `flag_type` | `FlagType.FACILITY` | Registry namespace used for flag-name validation |
+| `entity_field_name` | `"facility"` | Tells `BaseFlag` which FK is the owning entity; drives `entity` / `entity_id` properties |
+
+Module-level constants mirror these templates: `FACILITY_FLAG_CACHE_KEY` (`"facility_flag_cache:{facility_id}:{flag_name}"`), `FACILITY_ALL_FLAGS_CACHE_KEY` (`"facility_all_flags_cache:{facility_id}"`), and `FACILITY_FLAG_CACHE_TTL` (`60 * 60 * 24`, 1 day).
+
+### Constraints
+
+```text
+UniqueConstraint(
+ fields=["facility", "flag"],
+ condition=Q(deleted=False),
+ name="unique_facility_flag",
+)
+```
+
+A facility may hold a given flag at most once among non-deleted rows. Soft-deleted rows are excluded, so a flag can be removed and re-added. `Meta.verbose_name = "Facility Flag"`.
+
+## Methods & save behaviour
+
+### `save()` (inherited from `BaseFlag`)
+
+On every save:
+
+1. `validate_flag(self.flag)` runs `FlagRegistry.validate_flag_name(FlagType.FACILITY, flag)`. This first validates the flag type, then checks the name is registered. An unknown type or unregistered name raises `FlagNotFoundError` (a subclass of Django `ValidationError`) and the row is **not** written.
+2. The per-flag cache key (`facility_flag_cache:{facility_id}:{flag}`) is deleted.
+3. The all-flags cache key (`facility_all_flags_cache:{facility_id}`) is deleted.
+4. The row is persisted via `super().save()`.
+
+Deletes use the `BaseModel` soft-delete (`delete()` sets `deleted = True` and saves `update_fields=["deleted"]`); cache entries are **not** purged on delete, so they expire by TTL or are rebuilt on the next miss.
+
+### Lookups
+
+| Method | Returns | Notes |
+| --- | --- | --- |
+| `check_facility_has_flag(facility_id, flag_name)` | `bool` | Delegates to `BaseFlag.check_entity_has_flag`: validates the flag name, then `cache.get_or_set` on the per-flag key with an `.exists()` query; cached for `FLAGS_CACHE_TTL` (1 day) |
+| `get_all_flags(facility_id)` | `tuple[FlagName]` | Delegates to `BaseFlag.get_all_flags`: `cache.get_or_set` on the all-flags key, materializing `values_list("flag", flat=True)` for the facility; cached for 1 day |
+
+Both wrap the generic `BaseFlag` methods using `entity_field_name="facility"`. `Facility.get_facility_flags()` is a thin wrapper that calls `FacilityFlag.get_all_flags(self.id)`.
+
+### Properties (inherited)
+
+- `entity` → the related `Facility` instance (`getattr(self, "facility")`)
+- `entity_id` → `facility_id` (`getattr(self, "facility_id")`)
+
+## Resource specs (API schema)
+
+**`FacilityFlag` has no Pydantic resource spec and no REST endpoint of its own.** There is no `FacilityFlagCreateSpec` / `FacilityFlagUpdateSpec` / `FacilityFlagListSpec` / `FacilityFlagRetrieveSpec`, no serializer, and no viewset — confirmed by absence in `care/emr/resources/` and `care/facility/api/`. Flags are administered through Django admin or management/registration code, not the public API.
+
+The one place flags surface to API clients is the **Facility** resource, which exposes them as a derived read-only field:
+
+| Spec | Field | Role | Behaviour |
+| --- | --- | --- | --- |
+| `FacilityRetrieveSpec` ([`facility/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py)) | `flags: list[str]` | read · detail | Populated in `perform_extra_serialization` via `mapping["flags"] = obj.get_facility_flags()`. Server-maintained, read-only; clients cannot set or mutate flags through the Facility write specs |
+
+Notes on this binding:
+
+- The `flags` array is the materialized list of registered flag names attached to the facility (from `FacilityFlag.get_all_flags`, cache-backed, 1-day TTL).
+- The parallel pattern exists for users: the User resource exposes `flags` via `obj.get_all_flags()` backed by `UserFlag` (see [User](../access/user.mdx)).
+- There is no value set bound to `flag`; allowed values are the runtime `FACILITY` registry entries, not a coded concept.
+
+## Related models
+
+`FacilityFlag` defines no secondary models. Its behaviour is shared via two utilities in `care/utils`.
+
+### `BaseFlag` (abstract)
+
+```text
+flag → CharField(max_length=1024)
+(abstract base — no table of its own)
+class attrs: cache_key_template, all_flags_cache_key_template, flag_type, entity_field_name
+```
+
+Provides save-time validation, cache invalidation, and the `check_entity_has_flag` / `get_all_flags` lookups. Subclasses set `flag_type`, `entity_field_name`, and the two cache-key templates. See [`care/utils/models/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/utils/models/base.py).
+
+### `FlagRegistry`
+
+An in-process class-level registry (`_flags: dict[FlagType, dict[FlagName, bool]]`) of valid flag names per `FlagType`. Key methods:
+
+| Method | Notes |
+| --- | --- |
+| `register(flag_type, flag_name)` | Adds a name to a type's registry (creating the type bucket if needed) |
+| `register_wrapper(flag_type, flag_name)` | Class decorator form of `register` |
+| `unregister(flag_type, flag_name)` | Removes a name; logs a warning if absent |
+| `validate_flag_type(flag_type)` | Raises `FlagNotFoundError` ("Invalid Flag Type") if the type bucket is missing |
+| `validate_flag_name(flag_type, flag_name)` | Validates type, then raises `FlagNotFoundError` ("Flag not registered") if the name is absent |
+| `get_all_flags(flag_type)` | `list[FlagName]` of registered names |
+| `get_all_flags_as_choices(flag_type)` | `(value, value)` tuples for Django choices |
+
+Flags must be registered before a `FacilityFlag` referencing them can be saved. See [`care/utils/registries/feature_flag.py`](https://github.com/ohcnetwork/care/blob/develop/care/utils/registries/feature_flag.py).
+
+## API integration notes
+
+- `FacilityFlag` is a server-side gating primitive, not a FHIR resource and not a writable API resource. There is no CRUD endpoint; flags are managed via Django admin or registration/management code.
+- Clients observe a facility's flags only through the read-only `flags: list[str]` field on `FacilityRetrieveSpec` (see [Resource specs](#resource-specs-api-schema)).
+- Read access in application code should go through `check_facility_has_flag` / `get_all_flags` (or `Facility.get_facility_flags()`), which are cache-backed — do not query the table directly for hot-path checks.
+- Flag names are constrained to entries registered in the `FACILITY` `FlagRegistry`; clients cannot introduce arbitrary flag strings — an unregistered name fails validation on save with `FlagNotFoundError`.
+- The `{facility, flag}` uniqueness is enforced only over non-deleted rows, so soft-deleted history is preserved and a flag can be removed then re-added.
+
+## Related
+
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Facility Config](../facility/facility-config.mdx)
+- Reference: [User](../access/user.mdx) — the `USER` flag counterpart of the same `BaseFlag` machinery
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [facility_flag.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/facility/models/facility_flag.py)
diff --git a/versioned_docs/version-3.0/references/facility/facility.mdx b/versioned_docs/version-3.0/references/facility/facility.mdx
new file mode 100644
index 0000000..2649c7e
--- /dev/null
+++ b/versioned_docs/version-3.0/references/facility/facility.mdx
@@ -0,0 +1,283 @@
+---
+sidebar_position: 1
+---
+
+# Facility
+
+Technical reference for the `Facility` module in Care EMR.
+
+A `Facility` is a physical or virtual care site — hospital, clinic, lab, telemedicine endpoint — and the root of a deployment's administrative hierarchy. The Django **model** is the storage layer: several columns (`features`, `print_templates`) are opaque on the model, and the API contract (enums, validation, JSON-field shapes, read/write schemas) lives in the Pydantic **resource specs**.
+
+**Source:**
+- Model: [`care/facility/models/facility.py`](https://github.com/ohcnetwork/care/blob/develop/care/facility/models/facility.py)
+- Resource specs: [`care/emr/resources/facility/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py)
+- Base resource: [`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+- Common types: [`coding.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py), [`monetary_component.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Facility` | A care site and the root of a deployment's administrative org hierarchy |
+| `FacilityMonetoryConfig` | Per-facility billing config (one-to-one): discount codes, discount monetary components, discount configuration, invoice-number expression. Backs the discount/invoice fields of `FacilityRetrieveSpec`. See [Facility config](facility-config.mdx) |
+
+`Facility` extends [`BaseModel`](../foundation/base-model.mdx) (not `EMRBaseModel`), so it carries `external_id`, `created_date`/`modified_date`, and soft-delete via `deleted`, but **not** the `EMRBaseModel` audit/history/meta fields. It defines its own `created_by` FK directly and has no `updated_by`, `history`, or `meta` columns. `FacilityMonetoryConfig` extends `EMRBaseModel`.
+
+The model file also defines the choice sets and enums used by `Facility` — see [Choices & enums](#choices--enums).
+
+## `Facility` fields
+
+The **Type** column is the Django storage type; **Spec** notes the API representation, validation, and bound enum from the Pydantic specs where they differ.
+
+### Identity & classification
+
+| Field | Type | Spec / Notes |
+| --- | --- | --- |
+| `name` | `CharField(1000)` | Required (`blank=False, null=False`). Spec validates case-insensitive uniqueness across all facilities (`Lower(Trim(name))`); on update the current row is excluded. |
+| `description` | `TextField` | `blank=True, null=False`; defaults to empty string. Required (`str`) in `FacilityBaseSpec`. |
+| `facility_type` | `IntegerField` | Required; choices from `FACILITY_TYPES` (see below). **Wire format is the label string, not the integer:** create validates the label against `REVERSE_REVERSE_FACILITY_TYPES` then maps to the int; read maps the int back to the label via `REVERSE_FACILITY_TYPES`. |
+| `features` | `ArrayField[SmallIntegerField]` | Nullable; each element a `FacilityFeature` choice. Spec type `list[int]`. |
+| `is_active` | `BooleanField` | `default=True`. Not exposed by the specs (server-managed). |
+| `verified` | `BooleanField` | `default=False`. Not exposed by the specs (server-managed). |
+| `is_public` | `BooleanField` | `default=False`; controls public visibility. Exposed as `bool` on `FacilityBaseSpec`. |
+
+### Location & contact
+
+| Field | Type | Spec / Notes |
+| --- | --- | --- |
+| `address` | `TextField` | Free-text address. Required (`str`). |
+| `pincode` | `IntegerField` | `default=None, null=True` on model; required `int` in `FacilityBaseSpec`. |
+| `longitude` | `DecimalField(22, 16)` | Nullable. Spec type `Longitude \| None` (`pydantic_extra_types`, range −180…180). |
+| `latitude` | `DecimalField(22, 16)` | Nullable. Spec type `Latitude \| None` (`pydantic_extra_types`, range −90…90). |
+| `phone_number` | `CharField(14)` | `blank=True`; validated with `mobile_or_landline_number_validator`. Spec type `str`. |
+| `middleware_address` | `CharField(200)` | Nullable, `default=None`. Hostname of an external device/middleware integration. Spec type `str \| None`. |
+
+### Organization links & caches
+
+| Field | Type | Spec / Notes |
+| --- | --- | --- |
+| `geo_organization` | `FK → emr.Organization` | `on_delete=SET_NULL`, nullable. Geographic/administrative org placement. **Write** (`FacilityCreateSpec`): a required `UUID4` (the org's `external_id`); deserialization resolves it to an `Organization` with `org_type="govt"`. **Read**: a nested `OrganizationReadSpec` dict (empty `{}` when unset). |
+| `geo_organization_cache` | `ArrayField[int]` | `default=list`. Denormalized cache of `geo_organization`'s `parent_cache` chain plus its own id; rebuilt by `sync_cache()`. Not exposed by specs. |
+| `default_internal_organization` | `FK → emr.FacilityOrganization` | `on_delete=SET_NULL`, nullable, `related_name="default_facilities"`. The auto-created root `Administration` org. Not exposed by specs. |
+| `internal_organization_cache` | `ArrayField[int]` | `default=list`. Denormalized cache of all `FacilityOrganization` ids (with parent chains) under this facility; rebuilt by `sync_cache()`. Not exposed by specs. |
+
+Both cache fields are **platform-maintained denormalizations** rebuilt on every `save()` for fast org-scoped filtering without deep joins — do not write them directly. See [Methods & save behaviour](#methods--save-behaviour).
+
+### Media & presentation
+
+| Field | Type | Spec / Notes |
+| --- | --- | --- |
+| `cover_image_url` | `CharField(500)` | Nullable, `default=None`. Stores an **object key**, not a URL. Read specs expose both the raw `cover_image_url` and a resolved `read_cover_image_url` (full URL via `read_cover_image_url()`). |
+| `print_templates` | `JSONField` | `default=list`. Opaque on the model; the real shape is `list[PrintTemplate]` (see [PrintTemplate](#printtemplate-json-shape)). Accepted typed on create; returned as `list[dict]` on retrieve. |
+
+### Integration & audit
+
+| Field | Type | Spec / Notes |
+| --- | --- | --- |
+| `created_by` | `FK → users.User` | `on_delete=SET_NULL`, nullable. Set to the facility administrator when the root org is provisioned. Read specs expose it as a nested `UserSpec` dict (cache-backed), `None` when unset. |
+
+## Choices & enums
+
+The model module defines these choice sets consumed by `Facility` and related code.
+
+### `FacilityFeature` (`IntegerChoices`)
+
+| Value | Name | Label |
+| --- | --- | --- |
+| 1 | `CT_SCAN_FACILITY` | CT Scan Facility |
+| 2 | `MATERNITY_CARE` | Maternity Care |
+| 3 | `X_RAY_FACILITY` | X-Ray Facility |
+| 4 | `NEONATAL_CARE` | Neonatal Care |
+| 5 | `OPERATION_THEATER` | Operation Theater |
+| 6 | `BLOOD_BANK` | Blood Bank |
+
+### `HubRelationship` (`IntegerChoices`)
+
+| Value | Name | Label |
+| --- | --- | --- |
+| 1 | `REGULAR_HUB` | Regular Hub |
+| 2 | `TELE_ICU_HUB` | Tele ICU Hub |
+
+### `FACILITY_TYPES`
+
+List of `(int code, label)`. Codes are sparse and namespaced (8xx govt hospitals/health centres, 9xx labs/cooperatives, 10xx–16xx COVID-era centres, 3xxx/4xxx NGO/CBO). Commented-out legacy codes (`8`, `801`, `820`, `831`, `850`, `950`, `1000`) are retained in source as historical mapping notes and are **not** valid. The API binds on the **label**, not the code.
+
+| Code | Label | Code | Label |
+| --- | --- | --- | --- |
+| 1 | Educational Inst | 870 | Govt Medical College Hospitals |
+| 2 | Private Hospital | 900 | Co-operative hospitals |
+| 3 | Other | 910 | Autonomous healthcare facility |
+| 4 | Hostel | 1010 | COVID-19 Domiciliary Care Center |
+| 5 | Hotel | 1100 | First Line Treatment Centre |
+| 6 | Lodge | 1200 | Second Line Treatment Center |
+| 7 | TeleMedicine | 1300 | Shifting Centre |
+| 9 | Govt Labs | 1400 | Covid Management Center |
+| 10 | Private Labs | 1500 | Request Approving Center |
+| 800 | Primary Health Centres | 1510 | Request Fulfilment Center |
+| 802 | Family Health Centres | 1600 | District War Room |
+| 803 | Community Health Centres | 3000 | Clinical Non Governmental Organization |
+| 830 | Taluk Hospitals | 3001 | Non Clinical Non Governmental Organization |
+| 840 | Women and Child Health Centres | 4000 | Community Based Organization |
+| 860 | District Hospitals | | |
+
+`REVERSE_FACILITY_TYPES` (code → label) and `REVERSE_REVERSE_FACILITY_TYPES` (label → code) are derived from `FACILITY_TYPES` via `reverse_choices`. The create spec validates against the label set and stores the code; read specs map back to the label.
+
+### `DOCTOR_TYPES`
+
+List of `(int, label)` — 64 medical specialties (`1` General Medicine, `2` Pulmonology, `8` Cardiologist, `48` Nurse, `64` Critical Care Physician, etc.). Defined in this module but not referenced by the `Facility` model or facility specs.
+
+### `MonetaryComponentType` (str enum, from `monetary_component.py`)
+
+Used by the discount monetary components in `FacilityMonetaryCodeSpec` / `FacilityRetrieveSpec`.
+
+| Value |
+| --- |
+| `base` |
+| `surcharge` |
+| `discount` |
+| `tax` |
+| `informational` |
+
+### `DiscountApplicability` (str enum, from `monetary_component.py`)
+
+| Value |
+| --- |
+| `total_asc` |
+| `total_desc` |
+
+## Nested JSON shapes
+
+These Pydantic models define the real structure of the model's JSON/array fields and of the discount config.
+
+### `PrintTemplate` JSON shape
+
+Element type of the `print_templates` field.
+
+```text
+PrintTemplate {
+ slug: str (required)
+ page: PageConfig | null
+ print_setup: PrintSetupConfig | null
+ branding: BrandingConfig | null
+ watermark: WatermarkConfig | null
+}
+PageConfig { size: "A4"|"A5"|"Letter"|"Legal" | null,
+ orientation: "portrait"|"landscape" | null,
+ margin: PageMargin | null }
+PageMargin { top: float≥0, bottom: float≥0, left: float≥0, right: float≥0 } # all required
+PrintSetupConfig { auto_print: bool | null }
+BrandingConfig { logo: LogoConfig | null, header_image: HeaderImageConfig | null,
+ footer_image: FooterImageConfig | null }
+LogoConfig { url: str (required), width: float | null, height: float | null,
+ alignment: "left"|"center"|"right" (required) }
+HeaderImageConfig { url: str (required), height: float | null }
+FooterImageConfig { url: str | null, height: float | null }
+WatermarkConfig { enabled: bool | null, text: str | null,
+ opacity: float | null (0…1), rotation: float | null }
+```
+
+### Discount config shapes (`FacilityMonetaryCodeSpec` / `FacilityMonetoryConfig`)
+
+```text
+discount_codes: list[Coding] # see Coding below
+discount_monetary_components: list[MonetaryComponentDefinition]
+discount_configuration: DiscountConfiguration | null
+
+Coding { system: str|null, version: str|null, code: str (required), display: str|null } # extra="forbid"
+
+MonetaryComponentDefinition (extends MonetaryComponent) {
+ title: str (required)
+ monetary_component_type: MonetaryComponentType # base not allowed in a definition
+ code: Coding | null
+ factor: Decimal | null (max_digits=20, decimal_places=6)
+ amount: Decimal | null (max_digits=20, decimal_places=6)
+ tax_included_amount: Decimal | null (base component only)
+ global_component: bool = false
+ conditions: list[EvaluatorConditionSpec] = []
+}
+EvaluatorConditionSpec { metric: str, operation: str, value: dict|str } # metric validated against EvaluatorMetricsRegistry
+DiscountConfiguration { max_applicable: int≥0 (required), applicability_order: DiscountApplicability (required) }
+```
+
+`PeriodSpec` (`base.py`) — the shared period shape used across EMR resources (not used directly by `Facility`, included for reference): `{ start: datetime|null, end: datetime|null }`, both must be **timezone-aware**, and `start ≤ end`.
+
+## Resource specs (API schema)
+
+All specs subclass `EMRResource` (`base.py`), which provides `serialize` (DB object → Pydantic), `de_serialize` (Pydantic → DB object), and the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__exclude__` lists fields skipped during (de)serialization; `__store_metadata__` (default `False`) controls writing unknown fields into the model's `meta`.
+
+The class hierarchy is `FacilityBareMinimumSpec → FacilityBaseSpec → {FacilityCreateSpec, FacilityReadSpec → FacilityRetrieveSpec, FacilityMinimalReadSpec}`. There is **no separate `UpdateSpec`**: `FacilityCreateSpec` is reused for both create and update (its validators read `is_update`/`object` from the serializer context).
+
+| Spec class | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `FacilityBareMinimumSpec` | shared base (`@cacheable`, base manager) | `id: UUID4`, `name`. `__exclude__=["geo_organization"]`. Cacheable, fetchable via `model_from_cache`. |
+| `FacilityBaseSpec` | shared base | Adds `description`, `longitude`, `latitude`, `pincode`, `address`, `phone_number`, `middleware_address`, `facility_type`, `is_public`. |
+| `FacilityCreateSpec` | write · create & update | Adds `geo_organization: UUID4`, `features: list[int]`, `print_templates: list[PrintTemplate] = []`. Validates `facility_type` label and case-insensitive `name` uniqueness. `perform_extra_deserialization` resolves `geo_organization` (must be `org_type="govt"`) and maps the `facility_type` label → int. |
+| `FacilityReadSpec` | read · list | `FacilityBaseSpec` + `features`, `cover_image_url`, `read_cover_image_url`, `geo_organization: dict`, `created_by: dict\|null`. `perform_extra_serialization` sets `id=external_id`, resolves the cover image URL, maps `facility_type` int → label, serializes `geo_organization` via `OrganizationReadSpec`, and resolves `created_by` via cached `UserSpec`. |
+| `FacilityRetrieveSpec` | read · detail | Extends `FacilityReadSpec` + `FacilityPermissionsMixin`. Adds `flags` (from `get_facility_flags()`), facility + instance discount/tax/informational code lists, patient identifier configs, `invoice_number_expression`, and `print_templates: list[dict]`. Permissions mixin adds `permissions`, `root_org_permissions`, `child_org_permissions`. Discount/invoice fields come from `FacilityMonetoryConfig.get_monetory_config()`; instance-level lists come from Django `settings`. |
+| `FacilityMinimalReadSpec` | read · minimal | `FacilityBaseSpec` + `features`, `cover_image_url`, `read_cover_image_url`, `geo_organization: dict`. Same serialization as `FacilityReadSpec` minus `created_by`. |
+| `FacilityMonetaryCodeSpec` | write · billing config | Bound to `FacilityMonetoryConfig`. Validates ≤ 100 discount codes and ≤ 100 monetary components, forbids duplicate / system-redefining codes, and requires every monetary-component code to be a defined facility or system code. |
+| `FacilityInvoiceExpressionSpec` | write · invoice config | `invoice_number_expression: str`, validated by `evaluate_invoice_dummy_expression` (raises "Invalid Expression"). |
+
+### Validation & server-side behaviour
+
+- **`name`** — must be unique case-insensitively (`Lower(Trim(name))`); on update the current object (from context) is excluded from the check.
+- **`facility_type`** — write accepts the **label**; rejected with the sorted valid-label list if unknown. Stored as the integer code; read maps back to the label.
+- **`geo_organization`** — write is a `UUID4` resolved to an `Organization` filtered by `org_type="govt"`; resolves to `None` if no match. Read embeds the full `OrganizationReadSpec`.
+- **`features`** — `list[int]`; each int should correspond to a `FacilityFeature` value.
+- **Bound value sets / coded fields** — discount/tax/informational components use `Coding` (`extra="forbid"`); `MonetaryComponentDefinition` enforces no `base` type, single base rules inherited from `MonetaryComponent`, and amount/factor mutual exclusion.
+- **`cover_image_url`** — stores an object key; `read_cover_image_url` is the resolved URL only on read specs.
+- Permissions on `FacilityRetrieveSpec` are computed per requesting user from facility root and sub-org roles (`FacilityAccess`); `can_update_facility` is excluded from `child_org_permissions`.
+
+## Related models
+
+`Facility` is the anchor for the administrative hierarchy; its relationships are resolved through models in adjacent modules:
+
+```text
+geo_organization → FK emr.Organization (SET_NULL) # write: UUID4 of a govt org
+default_internal_organization → FK emr.FacilityOrganization (SET_NULL) # auto-provisioned root org
+created_by → FK users.User (SET_NULL)
+FacilityMonetoryConfig → OneToOne facility.Facility (CASCADE) # billing/discount config
+```
+
+On creation, `save()` provisions a root [`FacilityOrganization`](organization.mdx) named `Administration` (`org_type="root"`, `system_generated=True`) and a `FacilityOrganizationUser` linking `created_by` to it with the `FACILITY_ADMIN_ROLE`. Feature flags are stored in [`FacilityFlag`](facility-flag.mdx) and surfaced via `get_facility_flags()`.
+
+## Methods & save behaviour
+
+`Facility` overrides `save()` and adds several helpers.
+
+| Member | Behaviour |
+| --- | --- |
+| `save()` | On **create** only: persists the row, then creates the root `Administration` `FacilityOrganization`, sets it as `default_internal_organization` (extra write), and creates a `FacilityOrganizationUser` granting `created_by` the `FACILITY_ADMIN_ROLE`. On **every** save: calls `sync_cache()`. |
+| `sync_cache()` | Rebuilds `geo_organization_cache` (parent chain of `geo_organization` + its id) and `internal_organization_cache` (all `FacilityOrganization` parent chains + ids, de-duplicated), then persists only those two fields via `update_fields`. |
+| `read_cover_image_url()` | Resolves `cover_image_url` to a full URL via `settings.FACILITY_CDN`, else the S3 external endpoint/bucket; returns `None` when unset. |
+| `get_facility_flags()` | Delegates to `FacilityFlag.get_all_flags(self.id)` (cache-backed). |
+| `__str__()` | Returns `name`. |
+
+Integrators should expect **multiple write passes** when creating a facility through the ORM: the initial insert, the `default_internal_organization` update, and the `sync_cache()` write of the two cache columns.
+
+Deletes are soft (inherited `BaseModel.delete()` sets `deleted=True`); the row and its child organizations are retained.
+
+## API integration notes
+
+- `external_id` (UUID) is the public identifier exposed as `id` by every read spec — the integer `pk` is internal.
+- `facility_type` crosses the wire as the **label string** (e.g. `"Private Hospital"`), validated against `REVERSE_REVERSE_FACILITY_TYPES`; the integer code is storage-only.
+- `features` are integer `FacilityFeature` codes; map to labels client-side.
+- `geo_organization` is **written** as the org's UUID (must be a `govt` org) and **read** as an embedded `OrganizationReadSpec` object.
+- `print_templates` follows the typed [`PrintTemplate`](#printtemplate-json-shape) shape on write and is returned as `list[dict]` on retrieve.
+- `geo_organization_cache` / `internal_organization_cache` are platform-maintained — never set them from clients; they are rebuilt on every save.
+- Creating a facility automatically provisions its root `Administration` organization and an admin membership for `created_by`; do not create these manually.
+- `cover_image_url` stores an object key; clients should render `read_cover_image_url`.
+- Discount/tax/invoice billing config is managed through `FacilityMonetaryCodeSpec` / `FacilityInvoiceExpressionSpec` and surfaced on `FacilityRetrieveSpec`; it is backed by [`FacilityMonetoryConfig`](facility-config.mdx).
+- `FacilityRetrieveSpec` also returns the requesting user's permissions and feature flags; flags are read via `get_facility_flags()` rather than queried row-by-row.
+
+## Related
+
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Reference: [Facility config](facility-config.mdx)
+- Reference: [Facility flag](facility-flag.mdx)
+- Reference: [Organization](organization.mdx)
+- Reference: [Location](location.mdx)
+- Reference: [Healthcare service](healthcare-service.mdx)
+- Reference: [Device](device.mdx)
+- Source: [facility.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/facility/models/facility.py)
+- Source: [facility/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py)
diff --git a/versioned_docs/version-3.0/references/facility/healthcare-service.mdx b/versioned_docs/version-3.0/references/facility/healthcare-service.mdx
new file mode 100644
index 0000000..565854a
--- /dev/null
+++ b/versioned_docs/version-3.0/references/facility/healthcare-service.mdx
@@ -0,0 +1,171 @@
+---
+sidebar_position: 6
+---
+
+# Healthcare Service
+
+Technical reference for the `HealthcareService` module in Care EMR.
+
+A `HealthcareService` describes a service offered at a facility (e.g. cardiology, lab, radiology, pharmacy), scoped to one or more locations and a managing facility organization. The Django model is the **storage** layer — `service_type` and `styling_metadata` are opaque `JSONField`s whose real structure lives in the Pydantic **resource specs**, which also define the bound value set, the enum for `internal_type`, and the read/write API schemas.
+
+**Source:**
+
+- Model: [`care/emr/models/healthcare_service.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/healthcare_service.py)
+- Spec: [`care/emr/resources/healthcare_service/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/healthcare_service/spec.py)
+- Value set: [`care/emr/resources/healthcare_service/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/healthcare_service/valueset.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `HealthcareService` | A bookable/offerable service provided at a facility (e.g. cardiology, lab, radiology), scoped to locations and a managing organization |
+
+`HealthcareService` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base giving `external_id`, audit fields, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `HealthcareService` fields
+
+### Identity & classification
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(1024)` | Display name of the service. Required (no default) |
+| `service_type` | `JSONField` → `Coding` | Defaults to `{}` (`dict`). Stores a single FHIR-style `Coding` object, not a string. In the API this field is a `ValueSetBoundCoding` bound to the **Healthcare Service Type Code** value set (see below). Optional (`None`) |
+| `internal_type` | `CharField(255)` | Nullable, default `None`. In the API constrained to the `HealthcareServiceInternalType` enum (`pharmacy` / `lab` / `scheduling` / `store`). Optional |
+| `extra_details` | `TextField` | Free-text additional details. Not nullable and has no DB default, so a value (empty string allowed) must be supplied. Required in write specs |
+
+### Scope & placement
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` | `PROTECT`; nullable (`default=None`, `blank=True`). The facility offering the service. Excluded from all specs (`__exclude__ = ["facility"]`) — set server-side from the URL context, never via the request body |
+| `locations` | `ArrayField[int]` | Defaults to `[]`; list of `FacilityLocation` **primary-key integers** (not FK rows) where the service is provided. Write specs accept location `external_id`s (UUIDs); retrieve specs return serialized location objects |
+| `managing_organization` | `FK → emr.FacilityOrganization` | `PROTECT`; nullable. The facility-scoped org (department/team) responsible for the service. Write specs accept its `external_id` (UUID); resolved server-side |
+
+### Presentation
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `styling_metadata` | `JSONField` → `dict` | Nullable in DB; defaults to `{}` in the specs. Open JSON bag of UI presentation hints (e.g. colors, icons). No backend semantics |
+
+## Coded field shapes (JSON fields)
+
+### `service_type` → `Coding`
+
+`service_type` stores a single `Coding` object. In the API it is a `ValueSetBoundCoding` whose `code` is validated against the **Healthcare Service Type Code** value set on de-serialization. Optional (`None`).
+
+```text
+Coding {
+ system: str | None # e.g. "http://terminology.hl7.org/CodeSystem/service-type"
+ version: str | None
+ code: str # required; validated against the bound value set
+ display: str | None
+}
+# extra="forbid" — unknown keys rejected
+```
+
+### Bound value set — Healthcare Service Type Code
+
+| Property | Value |
+| --- | --- |
+| Name | `Healthcare Service Type Code` |
+| Slug | `healthcare-service-type-code` |
+| Status | `active` |
+| Compose / include system | `http://terminology.hl7.org/CodeSystem/service-type` |
+| System version | `2.0.0` |
+
+Registered via `register_valueset(...)` and `register_as_system()`. The `service_type.code` submitted on create/update must resolve within this value set.
+
+## Enums
+
+### `HealthcareServiceInternalType` values
+
+Constrains `internal_type` in the specs (`str` enum; the model column is a plain `CharField` so legacy/other values may exist in storage).
+
+| Value |
+| --- |
+| `pharmacy` |
+| `lab` |
+| `scheduling` |
+| `store` |
+
+## Related models
+
+### `facility.Facility`
+
+```text
+facility → FK Facility (PROTECT, nullable)
+```
+
+The facility at which the service is offered. `PROTECT` prevents deleting a facility that still has healthcare services. Excluded from specs and set from request context. See [Facility](./facility.mdx).
+
+### `emr.FacilityOrganization`
+
+```text
+managing_organization → FK FacilityOrganization (PROTECT, nullable)
+```
+
+The facility-scoped organization (department/team) that owns the service. Write specs take its `external_id` (UUID); the retrieve spec embeds it serialized via `FacilityOrganizationReadSpec`. See [Organization](./organization.mdx).
+
+### Locations
+
+`locations` is an `ArrayField` of integer PKs rather than a relational join. Each entry references a `FacilityLocation` row by primary key; resolution and validation are handled in application code, not by a database foreign key. Write specs accept location `external_id`s (UUIDs); the retrieve spec embeds each location serialized via `FacilityLocationListSpec`. See [Location](./location.mdx).
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB object → spec) and `de_serialize` (spec → DB object) with the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__exclude__ = ["facility"]` keeps `facility` out of every payload.
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BaseHealthcareServiceSpec` | shared base | Fields: `id`, `service_type`, `internal_type`, `name`, `styling_metadata`, `extra_details`. `__model__ = HealthcareService`, `__exclude__ = ["facility"]` |
+| `HealthcareServiceWriteSpec` | write · create & update | Base fields **plus** `locations: list[UUID4]` (default `[]`) and `managing_organization: UUID4 \| None`. Resolves `managing_organization` from its `external_id` in `perform_extra_deserialization` |
+| `HealthcareServiceReadSpec` | read · list | Base fields. `perform_extra_serialization` sets `id` to `obj.external_id` |
+| `HealthcareServiceRetrieveSpec` | read · detail | Extends `ReadSpec`; overrides `locations: list[dict]` (each location serialized via `FacilityLocationListSpec`) and `managing_organization: dict \| None` (serialized via `FacilityOrganizationReadSpec`) |
+
+### Field exposure by spec
+
+| Field | Base | Write | Read (list) | Retrieve (detail) |
+| --- | --- | --- | --- | --- |
+| `id` (UUID4) | ✓ (read-only) | ✓ | ✓ (= `external_id`) | ✓ (= `external_id`) |
+| `name` (str, required) | ✓ | ✓ | ✓ | ✓ |
+| `service_type` (bound `Coding`, optional) | ✓ | ✓ | ✓ | ✓ |
+| `internal_type` (enum, optional) | ✓ | ✓ | ✓ | ✓ |
+| `styling_metadata` (dict, default `{}`) | ✓ | ✓ | ✓ | ✓ |
+| `extra_details` (str, required) | ✓ | ✓ | ✓ | ✓ |
+| `locations` | — | `list[UUID4]` (write) | — | `list[dict]` (embedded) |
+| `managing_organization` | — | `UUID4 \| None` (write) | — | `dict \| None` (embedded) |
+
+### Validation & server-side behaviour
+
+- **`service_type`** — coded as a `ValueSetBoundCoding`; on de-serialization the `code` is validated against the `healthcare-service-type-code` value set (`validate_valueset`). `extra="forbid"` on the underlying `Coding` rejects unknown keys.
+- **`managing_organization`** (write) — `perform_extra_deserialization` resolves the supplied UUID via `get_object_or_404(FacilityOrganization, external_id=...)` and assigns the FK; if falsy, sets `managing_organization = None`.
+- **`locations`** (write) — supplied as UUIDs; the retrieve spec resolves each PK back to a `FacilityLocation` and serializes it, silently skipping any that fail to load (`try/except … pass`).
+- **`id` / `external_id`** — never written from the body; `de_serialize` skips `id`/`external_id`, and `serialize`/`perform_extra_serialization` populate `id` from `obj.external_id`.
+- **`facility`** — excluded from all specs; assigned server-side, so it cannot be set or changed through the API body.
+- No `status` field exists on this resource, so there is no server-maintained `status_history`.
+
+## Methods & save behaviour
+
+- Inherits `save()`, soft-delete (`deleted`), audit fields (`created_by`/`updated_by`), `history`/`meta`, and `external_id` from [`EMRBaseModel`](../foundation/base-model.mdx).
+- `EMRResource.serialize` / `de_serialize` drive read/write conversion; the extra (de)serialization hooks above run the FK resolution and embedded serialization.
+- `facility` and `managing_organization` use `on_delete=PROTECT`; deleting either while a service references it is blocked at the DB level.
+
+## API integration notes
+
+- Exposed through Care's REST API. The bookable surface is built from the spec classes, not the raw model.
+- `service_type` must be a `Coding` object whose `code` is in the bound value set — supply a coding structure, not a bare string.
+- `internal_type` is one of `pharmacy`, `lab`, `scheduling`, `store` on the API boundary.
+- `locations` on write is a list of location `external_id`s (UUIDs); detail responses embed full location objects. There is no DB-level integrity check that referenced locations belong to the same `facility`.
+- `managing_organization` on write is the org `external_id`; detail responses embed the serialized organization.
+- `styling_metadata` is an open JSON bag for UI hints and carries no backend semantics.
+- `facility` is never accepted in the request body.
+
+## Related
+
+- Reference: [Facility](./facility.mdx)
+- Reference: [Organization](./organization.mdx)
+- Reference: [Location](./location.mdx)
+- Reference: [Value set](../forms/valueset.mdx)
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Source: [healthcare_service.py model on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/healthcare_service.py)
+- Source: [healthcare_service spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/healthcare_service/spec.py)
+- Source: [healthcare_service valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/healthcare_service/valueset.py)
diff --git a/versioned_docs/version-3.0/references/facility/location.mdx b/versioned_docs/version-3.0/references/facility/location.mdx
new file mode 100644
index 0000000..1159f46
--- /dev/null
+++ b/versioned_docs/version-3.0/references/facility/location.mdx
@@ -0,0 +1,251 @@
+---
+sidebar_position: 5
+---
+
+# Location
+
+Technical reference for the `FacilityLocation` module in Care EMR.
+
+**Source:**
+
+- Model: [`care/emr/models/location.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/location.py)
+- Resource spec: [`care/emr/resources/location/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/location/spec.py)
+- Base resource: [`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+- Coding type: [`care/emr/resources/common/coding.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py)
+
+Locations are abstract, hierarchical entities (buildings, wards, rooms, beds) that describe where resources sit and where care is delivered. Nesting is unbounded: every location may have a parent and children. Encounters and organizations attach to locations, and location-level access is resolved through an async cascade rather than synchronously. The model is derived from the [FHIR Location](https://build.fhir.org/location.html) resource.
+
+The Django model is the **storage** layer; several fields (`location_type`, `metadata`, `cached_parent_json`) are opaque `JSONField`s whose real shape comes from the Pydantic **resource specs** (`care/emr/resources/location/spec.py`). The specs also define every enum/choice and the read/write API schemas. See [Resource specs (API schema)](#resource-specs-api-schema).
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `FacilityLocation` | A node in a facility's location tree (building, ward, room, bed, …) |
+| `FacilityLocationOrganization` | Grants a `FacilityOrganization` access to a location |
+| `FacilityLocationEncounter` | Records how/when an encounter occupied a location (e.g. a bed) |
+
+All models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+## `FacilityLocation` fields
+
+### Descriptive
+
+| Field | Type | Spec values / shape | Notes |
+| --- | --- | --- | --- |
+| `name` | `CharField(255)` | `str`, required | Location name; unique within its level under a root (see [validation](#methods--save-behaviour)) |
+| `description` | `CharField(255)` | `str`, required | Free-text description |
+| `status` | `CharField(255)` | `StatusChoices` enum | Location status; one of [`StatusChoices`](#statuschoices-values) |
+| `operational_status` | `CharField(255)` | `FacilityLocationOperationalStatusChoices` enum | FHIR operational status code; one of [`FacilityLocationOperationalStatusChoices`](#facilitylocationoperationalstatuschoices-values) |
+| `system_availability_status` | `CharField(255)` | `str` (read-only) | Server-maintained; derived from encounter association. Conceptually one of [`LocationAvailabilityStatusChoices`](#locationavailabilitystatuschoices-values) (`available` / `reserved`) |
+| `mode` | `CharField(255)` | `FacilityLocationModeChoices` enum | `kind` (a type, e.g. a building/ward) or `instance` (a single location, e.g. Bed 1). See [`FacilityLocationModeChoices`](#facilitylocationmodechoices-values). Write-only on create; not updatable |
+| `location_type` | `JSONField` (`default=dict`, nullable) | `Coding \| None` (default `None`) | A single FHIR [`Coding`](#coding-shape) — **not** a `CodeableConcept`. No bound value set; free coding |
+| `form` | `CharField(255)` | `FacilityLocationFormChoices` enum | FHIR physical form of the location; one of [`FacilityLocationFormChoices`](#facilitylocationformchoices-values) |
+
+### Hierarchy & tree caches
+
+These fields denormalize the location tree so descendants can be queried without recursive joins. They are maintained by `save()` and the cascade task — clients should not set them directly.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `parent` | `FK → FacilityLocation` | `SET_NULL`, nullable. Immediate parent node. Sent on write as a UUID (`parent`), excluded from read serialization (replaced with serialized parent JSON) |
+| `root_location` | `FK → self` | `CASCADE`, `related_name="root"`, nullable. Top of this node's tree. Excluded from specs |
+| `has_children` | `BooleanField` | `default=False`; flipped to `True` on the parent when a child is created. Exposed read-only in list/retrieve specs |
+| `level_cache` | `IntegerField` | `default=0`; depth in the tree (`parent.level_cache + 1`) |
+| `parent_cache` | `ArrayField[int]` | Ordered ancestor IDs (`parent.parent_cache + [parent.id]`) |
+| `cached_parent_json` | `JSONField` (`default=dict`) | Serialized parent chain (`FacilityLocationListSpec`) with a `cache_expiry` ISO timestamp; rebuilt lazily by `get_parent_json()`, invalidated by the cascade task. `cache_expiry_days = 15` |
+| `sort_index` | `IntegerField` | `default=0`; in specs `int \| None`, default `0`, constrained `0 ≤ sort_index ≤ 10000` (`MIN_SORT_INDEX`/`MAX_SORT_INDEX`). Auto-assigned as `max(sibling sort_index) + 1` when unset |
+
+### Access & encounter
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` | `PROTECT`. Owning facility. Excluded from specs (resolved from route/context) |
+| `facility_organization_cache` | `ArrayField[int]` | `default=list`. Org IDs that can access this location; rebuilt by `sync_organization_cache()` |
+| `current_encounter` | `FK → Encounter` | `SET_NULL`, nullable, `default=None`. Populated from `FacilityLocationEncounter`. Excluded from base spec; surfaced as serialized JSON in list/retrieve specs |
+| `metadata` | `JSONField` | `default=dict`. Open extension bag for deployment-specific data |
+
+## Enum values
+
+All enums are `str` `Enum`s defined in `care/emr/resources/location/spec.py`. Values below are character-for-character from source.
+
+### `StatusChoices` values
+
+| Value | Meaning |
+| --- | --- |
+| `active` | Location is active |
+| `inactive` | Location is inactive |
+| `unknown` | Status unknown |
+
+### `FacilityLocationOperationalStatusChoices` values
+
+FHIR HL7 v2 bed/location operational status codes.
+
+| Code | Meaning |
+| --- | --- |
+| `C` | Closed |
+| `H` | Housekeeping |
+| `O` | Occupied |
+| `U` | Unoccupied |
+| `K` | Contaminated |
+| `I` | Isolated |
+
+### `FacilityLocationModeChoices` values
+
+| Value | Meaning |
+| --- | --- |
+| `instance` | A single, concrete location (e.g. Bed 1). Instances **cannot have children** |
+| `kind` | A class/type of location (e.g. a ward or building) |
+
+### `FacilityLocationFormChoices` values
+
+FHIR `location-physical-type` codes.
+
+| Code | Meaning |
+| --- | --- |
+| `si` | Site |
+| `bu` | Building |
+| `wi` | Wing |
+| `wa` | Ward |
+| `lvl` | Level |
+| `co` | Corridor |
+| `ro` | Room |
+| `bd` | Bed |
+| `ve` | Vehicle |
+| `ho` | House |
+| `ca` | Cabinet |
+| `rd` | Road |
+| `area` | Area |
+| `jdn` | Jurisdiction |
+| `vi` | Virtual |
+
+### `LocationEncounterAvailabilityStatusChoices` values
+
+Used by `FacilityLocationEncounter.status` (how an encounter occupies a location).
+
+| Value | Meaning |
+| --- | --- |
+| `planned` | Occupancy planned |
+| `active` | Currently occupied |
+| `reserved` | Reserved |
+| `completed` | Occupancy ended |
+
+### `LocationAvailabilityStatusChoices` values
+
+Conceptual values for `system_availability_status` (server-derived).
+
+| Value | Meaning |
+| --- | --- |
+| `available` | Not actively tied to an encounter |
+| `reserved` | Tied to an encounter |
+
+### `Coding` shape
+
+`location_type` is a single `Coding` object (`extra="forbid"`):
+
+```text
+Coding {
+ system: str | None = None
+ version: str | None = None
+ code: str # required
+ display: str | None = None
+}
+```
+
+## Related models
+
+### `FacilityLocationOrganization`
+
+Denotes which organization can access a given location. Users under that organization inherit access to the location (and its encounters) through the role they hold on the organization.
+
+```text
+location → FK FacilityLocation (CASCADE)
+organization → FK FacilityOrganization (CASCADE)
+```
+
+Saving a `FacilityLocationOrganization` calls `location.save()` (rebuilding `facility_organization_cache`) and then `location.cascade_changes()`, which propagates the change to descendants asynchronously. On write, the set of organizations is supplied through `FacilityLocationWriteSpec.organizations` (a list of UUIDs).
+
+### `FacilityLocationEncounter`
+
+Records how an encounter was associated to a location (e.g. a bed assignment over a time window).
+
+| Field | Type | Spec | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(25)` | `LocationEncounterAvailabilityStatusChoices` | One of `planned` / `active` / `reserved` / `completed` |
+| `location` | `FK → FacilityLocation` | excluded / nested | `CASCADE`. Surfaced as JSON in `...ListSpecWithLocation` |
+| `encounter` | `FK → Encounter` | UUID on write, JSON on read | `CASCADE`. Validated to exist on create |
+| `start_datetime` | `DateTimeField` | `datetime` (required) | Occupancy start |
+| `end_datetime` | `DateTimeField` (nullable) | `datetime \| None` | Occupancy end (open-ended when null) |
+
+`FacilityLocation.current_encounter` is populated from these rows.
+
+## Methods & save behaviour
+
+### `save()` side effects
+
+On insert (no `id` yet):
+
+1. If `parent` is set → `level_cache`, `root_location`, and `parent_cache` are derived from the parent; the parent's `has_children` is flipped to `True` if needed.
+2. If `sort_index` is unset → it is assigned `max(sibling sort_index) + 1`.
+
+On update, `cached_parent_json` is cleared so it rebuilds lazily. After every `super().save()`, `sync_organization_cache()` runs and persists `facility_organization_cache` via a second `save(update_fields=[...])`.
+
+### Caches & cascade
+
+- `sync_organization_cache()` — unions the parent's org cache, all linked `FacilityLocationOrganization` org chains, and the facility's `default_internal_organization_id`, then writes `facility_organization_cache`.
+- `get_parent_json()` — returns the cached parent chain, refreshing `cached_parent_json` (via `FacilityLocationListSpec.serialize(parent)`) when expired (`cache_expiry_days = 15`).
+- `cascade_changes()` / `handle_cascade(base_location)` — a Celery task that walks every descendant and re-saves it to invalidate its `cached_parent_json`. Triggered when a location's organizations change; expect propagation to be **eventually consistent**.
+
+### `validate_uniqueness()`
+
+Classmethod enforcing that `name` is unique among siblings at the same `level_cache` within the same `root_location` (or among root-level locations when there is no root). For a new instance with a `parent`, `level_cache` and `root_location` are computed from that parent before checking.
+
+## Resource specs (API schema)
+
+The Pydantic specs in `care/emr/resources/location/spec.py` define the API request/response shapes. All extend `EMRResource` (`serialize`/`de_serialize`, with `perform_extra_serialization`/`perform_extra_deserialization` hooks; see [base model](../foundation/base-model.mdx) and [`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)).
+
+### Location specs
+
+| Spec class | Role | Exposes / notes |
+| --- | --- | --- |
+| `FacilityLocationBaseSpec` | shared base | `__model__ = FacilityLocation`; `id: UUID4 \| None`. `__exclude__ = [parent, facility, organizations, root_location, current_encounter]` |
+| `FacilityLocationSpec` | shared fields | `status`, `operational_status`, `name`, `description`, `location_type` (`Coding \| None`), `form`, `sort_index` (`0..10000`, default `0`) |
+| `FacilityLocationWriteSpec` | write · create | Adds `parent: UUID4 \| None`, `organizations: list[UUID4]` (required), `mode: FacilityLocationModeChoices`. Validator: if `parent` set, parent must exist and must not be `mode = instance` ("Instances cannot have children"). `perform_extra_deserialization` resolves `parent` UUID → FK |
+| `FacilityLocationUpdateSpec` | write · update | Same fields as `FacilityLocationSpec` (no `parent`/`organizations`/`mode` — hierarchy and mode are fixed after creation) |
+| `FacilityLocationMinimalListSpec` | read · minimal | Adds `parent: dict`, `mode: str`, `has_children: bool`, `system_availability_status: str`. `perform_extra_serialization` sets `id = external_id` and `parent = obj.get_parent_json()` |
+| `FacilityLocationListSpec` | read · list | Extends minimal; adds `current_encounter: dict \| None`, serialized via `EncounterListSpec` when present |
+| `FacilityLocationRetrieveSpec` | read · detail | Extends list; adds `created_by`/`updated_by` (via `serialize_audit_users`) |
+
+### Encounter-association specs
+
+| Spec class | Role | Exposes / notes |
+| --- | --- | --- |
+| `FacilityLocationEncounterBaseSpec` | shared base | `__model__ = FacilityLocationEncounter`; `id: UUID4 \| None`. `__exclude__ = [encounter, location]` |
+| `FacilityLocationEncounterCreateSpec` | write · create | `status` (`LocationEncounterAvailabilityStatusChoices`), `encounter: UUID4`, `start_datetime`, `end_datetime: datetime \| None`. Validator: encounter must exist. `perform_extra_deserialization` resolves `encounter` UUID → FK |
+| `FacilityLocationEncounterUpdateSpec` | write · update | `status`, `start_datetime`, `end_datetime` (encounter not re-assignable) |
+| `FacilityLocationEncounterListSpec` | read · list | `encounter: UUID4`, `start_datetime`, `end_datetime`, `status: str`; `id = external_id` |
+| `FacilityLocationEncounterListSpecWithLocation` | read · list | Extends list; adds `location: dict` (serialized via `FacilityLocationListSpec`) |
+| `FacilityLocationEncounterReadSpec` | read · detail | `encounter: dict` (serialized via `EncounterRetrieveSpec`), `start_datetime`, `end_datetime`, `status`, `created_by`/`updated_by` |
+
+### Server-maintained behaviour
+
+- `system_availability_status`, `has_children`, `level_cache`, `parent_cache`, `cached_parent_json`, `facility_organization_cache`, `current_encounter`, `sort_index` (when unset) are platform-maintained — do not write them from clients.
+- On read, `parent` is replaced by the serialized parent chain (`get_parent_json()`), not the raw FK.
+- Granting access via `organizations` / `FacilityLocationOrganization` is **asynchronous**; the org cache and descendant caches settle after the cascade task runs.
+- `location_type` binds to a free `Coding` (no value set slug); `metadata` is an open extension bag — both add coded/extension data without schema migrations.
+
+## API integration notes
+
+- Locations are exposed through Care's REST API and modelled on the FHIR `Location` resource; payload field names may differ from these Django names.
+- `mode` distinguishes `kind` (a location type) from `instance` (a concrete location such as a bed) — beds are `FacilityLocation` instances, not a separate model. `instance` locations cannot have children.
+- `parent`, `organizations`, and `mode` are settable only at creation (`FacilityLocationWriteSpec`); updates use `FacilityLocationUpdateSpec`, which omits them.
+- `level_cache`, `parent_cache`, `cached_parent_json`, `has_children`, `sort_index`, `facility_organization_cache`, and `current_encounter` are platform-maintained — do not write them from clients.
+
+## Related
+
+- Reference: [Facility](./facility.mdx)
+- Reference: [Organization](./organization.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [location.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/location.py)
+- Source: [location/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/location/spec.py)
diff --git a/versioned_docs/version-3.0/references/facility/organization.mdx b/versioned_docs/version-3.0/references/facility/organization.mdx
new file mode 100644
index 0000000..d31576e
--- /dev/null
+++ b/versioned_docs/version-3.0/references/facility/organization.mdx
@@ -0,0 +1,215 @@
+---
+sidebar_position: 4
+---
+
+# Organization
+
+Technical reference for the `Organization` module in Care EMR.
+
+**Source:**
+[`care/emr/models/organization.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/organization.py) ·
+[`resources/organization/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/organization/spec.py) ·
+[`resources/organization/organization_user_spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/organization/organization_user_spec.py)
+
+Organizations are Care's FHIR-aligned grouping primitive: a nested tree used to group permissions and resources. An organization might represent all doctors, a `Cardiology` sub-team, or a governance unit. Permissions attached to a parent are implicitly available in its descendants.
+
+The Django model is the **storage** layer — several columns are opaque `JSONField`s (`metadata`, `cached_parent_json`) and arrays (`parent_cache`, `managing_organizations`) whose real structure lives in the **resource specs** (`care/emr/resources/organization/`). The specs define the `org_type` enum, validation, the nested JSON shape returned for `parent`, and the read/write API schemas. See [Resource specs (API schema)](#resource-specs-api-schema).
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `OrganizationCommonBase` | Abstract base holding the tree structure, caching, and uniqueness logic shared by both organization types |
+| `Organization` | Instance-wide organization (permission/governance tree, not tied to a facility) |
+| `FacilityOrganization` | Facility-scoped organization (departments/teams within one `Facility`) |
+| `OrganizationUser` | Membership row linking a `User` to an `Organization` with a `RoleModel` |
+| `FacilityOrganizationUser` | Membership row linking a `User` to a `FacilityOrganization` with a `RoleModel` |
+
+`Organization` and `FacilityOrganization` extend `OrganizationCommonBase`, which itself extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base giving `external_id`, audit fields, soft-delete, and `history`/`meta` JSON). `OrganizationUser` and `FacilityOrganizationUser` extend `EMRBaseModel` directly. `OrganizationCommonBase` is `abstract = True` and defines no table of its own.
+
+## `OrganizationCommonBase` fields
+
+These columns are contributed to both the `Organization` and `FacilityOrganization` tables.
+
+### Identity & classification
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `name` | `CharField(255)` | yes | — | Display name; participates in the sibling uniqueness check (`validate_uniqueness`) |
+| `org_type` | `CharField(255)` | yes | — | Category of the organization. Free text in the DB, but writes bind to the `OrganizationTypeChoices` enum. See [Organization type values](#organization-type-values) |
+| `description` | `TextField` | no | `null` | Nullable, blank-able. On the API the write/read specs default it to `""` |
+| `active` | `BooleanField` | no | `True` | |
+| `system_generated` | `BooleanField` | no | `False` | System-generated orgs cannot be edited or deleted. Read-only on the API (only surfaced by `OrganizationReadSpec`) |
+| `metadata` | `JSONField` | no | `{}` (`dict`) | Open key-value bag for deployment-specific data; no fixed schema, no migration needed. Exposed verbatim by the specs as `metadata: dict` |
+
+### Tree structure
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `parent` | `FK → self` | no | `null` | `CASCADE`, `related_name="children"`. Null parent means a root org. On write, supplied as the parent's `external_id` (UUID); on read, replaced by a nested JSON object — see [`parent` read shape](#parent-read-shape) |
+| `root_org` | `FK → self` | no | `null` | `CASCADE`, `related_name="root"`. Top of the tree, derived on save by `set_organization_cache()` |
+| `has_children` | `BooleanField` | no | `False` | Flipped to `True` on the parent when its first child is created. Read-only on the API |
+
+### Denormalized caches
+
+Platform-maintained — do **not** set these from clients. Rebuilt on insert to avoid recursive joins when reading the tree.
+
+| Field | Type | Default | Rebuilt by | Shape |
+| --- | --- | --- | --- | --- |
+| `level_cache` | `IntegerField` | `0` | `set_organization_cache()` | Depth in the tree (`parent.level_cache + 1`) |
+| `parent_cache` | `ArrayField[int]` | `[]` (`list`) | `set_organization_cache()` | Full ancestor **internal id** chain (`parent.parent_cache + [parent.id]`) |
+| `cached_parent_json` | `JSONField` | `{}` (`dict`) | `get_parent_json()` | Materialized parent record; see shape below. Rebuilt after `cache_expiry_days` (15) |
+
+`cached_parent_json` shape (built in `get_parent_json()`; `{}` for root orgs):
+
+```text
+{
+ id: str (parent.external_id, UUID as string)
+ name: str
+ description: str | null
+ org_type: str (OrganizationTypeChoices value)
+ metadata: dict
+ parent: dict (recursively, the parent's own cached_parent_json)
+ level_cache: int
+ cache_expiry: str (ISO datetime; cache is reused while now < cache_expiry)
+}
+```
+
+This same structure is what `OrganizationReadSpec.parent` returns on read (see [`parent` read shape](#parent-read-shape)).
+
+## Related models
+
+### `Organization`
+
+Instance-wide organization. Adds one field on top of `OrganizationCommonBase`:
+
+| Field | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `managing_organizations` | `ArrayField[int]` | `[]` (`list`) | Internal ids of orgs that manage this one. Expanded to a list of nested org JSON (`OrganizationReadSpec`) only by `OrganizationRetrieveSpec` |
+
+### `FacilityOrganization`
+
+Facility-scoped organization. Adds one field on top of `OrganizationCommonBase`:
+
+```text
+facility → FK facility.Facility (CASCADE)
+```
+
+See [Facility](../facility/facility.mdx).
+
+### `OrganizationUser`
+
+Grants a user access to resources owned by an organization through a role. Membership does not imply access to all resources of the org — only those assigned to it.
+
+```text
+organization → FK Organization (CASCADE)
+user → FK users.User (CASCADE)
+role → FK security.RoleModel (CASCADE)
+```
+
+### `FacilityOrganizationUser`
+
+The facility-scoped equivalent of `OrganizationUser`.
+
+```text
+organization → FK FacilityOrganization (CASCADE)
+user → FK users.User (CASCADE)
+role → FK security.RoleModel (CASCADE)
+```
+
+## Enum & coded values
+
+### Organization type values
+
+`org_type` is a `CharField` in the DB, but all writes bind to `OrganizationTypeChoices` (`resources/organization/spec.py`).
+
+| Value | Meaning |
+| --- | --- |
+| `team` | A team / department-style grouping |
+| `govt` | Governance / governmental unit (typically superadmin-managed) |
+| `role` | Role-grouping org. An `OrganizationUser` on a `role`-typed org invalidates the user's cached role-org list (see [`OrganizationUser.save()`](#organizationusersave)) |
+| `product_supplier` | Supplier organization (links to the supply chain) |
+
+`FacilityOrganization` in practice uses `team` and `root` types; the enum bound on the spec is the same `OrganizationTypeChoices` (instance scope). `govt`/`role` orgs and root orgs are restricted to superadmin management.
+
+## Resource specs (API schema)
+
+Resource specs (`EMRResource` subclasses) are the API/implementation layer. `serialize` builds a read object from a DB row (running `perform_extra_serialization` / `perform_extra_user_serialization` hooks); `de_serialize` builds a DB row from a write payload (running `perform_extra_deserialization`). `__exclude__` lists model fields the base serializer skips (handled manually in the hooks).
+
+### Organization specs (`resources/organization/spec.py`)
+
+| Spec | Role | Fields exposed | Notes |
+| --- | --- | --- | --- |
+| `OrganizationBaseSpec` | shared | `id`, `active`, `org_type`, `name`, `description`, `metadata` | `__model__ = Organization`, `__exclude__ = ["parent"]`. `org_type` bound to `OrganizationTypeChoices`; `description` defaults `""`; `metadata` defaults `{}` |
+| `OrganizationUpdateSpec` | write · update | (inherits `OrganizationBaseSpec`) | No extra fields — `parent` cannot be changed on update |
+| `OrganizationWriteSpec` | write · create | base + `parent: UUID4 \| null` | Validates `parent` exists (`validate_parent_organization`); on create, `perform_extra_deserialization` resolves `parent` from `external_id` (or sets `None`) |
+| `OrganizationReadSpec` | read · list | base + `level_cache`, `system_generated`, `has_children`, `parent: dict` | `perform_extra_serialization` sets `id = external_id` and `parent = obj.get_parent_json()` — see [`parent` read shape](#parent-read-shape) |
+| `OrganizationRetrieveSpec` | read · detail | `OrganizationReadSpec` + `permissions: list[str]`, `managing_organizations: list[dict]` | `perform_extra_user_serialization` fills `permissions` (via `AuthorizationController.get_permission_on_organization` for the requesting user), expands `managing_organizations` to nested `OrganizationReadSpec` JSON, and adds audit users (`serialize_audit_users`) |
+
+#### `parent` read shape
+
+`OrganizationReadSpec.parent` (and `OrganizationRetrieveSpec.parent`) is **not** a UUID — it is the nested object returned by `get_parent_json()`. Same shape as [`cached_parent_json`](#denormalized-caches): `{ id, name, description, org_type, metadata, parent (recursive), level_cache, cache_expiry }`, or `{}` for root orgs. On write, `parent` is instead a plain `UUID4` (the parent's `external_id`).
+
+### Membership specs (`resources/organization/organization_user_spec.py`)
+
+| Spec | Role | Fields exposed | Notes |
+| --- | --- | --- | --- |
+| `OrganizationUserBaseSpec` | shared | (none) | `__model__ = OrganizationUser`, `__exclude__ = ["user", "role"]` |
+| `OrganizationUserUpdateSpec` | write · update | `role: UUID4` | `validate_role` requires the `RoleModel` to exist; `perform_extra_deserialization` resolves `role` from `external_id`. Only the role can change on update |
+| `OrganizationUserWriteSpec` | write · create | `role: UUID4` + `user: UUID4` | Extends update spec; `validate_user` requires the `User` to exist. On create, resolves both `user` and `role` from `external_id` |
+| `OrganizationUserReadSpec` | read | `id: UUID4`, `user: dict`, `role: dict` | `user` = cached `UserSpec` JSON (`model_from_cache`); `role` = `RoleReadSpec` JSON (full permissions) |
+| `OrganizationUserExtendedReadSpec` | read | `id: UUID4`, `role: dict`, `organization: dict` | Used by `OrganizationUser.get_cached_role_orgs()`. `organization` = `OrganizationReadSpec` JSON; `role` = `RoleReadMinimalSpec` JSON (no permission list) |
+
+Nested membership JSON shapes (from `resources/role/spec.py`, `resources/user/spec.py`):
+
+```text
+role (RoleReadSpec): { id: UUID, name, description, is_system: bool,
+ is_archived: bool, contexts: [RoleContext],
+ permissions: [{ name, description, slug, context }] }
+role (RoleReadMinimalSpec): same as above without `permissions`
+user (UserSpec): cached user summary JSON
+```
+
+## Methods & save behaviour
+
+### `OrganizationCommonBase.save()`
+
+On **insert** (no `id` yet) the base saves the row, then calls `set_organization_cache()` to populate the tree caches. On **update** it saves normally without recomputing caches.
+
+`set_organization_cache()` side effects when a `parent` is set:
+
+1. Computes `parent_cache` = `parent.parent_cache + [parent.id]` and `level_cache` = `parent.level_cache + 1`.
+2. Derives `root_org` from the parent (`parent.root_org`, or the parent itself if the parent is a root).
+3. If the parent did not previously have children, flips `parent.has_children = True` and persists only that field.
+
+### `get_parent_json()`
+
+Returns the cached parent JSON if `cached_parent_json` is present and `cache_expiry` is still in the future; otherwise it recursively rebuilds the nested parent record, stamps a new expiry (`cache_expiry_days = 15`), and persists `cached_parent_json`. Returns `{}` for root orgs. This is what `OrganizationReadSpec.parent` exposes on every read.
+
+### `validate_uniqueness(queryset, pydantic_instance, model_instance)`
+
+Classmethod enforcing that `name` is unique among **siblings** — same `root_org` and same `level_cache`. For a new org it derives `level_cache`/`root_org` from the supplied `parent` (`external_id`); a null parent means `level_cache = 0` and a null `root_org`. Used by the resource layer before create/update.
+
+### `OrganizationUser.save()`
+
+After saving, if the linked organization's `org_type` is `role` (`OrganizationTypeChoices.role`), it clears `user.cached_role_orgs` (sets it to `None` and persists that field) so the user's role-org cache is rebuilt on next read.
+
+`OrganizationUser.get_cached_role_orgs(user_id)` is a classmethod that serializes all `role`-typed org memberships for a user via `OrganizationUserExtendedReadSpec`.
+
+## API integration notes
+
+- Organizations are exposed through Care's REST API and align with the FHIR `Organization` resource. Tree position (`level_cache`, `parent_cache`, `root_org`), `has_children`, and `cached_parent_json` are platform-maintained — do not set them directly from clients.
+- **Write path:** `OrganizationWriteSpec` (create) / `OrganizationUpdateSpec` (update). `org_type` is constrained to `OrganizationTypeChoices`; `parent` is supplied as the parent's `external_id` (UUID) and validated to exist; `validate_uniqueness` enforces sibling-name uniqueness. `parent` is immutable after create (the update spec omits it).
+- **Read path:** `OrganizationReadSpec` (list) returns `parent` as nested JSON (`get_parent_json()`) plus `level_cache`/`system_generated`/`has_children`; `OrganizationRetrieveSpec` (detail) additionally returns the caller's `permissions`, expanded `managing_organizations`, and audit users.
+- Membership is managed through `OrganizationUser` / `FacilityOrganizationUser` (specs above); `role` is required and bounded by the assigning user's own role in that org. Adding a member to a `role`-typed org invalidates that user's `cached_role_orgs`.
+- `metadata` is the supported place for deployment-specific key-value data without schema migrations; it is passed through the specs verbatim as `dict`.
+- Soft-delete applies (via `EMRBaseModel`); an organization cannot be deleted while it still has children, and system-generated orgs cannot be deleted.
+
+## Related
+
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Reference: [User](../access/user.mdx) · [Role](../access/role.mdx) · [Permission](../access/permission.mdx)
+- Source — models: [organization.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/organization.py)
+- Source — specs: [organization/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/organization/spec.py) · [organization/organization_user_spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/organization/organization_user_spec.py) · [resources/base.py (`EMRResource`)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+- Source — related: [role/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py) · [user/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/user/spec.py) · [security/models/role.py](https://github.com/ohcnetwork/care/blob/develop/care/security/models/role.py) · [users/models.py](https://github.com/ohcnetwork/care/blob/develop/care/users/models.py)
diff --git a/versioned_docs/version-3.0/references/forms/_category_.json b/versioned_docs/version-3.0/references/forms/_category_.json
new file mode 100644
index 0000000..979e57e
--- /dev/null
+++ b/versioned_docs/version-3.0/references/forms/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Forms & Terminology",
+ "position": 8,
+ "key": "forms-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Forms & Terminology",
+ "description": "Questionnaires that drive structured data capture, and the value sets that constrain coded answers."
+ }
+}
diff --git a/versioned_docs/version-3.0/references/forms/questionnaire-response-template.mdx b/versioned_docs/version-3.0/references/forms/questionnaire-response-template.mdx
new file mode 100644
index 0000000..a78b804
--- /dev/null
+++ b/versioned_docs/version-3.0/references/forms/questionnaire-response-template.mdx
@@ -0,0 +1,90 @@
+---
+sidebar_position: 3
+---
+
+# Questionnaire Response Template
+
+Technical reference for the `QuestionnaireResponseTemplate` module in Care EMR — a reusable, shareable template that pre-fills a [questionnaire response](./questionnaire-response.mdx) along with associated medication requests and activity definitions (a clinician's "favourite" / order set).
+
+The Django model is the **storage** layer; the Pydantic **resource specs** define the structured `template_data` payload, validation, and the read/write API schemas.
+
+**Source:**
+- Model: [`care/emr/models/questionnaire.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/questionnaire.py)
+- Spec: [`resources/questionnaire_response_template/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire_response_template/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `QuestionnaireResponseTemplate` | A named template bundling pre-filled questionnaire answers, medication requests, and activity definitions, scoped to a facility/users/organizations |
+
+`QuestionnaireResponseTemplate` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, soft-delete, and `history`/`meta` JSON).
+
+## `QuestionnaireResponseTemplate` fields
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(255)` | Template name |
+| `description` | `TextField` | `default=""` |
+| `template_data` | `JSONField` | `default=dict`. Structured payload — shape defined by `TemplateData` (see [shape](#templatedata-shape)) |
+| `facility` | `FK → Facility` | `CASCADE`, nullable. Owning facility (instance-wide when null) |
+| `questionnaire` | `FK → Questionnaire` | `CASCADE`, nullable, `default=None`. Optional questionnaire the template targets |
+| `facility_organizations` | `ArrayField[int]` | `default=list`. Facility-organization ids the template is shared with |
+| `users` | `ArrayField[int]` | `default=list`. User ids the template is shared with |
+| `available_keys` | `ArrayField[CharField(255)]` | `default=list`. Platform-maintained — the populated top-level keys of `template_data`, recomputed on every write |
+
+## `TemplateData` shape
+
+The real structure of the opaque `template_data` `JSONField` (`resources/questionnaire_response_template/spec.py`). Every section is optional; `available_keys` records which are populated.
+
+```text
+TemplateData {
+ medication_request: list[MedicationRequestTemplateSpec] | None
+ questionnaire: list[QuestionnaireAnswer] | None
+ activity_definition: list[ActivityDefinitionTemplateSpec] | None
+ meta: dict | None
+}
+
+QuestionnaireAnswer { question_id: str, answer: dict, meta: dict }
+
+MedicationRequestTemplateSpec # extends the medication request write spec
+ + requested_product: str | None # validated against ProductKnowledge.slug
+
+ActivityDefinitionTemplateSpec {
+ slug: str # validated against ActivityDefinition.slug
+ service_request: ServiceRequestUpdateSpec
+}
+```
+
+See [Medication Request](../medications/medication-request.mdx), [Activity Definition](../clinical/activity-definition.mdx), and [Service Request](../clinical/service-request.mdx) for the embedded specs.
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`resources/base.py`).
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `QuestionnaireResponseTemplateBaseSpec` | shared | `__model__ = QuestionnaireResponseTemplate`; `id` (`UUID4?`), `template_data` (`TemplateData`), `name`, `description` (`= ""`) |
+| `QuestionnaireResponseTemplateCreateSpec` | write · create | Adds `questionnaire` (slug `str?`), `facility` (`UUID4?`), `users` (`list[str]`), `facility_organizations` (`list[UUID4]`). Validates that a `facility` is present when `facility_organizations` are given; resolves the questionnaire by slug and facility by external id; recomputes `available_keys` |
+| `QuestionnaireResponseTemplateUpdateSpec` | write · update | `users`, `facility_organizations`; recomputes `available_keys`. Cannot change `facility`/`questionnaire` |
+| `QuestionnaireResponseTemplateReadSpec` | read · list | Adds `created_date`/`modified_date` |
+| `QuestionnaireResponseTemplateRetrieveSpec` | read · detail | Expands `users` (`UserSpec` via cache) and `facility_organizations` (`FacilityOrganizationReadSpec`), plus `created_by`/`updated_by` |
+
+### Validation & server-maintained behaviour
+
+- `validate_facility` (`model_validator(after)` on create): rejects facility organizations without a facility.
+- `available_keys` is derived on every create/update in `perform_extra_deserialization` — it lists the `template_data` keys whose value is non-empty. Clients should not set it directly.
+- Embedded `requested_product` and activity-definition `slug` are validated to exist (`ProductKnowledge`, `ActivityDefinition`) before save.
+
+## API integration notes
+
+- This is a Care-specific authoring/convenience resource (not a FHIR resource): one template can seed a questionnaire response, medication requests, and activity-definition-driven service requests at once.
+- Sharing is controlled by `facility`, `facility_organizations`, and `users`; instance-wide templates leave `facility` null.
+- `available_keys` lets clients cheaply discover which sections a template carries without parsing `template_data`.
+
+## Related
+
+- Reference: [Questionnaire Response](./questionnaire-response.mdx) — what a template helps pre-fill
+- Reference: [Questionnaire](./questionnaire.mdx)
+- Reference: [Medication Request](../medications/medication-request.mdx) · [Activity Definition](../clinical/activity-definition.mdx) · [Service Request](../clinical/service-request.mdx)
+- Source: [questionnaire.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/questionnaire.py)
diff --git a/versioned_docs/version-3.0/references/forms/questionnaire-response.mdx b/versioned_docs/version-3.0/references/forms/questionnaire-response.mdx
new file mode 100644
index 0000000..6ca9de7
--- /dev/null
+++ b/versioned_docs/version-3.0/references/forms/questionnaire-response.mdx
@@ -0,0 +1,139 @@
+---
+sidebar_position: 2
+---
+
+# Questionnaire Response
+
+Technical reference for the `QuestionnaireResponse` and `FormSubmission` modules in Care EMR — the **submitted answers** to a [questionnaire](./questionnaire.mdx) for a patient, and the submission envelope that carries a draft/finalised payload.
+
+The Django models are the **storage** layer; the Pydantic **resource specs** define the submit payload, the answer structure, status enums, and the read/write API schemas.
+
+**Source:**
+- Model: [`care/emr/models/questionnaire.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/questionnaire.py)
+- Specs: [`resources/questionnaire_response/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire_response/spec.py) · [`resources/form_submission/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/form_submission/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `QuestionnaireResponse` | A completed set of answers to a `Questionnaire` for a subject (patient/encounter) |
+| `FormSubmission` | A submission envelope (draft → submitted) holding a raw response dump for a questionnaire |
+
+Both extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, soft-delete via `deleted`, and `history`/`meta` JSON).
+
+## `QuestionnaireResponse` fields
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `questionnaire` | `FK → Questionnaire` | `CASCADE`, nullable. The questionnaire that was answered |
+| `subject_id` | `UUIDField` | The subject the response is about (e.g. patient external id) |
+| `responses` | `JSONField` | `default=list`. The raw answers — a list of `{ "question_id": UUID, ... }` entries |
+| `structured_responses` | `JSONField` | `default=dict`. Extracted/structured representation of the answers |
+| `structured_response_type` | `CharField` | Nullable. Discriminator for the structured payload |
+| `patient` | `FK → Patient` | `CASCADE`. The patient |
+| `encounter` | `FK → Encounter` | `CASCADE`, nullable. The encounter, when answered in a visit context |
+| `form_submission` | `FK → FormSubmission` | `CASCADE`, nullable. The submission this response came from |
+| `status` | `CharField(255)` | `default="completed"`. One of `QuestionnaireResponseStatusChoices` (see [enum](#questionnaire-response-status)) |
+
+### `render_responses()`
+
+Joins each entry in `responses` with the matching question definition from `questionnaire.get_questions_by_id()`, producing a list of `{ "answer": , "question": }` — a human-readable view of the answers against the current questionnaire. Returns `[]` when there are no responses or no linked questionnaire.
+
+## `FormSubmission` fields
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `questionnaire` | `FK → Questionnaire` | `CASCADE`. The questionnaire being submitted |
+| `patient` | `FK → Patient` | `CASCADE`. The patient |
+| `encounter` | `FK → Encounter` | `CASCADE`, nullable. Encounter context, if any |
+| `status` | `CharField(255)` | One of `FormSubmissionStatusChoices` (see [enum](#form-submission-status)) |
+| `response_dump` | `JSONField` | `default=dict`. Raw submitted payload |
+
+## Enums
+
+### Questionnaire response status
+
+`QuestionnaireResponseStatusChoices` (`resources/questionnaire_response/spec.py`), `str` values.
+
+| Value | Meaning |
+| --- | --- |
+| `completed` | The response is finalised (default) |
+| `entered_in_error` | The response was recorded in error |
+
+### Form submission status
+
+`FormSubmissionStatusChoices` (`resources/form_submission/spec.py`), `str` values.
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Saved but not finalised |
+| `submitted` | Finalised submission |
+| `entered_in_error` | Recorded in error |
+
+## Submit payload (nested shapes)
+
+Answers are submitted through the questionnaire submit endpoint, whose body is `QuestionnaireSubmitRequest`:
+
+```text
+QuestionnaireSubmitRequest {
+ resource_id: UUID4 # questionnaire being answered
+ patient: UUID4 (required)
+ encounter: UUID4 | None
+ form_submission: UUID4 | None
+ results: list[QuestionnaireSubmitResult]
+}
+
+QuestionnaireSubmitResult {
+ question_id: UUID4 | UUID5 (required)
+ body_site: Coding | None
+ method: Coding | None
+ taken_at: datetime | None
+ values: list[QuestionnaireSubmitResultValue]
+ note: str | None
+ sub_results: list[list[QuestionnaireSubmitResult]] # nested, for group questions
+}
+
+QuestionnaireSubmitResultValue {
+ value: str | None
+ unit: Coding | None # for Quantity answers
+ coding: Coding | None # for coded answers
+}
+```
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`resources/base.py`).
+
+### `QuestionnaireResponse` (`resources/questionnaire_response/spec.py`)
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `EMRQuestionnaireResponseBase` | shared | `__model__ = QuestionnaireResponse` |
+| `QuestionnaireResponseUpdate` | write · update | Only `status` (`QuestionnaireResponseStatusChoices`, default `completed`) — used to mark a response `entered_in_error` |
+| `QuestionnaireResponseReadSpec` | read | `id`, `status`, `questionnaire` (nested `QuestionnaireReadSpec`), `subject_id`, `responses`, `encounter` (external id or `null`), `structured_responses`, `structured_response_type`, `created_by`/`updated_by` (`UserSpec`), `created_date`/`modified_date` |
+
+New responses are created via the questionnaire submit flow (`QuestionnaireSubmitRequest`), not a generic create spec.
+
+### `FormSubmission` (`resources/form_submission/spec.py`)
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BaseFormSubmissionSpec` | shared | `__model__ = FormSubmission`; `id` (`UUID4?`) |
+| `FormSubmissionUpdateSpec` | write · update | `status` (`FormSubmissionStatusChoices`), `response_dump` (`dict`) |
+| `FormSubmissionWriteSpec` | write · create | Adds `questionnaire` (slug `str`), `patient` (`UUID4`), `encounter` (`UUID4?`). `perform_extra_deserialization` resolves the questionnaire by slug and the patient/encounter by external id; when an encounter is given, the patient is taken from it |
+| `FormSubmissionReadSpec` | read | `status`, `response_dump`, `created_date`/`modified_date`, `created_by`/`updated_by` (`UserSpec?`) |
+
+## API integration notes
+
+- Both align loosely with the FHIR `QuestionnaireResponse` resource; `responses` holds the raw answers, while `structured_responses` is the extracted, query-friendly form.
+- A `QuestionnaireResponse` is normally created by submitting a `QuestionnaireSubmitRequest`; the update spec exists mainly to flag responses `entered_in_error`.
+- `FormSubmission` supports a draft→submitted lifecycle; `response_dump` is an opaque JSON payload validated by the questionnaire definition, not by the model.
+- Audit users (`created_by`/`updated_by`) are platform-maintained.
+
+## Related
+
+- Reference: [Questionnaire](./questionnaire.mdx) — the form definition being answered
+- Reference: [Questionnaire Response Template](./questionnaire-response-template.mdx) — reusable pre-fill templates
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Concept: [Patient](../../concepts/clinical/patient.mdx)
+- Source: [questionnaire.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/questionnaire.py)
diff --git a/versioned_docs/version-3.0/references/forms/questionnaire.mdx b/versioned_docs/version-3.0/references/forms/questionnaire.mdx
new file mode 100644
index 0000000..1f7f3ef
--- /dev/null
+++ b/versioned_docs/version-3.0/references/forms/questionnaire.mdx
@@ -0,0 +1,390 @@
+---
+sidebar_position: 1
+---
+
+# Questionnaire
+
+Technical reference for the `Questionnaire` module in Care EMR.
+
+Questionnaires (a.k.a. **forms**) are generalized, FHIR-inspired data-collection structures: a versioned tree of questions answered about a subject (a patient or an encounter). They power both clinical data capture (answers become [Observations](../clinical/observation.mdx)) and non-clinical structured data. The Django model is the **storage** layer; the Pydantic **resource specs** in `care/emr/resources/questionnaire*` define the real API schema — enums, the nested shape of the JSON fields, validation, and the read/write contracts.
+
+**Source (model):** [`care/emr/models/questionnaire.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/questionnaire.py)
+
+**Source (specs):**
+[`resources/questionnaire/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire/spec.py) ·
+[`resources/questionnaire/utils.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire/utils.py) ·
+[`resources/questionnaire/questionnaire_organization.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire/questionnaire_organization.py) ·
+[`resources/questionnaire_response/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire_response/spec.py) ·
+[`resources/questionnaire_response_template/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire_response_template/spec.py) ·
+[`resources/form_submission/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/form_submission/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Questionnaire` | A versioned form definition: questions, styling, subject type, and status |
+| `FormSubmission` | A submission of a questionnaire against a patient (and optional encounter) |
+| `QuestionnaireResponse` | A stored set of answers for a subject, optionally tied to a submission |
+| `QuestionnaireOrganization` | Scopes a questionnaire to an instance-level `Organization` |
+| `QuestionnaireFacilityOrganization` | Scopes a questionnaire to a `FacilityOrganization` |
+| `QuestionnaireResponseTemplate` | Reusable prefill template for questionnaire responses |
+
+All models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+## `Questionnaire` fields
+
+### Definition
+
+| Field | Type | Req | Notes |
+| --- | --- | --- | --- |
+| `version` | `CharField(255)` | yes | Version label. Write spec frozen to `"1.0"` |
+| `slug` | `CharField(255)` | yes | `unique`; defaults to a generated `uuid4`. Write spec validates `SlugType` (5–50 chars, URL-safe, must not shadow an internal questionnaire type) |
+| `title` | `CharField(255)` | yes | Display title; write spec rejects blank/whitespace and strips it |
+| `description` | `TextField` | no | Defaults to `""` |
+| `subject_type` | `CharField(255)` | yes | `SubjectType` enum — see [values](#subjecttype-values) |
+| `status` | `CharField(255)` | yes | `QuestionnaireStatus` enum — see [values](#questionnairestatus-values) |
+| `styling_metadata` | `JSONField` | no | Defaults to `{}`; opaque UI/layout hints, **no validation** performed |
+| `questions` | `JSONField` | yes | Defaults to `{}` at the DB; in practice a `list[Question]` tree — see [Question shape](#question-nested-shape) |
+| `organization_cache` | `ArrayField[int]` | — | Denormalized cache, maintained server-side (see [cache sync](#organization-cache-sync)) |
+| `internal_organization_cache` | `ArrayField[int]` | — | Denormalized cache, maintained server-side (see [cache sync](#organization-cache-sync)) |
+
+The write spec also carries a `type: str` field (default `"custom"`) that is **not** persisted as a model column.
+
+#### Organization scope caches
+
+These are **denormalized caches** of organization IDs maintained by the through-models below, used for access filtering without deep joins.
+
+| Field | Maintained by |
+| --- | --- |
+| `organization_cache` | `QuestionnaireOrganization.sync_questionnaire_cache()` — instance `Organization` IDs plus each org's `parent_cache` |
+| `internal_organization_cache` | `QuestionnaireFacilityOrganization.sync_questionnaire_cache()` — `FacilityOrganization` IDs plus each org's `parent_cache` |
+
+## Enum values
+
+### `QuestionnaireStatus` values
+
+Status of the form definition. Inspired by FHIR publication-status; once a questionnaire is `active` it should not be edited/deleted, only `retired`.
+
+| Value |
+| --- |
+| `active` |
+| `retired` |
+| `draft` |
+
+### `SubjectType` values
+
+The kind of resource a form is about.
+
+| Value |
+| --- |
+| `patient` |
+| `encounter` |
+
+### `QuestionType` values
+
+The `type` of each question (`Question.type`). Commented-out members (`open_choice`, `attachment`, `reference`) are not implemented.
+
+| Value | Notes |
+| --- | --- |
+| `group` | Container; must have ≥1 sub-question |
+| `boolean` | Validated against `true/false/1/0` on submit |
+| `decimal` | |
+| `integer` | |
+| `string` | |
+| `text` | Length capped by `settings.MAX_QUESTIONNAIRE_TEXT_RESPONSE_SIZE` on submit |
+| `display` | Display-only, no answer |
+| `date` | ISO date |
+| `dateTime` | Must include timezone on submit |
+| `time` | `%H:%M:%S` |
+| `choice` | Requires `answer_option` or `answer_value_set` |
+| `url` | Must have scheme + netloc |
+| `quantity` | Requires `answer_option` or `answer_value_set`; answers need a `unit` |
+| `structured` | Skipped by response validation |
+
+### `EnableOperator` values
+
+Operator for an `enable_when` condition (`EnableWhen.operator`).
+
+| Value |
+| --- |
+| `exists` |
+| `equals` |
+| `not_equals` |
+| `greater` |
+| `less` |
+| `greater_or_equals` |
+| `less_or_equals` |
+
+### `EnableBehavior` values
+
+How multiple `enable_when` conditions combine (`Question.enable_behavior`).
+
+| Value | Meaning |
+| --- | --- |
+| `all` | Enable only if all conditions pass (default on submit) |
+| `any` | Enable if any condition passes |
+
+### `DisabledDisplay` values
+
+How a disabled question renders (`Question.disabled_display`).
+
+| Value |
+| --- |
+| `hidden` |
+| `protected` |
+
+### `AnswerConstraint` values
+
+Whether free input outside options is allowed (`Question.answer_constraint`).
+
+| Value |
+| --- |
+| `required` |
+| `optional` |
+
+### `QuestionnaireResponseStatusChoices` values
+
+`QuestionnaireResponse.status` (model default `"completed"`).
+
+| Value |
+| --- |
+| `completed` |
+| `entered_in_error` |
+
+### `FormSubmissionStatusChoices` values
+
+`FormSubmission.status`.
+
+| Value |
+| --- |
+| `draft` |
+| `submitted` |
+| `entered_in_error` |
+
+## `Question` nested shape
+
+`Questionnaire.questions` is an opaque `JSONField`, but the API contract is a recursive list of `Question` (`QuestionnaireBaseSpec`). Fields, with their real types and validation:
+
+| Field | Type | Req | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `link_id` | `str` | yes | — | Human-readable link ID; must be unique across the whole tree |
+| `id` | `UUID4 \| UUID5` | no | `uuid4()` | Machine ID; must be unique across the whole tree |
+| `code` | `Coding` bound to value set `system-observation` | no | `None` | LOINC-bound; required to emit observations |
+| `collect_time` | `bool` | no | `false` | Collect a per-answer timestamp |
+| `collect_performer` | `bool` | no | `false` | Collect a `Performer` reference |
+| `text` | `str` | yes | — | Question text |
+| `description` | `str \| None` | no | `None` | |
+| `type` | `QuestionType` | yes | — | See [values](#questiontype-values) |
+| `structured_type` | `str \| None` | no | `None` | |
+| `enable_when` | `list[EnableWhen] \| None` | no | `None` | Conditional display rules |
+| `enable_behavior` | `EnableBehavior \| None` | no | `None` | |
+| `disabled_display` | `DisabledDisplay \| None` | no | `None` | |
+| `collect_body_site` | `bool \| None` | no | `None` | |
+| `collect_method` | `bool \| None` | no | `None` | |
+| `required` | `bool \| None` | no | `None` | |
+| `repeats` | `bool \| None` | no | `None` | Multi-select / repeating group |
+| `read_only` | `bool \| None` | no | `None` | |
+| `max_length` | `int \| None` | no | `None` | |
+| `answer_constraint` | `AnswerConstraint \| None` | no | `None` | |
+| `answer_option` | `list[AnswerOption] \| None` | no | `None` | Inline choices |
+| `answer_value_set` | `str \| None` | no | `None` | Slug of a `ValueSet`; validated to exist |
+| `is_observation` | `bool \| None` | no | `None` | Store answer as an observation |
+| `unit` | `Coding` bound to value set `system-ucum-units` | no | `None` | UCUM-bound unit |
+| `questions` | `list[Question]` | no | `[]` | Recursive children |
+| `formula` | `str \| None` | no | `None` | Client-side calculated field |
+| `styling_metadata` | `dict` | no | `{}` | |
+| `templates` | `list[TemplateConfig]` | no | `[]` | |
+| `is_component` | `bool` | no | `false` | Emit child answers as observation components |
+
+**`Question` validation** (`model_validator`, mode `after`):
+- `choice` / `quantity` type → must have `answer_option` **or** `answer_value_set`.
+- `group` type → must have at least one sub-question.
+- `answer_value_set` → the slug must reference an existing `ValueSet`.
+
+### Sub-specs of `Question`
+
+| Spec | Shape |
+| --- | --- |
+| `EnableWhen` | `{ question: str (link_id), operator: EnableOperator, answer: Any }` |
+| `AnswerOption` | `{ value: Any (non-blank, stripped), initial_selected: bool = false }` |
+| `Performer` | `{ performer_type: str, performer_id: str \| None, text: str \| None }` |
+| `TemplateConfig` | `{ name: str, content: str, structured_content: dict \| None, meta: dict \| None }` |
+
+## Resource specs (API schema)
+
+All specs build on `EMRResource` (`serialize` / `de_serialize`; read specs run `perform_extra_serialization`, write specs run `perform_extra_deserialization`).
+
+### Questionnaire
+
+| Spec | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `QuestionnaireBaseSpec` | shared | `__model__ = Questionnaire` |
+| `QuestionnaireWriteSpec` | write (base) | `version` (frozen `"1.0"`), `slug` (`SlugType`), `title`, `description`, `type` (`"custom"`), `status`, `subject_type`, `styling_metadata`, `questions`. Validates: slug uniqueness + not shadowing internal types, non-empty title, **unique `link_id`s and `id`s across the whole tree** |
+| `QuestionnaireSpec` | write · create | Extends write spec with `organizations: list[UUID4]` (`min_length=1`). `perform_extra_deserialization` stashes them on `obj._organizations` (the view links them via `QuestionnaireOrganization`, which rebuilds `organization_cache`) |
+| `QuestionnaireUpdateSpec` | write · update | Same as `QuestionnaireWriteSpec` (no `organizations`) |
+| `QuestionnaireReadSpec` | read · list/detail | `id` (= `external_id`), `slug`, `version`, `title`, `description`, `status`, `subject_type`, `styling_metadata`, `questions` (raw list), `created_by` / `updated_by` (resolved via `serialize_audit_users`) |
+
+Bound value sets on questions: `code` → `system-observation` (LOINC), `unit` → `system-ucum-units` (UCUM); `answer_value_set` references any [`ValueSet`](./valueset.mdx) by slug.
+
+### Questionnaire response
+
+| Spec | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `EMRQuestionnaireResponseBase` | shared | `__model__ = QuestionnaireResponse` |
+| `QuestionnaireResponseUpdate` | write · update | `status: QuestionnaireResponseStatusChoices` (default `completed`) — used to mark a response `entered_in_error` |
+| `QuestionnaireResponseReadSpec` | read · detail | `id`, `status`, `questionnaire` (nested `QuestionnaireReadSpec`), `subject_id`, `responses` (raw list), `encounter` (external id or `None`), `structured_responses`, `structured_response_type`, `created_by` / `updated_by` (`UserSpec`), `created_date`, `modified_date` |
+
+#### Submit request schema (not a DB write spec)
+
+The submit endpoint (`/questionnaire//submit/`) takes a plain Pydantic request, validated and persisted by `handle_response()` in `utils.py`:
+
+| Spec | Shape |
+| --- | --- |
+| `QuestionnaireSubmitRequest` | `{ resource_id: UUID4, encounter: UUID4 \| None, patient: UUID4, results: list[QuestionnaireSubmitResult], form_submission: UUID4 \| None }` |
+| `QuestionnaireSubmitResult` | `{ question_id: UUID4\|UUID5, body_site: Coding \| None, method: Coding \| None, taken_at: datetime \| None, values: list[QuestionnaireSubmitResultValue], note: str \| None, sub_results: list[list[QuestionnaireSubmitResult]] }` |
+| `QuestionnaireSubmitResultValue` | `{ value: str \| None, unit: Coding \| None, coding: Coding \| None }` |
+
+### Form submission
+
+| Spec | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `BaseFormSubmissionSpec` | shared | `__model__ = FormSubmission`, `id: UUID4 \| None` |
+| `FormSubmissionUpdateSpec` | write · update | `status: FormSubmissionStatusChoices`, `response_dump: dict` |
+| `FormSubmissionWriteSpec` | write · create | Adds `questionnaire: str` (slug), `patient: UUID4`, `encounter: UUID4 \| None`. `perform_extra_deserialization` resolves the questionnaire by slug, the patient/encounter by `external_id`, and when an encounter is given **overrides `patient` with the encounter's patient** |
+| `FormSubmissionReadSpec` | read · detail | `id`, `status`, `response_dump`, `created_date`, `modified_date`, `created_by` / `updated_by` (`UserSpec`) |
+
+### Questionnaire response template
+
+| Spec | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `QuestionnaireResponseTemplateBaseSpec` | shared | `__model__ = QuestionnaireResponseTemplate`, `id: UUID4 \| None`, `template_data: TemplateData`, `name: str`, `description: str = ""` |
+| `QuestionnaireResponseTemplateCreateSpec` | write · create | Adds `questionnaire: str \| None` (slug), `facility: UUID4 \| None`, `users: list[str]`, `facility_organizations: list[UUID4]`. Validates `facility` is required when `facility_organizations` is set. Resolves questionnaire/facility and recomputes `available_keys` from the non-empty keys of `template_data` |
+| `QuestionnaireResponseTemplateUpdateSpec` | write · update | `users`, `facility_organizations`; recomputes `available_keys` |
+| `QuestionnaireResponseTemplateReadSpec` | read · list | `created_date`, `modified_date`; `id` = `external_id` |
+| `QuestionnaireResponseTemplateRetrieveSpec` | read · detail | Extends read with resolved `users: list[dict]`, `facility_organizations: list[dict]`, `created_by`, `updated_by` |
+
+#### `template_data` (`TemplateData`) shape
+
+`QuestionnaireResponseTemplate.template_data` is an opaque `JSONField`; the spec validates it as:
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `medication_request` | `list[MedicationRequestTemplateSpec] \| None` | Extends [MedicationRequest](../medications/medication-request.mdx) write spec with `requested_product: str \| None` (validated against `ProductKnowledge.slug`) |
+| `questionnaire` | `list[QuestionnaireAnswer] \| None` | `QuestionnaireAnswer = { question_id: str, answer: dict, meta: dict }` |
+| `activity_definition` | `list[ActivityDefinitionTemplateSpec] \| None` | `{ slug (validated against ActivityDefinition), service_request: ServiceRequestUpdateSpec }` |
+| `meta` | `dict \| None` | |
+
+`available_keys` (model `ArrayField`) is server-maintained: on create/update it is set to the list of `template_data` keys whose value is truthy.
+
+## Related models
+
+### `FormSubmission`
+
+A submission event linking a questionnaire to a patient and (optionally) an encounter.
+
+```text
+questionnaire → FK Questionnaire (CASCADE)
+patient → FK emr.Patient (CASCADE)
+encounter → FK emr.Encounter (CASCADE, nullable)
+status → CharField(255) # FormSubmissionStatusChoices
+response_dump → JSONField (default {})
+```
+
+### `QuestionnaireResponse`
+
+The answers for a subject. The `questionnaire` FK is nullable, so responses can outlive or detach from a definition.
+
+```text
+questionnaire → FK Questionnaire (CASCADE, nullable)
+subject_id → UUIDField
+responses → JSONField (default []) # raw submitted results
+structured_responses → JSONField (default {}) # extracted/structured data
+structured_response_type → CharField (nullable)
+patient → FK emr.Patient (CASCADE)
+encounter → FK emr.Encounter (CASCADE, nullable)
+form_submission → FK FormSubmission (CASCADE, nullable)
+status → CharField(255), default "completed"
+```
+
+### `QuestionnaireOrganization` / `QuestionnaireFacilityOrganization`
+
+Through-models that scope a questionnaire to organizations. The first links to an instance-level `Organization`, the second to a `FacilityOrganization`.
+
+```text
+questionnaire → FK Questionnaire (CASCADE)
+organization → FK Organization | FacilityOrganization (CASCADE)
+```
+
+Each overrides `save()` to recompute the matching cache on the parent `Questionnaire` (see [Methods & save behaviour](#organization-cache-sync)). Their specs (`questionnaire_organization.py`) exclude both FKs from serialization (`__exclude__ = ["questionnaire", "organization"]`); the write spec accepts `organization: UUID4`, the read spec returns `organization: dict`.
+
+### `QuestionnaireResponseTemplate`
+
+A reusable template that prefills questionnaire responses, optionally scoped to a facility and to specific facility organizations / users.
+
+```text
+facility → FK facility.Facility (CASCADE, nullable)
+name → CharField(255)
+description → TextField (default "")
+template_data → JSONField (default {}) # TemplateData shape
+questionnaire → FK Questionnaire (CASCADE, nullable, default None)
+facility_organizations → ArrayField[int] (default [])
+users → ArrayField[int] (default [])
+available_keys → ArrayField[CharField(255)] (default []) # server-maintained
+```
+
+## Methods & save behaviour
+
+### `Questionnaire.get_questions_by_id()`
+
+Walks the `questions` tree (recursing into nested `questions`) and returns a `{ str(question_id): question }` dict. The result is memoized on the instance via `_questions_by_id_cache`. If `questions` is not a list, an empty dict is returned.
+
+### `QuestionnaireResponse.render_responses()`
+
+Joins stored `responses` against the live questionnaire definition. For each answer it looks up the question via `questionnaire.get_questions_by_id()` and returns a list of `{ "answer": ..., "question": ... }`. Returns an empty list when there are no responses or no linked questionnaire; answers whose `question_id` is no longer in the definition are skipped.
+
+### Submission handling — `handle_response()`
+
+The submit flow (`resources/questionnaire/utils.py`) runs server-side on every submission:
+
+1. Rejects the submission if the questionnaire `status != "active"` (`questionnaire_inactive`).
+2. Resolves `encounter` (required when `subject_type == "encounter"`) and `patient` by `external_id`.
+3. Rejects empty submissions (`questionnaire_empty`).
+4. Prunes the question tree by `enable_when` rules; answers to disabled questions raise `enable_when_failed`.
+5. Validates each answer against its `type` (type checks per [QuestionType](#questiontype-values)), `required`, `answer_value_set` (coding must belong to the value set), and `quantity` units. Any failures abort with an `errors` payload.
+6. Builds `ObservationSpec` objects for coded/group questions and creates a `QuestionnaireResponse` (raw `responses` = the dumped results).
+7. When an encounter is present, bulk-creates the derived [Observations](../clinical/observation.mdx), linked back to the response.
+
+### Organization cache sync
+
+`QuestionnaireOrganization.save()` and `QuestionnaireFacilityOrganization.save()` call `super().save()` then `sync_questionnaire_cache()`, which:
+
+1. Loads all through-rows for the parent questionnaire.
+2. Collects each linked organization's `id` plus its `parent_cache` (ancestor chain).
+3. De-duplicates the IDs.
+4. Writes them back via `questionnaire.save(update_fields=[...])` — `organization_cache` for instance orgs, `internal_organization_cache` for facility orgs.
+
+Expect a **second write** to the `Questionnaire` row whenever an organization link is saved.
+
+## API integration notes
+
+- Questionnaires expose CRUD plus a submit endpoint (`/questionnaire//submit/`). Create uses `QuestionnaireSpec` (requires ≥1 `organizations`); update uses `QuestionnaireUpdateSpec`; reads use `QuestionnaireReadSpec`.
+- `questions` and `styling_metadata` are open `JSONField`s at the DB layer, but the API validates `questions` against the recursive [`Question`](#question-nested-shape) spec (unique `link_id`/`id`, choice/quantity/group constraints, value-set existence). `styling_metadata` is never validated.
+- `organization_cache` / `internal_organization_cache` and `QuestionnaireResponseTemplate.available_keys` are platform-maintained — do not set them from clients; write through the through-models / `template_data`.
+- Submitting answers validates type/required/value-set server-side and, for clinical questions, materializes [Observations](../clinical/observation.mdx); a response can be voided by setting its status to `entered_in_error` via `QuestionnaireResponseUpdate`.
+- `FormSubmissionWriteSpec` resolves the questionnaire by slug and, when an encounter is supplied, forces the submission's patient to the encounter's patient.
+
+## Related
+
+- Source: [questionnaire.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/questionnaire.py)
+- Spec: [questionnaire/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire/spec.py) · [response spec](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire_response/spec.py) · [submit utils](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire/utils.py) · [form_submission spec](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/form_submission/spec.py) · [response template spec](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire_response_template/spec.py)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Reference: [Questionnaire Response](./questionnaire-response.mdx) — submitted answers (`QuestionnaireResponse` / `FormSubmission`)
+- Reference: [Questionnaire Response Template](./questionnaire-response-template.mdx) — reusable pre-fill templates
+- Reference: [ValueSet](./valueset.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Observation](../clinical/observation.mdx)
+- Reference: [Organization](../facility/organization.mdx)
+- Reference: [MedicationRequest](../medications/medication-request.mdx)
+- Reference: [ServiceRequest](../clinical/service-request.mdx)
+- Reference: [ActivityDefinition](../clinical/activity-definition.mdx)
diff --git a/versioned_docs/version-3.0/references/forms/valueset.mdx b/versioned_docs/version-3.0/references/forms/valueset.mdx
new file mode 100644
index 0000000..a055449
--- /dev/null
+++ b/versioned_docs/version-3.0/references/forms/valueset.mdx
@@ -0,0 +1,196 @@
+---
+sidebar_position: 4
+---
+
+# Value Set
+
+Technical reference for the `ValueSet` module in Care EMR. A value set is a FHIR-aligned composition of coded concepts drawn from one or more code systems; it constrains the valid options a [questionnaire](../forms/questionnaire.mdx) can offer for a coded answer.
+
+The Django model is the **storage layer** — `compose` is an opaque `JSONField`. The actual structure (include/exclude clauses, filters, status enum, validation, and the read/write API schemas) lives in the Pydantic **resource specs**. This page documents both.
+
+**Source:**
+- Model: [`care/emr/models/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/valueset.py)
+- Resource spec: [`care/emr/resources/valueset/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/valueset/spec.py)
+- Compose schema: [`care/emr/resources/common/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/valueset.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ValueSet` | A named, FHIR-compatible composition of codes from one or more code systems |
+| `UserValueSetPreference` | Stores a user's favorited codes within a value set |
+| `RecentViewsManager` | Redis-backed helper for tracking a user's recently used codes (not a database model) |
+
+`ValueSet` and `UserValueSetPreference` extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, `history`/`meta` JSON, and soft-delete semantics). `RecentViewsManager` is a plain Python class backed by Redis, not a Django model.
+
+## `ValueSet` fields
+
+A value set is **not** stored as a flat list of codes. It is stored as a `compose` object describing *rules* (include/exclude clauses against code systems); the actual member codes are resolved in real time against terminology servers when the value set is searched or a code is looked up.
+
+| Field | Model type | Spec type | Required | Default | Notes |
+| --- | --- | --- | --- | --- | --- |
+| `slug` | `SlugField(255)` | `SlugType` | yes | — | `unique`, indexed. Public identifier. Spec constrains to min 5 / max 50 chars, URL-safe (`^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$`); must start/end alphanumeric. Uniqueness re-checked in spec; `system-` prefix reserved (see validation) |
+| `name` | `CharField(255)` | `str` | yes | — | Display name. Spec rejects blank/whitespace-only and strips surrounding whitespace |
+| `description` | `TextField` | `str` | yes (in spec) | `""` (model) | Free text. Model defaults to empty string; spec requires the field present |
+| `compose` | `JSONField` | `ValueSetCompose` | yes | `dict` | Composition rules — structured `include`/`exclude` clauses (see [`compose` shape](#compose-shape)). On write, persisted via `compose.model_dump(exclude_defaults=True, exclude_none=True)` |
+| `status` | `CharField(255)` | `ValueSetStatusOptions` | yes | — | Publication status enum (see [status values](#valuesetstatusoptions-values)) |
+| `is_system_defined` | `BooleanField` | `bool` | no | `False` | Marks value sets shipped/maintained by the platform rather than authored by a deployment |
+
+### `ValueSetStatusOptions` values
+
+Enum (`str`) defined in `resources/valueset/spec.py`. The model column is a free `CharField`, but the spec restricts writes to these values.
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Authored, not yet in use |
+| `active` | Published and usable |
+| `retired` | No longer recommended for use |
+| `unknown` | Status not known |
+
+### `compose` shape
+
+`compose` holds a `ValueSetCompose` structure (`care.emr.resources.common.valueset`). All nested models set `extra="forbid"` — unknown keys are rejected. Each clause targets a `system` (code system URI) and may select concepts directly or via filters.
+
+#### `ValueSetCompose`
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `id` | `str \| None` | no | `None` | Optional identifier |
+| `include` | `list[ValueSetInclude]` | yes | — | Inclusion clauses (at least the field must be present) |
+| `exclude` | `list[ValueSetInclude] \| None` | no | `None` | Exclusion clauses; same shape as `include` |
+| `property` | `list[str] \| None` | no | `None` | Properties to return for member concepts |
+
+#### `ValueSetInclude` (used for both `include` and `exclude`)
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `id` | `str \| None` | no | `None` | Optional identifier |
+| `system` | `str \| None` | no | `None` | Code system URI this clause targets |
+| `version` | `str \| None` | no | `None` | Optional code system version |
+| `concept` | `list[ValueSetConcept] \| None` | no | `None` | Explicitly pinned concepts |
+| `filter` | `list[ValueSetFilter] \| None` | no | `None` | Rule-based selection |
+
+Validation: a single clause may set **`concept` or `filter`, not both** (`check_concept_or_filter`, mode=after).
+
+#### `ValueSetConcept`
+
+| Field | Type | Required | Default |
+| --- | --- | --- | --- |
+| `id` | `str \| None` | no | `None` |
+| `code` | `str \| None` | no | `None` |
+| `display` | `str \| None` | no | `None` |
+
+#### `ValueSetFilter`
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `id` | `str \| None` | no | `None` | |
+| `property` | `str \| None` | no | `None` | The concept property to filter on |
+| `op` | `str \| None` | no | `None` | Filter operator — validated against the allowed list below |
+| `value` | `str \| None` | no | `None` | Value to compare against |
+
+##### `op` allowed values
+
+`op` is validated by `validate_op`; any other value raises. Allowed: `=`, `is-a`, `descendent-of`, `is-not-a`, `regex`, `in`, `not-in`, `generalizes`, `child-of`, `descendent-leaf`, `exists`.
+
+```text
+compose:
+ include:
+ - system:
+ version:
+ concept: [ { code, display } ] # pin concepts (mutually exclusive with filter)
+ filter: [ { property, op, value } ] # rule-based selection
+ exclude:
+ - ... same ValueSetInclude shape ...
+ property: [ ]
+```
+
+`create_composition()` regroups these clauses by `system` into `{ : {include: [...], exclude: [...]} }` so each system can be queried independently, dumping each clause with `exclude_defaults=True`.
+
+> Note: a second, simpler `ValueSet` BaseModel (`name`, `status`, `compose`) and `ValueSetConcept`/`ValueSetFilter`/`ValueSetInclude`/`ValueSetCompose` all live in `resources/common/valueset.py`. The persisted/API resource model is `ValueSetBaseSpec` in `resources/valueset/spec.py`, which reuses the same `ValueSetCompose`.
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`resources/base.py`), which provides `serialize` (DB object → pydantic, via `model_construct`) and `de_serialize` (pydantic → DB object). `ValueSet` does not set `__store_metadata__`, so spec fields map directly to model columns (no `meta` bag).
+
+| Spec class | Role | Fields exposed |
+| --- | --- | --- |
+| `ValueSetBaseSpec` | shared base (`__model__ = ValueSet`) | `id` (UUID4), `slug`, `name`, `description`, `compose`, `status`, `is_system_defined` |
+| `ValueSetSpec` | write · create & update | inherits base + validation + `perform_extra_deserialization` |
+| `ValueSetReadSpec` | read · list & detail | inherits base + `created_by`, `updated_by` (dicts) |
+
+### Validation (`ValueSetSpec`)
+
+| Rule | Where | Behaviour |
+| --- | --- | --- |
+| Name not empty | `validate_name` (field) | Rejects blank/whitespace-only; strips surrounding whitespace |
+| Slug unique | `validate_slug` (field) | Queries existing `ValueSet` rows; on update, excludes the current object (via `context["object"].id` when `context["is_update"]`); raises `"Slug must be unique"` otherwise |
+| Reserved slug | `validate_slug_system` (model, after) | If **not** `is_system_defined` and slug contains `"system-"`, raises `"Cannot create valueset with system like slug"` |
+| Slug format | `SlugType` | min 5 / max 50 chars, URL-safe, alphanumeric start/end |
+| `op` allowlist | `ValueSetFilter.validate_op` | See [op allowed values](#op-allowed-values) |
+| `concept` xor `filter` | `ValueSetInclude.check_concept_or_filter` | Both present → error |
+
+### Server-side serialization hooks
+
+| Hook | Spec | Effect |
+| --- | --- | --- |
+| `perform_extra_deserialization(is_update, obj)` | `ValueSetSpec` | On write, sets `obj.compose = self.compose.model_dump(exclude_defaults=True, exclude_none=True)` — normalizes the JSON stored in the model |
+| `perform_extra_serialization(mapping, obj)` | `ValueSetReadSpec` | On read, sets `mapping["id"] = obj.external_id` and populates `created_by` / `updated_by` via `serialize_audit_users` |
+
+`ValueSetSpec.model_rebuild()` is called at module load so the forward reference to `ValueSetCompose` resolves.
+
+## Related models
+
+### `UserValueSetPreference`
+
+Stores per-user favorited codes for a value set.
+
+```text
+user → FK users.User (CASCADE)
+valueset → FK emr.ValueSet (CASCADE)
+favorite_codes → JSONField (default=list)
+```
+
+`unique_together = ("user", "valueset")` — one preference row per user per value set. The class constant `MAX_FAVORITES` (default `50`, overridable via `settings.MAX_FAVORITES_FOR_VALUESET`) caps how many codes a user may favorite.
+
+### `RecentViewsManager`
+
+Not a database model — a classmethod-only helper that tracks a user's recently viewed codes in a Redis list (one list per `cache_key`). Used to surface recently used codes alongside favorites in pickers.
+
+| Member | Behaviour |
+| --- | --- |
+| `get_client()` | Lazily opens the `"default"` Redis connection |
+| `get_recent_views(cache_key)` | Returns the decoded list of recent code objects |
+| `add_recent_view(cache_key, code_obj)` | De-dupes by `code` (no-op if `code` missing), `LPUSH`es the entry, then `LTRIM`s to `MAX_RECENT_VIEW` |
+| `remove_recent_view(cache_key, code_obj)` | Removes any list entry matching `code` (no-op if `code` missing) |
+| `clear_recent_views(cache_key)` | Deletes the whole list |
+| `_remove_by_code(cache_key, code)` | Internal: scans the list and `LREM`s matching entries; malformed JSON entries are skipped |
+
+`MAX_RECENT_VIEW` defaults to `20` (overridable via `settings.MAX_RECENT_VIEW_FOR_VALUESET`). Entries are JSON-encoded; malformed entries are skipped on read/remove.
+
+## Methods & save behaviour
+
+`ValueSet` does not override `save()`/`delete()` (soft-delete is inherited from the base). Its notable methods drive terminology resolution:
+
+- `create_composition()` — converts `compose` (dict or `ValueSetCompose`) into a per-system map of `include`/`exclude` clauses, dumping each clause with `exclude_defaults=True`.
+- `search(search="", count=10, display_language=None)` — for each system in the composition, runs `ValueSetResource().filter(...).search()` (optionally filtered by `display_language`) and concatenates the results. Codes are fetched from the terminology server at request time; nothing is materialized into the value set.
+- `lookup(code)` — checks whether `code` is a member of the value set by running `ValueSetResource().filter(...).lookup(code)` against each system; returns `True` if **any** system matches.
+
+Because membership is resolved live, editing `compose` immediately changes which codes the value set yields — there is no expansion/cache to rebuild.
+
+## API integration notes
+
+- Value sets are exposed through Care's REST API and align with the FHIR `ValueSet` resource; writes go through `ValueSetSpec`, reads through `ValueSetReadSpec`. `search` powers code pickers and `lookup` validates submitted codes.
+- The model column `compose` is opaque JSON; always write through the `ValueSetCompose` schema (clauses, filters, `op` allowlist, `concept` xor `filter`) — `perform_extra_deserialization` re-dumps it with `exclude_defaults`/`exclude_none` so stored JSON stays normalized.
+- Value sets store composition *rules*, not expanded code lists. Members are queried in real time from terminology servers, so results can vary as upstream systems change.
+- `is_system_defined` value sets are platform-maintained; deployments should treat them as read-only and author their own value sets for local terminology. The `system-` slug prefix is reserved for system value sets.
+- `favorite_codes` (via `UserValueSetPreference`) and recent views (via `RecentViewsManager`/Redis) are per-user personalization, not part of the value set definition.
+
+## Related
+
+- Reference: [Questionnaire](../forms/questionnaire.mdx) — consumes value sets to constrain coded answers
+- Reference: [Observation definition](../clinical/observation-definition.mdx) — binds coded fields to value sets
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Source (model): [valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/valueset.py)
+- Source (spec): [resources/valueset/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/valueset/spec.py)
+- Source (compose): [resources/common/valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/valueset.py)
diff --git a/versioned_docs/version-3.0/references/foundation/_category_.json b/versioned_docs/version-3.0/references/foundation/_category_.json
new file mode 100644
index 0000000..38962e1
--- /dev/null
+++ b/versioned_docs/version-3.0/references/foundation/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Foundation",
+ "position": 1,
+ "key": "foundation-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Foundation",
+ "description": "Shared base models and conventions every Care EMR model builds on — identifiers, audit fields, soft-delete, history, metadata, and facility-scoped slugs."
+ }
+}
diff --git a/versioned_docs/version-3.0/references/foundation/base-model.mdx b/versioned_docs/version-3.0/references/foundation/base-model.mdx
new file mode 100644
index 0000000..5b0fd62
--- /dev/null
+++ b/versioned_docs/version-3.0/references/foundation/base-model.mdx
@@ -0,0 +1,400 @@
+---
+sidebar_position: 1
+---
+
+# Base models & conventions
+
+Technical reference for the shared base layer that every Care EMR resource is built on. There are **two layers**:
+
+- **Storage layer** — abstract Django models in `care/emr/models` and `care/utils/models`. They supply opaque IDs, audit fields, soft-delete, history, slugs, and feature flags. Several columns are opaque `JSONField`s whose real structure is *not* visible in the model.
+- **API / implementation layer** — Pydantic resource specs built on `EMRResource` (`care/emr/resources/base.py`). They define enums, field validation, the structured shape of those JSON fields (via nested specs), and the read/write schemas. The shared structured types live in `care/emr/resources/common/` and are reused across nearly every resource.
+
+This page documents both layers and the shared common spec types. Concrete resources (for example [Patient](../clinical/patient.mdx)) inherit a storage base and define their own resource specs on top of `EMRResource`.
+
+**Source:**
+[`care/emr/models/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/base.py),
+[`care/utils/models/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/utils/models/base.py),
+[`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py),
+[`care/emr/resources/common/`](https://github.com/ohcnetwork/care/tree/develop/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`: they define no database table of their own. Concrete resource models inherit one of them and contribute the columns described below to their own table.
+
+The storage inheritance chain is:
+
+```text
+models.Model
+ └─ BaseModel (care/utils/models/base.py)
+ ├─ BaseFlag (care/utils/models/base.py)
+ └─ EMRBaseModel (care/emr/models/base.py)
+ └─ SlugBaseModel
+```
+
+The API specs are a parallel hierarchy rooted at `pydantic.BaseModel`:
+
+```text
+pydantic.BaseModel
+ └─ EMRResource (care/emr/resources/base.py)
+ └─ SpecBase / CreateSpec / ...ReadSpec / ...RetrieveSpec
+```
+
+---
+
+## Storage layer
+
+### `BaseModel` fields
+
+The root abstract model. Every other base — and therefore every persisted Care resource — inherits these 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`. Updated on every save |
+| `deleted` | `BooleanField` | yes | `False` | `db_index`. Soft-delete marker |
+
+`objects` is overridden to a [`BaseManager`](#basemanager) instance, so the default queryset only returns live rows.
+
+#### Soft delete
+
+`BaseModel.delete()` does not issue a SQL `DELETE`. It flips the flag and persists only that column:
+
+```python
+def delete(self, *args):
+ self.deleted = True
+ self.save(update_fields=["deleted"])
+```
+
+Rows are therefore retained for audit and referential integrity. To read 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. Extends `BaseModel`, so it also carries `external_id`, the timestamps, and `deleted`, plus the following:
+
+| 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 (audit / 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` templating 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 (for example value sets and definitions). It adds no new columns — concrete subclasses provide 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--` |
+| `calculate_slug_from_instance(slug)` | Class helper → `i-` |
+| `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:
+
+```text
+facility-scoped: f--
+instance-scoped: i-
+```
+
+`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`
+
+Default manager applied as `objects` on `BaseModel`. It filters out soft-deleted rows at the queryset level:
+
+```python
+def get_queryset(self):
+ return super().get_queryset().filter(deleted=False)
+```
+
+Any query through `Model.objects` is implicitly scoped to `deleted=False`.
+
+### `BaseFlag`
+
+Abstract base for per-entity feature-flag tables (for example facility or organization flags). It extends `BaseModel` and stores a single validated flag name, with all lookups served from the 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 `_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 class (`care/emr/resources/base.py`) that every resource spec extends. It converts between Django model instances and the API schema in both directions and centralises the create/read serialization rules.
+
+| 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 |
+
+**Spec naming convention.** Concrete resources expose a small set of specs built on `EMRResource`. Read these as a table per resource:
+
+| Spec class | Kind | Role |
+| --- | --- | --- |
+| `SpecBase` | shared | Common fields shared by the write/read specs |
+| `CreateSpec` | write · create | Request body for POST; `de_serialize` builds a new model instance |
+| `UpdateSpec` | write · update | Request body for PUT/PATCH; `de_serialize` updates an existing instance (`is_update=True`) |
+| `ListSpec` | read · list | Lightweight response for list endpoints |
+| `ReadSpec` / `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). Resource specs declare the binding (e.g. via `json_schema_extra={"slug": ""}` on the field) and validate the submitted `Coding` against it in `perform_extra_deserialization`. Status fields commonly maintain a server-side `status_history` appended on create/update. See each resource page for the 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`](#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 |
+
+Cross-field rule: 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. Used wherever a spec field accepts a phone number.
+
+---
+
+## Shared common specs
+
+`care/emr/resources/common/` holds the structured types reused across resources. These are the real shapes behind many of the opaque `JSONField`s 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` — 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](../billing/charge-item-definition.mdx), [Charge Item](../billing/charge-item.mdx)).
+
+`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 |
+
+Validation rules (model validators):
+
+- `tax_included_amount` is only allowed when `monetary_component_type == base`.
+- A `base` component must have no `conditions` and must set `amount`.
+- `amount` and `factor` are mutually exclusive (not both).
+- Either `amount` or `factor` must be present — unless `global_component` is set together with a `code`.
+
+#### `MonetaryComponentType` values
+
+| Value |
+| --- |
+| `base` |
+| `surcharge` |
+| `discount` |
+| `tax` |
+| `informational` |
+
+`MonetaryComponentsWithoutBase` (a `RootModel` over `list[MonetaryComponent]`) adds: 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: at most **one** `base` component.
+
+`MonetaryComponentDefinition` extends `MonetaryComponent` for definition-time use: 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](../forms/valueset.mdx).
+
+- `ValueSet` — `name: str` (required), `status: str | None`, `compose: ValueSetCompose` (required).
+- `ValueSetCompose` — `id`, `include: list[ValueSetInclude]` (required), `exclude: list[ValueSetInclude] | None`, `property: list[str] | None`.
+- `ValueSetInclude` — `id`, `system`, `version`, `concept: list[ValueSetConcept] | None`, `filter: list[ValueSetFilter] | None`. **`concept` and `filter` are mutually exclusive.**
+- `ValueSetConcept` — `id`, `code`, `display` (all optional).
+- `ValueSetFilter` — `id`, `property`, `op`, `value`. `op` must be one of: `=`, `is-a`, `descendent-of`, `is-not-a`, `regex`, `in`, `not-in`, `generalizes`, `child-of`, `descendent-leaf`, `exists`.
+
+All ValueSet types use `extra="forbid"`.
+
+### `MailTypeChoices`
+
+Defined in `common/mail_type.py`.
+
+| Name | Value |
+| --- | --- |
+| `create` | `create_password` |
+| `reset` | `reset_password` |
+
+---
+
+## Methods & save behaviour
+
+- `BaseModel.delete()` performs a soft delete (sets `deleted=True`, saves only that field) instead of a hard `DELETE`.
+- `BaseFlag.save()` validates `flag` against `FlagRegistry`, then deletes the single-flag and all-flags cache entries for the entity before persisting, keeping the cached lookups consistent.
+- `BaseFlag.check_entity_has_flag` / `get_all_flags` populate those caches on read with a 1-day TTL (`FLAGS_CACHE_TTL = 60 * 60 * 24`).
+- `SlugBaseModel` exposes slug encode/parse helpers but does not override `save()`; subclasses decide when to call `calculate_slug()`.
+- `EMRResource.de_serialize` is where write-time side effects happen — subclasses override `perform_extra_deserialization` to append to status histories, resolve FKs from `external_id`, and validate coded fields against bound value sets.
+- `cacheable(...)` (in `base.py`) is a decorator that marks a spec cacheable and wires a `post_save` signal to invalidate the per-instance serializer cache; `model_from_cache` then serves serialized specs from cache by `pk` / `id` / `external_id`.
+
+## API integration notes
+
+- `external_id` (UUID) is the identifier used across Care's REST API and FHIR resources; the integer primary key is internal and never exposed. `EMRResource.serialize` surfaces it as `id`, and `de_serialize` refuses to write `id` / `external_id` from request bodies.
+- Deletes are soft — a resource removed through the API still exists in the database with `deleted=True` and is hidden by the default manager.
+- `history` is not returned by standard endpoints; every version (including migration-time changes) is stored flattened with its performer and served through a separate, on-request audit API.
+- `meta` is the supported place for system metadata without schema migrations; specs with `__store_metadata__ = True` round-trip extra fields through it. `created_by` / `updated_by` are platform-maintained audit fields and should not be set directly by clients.
+- Periods sent on write (`PeriodSpec`) must be timezone-aware and ordered (`start ≤ end`); the read-side `Period` type does not enforce this.
+- Feature-flag state (`BaseFlag` subclasses) is read through the cached classmethods rather than queried row-by-row.
+
+## Related
+
+- Reference: [Patient](../clinical/patient.mdx) — a representative `EMRBaseModel` + `EMRResource` subclass
+- Reference: [ValueSet](../forms/valueset.mdx) — uses `SlugBaseModel` and the ValueSet compose types
+- Reference: [Charge Item Definition](../billing/charge-item-definition.mdx) — uses `MonetaryComponent` pricing types
+- Source: [`base.py` (emr models)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/base.py)
+- Source: [`base.py` (utils models)](https://github.com/ohcnetwork/care/blob/develop/care/utils/models/base.py)
+- Source: [`base.py` (resources / `EMRResource`)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+- Source: [`common/` shared spec types](https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/common)
diff --git a/versioned_docs/version-3.0/references/medications/_category_.json b/versioned_docs/version-3.0/references/medications/_category_.json
new file mode 100644
index 0000000..2218e89
--- /dev/null
+++ b/versioned_docs/version-3.0/references/medications/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Medications",
+ "position": 3,
+ "key": "medications-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Medications",
+ "description": "Prescribing, administration, dispensing, and statement of medications across an encounter."
+ }
+}
diff --git a/versioned_docs/version-3.0/references/medications/medication-administration.mdx b/versioned_docs/version-3.0/references/medications/medication-administration.mdx
new file mode 100644
index 0000000..e0b7118
--- /dev/null
+++ b/versioned_docs/version-3.0/references/medications/medication-administration.mdx
@@ -0,0 +1,213 @@
+---
+sidebar_position: 2
+---
+
+# Medication Administration
+
+Technical reference for the `MedicationAdministration` module in Care EMR.
+
+`MedicationAdministration` records that a medication was (or was attempted to be) given to a patient — the actual administration event that fulfils a [`MedicationRequest`](./medication-request.mdx). The Django model is the storage layer; several of its fields are opaque `JSONField`s whose real structure is defined by the Pydantic **resource specs** in `care/emr/resources/medication/administration/`. This page documents both layers.
+
+**Source:**
+- Model: [`care/emr/models/medication_administration.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_administration.py)
+- Spec: [`care/emr/resources/medication/administration/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/administration/spec.py)
+- Value sets: [`care/emr/resources/medication/valueset/`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/valueset)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `MedicationAdministration` | Records that a medication was (or was attempted to be) given to a patient — the actual administration event for a [`MedicationRequest`](./medication-request.mdx) |
+
+`MedicationAdministration` extends `EMRBaseModel` (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics — see [Base model](../foundation/base-model.mdx)). It maps to the FHIR [`MedicationAdministration`](https://hl7.org/fhir/medicationadministration.html) resource and closes the loop on the prescriber's intent captured in a `MedicationRequest`.
+
+## `MedicationAdministration` fields
+
+Types below reflect the **storage** column type and, where the column is a `JSONField`, the **structured shape** enforced by the resource spec.
+
+### Status & classification
+
+| Field | Storage | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(100)` | `MedicationAdministrationStatus` enum | Required. Administration event status — see [enum values](#medicationadministrationstatus-values) |
+| `status_reason` | `JSONField` (null) | `Coding` bound to `system-medication` value set | Optional. Coded reason for the status. NB: the spec binds this to the medication value set, not a "not given" reason set |
+| `category` | `CharField(100)` (null) | `MedicationAdministrationCategory` enum | Optional. Administration setting — see [enum values](#medicationadministrationcategory-values) |
+
+### Medication & product
+
+| Field | Storage | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `medication` | `JSONField` (default `{}`) | `Coding` bound to `system-medication` value set | Optional in spec. `{ system, version?, code, display? }`. Mutually exclusive with `administered_product` (model validator) |
+| `administered_product` | `FK → ProductKnowledge` (null, `CASCADE`) | `UUID4` (write) / nested `ProductKnowledgeReadSpec` (read) | Optional. The specific catalog product administered. Cannot be set together with `medication` |
+| `dosage` | `JSONField` (null) | `Dosage` nested spec | Optional. Dose, rate, route, site, and method actually administered — see [`Dosage`](#dosage-nested-spec) |
+
+### Subject & context
+
+| Field | Storage | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `patient` | `FK → Patient` (`CASCADE`) | not in spec (server-derived) | The patient who received the medication. Set server-side from the encounter's patient on create — not accepted from the client |
+| `encounter` | `FK → Encounter` (null, `CASCADE`) | `UUID4` (required, write) | The encounter during which the medication was administered. Validated to exist; read schema returns the encounter's `external_id` |
+| `request` | `FK → MedicationRequest` (null, `CASCADE`) | `UUID4` (required, write) | The order this administration fulfils. Validated to exist; read schema returns the request's `external_id` |
+
+### Timing & attribution
+
+| Field | Storage | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `authored_on` | `DateTimeField` (null) | `datetime` | Optional. When the administration record was authored |
+| `occurrence_period_start` | `DateTimeField` (default `datetime.now`) | `datetime` (required) | Start of the administration. Required by the create/read spec |
+| `occurrence_period_end` | `DateTimeField` (null) | `datetime` | Optional. End of the administration (for infusions / extended doses). Updatable via `MedicationAdministrationUpdateSpec` |
+| `recorded` | `DateTimeField` (null) | `datetime` | Optional. When the event was recorded into the system |
+| `performer` | `JSONField` (default `[]`) | `list[MedicationAdministrationPerformer]` | Optional. Actors who performed the administration and their function — see [`MedicationAdministrationPerformer`](#medicationadministrationperformer-nested-spec) |
+| `note` | `TextField` (null) | `str` | Optional. Free-text annotation. Updatable via `MedicationAdministrationUpdateSpec` |
+
+## Enum values
+
+### `MedicationAdministrationStatus` values
+
+`str` enum (`care/emr/resources/medication/administration/spec.py`). Note these use underscores (e.g. `not_done`), not the FHIR hyphenated forms.
+
+| Value |
+| --- |
+| `completed` |
+| `not_done` |
+| `entered_in_error` |
+| `stopped` |
+| `in_progress` |
+| `on_hold` |
+| `unknown` |
+| `cancelled` |
+
+### `MedicationAdministrationCategory` values
+
+| Value |
+| --- |
+| `inpatient` |
+| `outpatient` |
+| `community` |
+| `discharge` |
+
+### `MedicationAdministrationPerformerFunction` values
+
+| Value |
+| --- |
+| `performer` |
+| `verifier` |
+| `witness` |
+
+## Nested specs (JSON-field shapes)
+
+### `Dosage` (nested spec)
+
+Structured shape of the `dosage` `JSONField`. All fields optional.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `text` | `str \| None` | Free-text dosage instructions |
+| `site` | `Coding \| None` | Bound to `system-body-site` value set (SNOMED CT `<< 91723000`) |
+| `route` | `Coding \| None` | Bound to `system-route` value set (SNOMED CT `<< 284009009`) |
+| `method` | `Coding \| None` | Bound to `system-administration-method` value set (SNOMED CT `<< 736665006`) |
+| `dose` | `Quantity \| None` | The amount of medication administered |
+| `rate` | `Quantity \| None` | The speed of administration |
+
+`Quantity` shape (`care/emr/resources/common/quantity.py`, `extra="forbid"`): `{ value: Decimal? (max_digits 20, decimal_places 6), unit: Coding?, code: Coding?, meta: dict? }`.
+
+`Coding` shape (`care/emr/resources/common/coding.py`, `extra="forbid"`): `{ system: str?, version: str?, code: str (required), display: str? }`.
+
+### `MedicationAdministrationPerformer` (nested spec)
+
+Element shape of the `performer` `JSONField` list.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `actor` | `UUID4` (required) | The user who performed the administration. Validated server-side to reference an existing [`User`](../access/user.mdx) (`field_validator` raises "User not found") |
+| `function` | `MedicationAdministrationPerformerFunction \| None` | The performer's function — see [enum values](#medicationadministrationperformerfunction-values) |
+
+## Bound value sets
+
+Coded fields bind to Care system value sets (SNOMED CT). Validation rejects codes outside the bound set via `ValueSetBoundCoding`.
+
+| Field | Value set | Slug | SNOMED CT constraint |
+| --- | --- | --- | --- |
+| `medication`, `status_reason` | Medication | `system-medication` | `<< 763158003` (Medicinal product) |
+| `dosage.site` | Body Site | `system-body-site` | `is-a 91723000` |
+| `dosage.route` | Route | `system-route` | `is-a 284009009` |
+| `dosage.method` | Administration Method | `system-administration-method` | `is-a 736665006` |
+
+Other medication value sets defined alongside (not referenced by this resource's specs): Medication Not Given Reason (`system-medication-not-given`, `is-a 242990004` + `is-a 182895007`), Additional Instruction (`system-additional-instruction`, `is-a 419492006`), As Needed (`system-as-needed-reason`, `is-a 404684003`).
+
+## Resource specs (API schema)
+
+All specs build on `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB object → read schema) and `de_serialize` (write schema → DB object) with the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__exclude__` lists fields handled manually by those hooks rather than copied field-for-field.
+
+| Spec class | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `BaseMedicationAdministrationSpec` | shared base | All common fields: `status`, `status_reason`, `category`, `medication`, `authored_on`, `occurrence_period_start`, `occurrence_period_end`, `recorded`, `encounter`, `request`, `performer`, `dosage`, `note`. `__exclude__ = ["patient", "encounter", "request", "administered_product"]` |
+| `MedicationAdministrationSpec` | write · create | Extends base; adds `administered_product: UUID4 \| None`. Validates `encounter` and `request` exist; rejects `medication` + `administered_product` set together |
+| `MedicationAdministrationUpdateSpec` | write · update | Narrow update schema: only `status`, `note`, `occurrence_period_end`. `__exclude__ = ["patient", "encounter", "request"]` — those are immutable after create |
+| `MedicationAdministrationReadSpec` | read · detail/list | Extends base; adds audit fields `created_by`, `updated_by`, `created_date`, `modified_date`, and nested `administered_product: dict` |
+| `Dosage` | nested | Shape of the `dosage` JSON field (see above) |
+| `MedicationAdministrationPerformer` | nested | Element of the `performer` JSON list (see above) |
+
+### Validation rules
+
+- **Mutual exclusion** (`MedicationAdministrationSpec.validate_administered_product`, `model_validator(mode="after")`): `medication` and `administered_product` cannot both be set.
+- **Existence checks** (field validators, create): `encounter` must reference an existing `Encounter`; `request` must reference an existing `MedicationRequest`; each `performer.actor` must reference an existing `User`.
+- **Bound codings**: `medication`, `status_reason`, and `dosage.{site,route,method}` are validated against their bound value sets; unknown codes raise.
+- **Required on create**: `status`, `occurrence_period_start`, `encounter`, `request`.
+
+### Server-maintained behaviour
+
+`MedicationAdministrationSpec.perform_extra_deserialization` (create only, `is_update == False`):
+
+- Resolves `encounter` from the supplied `external_id` and sets `obj.encounter`.
+- Derives `obj.patient` from `obj.encounter.patient` — `patient` is never taken from the client.
+- Resolves `request` from its `external_id` and sets `obj.request`.
+- If `administered_product` is supplied, resolves the `ProductKnowledge` and sets `obj.administered_product`.
+
+`MedicationAdministrationReadSpec.perform_extra_serialization`:
+
+- Sets `id` to `external_id`; flattens `encounter` and `request` to their `external_id`s.
+- Serializes `administered_product` (when present) via `ProductKnowledgeReadSpec`.
+- Adds `created_by` / `updated_by` via `serialize_audit_users`.
+
+## Related models
+
+`administered_product` references the supply-side catalog rather than carrying a nested record:
+
+```text
+administered_product → FK ProductKnowledge (nullable, CASCADE)
+patient → FK Patient (CASCADE)
+encounter → FK Encounter (nullable, CASCADE)
+request → FK MedicationRequest (nullable, CASCADE)
+```
+
+`medication`, `dosage`, `status_reason`, and `performer` are stored inline as `JSONField`s rather than as separate tables, mirroring the FHIR `MedicationAdministration` structure; their real shape comes from the resource specs above.
+
+## Methods & save behaviour
+
+- `EMRResource.serialize(obj)` builds a read schema from a DB row, copying mapped fields then calling `perform_extra_serialization` (sets `id`, flattens FK references, serializes nested `administered_product`, adds audit users).
+- `EMRResource.de_serialize(obj?)` builds/updates a DB row from a write schema using `model_dump(exclude_defaults=True)`, then calls `perform_extra_deserialization`. On create it resolves and assigns `encounter`, `patient`, `request`, and `administered_product`.
+- Audit and soft-delete fields (`external_id`, `created_by`, `created_date`, `modified_date`, `deleted`, etc.) are inherited from `EMRBaseModel` and maintained by the platform.
+
+## API integration notes
+
+- Write requests use `MedicationAdministrationSpec` (create) and `MedicationAdministrationUpdateSpec` (update); reads return `MedicationAdministrationReadSpec`. Field names mostly match the model; `occurrence_period_start` / `occurrence_period_end` map to the FHIR `occurence[x]` period.
+- Send coded fields (`medication`, `status_reason`, `dosage.site/route/method`) as `Coding` objects (`system` / `code` / `display`) drawn from the bound value sets — free strings and out-of-set codes are rejected.
+- `status` values use underscores (`in_progress`, `not_done`, `on_hold`, `entered_in_error`) — not the FHIR hyphenated spellings.
+- Provide `encounter` and `request` as `external_id` UUIDs; `patient` is derived server-side from the encounter and must not be sent.
+- Set either `medication` or `administered_product`, never both. `administered_product` is the bridge to inventory — set it when a specific catalogued product was used so administration can be reconciled against supply.
+- After create, only `status`, `note`, and `occurrence_period_end` are updatable; `encounter`, `request`, and `patient` are immutable.
+
+## Related
+
+- Reference: [Medication Request](./medication-request.mdx)
+- Reference: [Medication Dispense](./medication-dispense.mdx)
+- Reference: [Medication Statement](./medication-statement.mdx)
+- Reference: [Product Knowledge](../supply/product-knowledge.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [User](../access/user.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [medication_administration.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_administration.py)
+- Source: [administration/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/administration/spec.py)
+- Source: [medication value sets on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/valueset)
diff --git a/versioned_docs/version-3.0/references/medications/medication-dispense.mdx b/versioned_docs/version-3.0/references/medications/medication-dispense.mdx
new file mode 100644
index 0000000..0d54c28
--- /dev/null
+++ b/versioned_docs/version-3.0/references/medications/medication-dispense.mdx
@@ -0,0 +1,330 @@
+---
+sidebar_position: 3
+---
+
+# Medication Dispense
+
+Technical reference for the `MedicationDispense` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/medication_dispense.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_dispense.py)
+- Spec: [`care/emr/resources/medication/dispense/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/dispense/spec.py)
+- Spec: [`care/emr/resources/medication/dispense/dispense_order.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/dispense/dispense_order.py)
+- Viewset: [`care/emr/api/viewsets/medication_dispense.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/medication_dispense.py)
+
+`MedicationDispense` records products that have been dispensed to a patient. It both completes the workflow started by a [Medication Request](./medication-request.mdx) and drives inventory consumption within a location. It applies to inpatient settings (items dispensed and held in the room, refilled as needed) and outpatient settings (dispensed after an encounter is completed). A dispense may optionally carry a [Charge Item](../billing/charge-item.mdx) for billing.
+
+The Django model is the **storage** layer — several fields are opaque `JSONField`s. The Pydantic **resource specs** (`care/emr/resources/medication/dispense/`) define the API schema: enums, JSON-field shapes, validation, and the read/write contracts. Where the two disagree, the spec is the source of truth (e.g. `status` stores enum values with underscores such as `in_progress`, not the FHIR hyphenated forms).
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `MedicationDispense` | A single dispense event: a quantity of one inventory item handed to a patient |
+| `DispenseOrder` | Groups dispense events at a location into a named order with a shared status |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+## `MedicationDispense` fields
+
+### Status & classification
+
+| Field | Model type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(100)` | `MedicationDispenseStatus` | Required. See [status values](#medicationdispensestatus-values). Cancelling statuses block further updates |
+| `not_performed_reason` | `CharField(100)`, null | `MedicationDispenseNotPerformedReason \| None` | Coded reason a dispense was not performed. See [values](#medicationdispensenotperformedreason-values) |
+| `category` | `CharField(100)`, null | `MedicationDispenseCategory \| None` | Setting/location. See [values](#medicationdispensecategory-values) |
+
+### Timing & instructions
+
+| Field | Model type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `when_prepared` | `DateTimeField`, null | `datetime \| None` | When the product was prepared |
+| `when_handed_over` | `DateTimeField`, null | `datetime \| None` | When the product was handed to the patient |
+| `note` | `TextField`, null | `str \| None` | Free-text annotation |
+| `dosage_instruction` | `JSONField` (default `list`) | `list[DosageInstruction]` (default `[]`) | List of structured dosage directions. See [`DosageInstruction` shape](#dosageinstruction-shape) |
+| `substitution` | `JSONField` (default `dict`) | `MedicationDispenseSubstitution \| None` | Substitution record. See [shape](#medicationdispensesubstitution-shape) |
+
+### Quantities
+
+| Field | Model type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `quantity` | `DecimalField(20, 6)` | `Decimal` (write: `max_digits=20, decimal_places=0`) | Required. Amount dispensed. Validated against inventory stock on create |
+| `days_supply` | `DecimalField(20, 6)`, null | `Decimal \| None` (`max_digits=20, decimal_places=0`) | Expected number of days the dispensed amount lasts |
+
+### Relationships
+
+| Field | Model type | Notes |
+| --- | --- | --- |
+| `encounter` | `FK → Encounter` | `CASCADE`. Resolved from `encounter` UUID on write; `patient` is derived from it (not sent directly) |
+| `patient` | `FK → Patient` | `CASCADE`. Server-set from `encounter.patient` |
+| `location` | `FK → FacilityLocation` | `CASCADE`. Must belong to the encounter's facility |
+| `authorizing_request` | `FK → MedicationRequest` | `SET_NULL`, null. The prescription this dispense fulfils; must be on the same encounter. Cleared server-side when the dispense is cancelled |
+| `item` | `FK → InventoryItem` | `CASCADE`. Stock item consumed; must be in a location of the encounter's facility |
+| `charge_item` | `FK → ChargeItem` | `CASCADE`, null. Server-created from the product's charge item definition when one exists |
+| `order` | `FK → DispenseOrder` | `CASCADE`, null. Parent dispense order, when grouped |
+
+`patient`, `encounter`, `authorizing_request`, `item`, and `location` are listed in `__exclude__` on the spec base — they are resolved manually in `perform_extra_deserialization` rather than copied straight off the Pydantic object.
+
+## Enum values
+
+### `MedicationDispenseStatus` values
+
+From `MedicationDispenseStatus` (`spec.py`).
+
+| Value |
+| --- |
+| `preparation` |
+| `in_progress` |
+| `cancelled` |
+| `on_hold` |
+| `completed` |
+| `entered_in_error` |
+| `stopped` |
+| `declined` |
+
+The set `MEDICATION_DISPENSE_CANCELLED_STATUSES = [cancelled, entered_in_error, stopped, declined]` is treated as terminal/cancelling: no updates are allowed once a dispense is in one of these, and transitioning into one triggers charge-item cancellation (see [Methods & save behaviour](#methods--save-behaviour)).
+
+### `MedicationDispenseNotPerformedReason` values
+
+From `MedicationDispenseNotPerformedReason` (`spec.py`). Coded reasons (FHIR `medicationdispense-status-reason`).
+
+| Value | Meaning |
+| --- | --- |
+| `outofstock` | Out of stock |
+| `washout` | Washout |
+| `surg` | Surgery |
+| `sintol` | Sensitivity / intolerance to drug |
+| `sddi` | Drug interaction |
+| `sdupther` | Duplicate therapy |
+| `saig` | Allergy to ingredient of medication |
+| `preg` | Patient pregnant |
+
+### `MedicationDispenseCategory` values
+
+From `MedicationDispenseCategory` (`spec.py`).
+
+| Value |
+| --- |
+| `inpatient` |
+| `outpatient` |
+| `community` |
+| `discharge` |
+
+### `SubstitutionType` values
+
+From `SubstitutionType` (`spec.py`) — used in `substitution.substitution_type`.
+
+| Value | Meaning |
+| --- | --- |
+| `E` | Equivalent |
+| `EC` | Equivalent composition |
+| `BC` | Brand composition |
+| `G` | Generic composition |
+| `TE` | Therapeutic alternative |
+| `TB` | Therapeutic brand |
+| `TG` | Therapeutic generic |
+| `F` | Formulary |
+| `N` | None |
+
+### `SubstitutionReason` values
+
+From `SubstitutionReason` (`spec.py`) — used in `substitution.reason`.
+
+| Value | Meaning |
+| --- | --- |
+| `CT` | Continuing therapy |
+| `FP` | Formulary policy |
+| `OS` | Out of stock |
+| `RR` | Regulatory requirement |
+
+## JSON field shapes
+
+### `MedicationDispenseSubstitution` shape
+
+`substitution` field (`MedicationDispenseSubstitution`). All three fields required when `substitution` is present:
+
+```text
+substitution = {
+ was_substituted: bool # required
+ substitution_type: SubstitutionType # required; see SubstitutionType values
+ reason: SubstitutionReason # required; see SubstitutionReason values
+}
+```
+
+### `DosageInstruction` shape
+
+`dosage_instruction` is a `list[DosageInstruction]`. `DosageInstruction` is reused from the [Medication Request](./medication-request.mdx) spec (`care/emr/resources/medication/request/spec.py`):
+
+```text
+DosageInstruction {
+ sequence: int | None
+ text: str | None
+ additional_instruction: list[Coding]@system-additional-instruction | None
+ patient_instruction: str | None
+ timing: Timing | None
+ as_needed_boolean: bool # required
+ as_needed_for: Coding@system-as-needed-reason | None
+ site: Coding@system-body-site | None
+ route: Coding@system-route | None
+ method: Coding@system-administration-method | None
+ dose_and_rate: DoseAndRate | None
+ max_dose_per_period: DoseRange | None
+}
+
+Timing { repeat: TimingRepeat, code: Coding | None }
+TimingRepeat{ frequency: int, period: Decimal(20,0), period_unit: TimingUnit, bounds_duration: TimingQuantity }
+TimingQuantity { value: Decimal(20,0), unit: TimingUnit }
+DoseAndRate { type: DoseType, dose_range: DoseRange | None, dose_quantity: DosageQuantity | None }
+DoseRange { low: DosageQuantity, high: DosageQuantity }
+DosageQuantity { value: Decimal(20,6), unit: Coding }
+```
+
+`Coding@` denotes a `Coding` bound to a Care value set (`ValueSetBoundCoding`); `Coding = { system?, version?, code (required), display? }`. `TimingUnit ∈ {s, min, h, d, wk, mo, a}`; `DoseType ∈ {ordered, calculated}`. The shared `PeriodSpec` (from `base.py`) — `{ start: datetime, end: datetime }`, both tz-aware, `start ≤ end` — is the standard period shape used elsewhere; `MedicationDispense` itself does not expose a period field.
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`base.py`) and use its `serialize` / `de_serialize` plumbing; the `serialize`-side data is assembled in `perform_extra_serialization`, and write-side resolution/side effects happen in `perform_extra_deserialization`.
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BaseMedicationDispenseSpec` | shared base | `__model__ = MedicationDispense`. Exposes `status`, `not_performed_reason`, `category`, `when_prepared`, `when_handed_over`, `note`, `dosage_instruction`, `substitution`. Excludes the FK fields |
+| `MedicationDispenseWriteSpec` | write · create | Adds `encounter`, `location`, `authorizing_request`, `item` (UUIDs), `quantity`, `days_supply`, `fully_dispensed`, `order`, `create_dispense_order`. Resolves FKs and runs create-time logic |
+| `MedicationDispenseUpdateSpec` | write · update | Base fields plus `fully_dispensed` and `order`. Cannot re-point `encounter`/`item`/`location` |
+| `MedicationDispenseReadSpec` | read · list | Serializes nested `item`, `charge_item`, `location`, `authorizing_request`, `order`; adds `created_date`, `modified_date` |
+| `MedicationDispenseRetrieveSpec` | read · detail | Extends `ReadSpec`, additionally serializes the full `encounter` |
+| `MedicationDispenseSubstitution` | nested | Shape of the `substitution` JSON field |
+| `CreateDispenseOrder` | nested (write) | Inline order creation payload — see below |
+
+### Write-spec fields
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `encounter` | `UUID4` | yes | Resolved to `Encounter`; `patient` derived from it |
+| `location` | `UUID4` | yes | Must be in `encounter.facility` |
+| `item` | `UUID4` | yes | `InventoryItem` in a location of `encounter.facility` |
+| `authorizing_request` | `UUID4 \| None` | no | `MedicationRequest` on the same encounter |
+| `quantity` | `Decimal` | yes | Write spec constrains to `decimal_places=0` (whole units) |
+| `days_supply` | `Decimal \| None` | no | `decimal_places=0` |
+| `fully_dispensed` | `bool \| None` | no | Drives the authorizing request's `dispense_status` (see below). Stashed on `instance._fully_dispensed`, not persisted on `MedicationDispense` |
+| `order` | `UUID4 \| None` | no | Existing `DispenseOrder` to attach to |
+| `create_dispense_order` | `CreateDispenseOrder \| None` | no | Inline order creation (get-or-create) |
+
+**Validation:** `validate_prescription` rejects sending both `order` and `create_dispense_order`.
+
+### `CreateDispenseOrder`
+
+Inline order payload on the write spec. When the matching order (by `alternate_identifier` + `patient` + `location`) does not exist it is created; if it exists but its status is not `draft`/`in_progress`, the request is rejected (`"Prescription is not active"`).
+
+```text
+CreateDispenseOrder {
+ name: str | None
+ note: str | None
+ alternate_identifier: str # required
+ status: CreateDispenseOrderStatusOptions # default "draft"
+}
+```
+
+`CreateDispenseOrderStatusOptions ∈ {draft, in_progress}`.
+
+### Bound value sets
+
+`dosage_instruction` codings bind to Care value sets (all SNOMED CT, registered as systems):
+
+| Field (within `DosageInstruction`) | Value set slug |
+| --- | --- |
+| `additional_instruction` | `system-additional-instruction` |
+| `as_needed_for` | `system-as-needed-reason` |
+| `site` | `system-body-site` |
+| `route` | `system-route` |
+| `method` | `system-administration-method` |
+
+The dispense-level `not_performed_reason` aligns with the `system-medication-not-given` value set (`medication_not_given_reason.py`) but is stored/validated as the `MedicationDispenseNotPerformedReason` enum, not a bound `Coding`.
+
+## Related models
+
+### `DispenseOrder`
+
+Groups one or more `MedicationDispense` events at a location into a named order with a single status.
+
+```text
+location → FK FacilityLocation (CASCADE)
+patient → FK Patient (CASCADE)
+facility → FK Facility (CASCADE)
+name → CharField(255), nullable
+status → CharField(255)
+note → TextField, nullable
+tags → ArrayField[int], default []
+alternate_identifier → CharField(100), nullable
+```
+
+`DispenseOrder` enforces the uniqueness constraint `unique_alternate_identifier_encounter_location` on `(alternate_identifier, patient, location)`, so an external identifier can appear at most once per patient and location.
+
+**Source / specs:**
+- Model: `DispenseOrder` in [`medication_dispense.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_dispense.py)
+- Specs: [`dispense_order.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/dispense/dispense_order.py)
+- Viewset: [`api/viewsets/inventory/dispense_order.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/inventory/dispense_order.py)
+
+#### `MedicationDispenseOrderStatusOptions` values
+
+| Value |
+| --- |
+| `draft` |
+| `in_progress` |
+| `completed` |
+| `abandoned` |
+| `entered_in_error` |
+
+`MEDICATION_DISPENSE_ORDER_COMPLETED_STATUSES = [abandoned, entered_in_error, completed]` are terminal-ish. `abandoned` and `entered_in_error` are the cancel transitions.
+
+#### `DispenseOrder` specs
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BaseMedicationDispenseOrderSpec` | shared base / update | Exposes `status`, `name`, `note`. Used directly as the update spec |
+| `MedicationDispenseOrderWriteSpec` | write · create | Adds `patient`, `location` (UUIDs), resolved in deserialization. `facility` is set server-side from the URL |
+| `MedicationDispenseOrderReadSpec` | read · list | Serializes nested `patient` (list spec), `location`; adds `created_date`, `modified_date` |
+| `MedicationDispenseOrderRetrieveSpec` | read · detail | Full `patient` (retrieve spec) plus `created_by` / `updated_by` audit users |
+
+## Methods & save behaviour
+
+The model has no custom `save()`; the dispense workflow lives in the viewset (`MedicationDispenseViewSet`) inside a `transaction.atomic()` block, with side effects layered on top of the spec's `perform_extra_deserialization`.
+
+**On create (`perform_create`):**
+- Acquires an `InventoryItemLock` on `item` and rejects if `item.net_content < quantity` (`"Inventory item does not have enough stock"`).
+- If `item.product.charge_item_definition` exists, applies it to create a `ChargeItem` (resource `medication_dispense`, linked to this dispense's `external_id`; `performer_actor` = the authorizing request's requester when present) and attaches it via `charge_item`.
+- Calls `sync_inventory_item(item.location, item.product)` to draw down stock.
+- If `fully_dispensed` is set and an `authorizing_request` exists, sets that request's `dispense_status` to `complete` (when `True`) or `partial` (when `False`).
+- Order resolution: `create_dispense_order` does a get-or-create by `(alternate_identifier, patient, location)`; an existing non-`draft`/`in_progress` order is rejected.
+
+**On update (`perform_update`):**
+- `validate_data` rejects any update when the current status is in `MEDICATION_DISPENSE_CANCELLED_STATUSES`.
+- A transition **into** a cancelling status while a `charge_item` is attached cancels the charge item (`handle_charge_item_cancel`, status → `aborted`), sets the authorizing request's `dispense_status` to `incomplete`, and detaches `authorizing_request` (`SET_NULL`).
+- Re-syncs inventory and re-applies the `fully_dispensed` → `dispense_status` (`complete`/`partial`) logic.
+
+**Dispense-order cancellation (`cancel_dispense_order` in the order viewset):** moving an order to `abandoned` or `entered_in_error` cascades to every member dispense — cancels their charge items, marks each dispense `cancelled` / `entered_in_error` respectively, sets authorizing requests' `dispense_status` to `incomplete`, and detaches them. An order already `abandoned`/`entered_in_error` cannot transition; a `completed` order can only be cancelled.
+
+## API integration notes
+
+- Exposed through `MedicationDispenseViewSet` (create, retrieve, update, list, upsert) and aligns with the FHIR `MedicationDispense` resource; API field names may differ from FHIR (e.g. `authorizing_request` ≈ FHIR `authorizingPrescription`). Enum **values** use underscores (`in_progress`, `entered_in_error`), not FHIR hyphens.
+- List/summary require either a `location` or `encounter` query param (else `400`); `include_children=true` widens a location query to descendant locations via `parent_cache`. Filters: `status`, `exclude_status`, `category`, `encounter`, `patient`, `item`, `authorizing_prescription`, `authorizing_request`, `location`, `order`. A `summary` action returns per-encounter dispense counts.
+- Permissions are location-centric: creates only require write access to the dispensing `location` (pharmacists often lack encounter access); reads require either location-list or encounter-view permission.
+- Send `item` as an `InventoryItem` UUID, not a bare product code — this is what draws down stock.
+- `quantity` / `days_supply` are constrained to whole units (`decimal_places=0`) on the write spec even though the column stores 6 decimal places.
+- `charge_item` is server-managed (created from the product's charge item definition, cancelled on dispense/order cancellation) — do not set it directly.
+- Use `fully_dispensed` to roll the linked [Medication Request](./medication-request.mdx) into `complete` / `partial` so pharmacists do not re-dispense an already-fulfilled request.
+- Use `create_dispense_order` to start an order inline, or `order` to attach to an existing one — never both.
+
+## Related
+
+- Reference: [Medication Request](./medication-request.mdx)
+- Reference: [Medication Administration](./medication-administration.mdx)
+- Reference: [Medication Statement](./medication-statement.mdx)
+- Reference: [Inventory Item](../supply/inventory-item.mdx)
+- Reference: [Charge Item](../billing/charge-item.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [medication_dispense.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_dispense.py)
+- Spec: [dispense/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/dispense/spec.py)
+- Spec: [dispense/dispense_order.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/dispense/dispense_order.py)
diff --git a/versioned_docs/version-3.0/references/medications/medication-request.mdx b/versioned_docs/version-3.0/references/medications/medication-request.mdx
new file mode 100644
index 0000000..af8d48f
--- /dev/null
+++ b/versioned_docs/version-3.0/references/medications/medication-request.mdx
@@ -0,0 +1,329 @@
+---
+sidebar_position: 1
+---
+
+# Medication Request
+
+Technical reference for the `MedicationRequest` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/medication_request.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_request.py)
+- Spec: [`resources/medication/request/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/request/spec.py)
+- Spec: [`resources/medication/request_prescription/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/request_prescription/spec.py)
+- Value sets: [`resources/medication/valueset/`](https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/medication/valueset)
+
+A medication request captures the prescriber's intent to supply and/or administer a medication to a patient — the FHIR [`MedicationRequest`](https://hl7.org/fhir/medicationrequest.html) resource. Requests carry the medication, dosage instructions, timing, and route as structured JSON, and are grouped under a prescription per encounter.
+
+The Django model is the **storage** layer: several fields are opaque `JSONField`s whose real structure is not visible in the model. The Pydantic **resource specs** (`care/emr/resources/medication/request/`) are the API/implementation layer — they define the enums, the nested shape of those JSON fields, validation, the read/write schemas, and server-side side effects. The tables below combine both.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `MedicationRequestPrescription` | Groups medication requests authored together for an encounter (a prescription) |
+| `MedicationRequest` | A single medication order/request for a patient within an encounter |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) — the shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON.
+
+## `MedicationRequest` fields
+
+### Order status & intent
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(100)` → `MedicationRequestStatus` | Yes (spec) | Model nullable; spec requires it. See [status values](#medicationrequeststatus-values). Defaults to `active` per product spec |
+| `status_reason` | `CharField(100)` → `StatusReason \| None` | No | Discontinuation/change reason. See [status reason values](#statusreason-values) |
+| `intent` | `CharField(100)` → `MedicationRequestIntent` | Yes (spec) | Model nullable; spec requires it. See [intent values](#medicationrequestintent-values) |
+| `category` | `CharField(100)` → `MedicationRequestCategory` | Yes (spec) | Model nullable; spec requires it. See [category values](#medicationrequestcategory-values) |
+| `priority` | `CharField(100)` → `MedicationRequestPriority` | Yes (spec) | Model nullable; spec requires it. See [priority values](#medicationrequestpriority-values) |
+| `do_not_perform` | `BooleanField` → `bool` | Yes | `True` when the medication is explicitly **not** to be given |
+| `dispense_status` | `CharField(100)` → `MedicationRequestDispenseStatus \| None` | No | Model default `None`. Tracks dispense progress. See [dispense status values](#medicationrequestdispensestatus-values) |
+
+### Medication & dosage
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `medication` | `JSONField` → `ValueSetBoundCoding[system-medication] \| None` | Conditional | Model default `{}`. Single `Coding { system, code, display }` bound to the [Medication](#bound-value-sets) value set (SNOMED CT medicinal products). Exactly one of `medication` / `requested_product` must be set |
+| `method` | `JSONField` | n/a | Model default `{}`. Present in storage but **not exposed by any current spec** — administration method is carried per dosage line via `dosage_instruction[].method` |
+| `dosage_instruction` | `JSONField` → `list[DosageInstruction]` | Yes | Model default `[]`. List of structured dosage lines. See [DosageInstruction shape](#dosageinstruction-nested-spec) |
+| `note` | `TextField` → `str \| None` | No | Free-text note on the request |
+
+### Subject & provenance
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `patient` | `FK → Patient` | server-set | `CASCADE`. Not accepted from the client — set server-side from `encounter.patient` |
+| `encounter` | `FK → Encounter` | Yes (create) | `CASCADE`. Write spec accepts `encounter` as a UUID; validated to exist |
+| `authored_on` | `DateTimeField` → `datetime` | Yes (spec) | Model default `timezone.now`. When the request was authored (tz-aware) |
+| `requester` | `FK → users.User` | No | `SET_NULL`. Write spec accepts `requester` UUID; the prescriber |
+| `requested_product` | `FK → ProductKnowledge` | Conditional | `SET_NULL`, default `None`. Write spec accepts `requested_product` UUID. Exactly one of `medication` / `requested_product` must be set |
+| `prescription` | `FK → MedicationRequestPrescription` | No | `SET_NULL`, default `None`. Set via `prescription` UUID or `create_prescription` (see [validation](#resource-specs-api-schema)) |
+
+## Enum values
+
+The spec enums use **underscored** member values (e.g. `on_hold`, `entered_in_error`, `original_order`) — not the hyphenated FHIR spellings. These are the literal strings the API accepts and returns.
+
+### `MedicationRequestStatus` values
+
+| Value |
+| --- |
+| `active` |
+| `on_hold` |
+| `ended` |
+| `stopped` |
+| `completed` |
+| `cancelled` |
+| `entered_in_error` |
+| `draft` |
+| `unknown` |
+
+### `StatusReason` values
+
+| Value | Meaning (FHIR) |
+| --- | --- |
+| `altchoice` | Alternative choice |
+| `clarif` | Prescription requires clarification |
+| `drughigh` | Drug level too high |
+| `hospadm` | Admission to hospital |
+| `labint` | Lab interference issues |
+| `non_avail` | Patient not available |
+| `preg` | Patient is pregnant or breastfeeding |
+| `salg` | Allergy |
+| `sddi` | Drug interaction |
+| `sdupther` | Duplicate therapy |
+| `sintol` | Suspected intolerance |
+| `surg` | Patient scheduled for surgery |
+| `washout` | Washout |
+
+### `MedicationRequestIntent` values
+
+| Value |
+| --- |
+| `proposal` |
+| `plan` |
+| `order` |
+| `original_order` |
+| `reflex_order` |
+| `filler_order` |
+| `instance_order` |
+
+### `MedicationRequestPriority` values
+
+| Value |
+| --- |
+| `routine` |
+| `urgent` |
+| `asap` |
+| `stat` |
+
+### `MedicationRequestCategory` values
+
+| Value |
+| --- |
+| `inpatient` |
+| `outpatient` |
+| `community` |
+| `discharge` |
+
+### `MedicationRequestDispenseStatus` values
+
+| Value |
+| --- |
+| `complete` |
+| `partial` |
+| `incomplete` |
+| `declined` |
+
+### `TimingUnit` values
+
+Used by `Timing.repeat.period_unit` and `bounds_duration.unit` (UCUM time units).
+
+| Value | Unit |
+| --- | --- |
+| `s` | second |
+| `min` | minute |
+| `h` | hour |
+| `d` | day |
+| `wk` | week |
+| `mo` | month |
+| `a` | year |
+
+### `DoseType` values
+
+Used by `DoseAndRate.type`.
+
+| Value |
+| --- |
+| `ordered` |
+| `calculated` |
+
+## `DosageInstruction` nested spec
+
+Each entry of the `dosage_instruction` JSON list deserializes to a `DosageInstruction` (Pydantic, snake_case keys). This is the real structure behind the opaque `JSONField`.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `sequence` | `int \| None` | No | Order of the instruction |
+| `text` | `str \| None` | No | Free-text dosage instruction (SIG) |
+| `additional_instruction` | `list[Coding] \| None` | No | Bound to [Additional Instruction](#bound-value-sets) value set |
+| `patient_instruction` | `str \| None` | No | Patient-facing instructions |
+| `timing` | `Timing \| None` | No | See `Timing` below |
+| `as_needed_boolean` | `bool` | **Yes** | `True` for PRN orders |
+| `as_needed_for` | `Coding \| None` | No | Bound to [As Needed](#bound-value-sets) value set (reason for PRN) |
+| `site` | `Coding \| None` | No | Bound to [Body Site](#bound-value-sets) value set |
+| `route` | `Coding \| None` | No | Bound to [Route](#bound-value-sets) value set |
+| `method` | `Coding \| None` | No | Bound to [Administration Method](#bound-value-sets) value set |
+| `dose_and_rate` | `DoseAndRate \| None` | No | See `DoseAndRate` below |
+| `max_dose_per_period` | `DoseRange \| None` | No | Upper dose limit per period |
+
+### Sub-types under `DosageInstruction`
+
+```text
+Timing {
+ repeat: TimingRepeat # required
+ code: Coding | None # e.g. BID/TID/QID timing abbreviation
+}
+
+TimingRepeat {
+ frequency: int # required — repetitions per period
+ period: Decimal(20,0) # required — duration the frequency applies to
+ period_unit: TimingUnit # required — s|min|h|d|wk|mo|a
+ bounds_duration: TimingQuantity # required — total span
+}
+
+TimingQuantity { # integer-valued time amount
+ value: Decimal(20,0) # required
+ unit: TimingUnit # required
+}
+
+DoseAndRate {
+ type: DoseType # required — ordered | calculated
+ dose_range: DoseRange | None # for titrated doses
+ dose_quantity: DosageQuantity | None # for regular doses
+}
+
+DoseRange {
+ low: DosageQuantity # required
+ high: DosageQuantity # required
+}
+
+DosageQuantity { # measured dose amount
+ value: Decimal(20,6) # required
+ unit: Coding # required
+}
+```
+
+Note: in storage these JSON keys are snake_case as defined by the Pydantic specs (`as_needed_boolean`, `period_unit`, `dose_and_rate`, `max_dose_per_period`, `bounds_duration`), unlike the camelCase FHIR wire format.
+
+## Bound value sets
+
+Coded fields bind to Care value sets via `ValueSetBoundCoding[]`; the supplied `Coding.code` is validated against the value set on write. All draw from SNOMED CT (`http://snomed.info/sct`).
+
+| Field | Value set slug | SNOMED CT scope |
+| --- | --- | --- |
+| `medication` | `system-medication` | `<< 763158003` Medicinal product |
+| `dosage_instruction[].route` | `system-route` | `is-a 284009009` Route of administration |
+| `dosage_instruction[].site` | `system-body-site` | `is-a 91723000` Anatomical structure |
+| `dosage_instruction[].method` | `system-administration-method` | `is-a 736665006` |
+| `dosage_instruction[].as_needed_for` | `system-as-needed-reason` | `is-a 404684003` Clinical finding |
+| `dosage_instruction[].additional_instruction` | `system-additional-instruction` | `is-a 419492006` |
+
+(`system-medication-not-given` exists in this directory but binds on [Medication Administration](medication-administration.mdx), not on the request.)
+
+## Resource specs (API schema)
+
+All specs extend [`EMRResource`](../foundation/base-model.mdx) (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). `MedicationRequestResource` sets `__exclude__ = [patient, encounter, requester, requested_product, prescription]` so those relations are handled explicitly rather than auto-mapped.
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `MedicationRequestAbstractSpec` | shared (fields) | Plain `BaseModel` holding the clinical fields: `status`, `status_reason`, `intent`, `category`, `priority`, `do_not_perform`, `medication`, `dosage_instruction`, `authored_on`, `note`, `dispense_status` |
+| `MedicationRequestResource` | shared (binding) | Binds the spec to the `MedicationRequest` model + `__exclude__` |
+| `BaseMedicationRequestSpec` | shared | `MedicationRequestResource` + `MedicationRequestAbstractSpec` + `id: UUID4` |
+| `MedicationRequestSpec` | write · create | Adds `encounter` (UUID, required), `requester`, `requested_product`, `prescription` (UUIDs), and `create_prescription`. Carries the validators and `perform_extra_deserialization` |
+| `MedicationRequestUpdateSpec` | write · update | Narrow: only `status`, `note`, `dispense_status` are mutable post-create |
+| `MedicationRequestReadWithoutPrescriptionSpec` | read · embedded | Full read minus prescription; expands `requested_product` (`ProductKnowledgeReadSpec`), `requester`/`created_by`/`updated_by` (`UserSpec`), plus `created_date`/`modified_date` |
+| `MedicationRequestReadSpec` | read · detail/list | Adds expanded `prescription` (`MedicationRequestPrescriptionReadSpec`) and `encounter` external id |
+| `CreatePrescription` | write · nested | `{ name?, note?, alternate_identifier (required) }` — inline prescription creation |
+
+### Validation (create — `MedicationRequestSpec`)
+
+- **Medication XOR product:** cannot set both `medication` and `requested_product`; one is required.
+- **Prescription XOR create:** cannot set both `prescription` and `create_prescription`.
+- **Encounter existence:** `encounter` UUID must match an existing `Encounter` (field validator).
+- `authored_on` (and any nested `PeriodSpec`/timing datetimes) must be timezone-aware.
+
+### Server-side side effects (`perform_extra_deserialization`)
+
+- `encounter` is resolved from its UUID; `patient` is set from `encounter.patient` (never client-supplied).
+- `requester` / `requested_product` resolved by `external_id`. If `requested_product.facility` is set and differs from `encounter.facility`, raises `"Product not found in facility"`.
+- `prescription` resolved by `external_id` scoped to the same `encounter`.
+- `create_prescription`: looks up an existing prescription by `(alternate_identifier, encounter)`. If found but **not** `active`, raises `"Prescription is not active"`. If not found, creates a new `MedicationRequestPrescription` (`status=active`, copying `name`/`note`, `prescribed_by=requester`). The request is then linked to it.
+
+### Serialization (read)
+
+- `perform_extra_serialization` sets `id = external_id`, expands `requested_product`, `requester`, and audit users (`created_by`/`updated_by`) from cache; `MedicationRequestReadSpec` additionally expands `prescription` and emits `encounter` as its external id.
+
+## Related models
+
+### `MedicationRequestPrescription`
+
+Groups the medication requests authored together for an encounter into a single prescription. Each request points back via `MedicationRequest.prescription`.
+
+```text
+encounter → FK Encounter (CASCADE)
+patient → FK Patient (CASCADE)
+name → CharField(100), nullable
+note → TextField, nullable
+prescribed_by → FK users.User (CASCADE, nullable)
+status → CharField(100), nullable → MedicationRequestPrescriptionStatus
+approval_status → CharField(100), nullable
+alternate_identifier → CharField(100), nullable
+tags → ArrayField[int], default []
+```
+
+A `UniqueConstraint` (`unique_alternate_identifier_encounter`) enforces that `alternate_identifier` is unique per `encounter`.
+
+#### `MedicationRequestPrescriptionStatus` values
+
+`active`, `on_hold`, `ended`, `stopped`, `completed`, `cancelled`, `entered_in_error`, `draft`.
+
+Pharmacist-modifiable statuses (`MEDICATION_PRESCRIPTION_PHARMACIST_ALLOWED_STATUS`): `active`, `on_hold`, `completed`.
+
+#### Prescription specs
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BaseMedicationRequestPrescriptionSpec` | shared | `id`, `status` (`MedicationRequestPrescriptionStatus`), `note`, `name` |
+| `MedicationRequestPrescriptionWriteSpec` | write · create | Adds `encounter` (UUID), `prescribed_by` (UUID); resolves `encounter`→`patient` and `prescribed_by` server-side |
+| `MedicationRequestPrescriptionUpdateSpec` | write · update | Same shared fields, no new FKs |
+| `MedicationRequestPrescriptionReadSpec` | read · list | Adds `created_date`, `modified_date`, expanded `prescribed_by` (`UserSpec`), `tags` (rendered via `SingleFacilityTagManager`) |
+| `MedicationRequestPrescriptionRetrieveSpec` | read · detail | Adds audit `created_by`/`updated_by` |
+| `MedicationRequestPrescriptionRetrieveMedicationsSpec` | read · detail+children | Adds `medications` (each via `MedicationRequestReadWithoutPrescriptionSpec`) and full `encounter` (`EncounterRetrieveSpec`) |
+| `MedicationRequestPrescriptionRetrieveDetailedSpec` | read · detail | Adds `encounter` via `EncounterListSpec` |
+
+## Methods & save behaviour
+
+- Inherited `EMRResource.serialize` / `de_serialize` drive read/write; `de_serialize` dumps with `exclude_defaults=True` and maps fields onto the model, then calls `perform_extra_deserialization` for the relation/prescription handling described above.
+- `patient` is always derived from `encounter`, never written directly by the client.
+- Inline prescription creation (`create_prescription`) is idempotent on `(alternate_identifier, encounter)` and only proceeds when an existing match is `active`.
+- `authored_on` defaults to `timezone.now` at the model layer when not supplied.
+
+## API integration notes
+
+- Coded fields use single `Coding` objects `{ system, code, display }` validated against bound SNOMED CT value sets — send the `code` from the correct value set or the write is rejected.
+- Enum members are underscored (`on_hold`, `entered_in_error`, `original_order`, etc.); send those literals, not the FHIR hyphenated forms.
+- `dosage_instruction` is the structured carrier for SIG text, timing/frequency, PRN flags, dose and rate, and max-dose limits — write the full nested shape (snake_case keys) rather than free text alone.
+- On create, supply either `medication` or `requested_product` (not both) and either `prescription` or `create_prescription` (not both, both optional).
+- Updates are restricted to `status`, `note`, and `dispense_status`.
+- `requester`, `requested_product`, and `prescription` use `SET_NULL`, so the request survives deletion of the referenced user, product, or prescription.
+
+## Related
+
+- Reference: [Medication Administration](medication-administration.mdx)
+- Reference: [Medication Dispense](medication-dispense.mdx)
+- Reference: [Medication Statement](medication-statement.mdx)
+- Reference: [Product Knowledge](../supply/product-knowledge.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
+- Source: [medication_request.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_request.py) · [request/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/request/spec.py) · [request_prescription/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/request_prescription/spec.py)
diff --git a/versioned_docs/version-3.0/references/medications/medication-statement.mdx b/versioned_docs/version-3.0/references/medications/medication-statement.mdx
new file mode 100644
index 0000000..195bfdd
--- /dev/null
+++ b/versioned_docs/version-3.0/references/medications/medication-statement.mdx
@@ -0,0 +1,162 @@
+---
+sidebar_position: 4
+---
+
+# Medication Statement
+
+Technical reference for the `MedicationStatement` module in Care EMR.
+
+A `MedicationStatement` records a medication the patient **is taking, has taken, or intends to take** — as *reported* (by the patient, a relative, or a clinician) rather than actively prescribed or dispensed. It aligns with the FHIR `MedicationStatement` resource.
+
+The Django model is the **storage** layer: `medication` and `effective_period` are opaque `JSONField`s whose real structure lives in the Pydantic **resource specs** (`care/emr/resources/medication/statement/`). The specs define the enums, the nested JSON shapes, validation, the value-set binding on `medication`, and the read/write API schemas. This page documents both.
+
+**Source:**
+
+- Model: [`care/emr/models/medication_statement.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_statement.py)
+- Spec: [`care/emr/resources/medication/statement/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/statement/spec.py)
+- Value set: [`care/emr/resources/medication/valueset/medication.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/valueset/medication.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `MedicationStatement` | A record of a medication the patient is taking, has taken, or intends to take — as reported rather than actively prescribed |
+
+`MedicationStatement` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_by`/`updated_by`, `created_date`/`modified_date`, `history`/`meta` JSON, and soft-delete semantics).
+
+## `MedicationStatement` fields
+
+The **Type** column gives the Django storage type; the **Spec shape** column gives the structured shape enforced by the Pydantic resource spec when reading/writing over the API.
+
+### Statement details
+
+| Field | Type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(100)` | `MedicationStatementStatus` enum | Required. See [Status values](#medicationstatementstatus-values). |
+| `reason` | `CharField(100)` | `str \| None` | Nullable / optional. Reason the medication is being taken. |
+| `medication` | `JSONField` (`default=dict`) | `Coding` bound to the `system-medication` value set | Required on create. A single SNOMED CT `Coding` (not a full `CodeableConcept`). See [`Coding` shape](#coding-shape) and [Bound value set](#bound-value-set). |
+| `information_source` | `CharField(100)` | `MedicationStatementInformationSourceType` enum \| `None` | Optional. Who supplied the statement. See [Information-source values](#medicationstatementinformationsourcetype-values). |
+| `effective_period` | `JSONField` (`default=dict`) | `PeriodSpec` \| `None` | Optional. Period the medication is/was taken. See [`PeriodSpec` shape](#periodspec-shape). |
+| `dosage_text` | `TextField` | `str \| None` | Nullable / optional. Free-text dosage instructions. (The spec notes a structured `Dosage` may replace this in future.) |
+| `note` | `TextField` | `str \| None` | Nullable / optional. Additional free-text annotation. |
+
+### Relationships
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `patient` | `FK → Patient` | `on_delete=CASCADE`. The patient the statement is about. Set server-side from the encounter — not accepted from clients. |
+| `encounter` | `FK → Encounter` | `on_delete=CASCADE`. The encounter in which the statement was recorded. Supplied as a UUID on create. |
+
+```text
+patient → FK Patient (CASCADE)
+encounter → FK Encounter (CASCADE)
+```
+
+Both foreign keys cascade on delete, so removing a patient or encounter removes the associated medication statements.
+
+## Enum values
+
+### `MedicationStatementStatus` values
+
+From `MedicationStatementStatus(str, Enum)` in `spec.py`. Bound to `status`.
+
+| Value | Meaning |
+| --- | --- |
+| `active` | The medication is being taken |
+| `on_hold` | Taking has been paused |
+| `completed` | The course has finished |
+| `stopped` | The medication was stopped |
+| `unknown` | Status is not known |
+| `entered_in_error` | The record was entered in error |
+| `not_taken` | The patient is not taking the medication |
+| `intended` | The patient intends to take the medication |
+
+### `MedicationStatementInformationSourceType` values
+
+From `MedicationStatementInformationSourceType(str, Enum)` in `spec.py`. Bound to `information_source`.
+
+| Value | Meaning |
+| --- | --- |
+| `related_person` | A relative or related person reported it |
+| `practitioner` | A clinician reported it |
+| `patient` | The patient self-reported it |
+
+## Nested JSON shapes
+
+### `Coding` shape
+
+`medication` is stored as JSON but typed as a single `Coding` (from `care/emr/resources/common/coding.py`), not a `CodeableConcept`. `extra="forbid"`.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `system` | `str \| None` | Code system URI (e.g. `http://snomed.info/sct`) |
+| `version` | `str \| None` | Code-system version |
+| `code` | `str` | **Required.** The code, validated against the bound value set |
+| `display` | `str \| None` | Human-readable label |
+
+#### Bound value set
+
+`medication` is declared as `ValueSetBoundCoding[CARE_MEDICATION_VALUESET.slug]`. On write, the `code` is validated against the **`system-medication`** value set ([`medication.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/valueset/medication.py)), which includes SNOMED CT concepts under the constraint `<< 763158003 |Medicinal product|`. An out-of-set code is rejected.
+
+> The `care/emr/resources/medication/valueset/` directory holds additional value sets used by other medication resources (route, body site, administration method, additional instruction, as-needed reason, medication-not-given reason). `MedicationStatement` itself only binds `medication` → `system-medication`.
+
+### `PeriodSpec` shape
+
+`effective_period` is stored as JSON but typed as `PeriodSpec` (from `care/emr/resources/base.py`).
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `start` | `datetime \| None` | Must be **timezone-aware** if present (naive datetimes are rejected) |
+| `end` | `datetime \| None` | Must be **timezone-aware** if present |
+
+Validation (`validate_period`, mode `after`): a naive `start`/`end` raises an error, and `start` must not be greater than `end`.
+
+## Resource specs (API schema)
+
+All specs subclass `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB object → pydantic) and `de_serialize` (pydantic → DB object). `__model__ = MedicationStatement`; `__exclude__ = ["patient", "encounter"]` means those fields are never written/read by the generic field copy and are instead handled by the extra hooks.
+
+| Spec class | Role | Fields exposed |
+| --- | --- | --- |
+| `BaseMedicationStatementSpec` | shared base | `id`, `status`, `reason`, `medication`, `dosage_text`, `effective_period`, `encounter`, `information_source`, `note` |
+| `MedicationStatementSpec` | write · create | Inherits `BaseMedicationStatementSpec`; adds `encounter` existence validation + extra deserialization |
+| `MedicationStatementUpdateSpec` | write · update | `status`, `effective_period`, `note` only |
+| `MedicationStatementReadSpec` | read · list & detail | Inherits base; adds `created_by`, `updated_by` (`UserSpec`), `created_date`, `modified_date` |
+
+Key validation and server-maintained behaviour:
+
+- **`encounter` existence** — `MedicationStatementSpec.validate_encounter_exists` (a `field_validator` on `encounter`) raises `"Encounter not found"` unless an `Encounter` with that `external_id` exists.
+- **`patient` derived server-side** — `MedicationStatementSpec.perform_extra_deserialization` runs only on create (`is_update` false): it resolves the `Encounter` from the supplied UUID and sets `obj.patient = obj.encounter.patient`. Clients never send `patient`.
+- **Update is restricted** — `MedicationStatementUpdateSpec` only accepts `status`, `effective_period`, and `note`; `medication`, `reason`, `dosage_text`, `information_source`, and `encounter` are not updatable through it.
+- **Read serialization** — `MedicationStatementReadSpec.perform_extra_serialization` sets `id = obj.external_id`, `encounter = obj.encounter.external_id`, and serializes audit users via `serialize_audit_users` (`created_by`/`updated_by` as `UserSpec`).
+- **`medication` value-set check** — enforced at validation time on the `Coding.code` (see [Bound value set](#bound-value-set)).
+- **`effective_period` tz-awareness / ordering** — enforced by `PeriodSpec` (see [`PeriodSpec` shape](#periodspec-shape)).
+
+There is **no** `status_history` on this resource — status is a single current value, not an appended log.
+
+## Methods & save behaviour
+
+- Inherited from [`EMRBaseModel`](../foundation/base-model.mdx): `external_id` (UUID), audit fields, soft delete. Deletes are soft — a removed statement is retained with `deleted=True` and hidden by the default manager.
+- Create flow: client sends `MedicationStatementSpec` → `de_serialize` copies scalar/JSON fields → `perform_extra_deserialization` resolves `encounter` and back-fills `patient` → model saved.
+- Read flow: `MedicationStatementReadSpec.serialize(obj)` copies fields, then `perform_extra_serialization` injects `id`, `encounter` (as UUIDs) and audit users.
+
+## API integration notes
+
+- Exposed through Care's REST API, aligned with FHIR `MedicationStatement`. Use the spec field names above for payloads.
+- `medication` is a single SNOMED CT `Coding` object whose `code` must be in the `system-medication` value set — **not** a free CodeableConcept and not a scalar string.
+- `effective_period` datetimes must be timezone-aware and ordered (`start ≤ end`).
+- `encounter` is supplied as a UUID; `patient` is derived from it server-side and must not be sent.
+- Updates go through `MedicationStatementUpdateSpec` and can only change `status`, `effective_period`, and `note`.
+- `information_source` distinguishes a self-reported or third-party statement from an actively managed prescription ([`MedicationRequest`](medication-request.mdx)).
+- `external_id` (UUID), `created_by`/`updated_by`, `created_date`/`modified_date`, `history`, and `meta` are platform-maintained — do not set them directly from clients.
+
+## Related
+
+- Reference: [Medication Request](medication-request.mdx)
+- Reference: [Medication Administration](medication-administration.mdx)
+- Reference: [Medication Dispense](medication-dispense.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Source (model): [medication_statement.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_statement.py)
+- Source (spec): [statement/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/statement/spec.py)
+- Source (value set): [valueset/medication.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/valueset/medication.py)
diff --git a/versioned_docs/version-3.0/references/platform/_category_.json b/versioned_docs/version-3.0/references/platform/_category_.json
new file mode 100644
index 0000000..6aeafd6
--- /dev/null
+++ b/versioned_docs/version-3.0/references/platform/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Platform",
+ "position": 10,
+ "key": "platform-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Platform",
+ "description": "Cross-cutting platform models — file uploads, generated reports and templates, tagging, resource requests, and metadata artifacts."
+ }
+}
diff --git a/versioned_docs/version-3.0/references/platform/favorites.mdx b/versioned_docs/version-3.0/references/platform/favorites.mdx
new file mode 100644
index 0000000..9d1752c
--- /dev/null
+++ b/versioned_docs/version-3.0/references/platform/favorites.mdx
@@ -0,0 +1,143 @@
+---
+sidebar_position: 7
+---
+
+# Favorites
+
+Technical reference for the `UserResourceFavorites` module in Care EMR.
+
+Favorites are a Care-internal personalization feature, not a FHIR resource. A user keeps one or more **named lists** of favorited resource IDs, scoped by a `resource_type` and (optionally) a `facility`. Unlike most EMR resources, favorites have **no `EMRResource` serialize/de_serialize spec** — the implementation layer is an enum (`FavoriteResourceChoices`), a default-list constant, a DRF filter backend (`FavoritesFilter`), and a viewset mixin (`EMRFavoritesMixin`) that exposes the read/write endpoints.
+
+**Source:**
+- Model: [`care/emr/models/favorites.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/favorites.py)
+- Spec (enum + constant): [`care/emr/resources/favorites/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/favorites/spec.py)
+- Filter backend: [`care/emr/resources/favorites/filters.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/favorites/filters.py)
+- Viewset mixin: [`care/emr/api/viewsets/favorites.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/favorites.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `UserResourceFavorites` | Stores a per-user, named list of favorited resource integer PKs, optionally scoped to a facility and constrained to one resource type |
+
+`UserResourceFavorites` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_by` / `updated_by` / `created_date` / `modified_date`, and soft-delete semantics).
+
+## `UserResourceFavorites` fields
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `user` | `FK → User` | optional | — | `CASCADE`, nullable. Owner of the favorites list |
+| `favorites` | `ArrayField(IntegerField)` | yes | `[]` | Ordered list of favorited resource **primary-key integers** (not `external_id`s). Newest-first; capped at `settings.MAX_FAVORITES_PER_LIST` (default `50`) |
+| `favorite_list` | `CharField(255)` | yes | — | Name of the list. A user may keep multiple named lists per `(resource_type, facility)`. Endpoints default it to `"default"` (`DEFAULT_FAVORITE_LIST`) |
+| `resource_type` | `CharField(255)` | yes | — | The kind of resource favorited. In practice always one of the `FavoriteResourceChoices` values (set server-side from the viewset's `FAVORITE_RESOURCE`), never accepted from the client |
+| `facility` | `FK → Facility` | optional | — | `CASCADE`, nullable. Optional facility scope; `null` means an instance-wide (cross-facility) list |
+
+A row is uniquely addressed in practice by the tuple `(user, resource_type, facility, favorite_list)` — this combination is what the cache keys and the `get_or_create` in `add_favorite` are built from.
+
+### `resource_type` values — `FavoriteResourceChoices`
+
+`resource_type` is constrained at the API layer to the string values of `FavoriteResourceChoices` (`care/emr/resources/favorites/spec.py`). Each value is wired to a viewset via that viewset's `FAVORITE_RESOURCE` attribute.
+
+| Value | Bound resource | Wired viewset |
+| --- | --- | --- |
+| `activity_definition` | [Activity definition](../clinical/activity-definition.mdx) | `ActivityDefinitionViewSet` |
+| `charge_item_definition` | [Charge item definition](../billing/charge-item-definition.mdx) | `ChargeItemDefinitionViewSet` |
+| `product_knowledge` | [Product knowledge](../supply/product-knowledge.mdx) | `ProductKnowledgeViewSet` |
+| `observation_definition` | [Observation definition](../clinical/observation-definition.mdx) | _(enum value defined; no viewset wires it yet)_ |
+| `questionnaire` | [Questionnaire](../forms/questionnaire.mdx) | `QuestionnaireViewSet` |
+| `facility_organization` | [Organization](../facility/organization.mdx) (facility org) | `FacilityOrganizationViewSet` |
+
+## Resource specs (API schema)
+
+Favorites do **not** use the `EMRResource` Create/Update/List/Retrieve pattern. The "spec" surface is:
+
+| Symbol | Kind | Role |
+| --- | --- | --- |
+| `FavoriteResourceChoices` | `str, Enum` (`spec.py`) | The allowed `resource_type` values (table above). A viewset selects one via `FAVORITE_RESOURCE = FavoriteResourceChoices..value` |
+| `DEFAULT_FAVORITE_LIST` | constant (`spec.py`) | `"default"` — the list name used when the request omits `favorite_list` |
+| `FavoriteRequest` | `pydantic.BaseModel` (viewset) | Request body for write actions. Single field `favorite_list: str = DEFAULT_FAVORITE_LIST` |
+| `FavoritesFilter` | DRF `BaseFilterBackend` (`filters.py`) | Read path. Adds the `favorite_list` query param to the resource's list endpoint and restricts/orders results to that user's favorites |
+| `EMRFavoritesMixin` | viewset mixin (`api/viewsets/favorites.py`) | Adds the `favorite_lists`, `add_favorite`, `remove_favorite` actions |
+
+### `FavoriteRequest`
+
+| Field | Type | Required | Default |
+| --- | --- | --- | --- |
+| `favorite_list` | `str` | optional | `"default"` (`DEFAULT_FAVORITE_LIST`) |
+
+There is no read schema for the favorited objects themselves: favorited resources are returned through the **owning resource's own list endpoint** (e.g. the questionnaire list serializer), filtered and ordered by `FavoritesFilter`. The `favorite_lists` action returns only `{"lists": [, ...]}`.
+
+## Related models
+
+- [`User`](../access/user.mdx) — owner of every favorites row (`user` FK)
+- [`Facility`](../facility/facility.mdx) — optional scope (`facility` FK); `null` = instance-wide
+- Each `resource_type` binds to one favoritable resource — see the [`FavoriteResourceChoices` table](#resource_type-values--favoriteresourcechoices)
+
+## Methods & save behaviour
+
+This model maintains a Django cache alongside the database row.
+
+### Cache keys
+
+Two module-level helpers build the cache keys (a missing facility is encoded as `-`):
+
+```text
+favorite_lists_cache_key(user, resource_type, facility)
+ → "user_favorites_lists:{user.id}:{resource_type}:{facility.id|-}"
+
+favorite_list_object_cache_key(user, resource_type, facility, favorite_list)
+ → "user_favorites_list_object:{user.id}:{resource_type}:{facility.id|-}:{favorite_list}"
+```
+
+- The **lists** key caches the ordered, deduplicated set of `favorite_list` names a user has for a `(resource_type, facility)`.
+- The **list object** key caches the `favorites` integer array for a single named list.
+
+### `refresh_cache(refresh_list=False)`
+
+- Always writes the current `favorites` array under the list-object key.
+- When `refresh_list=True`, also recomputes the deduplicated, `modified_date`-descending set of `favorite_list` names for the `(user, resource_type, facility)` and writes it under the lists key.
+
+### `save()` side effects
+
+`save()` sets `refresh_list = not self.pk` (i.e. only when creating a new row), calls `super().save()`, then `refresh_cache(refresh_list=refresh_list)`. Effects:
+
+1. Creating a new list refreshes both the lists cache and the list-object cache.
+2. Updating an existing list refreshes only the list-object cache (the set of list names is unchanged).
+
+Integrators reading favorites through the cache should treat these keys as platform-maintained; write through the model / endpoints so the cache stays consistent.
+
+## API integration notes
+
+Favorites are exposed through the **owning resource's viewset** via `EMRFavoritesMixin`, not a standalone favorites endpoint. A viewset opts in by mixing in `EMRFavoritesMixin`, setting `FAVORITE_RESOURCE`, and adding `FavoritesFilter` to `filter_backends`:
+
+```python
+class QuestionnaireViewSet(EMRModelViewSet, EMRFavoritesMixin):
+ filter_backends = [filters.DjangoFilterBackend, FavoritesFilter]
+ FAVORITE_RESOURCE = FavoriteResourceChoices.questionnaire.value
+```
+
+### Endpoints (relative to the owning resource)
+
+| Action | Method · route | Body | Behaviour |
+| --- | --- | --- | --- |
+| `add_favorite` | `POST {resource}/{id}/add_favorite/` | `FavoriteRequest` | `get_or_create` the `(user, favorite_list, resource_type, facility)` row, `insert(0, obj.id)` (newest-first), dedupe, **trim to `MAX_FAVORITES_PER_LIST`**, save. Returns `{}` |
+| `remove_favorite` | `POST {resource}/{id}/remove_favorite/` | `FavoriteRequest` | Pops `obj.id` from the list. If the list becomes empty, **deletes the row and both cache keys**. 404/validation error if the list does not exist. Returns `{}` |
+| `favorite_lists` | `GET {resource}/favorite_lists/` | — | Returns `{"lists": [, ...]}` (cache-first, falls back to DB). Resolves `facility` from `facility_external_id` kwarg or `?facility=` query param |
+| list (filtered) | `GET {resource}/?favorite_list=` | — | `FavoritesFilter` restricts the resource list to `id__in=favorites` and orders by favorite position (`sort_index`). Empty/unknown list → empty queryset |
+
+Server-maintained / validation behaviour:
+
+- `resource_type` is never client-supplied; it is fixed per viewset by `FAVORITE_RESOURCE`. The client only ever sends `favorite_list`.
+- `facility` is derived server-side via `retrieve_facility_obj(obj)` (defaults to `obj.facility`), so favorites inherit the favorited object's facility scope.
+- `favorites` stores **integer primary keys**, not `external_id`s. Ordering is significant and newest-first; `add_favorite` re-inserts at position 0 and dedupes.
+- The list is capped at `settings.MAX_FAVORITES_PER_LIST` (env `MAX_FAVORITES_PER_LIST`, default `50`); the oldest entries beyond the cap are dropped on `add_favorite`.
+- Do not set the `user_favorites_lists:*` or `user_favorites_list_object:*` cache keys directly from clients — they are maintained by `save()` / `refresh_cache()` and torn down by `remove_favorite` when a list empties.
+- A `null` `facility` denotes an instance-wide list; a set `facility` denotes a facility-scoped list.
+
+## Related
+
+- Reference: [User](../access/user.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Favoritable resources: [Activity definition](../clinical/activity-definition.mdx), [Charge item definition](../billing/charge-item-definition.mdx), [Product knowledge](../supply/product-knowledge.mdx), [Observation definition](../clinical/observation-definition.mdx), [Questionnaire](../forms/questionnaire.mdx), [Organization](../facility/organization.mdx)
+- Source: [favorites.py (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/favorites.py), [spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/favorites/spec.py), [filters.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/favorites/filters.py), [viewset mixin](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/favorites.py)
diff --git a/versioned_docs/version-3.0/references/platform/file-upload.mdx b/versioned_docs/version-3.0/references/platform/file-upload.mdx
new file mode 100644
index 0000000..c6dd96e
--- /dev/null
+++ b/versioned_docs/version-3.0/references/platform/file-upload.mdx
@@ -0,0 +1,176 @@
+---
+sidebar_position: 1
+---
+
+# File Upload
+
+Technical reference for the `FileUpload` resource in Care EMR.
+
+`FileUpload` is metadata for a file held in object storage (S3-compatible). The Django model is the **storage layer**: it persists the record and an opaque `meta` JSON column. The Pydantic **resource specs** in `care/emr/resources/file_upload/spec.py` are the API layer — they define the `file_type` / `file_category` enums, validate the upload, decide what the `meta` JSON holds (`mime_type`), and shape every read/write payload (including the derived `extension`, `signed_url`, and `read_signed_url` fields that do not exist as columns).
+
+**Source:**
+- Model: [`care/emr/models/file_upload.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/file_upload.py)
+- Specs: [`care/emr/resources/file_upload/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/file_upload/spec.py)
+- Viewset: [`care/emr/api/viewsets/file_upload.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/file_upload.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `FileUpload` | Metadata record for a file stored in object storage (S3), associated with another Care resource by `file_type` + `associating_id` |
+
+`FileUpload` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, `meta` JSON, audit fields, and soft-delete semantics).
+
+## `FileUpload` fields
+
+### File metadata
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(2000)` | User-facing file name. Required on the API (`FileUploadBaseSpec.name`) |
+| `internal_name` | `CharField(2000)` | Storage object key. Set server-side from `original_name` on create, then a random key is generated on `save()`. Never set by clients |
+| `associating_id` | `CharField(100)` | ID (`external_id`) of the owning resource. Required (`blank=False, null=False`). No DB `ForeignKey` — integrity is enforced in the app layer |
+| `file_type` | `CharField(100)` | Domain of the owning resource. API-constrained to [`FileTypeChoices`](#filetypechoices-values) |
+| `file_category` | `CharField(100)` | File category. API-constrained to [`FileCategoryChoices`](#filecategorychoices-values) |
+| `upload_completed` | `BooleanField` | Default `False`. Flipped to `True` once the client finishes the direct-to-S3 upload (or immediately, for the inline `upload-file` path) |
+
+### `meta` JSON contents
+
+`meta` is the opaque `JSONField` from `EMRBaseModel`. The specs store one structured key in it:
+
+| Key | Type | Shape / Notes |
+| --- | --- | --- |
+| `mime_type` | `str` | MIME type of the file. Written in `FileUploadCreateSpec.perform_extra_deserialization` (`obj.meta["mime_type"] = self.mime_type`); read back in serialization via `obj.meta.get("mime_type")`. Validated against `settings.ALLOWED_MIME_TYPES` |
+
+### Archival
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `is_archived` | `BooleanField` | Default `False` |
+| `archive_reason` | `TextField` | Blank-able; set when archiving (required in the `archive` action body) |
+| `archived_datetime` | `DateTimeField` | Nullable; set to `timezone.now()` when archived |
+| `archived_by` | `FK → User` (PROTECT) | Nullable; `related_name="archived_files"` |
+
+### Storage manager
+
+`FileUpload` declares a class-level `files_manager = S3FilesManager(BucketType.PATIENT)`. This is not a database field — it is the object-storage helper used to generate signed write/read URLs and to `put_object`, all against the `PATIENT` bucket.
+
+## Enums
+
+### `FileTypeChoices` values
+
+Owning-resource domain (`spec.py`). String enum.
+
+| Value | Links a file to |
+| --- | --- |
+| `patient` | A [patient](../clinical/patient.mdx) |
+| `encounter` | An [encounter](../clinical/encounter.mdx) |
+| `consent` | A [consent](../clinical/consent.mdx) |
+| `diagnostic_report` | A [diagnostic report](../clinical/diagnostic-report.mdx) |
+| `service_request` | A [service request](../clinical/service-request.mdx) |
+
+### `FileCategoryChoices` values
+
+File category (`spec.py`). String enum.
+
+| Value |
+| --- |
+| `audio` |
+| `xray` |
+| `identity_proof` |
+| `unspecified` |
+| `discharge_summary` |
+| `consent_attachment` |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`) — `serialize` (DB → Pydantic) and `de_serialize` (Pydantic → DB), with `perform_extra_serialization` / `perform_extra_deserialization` hooks. `FileUpload` does **not** set `__store_metadata__`, so `mime_type` is moved in/out of `meta` explicitly by the hooks rather than the generic meta machinery.
+
+| Spec | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `FileUploadBaseSpec` | shared base | `id: UUID4 \| None`, `name: str` (required) |
+| `FileUploadCreateSpec` | write · create | Adds `original_name`, `file_type`, `file_category`, `associating_id`, `mime_type`. Validates `mime_type` and `original_name`; on deserialize sets `_just_created=True`, `internal_name = original_name`, and `meta["mime_type"]` |
+| `FileUploadUpdateSpec` | write · update | Same fields as base (`id`, `name`) — only the display name is editable |
+| `FileUploadListSpec` | read · list | Read view (see fields below); resolves `extension`, `mime_type`, audit users, `uploaded_by`, `archived_by` |
+| `FileUploadRetrieveSpec` | read · detail | Extends `FileUploadListSpec` with `signed_url`, `read_signed_url`, `internal_name`; emits a **write** URL for just-created rows, otherwise a **read** URL |
+| `ConsentFileUploadCreateSpec` | write · create (consent) | `original_name` + `associating_id: UUID4`; forces `file_type=consent`, `file_category=consent_attachment` server-side |
+
+No field in these specs binds to a value set or uses `CodeableConcept` / `Period` / other `common` types — `file_type` and `file_category` are plain string enums.
+
+### `FileUploadCreateSpec` validation
+
+| Field | Rule |
+| --- | --- |
+| `name` | Required (inherited from base) |
+| `original_name` | Non-empty; run through `file_name_validator` (`care/utils/models/validators.py`): ≤ 255 chars, must not start with `.`, must have an extension, extension must be in `ALLOWED_FILE_EXTENSIONS` and not in `BLOCKED_FILE_EXTENSIONS` |
+| `mime_type` | Must be in `settings.ALLOWED_MIME_TYPES`, else `"Invalid mime type"` |
+| `file_type` | Must be a `FileTypeChoices` member |
+| `file_category` | Must be a `FileCategoryChoices` member |
+| `associating_id` | Required `str` |
+
+### `FileUploadListSpec` read fields
+
+| Field | Type | Source |
+| --- | --- | --- |
+| `id` | `UUID4` | `obj.external_id` (set in `perform_extra_serialization`) |
+| `name` | `str` | column |
+| `file_type` | `FileTypeChoices` | column |
+| `file_category` | `FileCategoryChoices` | column |
+| `associating_id` | `str` | column |
+| `upload_completed` | `bool` | column |
+| `is_archived` | `bool \| None` | column |
+| `archive_reason` | `str \| None` | column |
+| `archived_datetime` | `datetime \| None` | column |
+| `created_date` | `datetime` | column |
+| `extension` | `str` | derived via `obj.get_extension()` |
+| `mime_type` | `str` | `obj.meta.get("mime_type")` |
+| `uploaded_by` | `dict \| None` | `UserSpec` from cache (`obj.created_by_id`) |
+| `archived_by` | `UserSpec \| None` | `UserSpec` from cache (`obj.archived_by_id`) |
+| `created_by` / `updated_by` | `dict \| None` | `serialize_audit_users` |
+
+### `FileUploadRetrieveSpec` extra fields
+
+| Field | Type | When populated |
+| --- | --- | --- |
+| `signed_url` | `str \| None` | When `obj._just_created` is truthy — a **write** (PUT) URL from `files_manager.signed_url(obj)`. This is the upload target returned at create time |
+| `read_signed_url` | `str \| None` | Otherwise — a **read** (GET) URL from `files_manager.read_signed_url(obj)` |
+| `internal_name` | `str` | The storage object key (marked in source as possibly not needing to be returned) |
+
+## Methods & save behaviour
+
+### `get_extension()`
+
+Returns the file extension derived from `internal_name` via `parse_file_extension`, formatted as `.ext` (or `.tar.gz`-style multi-part), or `""` when none is found. Surfaced to clients as the `extension` read field.
+
+### `save()` side effects
+
+On save, when `internal_name` is empty or the row is new (`not self.id`) and the caller has **not** passed `skip_internal_name=True`:
+
+1. A random `internal_name` is generated as `uuid4() + int(time.time())`.
+2. If an extension can be derived, it is appended to that random name.
+3. `internal_name` is set before the row is persisted.
+
+This keeps the storage key free of the user-supplied `name`, limiting PII leakage if the bucket is compromised. The inline `upload-file` path calls `save(skip_internal_name=True)` after uploading so the generated key is preserved.
+
+## API integration notes
+
+### Two upload flows
+
+1. **Signed-URL (direct-to-S3), default:** `POST` a `FileUploadCreateSpec`. The server validates, deserializes (`_just_created=True`), and returns `FileUploadRetrieveSpec` including a write `signed_url`. The client `PUT`s the bytes directly to S3, then calls `POST .../{external_id}/mark_upload_completed/` to flip `upload_completed=True`.
+2. **Inline base64 (`POST .../upload-file/`):** the client sends `original_name` + base64 `file_data`. The server decodes it, enforces `settings.MAX_FILE_UPLOAD_SIZE` (MB), sniffs the real MIME type with `python-magic`, re-checks `ALLOWED_MIME_TYPES`, builds a `FileUploadCreateSpec`, sets `_just_created=False`, uploads via `files_manager.put_object`, sets `upload_completed=True`, and returns `FileUploadRetrieveSpec` (so the response carries `read_signed_url`, not a write URL).
+
+### Server-maintained behaviour
+
+- `internal_name` is platform-maintained — never set it from clients; the model generates a random PII-free key.
+- `mime_type` lives in `meta`, not a column; it is written by `FileUploadCreateSpec` and read back during serialization. On the inline path it is **re-derived from file content** (`magic.from_buffer`) rather than trusting the client.
+- `associating_id` + `file_type` together link a file to its owning resource; `list` requires both as query params and only returns `upload_completed=True` rows.
+- Every action runs `file_authorizer(user, file_type, associating_id, "read"|"write")`.
+- Archival (`POST .../{external_id}/archive/`, body `{ archive_reason }`) sets `is_archived`, `archive_reason`, `archived_datetime`, `archived_by` server-side and returns `FileUploadListSpec`. This soft state is distinct from the `EMRBaseModel` soft-delete (`deleted`).
+- `mark_upload_completed` and `archive` both return `FileUploadListSpec` (no signed URL).
+
+## Related
+
+- Base model: [EMRBaseModel](../foundation/base-model.mdx)
+- Owning resources: [patient](../clinical/patient.mdx), [encounter](../clinical/encounter.mdx), [consent](../clinical/consent.mdx), [diagnostic-report](../clinical/diagnostic-report.mdx), [service-request](../clinical/service-request.mdx)
+- User (audit / `uploaded_by` / `archived_by`): [user](../access/user.mdx)
+- Source: [file_upload.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/file_upload.py), [spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/file_upload/spec.py)
diff --git a/versioned_docs/version-3.0/references/platform/meta-artifact.mdx b/versioned_docs/version-3.0/references/platform/meta-artifact.mdx
new file mode 100644
index 0000000..1b3f091
--- /dev/null
+++ b/versioned_docs/version-3.0/references/platform/meta-artifact.mdx
@@ -0,0 +1,125 @@
+---
+sidebar_position: 2
+---
+
+# Meta Artifact
+
+Technical reference for the `MetaArtifact` module in Care EMR.
+
+`MetaArtifact` is a generic, polymorphic metadata record attached to any Care object by type + external ID. The Django model is the storage layer: `object_value` is an opaque `JSONField`, and `associating_type` / `object_type` are bare `CharField`s. The constrained values and the API request/response schemas live in the Pydantic resource specs.
+
+**Source:**
+
+- Model: [`care/emr/models/meta_artifact.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/meta_artifact.py)
+- Resource specs: [`care/emr/resources/meta_artifact/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/meta_artifact/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `MetaArtifact` | Generic, polymorphic metadata record attached to any Care object by type + external ID |
+
+`MetaArtifact` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_by` / `updated_by`, `created_date` / `modified_date`, and soft-delete `deleted`).
+
+## `MetaArtifact` fields
+
+### Association
+
+A `MetaArtifact` is not tied to a parent through a Django `ForeignKey`. Instead it points at any object generically by recording the object's type and its `external_id`.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `associating_type` | `CharField(255)` | Non-null. Identifies the kind of object this artifact is attached to. API-constrained to [`MetaArtifactAssociatingTypeChoices`](#metaartifactassociatingtypechoices-values) |
+| `associating_external_id` | `UUIDField` | Non-null. The `external_id` of the associated object. Set server-side from the spec's `associating_id` (see [Resource specs](#resource-specs-api-schema)) |
+
+These two fields are covered by a composite database index (see [Methods & save behaviour](#methods--save-behaviour)) so lookups by associated object are efficient.
+
+### Content
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(255)` | Display name for the artifact. Spec enforces non-blank on create |
+| `object_type` | `CharField(255)` | Describes the kind of payload held in `object_value`. API-constrained to [`MetaArtifactObjectTypeChoices`](#metaartifactobjecttypechoices-values) |
+| `object_value` | `JSONField` | Structured payload for the artifact. Spec types it as `dict \| list` (required, no default) |
+| `note` | `TextField` | Nullable free-text note. Spec: `str \| None`, default `None` |
+
+## Enum values
+
+These choice classes constrain `associating_type` and `object_type` at the API layer. They are defined in `spec.py` (there is no separate `constants.py` / `valueset.py` for this resource). Both are `str` enums.
+
+### `MetaArtifactAssociatingTypeChoices` values
+
+Binds `associating_type`.
+
+| Value |
+| --- |
+| `patient` |
+| `encounter` |
+
+### `MetaArtifactObjectTypeChoices` values
+
+Binds `object_type`.
+
+| Value |
+| --- |
+| `drawing` |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` ([`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)) — `serialize` (model → pydantic) / `de_serialize` (pydantic → model), with `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__model__ = MetaArtifact`.
+
+| Spec | Role | Fields exposed |
+| --- | --- | --- |
+| `MetaArtifactBaseSpec` | shared | `id` (`UUID4 \| None`), `object_value` (`dict \| list`), `note` (`str \| None`) |
+| `MetaArtifactCreateSpec` | write · create | base + `associating_type`, `associating_id` (`UUID4`), `object_type`, `name` |
+| `MetaArtifactUpdateSpec` | write · update | identical to `MetaArtifactBaseSpec` (only `object_value` / `note` are mutable; association and type are fixed after create) |
+| `MetaArtifactReadSpec` | read · detail/list | base + `associating_type`, `associating_id`, `object_type`, `name`, `created_date`, `modified_date`, `created_by` (`dict \| None`), `updated_by` (`dict \| None`) |
+
+Field-level details and validation:
+
+| Field | Spec type | Required | Default | Validation / behaviour |
+| --- | --- | --- | --- | --- |
+| `id` | `UUID4 \| None` | optional | `None` | Read: populated from `obj.external_id` in `perform_extra_serialization` |
+| `object_value` | `dict \| list` | yes | — | Arbitrary JSON object or array |
+| `note` | `str \| None` | no | `None` | Free text |
+| `associating_type` | `MetaArtifactAssociatingTypeChoices` | yes (create) | — | Bound enum; `patient` or `encounter` |
+| `associating_id` | `UUID4` | yes (create) | — | `external_id` of the parent. On create, copied to model field `associating_external_id` in `perform_extra_deserialization` |
+| `object_type` | `MetaArtifactObjectTypeChoices` | yes (create) | — | Bound enum; `drawing` |
+| `name` | `str` | yes (create) | — | `validate_name`: rejects blank/whitespace-only with `"Name cannot be empty"` |
+| `created_date` | `datetime` | read | — | From `EMRBaseModel` |
+| `modified_date` | `datetime` | read | — | From `EMRBaseModel` |
+| `created_by` / `updated_by` | `dict \| None` | read | `None` | Serialized via `serialize_audit_users` (cached `UserSpec`) |
+
+Server-maintained behaviour:
+
+- **Create** (`MetaArtifactCreateSpec.perform_extra_deserialization`): sets `obj.associating_external_id = self.associating_id`. The spec's `associating_id` (the parent's `external_id`) is the public handle; the model stores it as `associating_external_id`.
+- **Read** (`MetaArtifactReadSpec.perform_extra_serialization`): sets `mapping["id"] = obj.external_id` and calls `serialize_audit_users` to attach `created_by` / `updated_by`.
+- **Update**: `MetaArtifactUpdateSpec` exposes only `object_value` and `note` (plus the inherited optional `id`); `associating_type`, `associating_id`, `object_type`, and `name` are not re-accepted, so association and classification are immutable after creation.
+
+No coded field on this resource binds to a value set (no `valueset.py`); the only constrained vocabularies are the two `str` enums above.
+
+## Methods & save behaviour
+
+`MetaArtifact` does not override `save()` or `delete()` and defines no custom methods; it inherits all audit and soft-delete behaviour from [`EMRBaseModel`](../foundation/base-model.mdx).
+
+The model's `Meta` declares one composite index to support querying artifacts for a given parent object:
+
+```text
+Index(fields=["associating_type", "associating_external_id"])
+```
+
+## API integration notes
+
+- The generic `associating_type` + `associating_external_id` pair lets a single table store metadata for many different Care object types without per-type foreign keys; clients reference the target by its `external_id` (UUID), never its internal primary key. Clients send this as `associating_id` on create.
+- `associating_type` and `object_type` are open `CharField`s in storage but are constrained at the API by `str` enums (`patient` / `encounter` and `drawing` respectively). Adding new association or artifact kinds requires extending those enums in `spec.py`, not a schema migration.
+- `object_value` is an open `JSONField` typed as `dict | list` by the spec, so artifact payloads can carry deployment- or feature-specific structure without schema migrations; `object_type` records what shape to expect (e.g. `drawing`).
+- `name` is validated non-blank on create; whitespace-only names are rejected.
+- After creation the association (`associating_type` / `associating_id`), `object_type`, and `name` are fixed — only `object_value` and `note` are updatable via `MetaArtifactUpdateSpec`.
+- Audit fields (`created_by`, `updated_by`, `created_date`, `modified_date`), the public `external_id` (exposed as `id`), and soft-delete (`deleted`) are platform-maintained via `EMRBaseModel` — do not set them directly from clients.
+
+## Related
+
+- Base model: [EMRBaseModel](../foundation/base-model.mdx)
+- Common patient associations: [Patient](../clinical/patient.mdx), [Encounter](../clinical/encounter.mdx)
+- Model source: [meta_artifact.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/meta_artifact.py)
+- Spec source: [meta_artifact/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/meta_artifact/spec.py)
diff --git a/versioned_docs/version-3.0/references/platform/report.mdx b/versioned_docs/version-3.0/references/platform/report.mdx
new file mode 100644
index 0000000..b815ec2
--- /dev/null
+++ b/versioned_docs/version-3.0/references/platform/report.mdx
@@ -0,0 +1,199 @@
+---
+sidebar_position: 3
+---
+
+# Report & Templates
+
+Technical reference for the `ReportUpload` and `Template` modules in Care EMR.
+
+This page documents two layers:
+
+- **Storage layer** — the Django models (`care/emr/models/report/`). Several fields are opaque (`TextField`/`JSONField`) and their real shape is not visible here.
+- **API layer** — the Pydantic resource specs (`care/emr/resources/report/`), built on [`EMRResource`](../foundation/base-model.mdx). These define the enums, validation, the structure of the JSON fields, and the read/write schemas exposed by the API.
+
+**Source:**
+
+- Models: [`report_upload.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/report/report_upload.py), [`template.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/report/template.py)
+- Resource specs: [`report/report_upload/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/report/report_upload/spec.py), [`report/template/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/report/template/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ReportUpload` | An uploaded, generated report file (PDF/HTML/etc.) produced from a `Template` and linked to a subject resource via `associating_id` |
+| `Template` | A facility-scoped (or instance-wide) reusable report definition (markup + render options) used to generate reports |
+
+`ReportUpload` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, `meta`/`history` JSON, audit FKs, and soft-delete semantics).
+
+`Template` extends [`SlugBaseModel`](../foundation/base-model.mdx), the facility-scoped slug variant (`FACILITY_SCOPED = True`, adds `slug` handling on top of the EMR base). Slugs are stored prefixed: `f--` when facility-scoped, `i-` when instance-wide (see [`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/base.py)).
+
+## `ReportUpload` fields
+
+### Core
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `template` | `FK → Template (PROTECT)` | Template the report was generated from. Cannot be deleted while reports reference it |
+| `name` | `CharField(2000)` | Display name of the report. Required in every spec |
+| `internal_name` | `CharField(2000)` | Randomized storage key; auto-generated on save to avoid leaking PII in the file name (see [save behaviour](#methods--save-behaviour)). Exposed only in `RetrieveSpec` |
+| `associating_id` | `CharField(100)` | Non-null. Free-form identifier linking the report to its subject resource (e.g. an encounter) |
+| `upload_completed` | `BooleanField` | Default `False`. Set once the file upload to storage finishes |
+| `report_type` | `CharField(50)` | File/content type of the report; also exposed via the `file_type` property for `S3FilesManager` |
+
+### Archival
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `is_archived` | `BooleanField` | Default `False` |
+| `archive_reason` | `TextField` | Blank allowed |
+| `archived_datetime` | `DateTimeField` | Nullable; set when archived |
+| `archived_by` | `FK → User (PROTECT)` | Nullable. `related_name="archived_reports"` |
+
+### Inherited / metadata
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `meta` | `JSONField` (default `{}`) | Inherited from `EMRBaseModel`. Holds `mime_type` (surfaced by specs as `mime_type`) and other free-form metadata |
+| `history` | `JSONField` (default `{}`) | Inherited audit history |
+| `created_by` / `updated_by` | `FK → User (SET_NULL)` | Inherited audit FKs; serialized as `created_by`/`updated_by` `UserSpec` |
+
+### Storage
+
+`ReportUpload` declares a class-level `files_manager = S3FilesManager(BucketType.REPORT)` — the file body lives in object storage (REPORT bucket), not in the database. The model row holds metadata and the `internal_name` storage key.
+
+## `Template` fields
+
+The Django model stores most config as plain strings plus an opaque `options` `JSONField`. The spec layer constrains `status`, `default_format`, the slug, and the structure/compatibility of `template_type`, `context`, and `options`.
+
+| Field | Type (model) | Spec constraint | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → Facility (PROTECT, nullable)` | `UUID4 \| None` (write) | Null → instance-wide template; set → facility-scoped. Excluded from base serialization (`__exclude__`); resolved server-side on write |
+| `slug` | `CharField(255)` | written via `slug_value: SlugType`; read as `slug` + parsed `slug_config` | Stored prefixed (`f-…`/`i-…`). `slug_value` validated: 5–50 chars, URL-safe, must start/end alphanumeric |
+| `name` | `CharField(255)` | `str`, required | |
+| `status` | `CharField(255)` | `TemplateStatusOptions` enum | See [status values](#templatestatusoptions-values) |
+| `template_data` | `TextField` | `str`, required (write) | The report markup/body. Read only in `RetrieveSpec`, not in list/read |
+| `template_type` | `CharField(255)` | `str`, validated against `ReportTypeRegistry` | Must be a registered report type; must be compatible with `context` (matching `associating_model`) |
+| `default_format` | `CharField(255)` | `TemplateFormatOptions` enum | See [format values](#templateformatoptions-values). Drives which generator validates `options` |
+| `context` | `CharField(100)` (default `"encounter_base"`) | `str`, validated against `DataPointRegistry` | Default `encounter_base` → reports are encounter-oriented by default. Must be a registered context compatible with `template_type` |
+| `description` | `TextField` (blank, default `""`) | `str = ""` | |
+| `options` | `JSONField` (default `{}`) | `dict = {}`, validated against the format generator's `options_model` | Render/config options; the accepted keys depend on `default_format` (PDF vs HTML generator) |
+
+### `TemplateStatusOptions` values
+
+`status` is bound to this enum (`care/emr/resources/report/template/spec.py`):
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Template being authored, not yet usable |
+| `active` | Published / usable for generating reports |
+| `retired` | Withdrawn from use |
+
+### `TemplateFormatOptions` values
+
+`default_format` is bound to this enum:
+
+| Value | Meaning |
+| --- | --- |
+| `pdf` | Render to PDF (uses the PDF generator's `options_model`) |
+| `html` | Render to HTML (uses the HTML generator's `options_model`) |
+
+### `slug_config` shape (read)
+
+On read, `TemplateReadSpec` adds a parsed `slug_config` dict derived from the stored prefixed slug:
+
+```text
+slug_config (facility-scoped) → { facility: , slug_value: }
+slug_config (instance-wide) → { slug_value: } # parsed from "i-"
+```
+
+## Resource specs (API schema)
+
+All specs subclass [`EMRResource`](../foundation/base-model.mdx). Read flow: `serialize()` → DB-field copy → `perform_extra_serialization()` hook. Write flow: `de_serialize()` → DB-field copy → `perform_extra_deserialization()` hook.
+
+### `ReportUpload` specs (`report/report_upload/spec.py`)
+
+| Spec class | Role | Fields / behaviour |
+| --- | --- | --- |
+| `ReportUploadBaseSpec` | shared | `id: UUID4 \| None`, `name: str` |
+| `ReportUploadListSpec` | read · list | Adds `template` (nested `TemplateReadSpec`), `report_type`, `associating_id`, `archived_by` (`UserSpec`), `archived_datetime`, `upload_completed`, `is_archived`, `archive_reason`, `created_date`, `extension`, `uploaded_by`, `mime_type` |
+| `ReportUploadRetrieveSpec` | read · detail | Extends list spec; adds `signed_url`, `read_signed_url`, `internal_name` |
+
+Server-maintained read behaviour (`perform_extra_serialization`):
+
+- `id` ← `obj.external_id`.
+- `extension` ← `obj.get_extension()` (dotted extension parsed from `internal_name`).
+- `mime_type` ← `obj.meta["mime_type"]`.
+- `template` ← serialized via `TemplateReadSpec` (full nested template object).
+- `created_by` / `updated_by` populated from the user cache via `serialize_audit_users`.
+- **Signed URLs (retrieve only):** if the object was just created (`_just_created`), `signed_url` is set to a write/upload URL via `files_manager.signed_url(obj)`; otherwise `read_signed_url` is set via `files_manager.read_signed_url(obj)`. Exactly one is populated per response.
+
+> `ReportUpload` has no `CreateSpec`/`UpdateSpec` in this module — uploads are created through the file-upload flow ([File Upload](./file-upload.mdx)) and the report metadata/`internal_name` are platform-maintained. See [save behaviour](#methods--save-behaviour).
+
+### `Template` specs (`report/template/spec.py`)
+
+| Spec class | Role | Fields / behaviour |
+| --- | --- | --- |
+| `TemplateBaseSpec` | shared | `id`, `name`, `status` (`TemplateStatusOptions`), `default_format` (`TemplateFormatOptions`), `description = ""`, `options = {}`. `__exclude__ = ["facility"]` |
+| `TemplateCreateSpec` | write · create | Adds `facility: UUID4 \| None`, `slug_value: SlugType`, `template_data: str`, `template_type: str`, `context: str` |
+| `TemplateUpdateSpec` | write · update | Identical to `TemplateCreateSpec` (`pass` subclass) |
+| `TemplateReadSpec` | read · list | Base fields + `slug_config: dict`, `slug: str`, `template_type: str`, `context: str` (no `template_data`) |
+| `TemplateRetrieveSpec` | read · detail | Extends read spec; adds nested `facility` (`FacilityBareMinimumSpec`) and `template_data: str` |
+
+Write-side validation & side effects (`TemplateCreateSpec`):
+
+- `slug_value` — `SlugType`: 5–50 chars, pattern `^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$`.
+- `template_type` (`field_validator`) — must be non-empty and resolvable via `ReportTypeRegistry`; else `Invalid report type`.
+- `context` (`field_validator`) — must be non-empty and resolvable via `DataPointRegistry`; else `Invalid Context type`.
+- `validate_report_type_and_context` (`model_validator`, after):
+ - both `template_type` and `context` must resolve, else `Invalid report type or context`;
+ - their `associating_model` must match (`template_type.associating_model == context.__associating_model__`), else `Report Type and Context are not compatible`;
+ - `options` is validated against `GeneratorRegistry.get(default_format).options_model` — so the allowed `options` keys depend on whether `default_format` is `pdf` or `html`.
+- `perform_extra_deserialization` (side effects): resolves `facility` external ID to the `Facility` row (`get_object_or_404`) when provided, and sets `obj.slug = self.slug_value` (the raw value; the model prefixes it on `calculate_slug`).
+
+Read-side behaviour:
+
+- `TemplateReadSpec.perform_extra_serialization`: `id ← external_id`; `slug_config ← obj.parse_slug(obj.slug)` (see [`slug_config` shape](#slug_config-shape-read)).
+- `TemplateRetrieveSpec`: additionally serializes `facility` via `FacilityBareMinimumSpec` when set.
+
+## Methods & save behaviour
+
+### `ReportUpload.save()`
+
+On create (no `id`) or when `internal_name` is empty, `save()` generates a random `internal_name` from `uuid4()` plus the current epoch. This intermediate name decouples the stored object from the human-readable `name`, so a storage/data leak does not expose PII in file names.
+
+- Pass `skip_internal_name=True` to `save()` to bypass generation and keep an explicitly set `internal_name`.
+- If `internal_name` already carried a value, the file extension is parsed (via `parse_file_extension`) and appended to the new randomized name.
+
+### `ReportUpload.get_extension()`
+
+Returns the dotted extension(s) parsed from `internal_name` (e.g. `.pdf`), or `""` if none. Surfaced as the `extension` field in read specs.
+
+### `ReportUpload.file_type` (property)
+
+Alias for `report_type`, provided for compatibility with `S3FilesManager`.
+
+### `Template` slug methods (inherited from `SlugBaseModel`)
+
+- `calculate_slug()` — returns the prefixed slug: `f--` when facility-scoped, else `i-`.
+- `parse_slug(slug)` — inverse; returns `slug_config` (used by `TemplateReadSpec`).
+
+## API integration notes
+
+- Report files are stored in object storage (S3-compatible REPORT bucket) and accessed via `S3FilesManager`; the database row is metadata only.
+- `internal_name` is platform-maintained — do not set it from clients; it is generated on save to keep storage keys free of PII. Use `skip_internal_name` only for controlled migrations. It is exposed only via `ReportUploadRetrieveSpec`.
+- On a freshly created report, the retrieve response carries an upload `signed_url`; on subsequent reads it carries a `read_signed_url` instead.
+- `upload_completed` reflects upload state; clients should treat a report as available only once it is `True`.
+- `associating_id` is the link to the report's subject resource; populate it when generating a report so the file can be retrieved in that resource's context.
+- `Template.options` is validated server-side against the chosen format's generator schema — it is not an arbitrary bag; valid keys depend on `default_format` (`pdf`/`html`).
+- `template_type` and `context` must be a compatible pair (same `associating_model`); the default `context` is `encounter_base`, so templates target encounters unless changed.
+- `Template.facility` being nullable distinguishes instance-wide templates (`i-` slug) from facility-scoped ones (`f-` slug).
+
+## Related
+
+- Reference: [File Upload](./file-upload.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [User](../access/user.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [report_upload.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/report/report_upload.py), [template.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/report/template.py)
+- Source: [report_upload/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/report/report_upload/spec.py), [template/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/report/template/spec.py)
diff --git a/versioned_docs/version-3.0/references/platform/resource-category.mdx b/versioned_docs/version-3.0/references/platform/resource-category.mdx
new file mode 100644
index 0000000..0e29f54
--- /dev/null
+++ b/versioned_docs/version-3.0/references/platform/resource-category.mdx
@@ -0,0 +1,208 @@
+---
+sidebar_position: 5
+---
+
+# Resource Category
+
+Technical reference for the `ResourceCategory` module in Care EMR.
+
+`ResourceCategory` is the **storage** layer (Django model). The **API/implementation** layer is defined by the Pydantic resource specs in `care/emr/resources/resource_category/`, which add the `resource_type` enum, the structured shape of the `*_monetary_components` JSON fields (via `MonetaryComponent`), slug validation, and the read/write schemas. Several model fields are opaque `JSONField`s whose real structure only exists in the specs — see [Resource specs (API schema)](#resource-specs-api-schema).
+
+**Source:**
+- Model: [`care/emr/models/resource_category.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/resource_category.py)
+- Spec: [`care/emr/resources/resource_category/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/resource_category/spec.py)
+- Shared: [`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py) · [`common/monetary_component.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py) · [`common/coding.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py)
+
+## 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`](../foundation/base-model.mdx) (`FACILITY_SCOPED = True`), the facility-scoped slug variant of the base (adds `slug` handling on top of the EMR base fields: `external_id`, audit fields, soft-delete, and `history`/`meta` JSON).
+
+## `ResourceCategory` fields
+
+### Core
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → facility.Facility (PROTECT)` | yes | Owning facility; cannot be deleted while categories reference it. Excluded from spec write/read payloads (set server-side) |
+| `resource_type` | `CharField(255)` | yes | Top-level classification. Spec constrains to the `ResourceCategoryResourceTypeOptions` enum — see [values](#resourcecategoryresourcetypeoptions-values) |
+| `resource_sub_type` | `CharField(255)` | yes | Secondary classification; free string in the spec (`resource_sub_type: str`) |
+| `title` | `CharField(255)` | yes | Display name (`title: str`) |
+| `slug` | `CharField(255)` | yes | Facility-scoped encoded slug `f--`. Clients send the unencoded `slug_value` (validated `SlugType`); the model encodes via `calculate_slug()` |
+| `description` | `TextField` | no | Nullable, blank-able (`description: str \| None = None`) |
+
+The `Meta` declares a composite index on `(slug, facility)` for fast 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`; flipped to `True` on the parent when a child is created (see [save behaviour](#methods--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
+
+These denormalized fields are populated by `set_organization_cache()` and `get_parent_json()` so the ancestor chain can be read without recursive joins. They are platform-maintained — never written by clients.
+
+| Field | Type | Rebuilt by |
+| --- | --- | --- |
+| `parent_cache` | `ArrayField[int]` | `set_organization_cache()` — 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 (see shape below) with a `cache_expiry` timestamp; refreshed after `cache_expiry_days` (15) |
+
+`cached_parent_json` (and the `parent` field of `ResourceCategoryReadSpec`) shape, from `get_parent_json()`:
+
+```text
+{
+ "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](#monetarycomponent-nested-shape)). Only serialized 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](#methods--save-behaviour)). 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](../supply/product-knowledge.mdx) entries |
+| `activity_definition` | Category classifies [Activity Definitions](../clinical/activity-definition.mdx) |
+| `charge_item_definition` | Category classifies [Charge Item Definitions](../billing/charge-item-definition.mdx); enables 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`). `__model__ = ResourceCategory` and `__exclude__ = ["parent"]` (the `parent` FK is never auto-mapped; it is resolved 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`. `perform_extra_deserialization` resolves `obj.parent = ResourceCategory.objects.get(slug=self.parent)` when `parent` is set, and sets `obj.slug = self.slug_value` |
+| `ResourceCategoryUpdateSpec` | write · update | Base + `slug_value: SlugType`. `perform_extra_deserialization` sets `obj.slug = self.slug_value` (no `parent` reassignment on update) |
+| `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 |
+
+> The code labels these "ChargeItemDefinition Category" in docstrings; the actual model is `ResourceCategory`. There is no separate `*ListSpec`/`*RetrieveSpec` — `ResourceCategoryReadSpec` serves both.
+
+### `ResourceCategoryReadSpec` serialization (`perform_extra_serialization`)
+
+- `id = obj.external_id`
+- `parent = obj.get_parent_json()` — nested ancestor snapshot (shape above)
+- `slug_config = obj.parse_slug(obj.slug)` — for a facility-scoped slug returns `{"facility": , "slug_value": }`; for an instance slug `{"slug_value": }`
+- `calculated_monetary_components` / `configured_monetary_components` — set **only** when `resource_type == "charge_item_definition"`; otherwise the (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, `-`, `_`), must start and end alphanumeric
+
+The stored `slug` is the encoded form `f--` (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](#monetarycomponenttype-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:
+
+- `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's monetary fields are stored/validated 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 itself.
+
+## Methods & save behaviour
+
+`ResourceCategory` overrides `save()` and maintains tree/charge caches through several methods plus a Celery task.
+
+### `save()`
+
+- On **create** (`self.id` not yet set): calls `super().save()`, then `set_organization_cache()`, then enqueues monetary-component summarisation via `summarise_monetary_components(self)`.
+- On **update**: a plain `super().save(*args, **kwargs)` with no cache rebuild.
+
+### `set_organization_cache()`
+
+Run 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 (and lazily rebuilds) `cached_parent_json`. If a cached snapshot exists and is not past its `cache_expiry`, it is returned as-is; otherwise the parent chain is walked, a fresh nested snapshot is built with a new expiry (`now + cache_expiry_days`), persisted via `save(update_fields=["cached_parent_json"])`, and returned. 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, 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"])`.
+- 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, while components without a `code` are appended unmerged.
+
+## API integration notes
+
+- Categories are facility-scoped; the `(slug, facility)` index and `SlugBaseModel` slug encoding (`f--`) make slugs unique per facility. Clients send `slug_value` (5–50, URL-safe); the server encodes and `slug_config` decodes it.
+- `parent` is written by **slug** (`ResourceCategoryWriteSpec.parent` → `ResourceCategory.objects.get(slug=...)`), not id; on read it is the nested `get_parent_json()` snapshot.
+- `parent_cache`, `level_cache`, `root_org`, `has_children`, `cached_parent_json`, and `calculated_monetary_components` are platform-maintained — do not set them directly; write `parent` and `configured_monetary_components` and let `save()`/the Celery task derive the rest.
+- Cache rebuilds (`set_organization_cache`, monetary-component summarisation) run only on **create**; updating an existing node's `parent` or configured components does not auto-rebuild the caches.
+- Monetary-component propagation is asynchronous (Celery), so `calculated_monetary_components` on descendants is eventually consistent after a write.
+- Monetary-component fields are serialized only for `resource_type == "charge_item_definition"`; component identity in merges relies on the nested `code.system` + `code.code` pair.
+
+## Related
+
+- Reference: [Charge Item Definition](../billing/charge-item-definition.mdx)
+- Reference: [Charge Item](../billing/charge-item.mdx)
+- Reference: [Activity Definition](../clinical/activity-definition.mdx)
+- Reference: [Product Knowledge](../supply/product-knowledge.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
+- Source: [resource_category.py (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/resource_category.py) · [spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/resource_category/spec.py) · [monetary_component.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
diff --git a/versioned_docs/version-3.0/references/platform/resource-request.mdx b/versioned_docs/version-3.0/references/platform/resource-request.mdx
new file mode 100644
index 0000000..6f3b1a8
--- /dev/null
+++ b/versioned_docs/version-3.0/references/platform/resource-request.mdx
@@ -0,0 +1,164 @@
+---
+sidebar_position: 4
+---
+
+# Resource Request
+
+Technical reference for the `ResourceRequest` module in Care EMR.
+
+`ResourceRequest` models an inter-facility request for a resource (transfer, supply, or assistance), tracked from origin through approval and assignment. The Django model is the **storage layer**: `status`, `category`, and `priority` are open `CharField`/`IntegerField` columns with no model-level choices. The **API/implementation layer** lives in the Pydantic resource specs, which constrain those fields to enums, add validation, resolve foreign keys, and define the read/write schemas.
+
+**Sources:**
+
+- Model: [`care/emr/models/resource_request.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/resource_request.py)
+- Spec: [`care/emr/resources/resource_request/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/resource_request/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ResourceRequest` | An inter-facility request for a resource (transfer, supply, or assistance), tracked from origin through approval and assignment |
+| `ResourceRequestComment` | A free-text comment thread entry attached to a `ResourceRequest` |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields — `created_by`, `updated_by`, `created_date`, `modified_date` — and soft-delete semantics).
+
+## `ResourceRequest` fields
+
+### Facilities
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `origin_facility` | `FK → Facility` (PROTECT) | Required. Facility raising the request; `related_name="resource_requesting_facilities"`. Cannot be changed after create (set only when `is_update` is false). |
+| `approving_facility` | `FK → Facility` (SET_NULL) | Nullable; facility responsible for approving; `related_name="resource_approving_facilities"` |
+| `assigned_facility` | `FK → Facility` (SET_NULL) | Nullable; facility the request is assigned to/fulfilled by; `related_name="resource_assigned_facilities"`. Required when `assigned_to` is set. |
+
+### Request details
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `emergency` | `bool` (`BooleanField`) | Yes (spec) | `False` (model) | Flags urgent requests |
+| `title` | `str` (`CharField(255)`) | Yes | — | Not null, not blank |
+| `reason` | `str` (`TextField`) | Yes (spec) | `""` (model) | Free text |
+| `status` | `StatusChoices` enum (`CharField(100)`) | Yes | — | Constrained to [Status values](#statuschoices-values) by the spec; no model-level choices |
+| `category` | `CategoryChoices` enum (`CharField(100)`) | Yes | — | Constrained to [Category values](#categorychoices-values) by the spec; no model-level choices |
+| `priority` | `int` (`IntegerField`) | Yes (spec) | `None` (model, nullable) | Spec requires an integer; the model column is nullable |
+
+### Referring contact
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `referring_facility_contact_name` | `str` (`TextField`) | Yes (spec) | `""` (model) | Blank allowed at model level |
+| `referring_facility_contact_number` | `str` (`CharField(14)`) | Yes (spec) | `""` (model) | Validated with `mobile_or_landline_number_validator` at the model layer |
+
+### Assignment & patient
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `is_assigned_to_user` | `bool` (`BooleanField`) | Defaults to `False`; indicates a user-level assignment exists. Not exposed by the spec classes. |
+| `assigned_to` | `FK → User` (SET_NULL) | Nullable; user the request is assigned to; `related_name="resource_request_assigned"`. Requires `assigned_facility`, and the user must be a member of that facility (see [validation](#validation)). |
+| `related_patient` | `FK → Patient` (CASCADE) | Nullable; defaults to `None`; links the request to a patient when applicable. Set only on create. |
+
+## Enum values
+
+The spec layer binds `status` and `category` to string enums defined in `spec.py`. The Django model stores them as plain `CharField`s, so these values are enforced only through the API.
+
+### `StatusChoices` values
+
+| Value |
+| --- |
+| `pending` |
+| `approved` |
+| `rejected` |
+| `cancelled` |
+| `transportation_to_be_arranged` |
+| `transfer_in_progress` |
+| `completed` |
+
+### `CategoryChoices` values
+
+| Value |
+| --- |
+| `patient_care` |
+| `comfort_devices` |
+| `medicines` |
+| `financial` |
+| `supplies` |
+| `other` |
+
+## Resource specs (API schema)
+
+All specs build on `EMRResource` ([`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)), which provides `serialize` (DB object → pydantic, read path) and `de_serialize` (pydantic → DB object, write path), with the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__exclude__` lists fields the base (de)serializer skips so they can be handled explicitly (e.g. FK resolution).
+
+### `ResourceRequest` specs
+
+| Spec | Role | Fields beyond base |
+| --- | --- | --- |
+| `ResourceRequestBaseSpec` | shared | `id`, `emergency`, `title`, `reason`, `referring_facility_contact_name`, `referring_facility_contact_number`, `status`, `category`, `priority`. `__exclude__`s the five FK fields (`origin_facility`, `approving_facility`, `assigned_facility`, `related_patient`, `assigned_to`). |
+| `ResourceRequestCreateSpec` | write · create & update | Adds the FKs as `UUID4` external ids: `origin_facility` (required), `approving_facility`, `assigned_facility`, `related_patient`, `assigned_to` (all optional). Resolves each external id to a DB object in `perform_extra_deserialization`. |
+| `ResourceRequestListSpec` | read · list | `origin_facility: dict`, `assigned_facility: dict \| None`, `created_date`, `modified_date`. Inlines `origin_facility`/`assigned_facility` via `FacilityReadSpec`. |
+| `ResourceRequestRetrieveSpec` | read · detail | All of List plus `approving_facility`, `related_patient` (via `PatientListSpec`), `assigned_to` (via `UserSpec`, cache-backed), `created_by`, `updated_by`. |
+
+There is no separate `UpdateSpec`: `ResourceRequestCreateSpec` handles both create and update, branching on the `is_update` flag.
+
+#### Validation
+
+- `validate_assigned_to_user` (model validator, `mode="after"`): if `assigned_to` is set, `assigned_facility` is **required**, and the assigned user must be a member of the assigned facility (checked via `FacilityOrganizationUser`). Otherwise raises `ValueError`.
+
+#### Server-side behaviour (`perform_extra_deserialization`)
+
+- `origin_facility` external id → `Facility` object, **only on create** (`is_update` false); it is not reassigned on update.
+- `approving_facility`, `assigned_facility`, `assigned_to` external ids → their DB objects whenever present (create or update).
+- `related_patient` external id → `Patient` object, **only on create**.
+- Each lookup uses `get_object_or_404` on `external_id`, so an unknown id returns 404.
+
+#### Read serialization (`perform_extra_serialization`)
+
+- `id` is emitted as the string `external_id`.
+- `origin_facility` (and `assigned_facility`, `approving_facility` where present) are serialized inline via `FacilityReadSpec`.
+- `related_patient` via `PatientListSpec`; `assigned_to` via `UserSpec` resolved from cache (`model_from_cache` keyed on `assigned_to_id`).
+- `created_by` / `updated_by` populated by `serialize_audit_users`.
+
+### `ResourceRequestComment` specs
+
+| Spec | Role | Fields beyond base |
+| --- | --- | --- |
+| `ResourceRequestCommentBaseSpec` | shared | `comment: str`. `__exclude__`s `request`. |
+| `ResourceRequestCommentCreateSpec` | write · create | No additional fields (the parent `ResourceRequest` is bound from the URL, not the body). |
+| `ResourceRequestCommentListSpec` | read · list | Adds `created_by`, `created_date`; populates `created_by` via `serialize_audit_users`. |
+| `ResourceRequestCommentRetrieveSpec` | read · detail | Subclasses `ResourceRequestCommentListSpec` with no changes. |
+
+## Related models
+
+### `ResourceRequestComment`
+
+A comment entry on a resource request. Deleting a comment does not cascade to the request — the FK uses `PROTECT`.
+
+```text
+request → FK ResourceRequest (PROTECT, required)
+comment → TextField (default "")
+```
+
+## Methods & save behaviour
+
+- Read path: `.serialize(obj)` copies model fields, runs `perform_extra_serialization` (FK inlining, audit users, `id` as string), and constructs the pydantic instance without re-validation (`model_construct`).
+- Write path: `.de_serialize()` writes scalar fields onto the model (`model_dump(exclude_defaults=True)`), then `perform_extra_deserialization` resolves FK external ids and enforces the create-only constraints on `origin_facility`/`related_patient`.
+- Coded fields (`status`, `category`) carry no `status_history` in this resource; transitions are stored directly on the column.
+
+## API integration notes
+
+- `ResourceRequest` and `ResourceRequestComment` are exposed through Care's REST API; write bodies use the `CreateSpec` schema, reads use `ListSpec`/`RetrieveSpec`.
+- `status` and `category` are open `CharField`s in the database but are constrained to `StatusChoices` / `CategoryChoices` by the spec — only those values pass API validation.
+- `priority` is a required integer at the API layer even though the model column is nullable.
+- Foreign keys are sent/returned as `external_id` UUIDs in write payloads and as nested objects (`FacilityReadSpec`, `PatientListSpec`, `UserSpec`) in read payloads.
+- Assigning a request to a user requires `assigned_facility`, and the user must belong to that facility.
+- `referring_facility_contact_number` is validated against the shared `mobile_or_landline_number_validator`; submit numbers in a format that validator accepts.
+- `related_patient` is optional and cascades on patient deletion; `origin_facility` is `PROTECT`-ed and cannot be deleted while requests reference it, and cannot be changed after the request is created.
+
+## Related
+
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [User](../access/user.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [resource_request.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/resource_request.py)
+- Source: [resource_request/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/resource_request/spec.py)
diff --git a/versioned_docs/version-3.0/references/platform/tag-config.mdx b/versioned_docs/version-3.0/references/platform/tag-config.mdx
new file mode 100644
index 0000000..3dc2991
--- /dev/null
+++ b/versioned_docs/version-3.0/references/platform/tag-config.mdx
@@ -0,0 +1,186 @@
+---
+sidebar_position: 6
+---
+
+# Tagging
+
+Technical reference for the `TagConfig` module in Care EMR. Tags are hierarchical labels that can be applied to resources (patients, encounters, and more) for classification and filtering; `TagConfig` defines the available tags and their tree structure.
+
+The Django model (`care/emr/models/tag_config.py`) is the **storage layer**: it holds opaque `JSONField`s (`metadata`, `cached_parent_json`) whose real structure is not visible in the model. The Pydantic **resource specs** (`care/emr/resources/tag/config_spec.py`) are the API/implementation layer: they define the enums, the structured shape of those JSON fields, field validation, and the read/write schemas.
+
+**Source:**
+- Model: [`care/emr/models/tag_config.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/tag_config.py)
+- Resource spec: [`care/emr/resources/tag/config_spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/tag/config_spec.py)
+- Cache invalidation: [`care/emr/resources/tag/cache_invalidation.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/tag/cache_invalidation.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `TagConfig` | Definition of a single tag — its display, category, scope, status, metadata, and position in the tag tree |
+
+`TagConfig` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_by`/`updated_by`, `created_date`/`modified_date`, soft-delete via `deleted`, and `history`/`meta` JSON). Applied tag IDs are stored 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.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → Facility` | `PROTECT`, nullable, `default=None`. Facility that owns the tag |
+| `facility_organization` | `FK → FacilityOrganization` | `CASCADE`, nullable. Owning facility organization. Not allowed on instance-level (no-facility) tags |
+| `organization` | `FK → Organization` | `CASCADE`, nullable. Owning instance-wide organization |
+| `resource` | `CharField(255)` | The resource type the tag applies to. Constrained by `TagResource` in the write spec (see [enum](#tagresource-values)) |
+
+### Display & classification
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `status` | `CharField(255)` | Lifecycle status. Constrained by `TagStatus` in the spec — `active` / `archived` (see [enum](#tagstatus-values)) |
+| `display` | `CharField(255)` | Human-readable label. Required in the spec |
+| `description` | `TextField` | Nullable, blank-able. Required key in the base spec but accepts `null` |
+| `category` | `CharField(255)` | Grouping category. Constrained by `TagCategoryChoices` in the spec (see [enum](#tagcategorychoices-values)) |
+| `priority` | `IntegerField` | `default=100`. Ordering weight |
+| `metadata` | `JSONField` | `default=None`, nullable. Shape defined by `TagConfigMetadata`: `{ color: str?, icon: str? }` (see [nested spec](#tagconfigmetadata-shape-of-metadata)) |
+
+### Tree structure & caches
+
+`TagConfig` is self-referential — tags form a tree, with denormalized caches rebuilt on insert to avoid recursive joins (the same pattern as [`Organization`](../facility/organization.mdx)). These fields are platform-maintained and are **not** writable through the create/update specs.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `parent` | `FK → self` | `CASCADE`, nullable. `related_name="children"`. Null parent means a root tag |
+| `root_tag_config` | `FK → self` | `CASCADE`, nullable. `related_name="root"`. Top of the tree, derived on save |
+| `has_children` | `BooleanField` | `default=False`. Flipped to `True` on the parent when a child is created |
+| `level_cache` | `IntegerField` | `default=0`. Depth in the tree (`parent.level_cache + 1`) |
+| `parent_cache` | `ArrayField[int]` | `default=list`. Full ancestor id chain (`parent.parent_cache + [parent.id]`) |
+| `cached_parent_json` | `JSONField` | `default=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).
+
+| Value | Meaning |
+| --- | --- |
+| `active` | Tag is available for use |
+| `archived` | Tag retired; retained for historical references |
+
+## Nested specs (shape of JSON fields)
+
+### `TagConfigMetadata` (shape of `metadata`)
+
+Defines the real structure of the opaque `metadata` `JSONField`. Plain Pydantic `BaseModel` (not an `EMRResource`).
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `color` | `str \| None` | no | `None` | Display color hint |
+| `icon` | `str \| None` | no | `None` | Display icon hint |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB object → spec) / `de_serialize` (spec → DB object) and 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 class | Role | Notes |
+| --- | --- | --- |
+| `TagConfigBaseSpec` | shared | `__model__ = TagConfig`; `__exclude__ = [facility, facility_organization, organization, parent]`. Fields: `id` (`UUID4?`), `display`, `category` (`TagCategoryChoices`), `description` (`str \| None`), `priority` (`int = 100`), `status` (`TagStatus`), `metadata` (`TagConfigMetadata?`) |
+| `TagConfigWriteSpec` | write · create | Base + `facility?`, `facility_organization?`, `organization?`, `parent?` (all `UUID4`), `resource` (`TagResource`, required). Runs two `model_validator(after)` checks and resolves FKs in `perform_extra_deserialization` |
+| `TagConfigUpdateSpec` | write · update | Base + `facility_organization?`, `organization?` (`UUID4`). Cannot change `facility`, `parent`, or `resource`. Resolves `organization`/`facility_organization` in `perform_extra_deserialization` (facility organization scoped to `obj.facility`) |
+| `TagConfigReadSpec` | read · 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` |
+| `TagConfigRetrieveSpec` | read · detail | Extends 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_id`s 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 unless it is provided elsewhere it is not populated from storage. 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`. On every save it:
+
+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`), derives `root_tag_config` from the parent (the parent itself if the parent is a root, otherwise the parent's `root_tag_config`), and — if the parent had no children — flips `parent.has_children = True`, persisting only that field. Finishes with `super().save()`.
+
+### `get_parent_json()`
+
+Returns `cached_parent_json` when present and its `cache_expiry` is still in the future; otherwise 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 — a **second write pass**. On **update**, `save()` persists normally without recomputing caches. Either path triggers the `post_save` cache-invalidation signal above.
+
+## API integration notes
+
+- Tag definitions are exposed through Care's REST API; 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.
+- Create uses `TagConfigWriteSpec` (sets `resource`, scope FKs, and `parent`); update uses `TagConfigUpdateSpec` (only org/facility-org reassignment plus the shared display/category/status/metadata fields). List responses use `TagConfigReadSpec`; detail responses use `TagConfigRetrieveSpec`.
+- Tree position (`level_cache`, `parent_cache`, `root_tag_config`) and `has_children` are platform-maintained — do not set them directly from clients.
+- A tag is owned by at most one of `facility`, `facility_organization`, or `organization`; `resource` declares which resource type it targets and must match a value from [`TagResource`](#tagresource-values).
+- `metadata` is the supported place for display hints (`color`, `icon`) without schema migrations; its shape is enforced by `TagConfigMetadata`.
+
+## Related
+
+- Reference: [Organization](../facility/organization.mdx) — tags reuse the same tree-cache pattern and can be org-scoped
+- Reference: [Patient](../clinical/patient.mdx) — carries `instance_tags` / `facility_tags`
+- Reference: [Encounter](../clinical/encounter.mdx) — carries `tags`
+- Reference: [Facility](../facility/facility.mdx) — facility-scoped tags; read spec inlines a `FacilityBareMinimumSpec`
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Source: [tag_config.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/tag_config.py)
+- Source: [config_spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/tag/config_spec.py)
diff --git a/versioned_docs/version-3.0/references/scheduling/_category_.json b/versioned_docs/version-3.0/references/scheduling/_category_.json
new file mode 100644
index 0000000..944c54a
--- /dev/null
+++ b/versioned_docs/version-3.0/references/scheduling/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Scheduling",
+ "position": 5,
+ "key": "scheduling-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Scheduling",
+ "description": "Practitioner schedules and availability, patient bookings (appointments), and queue tokens."
+ }
+}
diff --git a/versioned_docs/version-3.0/references/scheduling/booking.mdx b/versioned_docs/version-3.0/references/scheduling/booking.mdx
new file mode 100644
index 0000000..c1ae339
--- /dev/null
+++ b/versioned_docs/version-3.0/references/scheduling/booking.mdx
@@ -0,0 +1,139 @@
+---
+sidebar_position: 2
+---
+
+# Booking
+
+Technical reference for the `TokenBooking` and `TokenSlot` modules in Care EMR.
+
+**Source:**
+
+- Model: [`care/emr/models/scheduling/booking.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/booking.py)
+- Resource spec: [`care/emr/resources/scheduling/slot/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/slot/spec.py)
+- Viewset (side effects): [`care/emr/api/viewsets/scheduling/booking.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/booking.py)
+
+A booking is the appointment of a patient into a concrete time slot generated from a [Schedule](../scheduling/schedule.mdx). `TokenSlot` materializes a bookable unit of availability; `TokenBooking` records a patient's appointment against that slot and tracks its status through the appointment lifecycle.
+
+The Django model is the **storage** layer — `status` is an unconstrained `CharField` and several relations are opaque at the DB level. The **Pydantic resource specs** (`care/emr/resources/scheduling/slot/`) are the API layer: they constrain `status` to a fixed enum, validate writes, and define the read schemas (which embed slot, resource, facility, token, charge item, encounter, and patient sub-objects).
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `TokenSlot` | A concrete, bookable time window generated from a schedulable resource's availability |
+| `TokenBooking` | A patient's appointment against a `TokenSlot`, with status, notes, and optional links to an encounter, token, and charge item |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `TokenBooking` fields
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `token_slot` | `FK → TokenSlot` (PROTECT) | The slot this appointment occupies; required. PROTECT prevents deleting a slot that has bookings |
+| `patient` | `FK → emr.Patient` (CASCADE) | The booked patient; required |
+| `booked_on` | `DateTimeField` | `auto_now_add` — set once at creation; platform-maintained |
+| `booked_by` | `FK → User` (CASCADE) | User who created the booking; nullable (e.g. self-service or system-created) |
+| `status` | `CharField` (storage) → `BookingStatusChoices` (API) | Appointment lifecycle status. Unbounded on the model; constrained to the [`BookingStatusChoices`](#bookingstatuschoices-values) enum by the write spec |
+| `note` | `TextField` | Free-text note; nullable in storage, required (`str`) on write |
+| `tags` | `ArrayField[int]` | Tag IDs; defaults to empty list. Managed via the `SingleFacilityTagManager` (read returns rendered tag objects, not raw IDs) |
+| `associated_encounter` | `FK → emr.Encounter` (PROTECT) | Encounter linked to the appointment; nullable, defaults to `None`. Only surfaced by `TokenBookingRetrieveSpec` |
+| `token` | `FK → emr.Token` (PROTECT) | Queue token issued for this booking; nullable, defaults to `None`, `related_name="token_booking"`. Created by the `generate_token` action |
+| `charge_item` | `FK → emr.ChargeItem` (CASCADE) | Billing charge item for the appointment; nullable. Auto-created on booking when the schedule has a `charge_item_definition` |
+
+### `BookingStatusChoices` values
+
+`BookingStatusChoices` (`str, Enum`) — the only values the write spec accepts for `status`:
+
+| Value | Notes |
+| --- | --- |
+| `proposed` | |
+| `pending` | |
+| `booked` | Default applied server-side when a booking is created via `lock_create_appointment` |
+| `arrived` | |
+| `fulfilled` | Terminal; in `COMPLETED_STATUS_CHOICES` |
+| `cancelled` | Set only via the `cancel` endpoint; in `CANCELLED_STATUS_CHOICES` + `COMPLETED_STATUS_CHOICES` |
+| `noshow` | Terminal; in `COMPLETED_STATUS_CHOICES` |
+| `entered_in_error` | Set only via the `cancel` endpoint; in `CANCELLED_STATUS_CHOICES` + `COMPLETED_STATUS_CHOICES` |
+| `checked_in` | |
+| `waitlist` | |
+| `in_consultation` | Cannot be cancelled (the cancel handler rejects it) |
+| `rescheduled` | Set only via the `reschedule` endpoint; in `CANCELLED_STATUS_CHOICES` + `COMPLETED_STATUS_CHOICES` |
+
+Derived sets (from `slot/spec.py`):
+
+| Set | Values |
+| --- | --- |
+| `CANCELLED_STATUS_CHOICES` | `entered_in_error`, `cancelled`, `rescheduled` |
+| `COMPLETED_STATUS_CHOICES` | `fulfilled`, `noshow`, `entered_in_error`, `cancelled`, `rescheduled` |
+
+`TokenBookingWriteSpec.perform_extra_deserialization` rejects any write whose `status` is in `CANCELLED_STATUS_CHOICES` with `"Cannot cancel a booking. Use the cancel endpoint"` — cancellation/rescheduling must go through the dedicated endpoints (see below).
+
+## Related models
+
+### `TokenSlot`
+
+A bookable window derived from a resource's availability. Bookings reference it via PROTECT, so slots with active bookings cannot be deleted.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `resource` | `FK → SchedulableResource` (CASCADE) | The schedulable resource (practitioner, location, or healthcare service) the slot belongs to; required |
+| `availability` | `FK → Availability` (CASCADE) | The availability rule that generated this slot; nullable |
+| `start_datetime` | `DateTimeField` | Slot start; required (tz-aware) |
+| `end_datetime` | `DateTimeField` | Slot end; required (tz-aware) |
+| `allocated` | `IntegerField` | Count of bookings currently allocated to the slot; used to enforce capacity. Defaults to `0`. Platform-maintained — never written by clients |
+
+`SchedulableResource` and `Availability` are defined in the [Schedule](../scheduling/schedule.mdx) module. `SchedulableResource.resource_type` is one of `SchedulableResourceTypeOptions`: `practitioner`, `location`, `healthcare_service`.
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` ([`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)) — `serialize` (DB → pydantic, read), `de_serialize` (pydantic → DB, write), with `perform_extra_serialization` / `perform_extra_deserialization` hooks.
+
+| Spec class | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `TokenBookingBaseSpec` | shared | `__model__ = TokenBooking`, `__exclude__ = ["token_slot", "patient"]` |
+| `TokenBookingWriteSpec` | write · create + update | Fields: `status: BookingStatusChoices`, `note: str`. Rejects `status` in `CANCELLED_STATUS_CHOICES` (use cancel/reschedule endpoints). Used as both `pydantic_model` and `pydantic_update_model` |
+| `TokenBookingMinimumReadSpec` | read · embed | Lightweight read used when a booking is embedded in another resource (e.g. token). Fields: `id`, `token_slot` (embedded `TokenSlotBaseSpec`), `booked_on`, `status: str`, `note`, `created_date`, `modified_date` |
+| `TokenBookingBaseReadSpec` | read · shared | Adds `booked_by: UserSpec`, `resource_type`, `resource` (serialized resource), `facility`, `created_by`/`updated_by`, `token: TokenReadSpec`, `tags: list[dict]` (rendered), `charge_item: dict` |
+| `TokenBookingReadSpec` | read · list/default | `TokenBookingBaseReadSpec` + `patient: PatientRetrieveSpec`. The viewset's `pydantic_read_model` (list responses) |
+| `TokenBookingOTPReadSpec` | read · OTP flow | `TokenBookingBaseReadSpec` + `patient: PatientOTPReadSpec` (public/OTP booking flow) |
+| `TokenBookingRetrieveSpec` | read · detail | `TokenBookingReadSpec` + `associated_encounter: dict` (serialized `EncounterListSpec`). The viewset's `pydantic_retrieve_model` |
+| `TokenSlotBaseSpec` | read · embed | `__model__ = TokenSlot`, `__exclude__ = ["resource", "availability"]`. Fields: `id`, `availability` (embedded as `{name, tokens_per_slot, id, schedule:{name,id}}`), `start_datetime`, `end_datetime`, `allocated` |
+
+### Read serialization details (`perform_extra_serialization`)
+
+- `id` is always set from `obj.external_id` (UUID), not the integer PK.
+- `token_slot` is embedded via `TokenSlotBaseSpec.serialize(...)`.
+- `resource_type` comes from `token_slot.resource.resource_type`; `resource` is built by `serialize_resource()` — a `UserSpec` for `practitioner`, `HealthcareServiceReadSpec` for `healthcare_service`, `FacilityLocationListSpec` for `location`.
+- `facility` is a cached `FacilityBareMinimumSpec`; `booked_by` / `created_by` / `updated_by` are cached `UserSpec`s.
+- `tags` are rendered through `SingleFacilityTagManager().render_tags(obj)` (objects, not raw IDs).
+- `token` (`TokenReadSpec`), `charge_item` (`ChargeItemReadSpec`), and `associated_encounter` (`EncounterListSpec`) are included only when present.
+
+## Methods & save behaviour
+
+`status` has no model-level `choices`; the lifecycle is enforced entirely in the spec/viewset layer (`TokenBookingViewSet`). Key server-maintained behaviour:
+
+- **Creation (`lock_create_appointment`)** — under a per-resource lock and a transaction: rejects past slots (`end_datetime < now`) and full slots (`allocated >= availability.tokens_per_slot`), rejects a duplicate active booking for the same patient/slot (any status not in `COMPLETED_STATUS_CHOICES`), then increments `token_slot.allocated`, creates the booking with `status="booked"`, and — if the schedule has a `charge_item_definition` — auto-creates the linked `charge_item`.
+- **Cancel (`cancel` action)** — accepts `CancelBookingSpec { reason: cancelled | entered_in_error | rescheduled, note?: str }`. Rejects cancelling an `in_consultation` booking. Decrements `token_slot.allocated` (unless already cancelled), sets `status = reason`, optionally overwrites `note`, sets `updated_by`, and aborts any linked `charge_item` (`handle_charge_item_cancel` + status `aborted`).
+- **Reschedule (`reschedule` action)** — accepts `RescheduleBookingSpec { new_slot: UUID, new_booking_note: str, previous_booking_note?: str, tags: list[UUID] }`. Requires `can_reschedule_booking`. Cancels the existing booking with reason `rescheduled`, then `lock_create_appointment` creates a fresh booking on `new_slot` for the same patient; rejects rescheduling to the same slot.
+- **Generate token (`generate_token` action)** — accepts `TokenGenerationSpec { category: UUID, note?: str, queue?: UUID }`. Rejects if a token already exists. Resolves/creates a `TokenQueue` for the slot's date and resource, allocates the next `number` under a queue lock, creates a `Token` (`status="CREATED"`), and links it to `booking.token`.
+- **`booked_on`** is `auto_now_add` — never set by clients.
+
+## API integration notes
+
+- `TokenSlot` and `TokenBooking` are exposed through Care's scheduling REST API and align with the FHIR `Slot` and `Appointment` resources; payload field names may differ from Django model names.
+- Writes use `TokenBookingWriteSpec` (`status` constrained to `BookingStatusChoices`, `note` required). `cancelled` / `entered_in_error` / `rescheduled` cannot be set through a plain update — use the `cancel` / `reschedule` actions.
+- List responses use `TokenBookingReadSpec`; detail responses use `TokenBookingRetrieveSpec` (adds `associated_encounter`).
+- `allocated` on `TokenSlot` is a capacity counter maintained by `lock_create_appointment` / cancel; do not write it directly.
+- `tags` is an array of integer tag IDs in storage but returned as rendered tag objects on read.
+- List requires a `resource_type` query param and authorization scoped to organizations or resource IDs.
+
+## Related
+
+- Reference: [Schedule](../scheduling/schedule.mdx)
+- Reference: [Token](../scheduling/token.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Charge item](../billing/charge-item.mdx)
+- Source: [booking.py (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/booking.py)
+- Source: [slot/spec.py (resource spec)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/slot/spec.py)
+- Source: [booking.py (viewset)](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/booking.py)
diff --git a/versioned_docs/version-3.0/references/scheduling/schedule.mdx b/versioned_docs/version-3.0/references/scheduling/schedule.mdx
new file mode 100644
index 0000000..b06a364
--- /dev/null
+++ b/versioned_docs/version-3.0/references/scheduling/schedule.mdx
@@ -0,0 +1,194 @@
+---
+sidebar_position: 1
+---
+
+# Schedule & Availability
+
+Technical reference for the `Schedule` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/scheduling/schedule.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/schedule.py)
+- Resource specs: [`schedule/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/schedule/spec.py), [`availability_exception/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/availability_exception/spec.py), [`resource/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/resource/spec.py)
+
+A schedule is a repeatable block of time during which a resource (a practitioner, location, or healthcare service) is available for booking. A schedule carries one or more **availabilities** that describe how that time is broken into slots, and **exceptions** that block out specific dates.
+
+The **Django model** (`care/emr/models/scheduling/schedule.py`) is the storage layer — including the opaque `Availability.availability` `JSONField`. The **Pydantic resource specs** (`care/emr/resources/scheduling/...`) are the API/implementation layer: they define the enums, the real shape of that JSON field, validation rules, and the read/write schemas. Everything below the `## Models` section reflects the specs.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `SchedulableResource` | A bookable resource (a user, location, or healthcare service) within a facility |
+| `Schedule` | A named, date-bounded block of availability attached to a resource |
+| `Availability` | Slot configuration for a schedule (slot size, tokens, weekly recurrence) |
+| `AvailabilityException` | A date/time range that blocks a resource (leave, holidays) |
+
+All models extend [`EMRBaseModel`](../foundation/base-model.mdx) — the shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON.
+
+## `Schedule` fields
+
+| Field | Type | Req. | Notes |
+| --- | --- | --- | --- |
+| `resource` | `FK → SchedulableResource` (CASCADE) | server | Set server-side from `resource_type` + `resource_id` (get-or-create). Not accepted directly in the body |
+| `name` | `CharField(255)` | yes | Display name (e.g. "Morning OPD") |
+| `valid_from` | `DateTimeField` | yes | Start of the effective window. Must be ≥ now; must be ≤ `valid_to` |
+| `valid_to` | `DateTimeField` | yes | End of the effective window. Must be ≥ now |
+| `revisit_allowed_days` | `IntegerField` | no | Nullable. Window within which a follow-up uses the revisit charge. Set only via `set_charge_item_definition` action |
+| `is_public` | `BooleanField` | yes | Whether the schedule is exposed for public booking. Model default `False` |
+
+### Billing links
+
+Both charge links are managed only through the `set_charge_item_definition` action (see [Resource specs](#resource-specs-api-schema)), never via the create/update body.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `charge_item_definition` | `FK → ChargeItemDefinition` (PROTECT) | Nullable; charge applied to bookings against this schedule. `related_name="schedule_charge_item_definition"` |
+| `revisit_charge_item_definition` | `FK → ChargeItemDefinition` (PROTECT) | Nullable; charge applied when a booking falls within `revisit_allowed_days`. `related_name="schedule_revisit_charge_item_definition"` |
+
+## Enums
+
+### `SchedulableResourceTypeOptions`
+
+The discriminator for what kind of entity a schedule/exception is attached to. Sent as `resource_type` on write; the matching `resource_id` is the `external_id` of that entity.
+
+| Value | Resolves to |
+| --- | --- |
+| `practitioner` | A [`User`](../access/user.mdx) who is a member of the facility (serialized via `UserSpec`) |
+| `location` | A `FacilityLocation` in the facility (serialized via `FacilityLocationListSpec`) |
+| `healthcare_service` | A [`HealthcareService`](../facility/healthcare-service.mdx) in the facility (serialized via `HealthcareServiceReadSpec`) |
+
+Note: the `SchedulableResource.resource_type` model column defaults to the string `"practitioner"`.
+
+### `SlotTypeOptions`
+
+Type of an individual `Availability` block (`Availability.slot_type` on the model, `slot_type` in the spec).
+
+| Value | Meaning |
+| --- | --- |
+| `open` | Open block; `slot_size_in_minutes` / `tokens_per_slot` are cleared to `null` on save |
+| `appointment` | Time-precise appointment block; requires both `slot_size_in_minutes` and `tokens_per_slot` |
+| `closed` | Closed block; `slot_size_in_minutes` / `tokens_per_slot` are cleared to `null` on save |
+
+## Related models
+
+### `SchedulableResource`
+
+A bookable entity within a facility. Exactly one of `user`, `location`, or `healthcare_service` identifies the underlying resource; `resource_type` discriminates which. It is **never created directly via the API** — `Schedule` and `AvailabilityException` writes call `get_or_create_resource(resource_type, resource_id, facility)` server-side, validating that the target belongs to the facility.
+
+```text
+facility → FK Facility (CASCADE)
+resource_type → CharField(255), default "practitioner"
+user → FK User (CASCADE, nullable)
+location → FK FacilityLocation (CASCADE, nullable)
+healthcare_service → FK HealthcareService (CASCADE, nullable)
+```
+
+Uniqueness is enforced per facility + `resource_type` against each target via three `UniqueConstraint`s:
+
+| Constraint | Fields |
+| --- | --- |
+| `unique_facility_resource_user` | `facility`, `resource_type`, `user` |
+| `unique_facility_resource_location` | `facility`, `resource_type`, `location` |
+| `unique_facility_resource_healthcare_service` | `facility`, `resource_type`, `healthcare_service` |
+
+On read, a `SchedulableResource` is serialized by `serialize_resource(obj)` (`resource/spec.py`), which dispatches on `resource_type` to `UserSpec` / `HealthcareServiceReadSpec` / `FacilityLocationListSpec`.
+
+### `Availability`
+
+Defines how a schedule's time is divided into bookable slots. A schedule can hold several availabilities (e.g. a morning block and an afternoon block).
+
+| Field | Type | Req. | Spec detail |
+| --- | --- | --- | --- |
+| `schedule` | `FK Schedule` (CASCADE) | server | Excluded from the availability spec; set from the URL / parent schedule |
+| `name` | `CharField(255)` | yes | Block name |
+| `slot_type` | `CharField` | yes | One of [`SlotTypeOptions`](#slottypeoptions) |
+| `slot_size_in_minutes` | `IntegerField` (nullable) | conditional | `int \| None`, `ge=1`. Required when `slot_type == appointment`; forced to `null` otherwise |
+| `tokens_per_slot` | `IntegerField` (nullable) | conditional | `int \| None`, `ge=1`. Capacity per slot. Required when `slot_type == appointment`; forced to `null` otherwise |
+| `create_tokens` | `BooleanField` | no | Default `False`. When `True`, a token is issued for each booking (queue/token workflows) |
+| `reason` | `TextField` (nullable) | no | Spec default `""` |
+| `availability` | `JSONField`, default `dict` | yes | **Not a dict** — the spec models it as a `list[AvailabilityDateTimeSpec]` (weekly recurrence). See below |
+
+#### `availability` JSON shape — `AvailabilityDateTimeSpec`
+
+Each entry is one weekly recurrence window. The `JSONField` stores a **list** of these objects.
+
+```text
+AvailabilityDateTimeSpec {
+ day_of_week: int # 0–6, validated le=6 (Monday=0 … Sunday=6, ISO-style)
+ start_time: time # HH:MM:SS
+ end_time: time # HH:MM:SS
+}
+```
+
+Validation on the list (`AvailabilityForScheduleSpec.validate_availability` + `validate_for_slot_type`):
+- For every entry, `start_time < end_time` (strict).
+- No two entries on the **same** `day_of_week` may overlap (`has_overlapping_availability`: ranges overlap when `a.start ≤ b.end and b.start ≤ a.end`). Overlap is checked across all availabilities of the same schedule on create.
+- When `slot_type == appointment`: `slot_size_in_minutes` and `tokens_per_slot` are mandatory; each window's duration must be an exact multiple of `slot_size_in_minutes`; and the resulting slot count must not exceed `settings.MAX_SLOTS_PER_AVAILABILITY` (default **30**).
+
+### `AvailabilityException`
+
+Blocks a resource for a date/time range regardless of its schedules — used for leave, holidays, or one-off closures.
+
+| Field | Type | Req. | Spec detail |
+| --- | --- | --- | --- |
+| `resource` | `FK SchedulableResource` (CASCADE) | server | Set from `resource_type` + `resource_id` (get-or-create); excluded from the spec body |
+| `name` | `CharField(255)` | yes | Exception name |
+| `reason` | `TextField` (nullable) | no | `str \| None` |
+| `valid_from` | `DateField` | yes | `date`. Must be ≥ today; must be ≤ `valid_to` |
+| `valid_to` | `DateField` | yes | `date`. Must be ≥ today |
+| `start_time` | `TimeField` | yes | `time` |
+| `end_time` | `TimeField` | yes | `time` |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` ([`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)), which provides `serialize()` (DB → pydantic) and `de_serialize()` (pydantic → DB) plus the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__exclude__` lists fields skipped during (de)serialization.
+
+| Spec | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `ScheduleBaseSpec` | shared | `id`, `is_public`. `__exclude__ = ["resource", "facility"]` |
+| `ScheduleCreateSpec` | write · create | `facility`, `name`, `valid_from`, `valid_to`, `availabilities: list[AvailabilityForScheduleSpec]`, `resource_type`, `resource_id`, `is_public`. Validates dates ≥ now, `valid_from ≤ valid_to`, and cross-availability non-overlap. `perform_extra_deserialization` stashes `facility`, `_resource_id`, `_resource_type`, and `availabilities` on the instance for the viewset |
+| `ScheduleUpdateSpec` | write · update | `name`, `valid_from`, `valid_to`, `is_public`. `perform_extra_deserialization` blocks narrowing validity that would drop allocated slots — compares `Sum(TokenSlot.allocated)` in the old vs new range and raises if they differ |
+| `ScheduleReadSpec` | read · list + detail | All of the above plus `availabilities` (re-serialized from `Availability` rows), `resource_type`, `charge_item_definition` / `revisit_charge_item_definition` (full `ChargeItemDefinitionReadSpec` or `null`), `revisit_allowed_days`, `created_by`, `updated_by` |
+| `AvailabilityBaseSpec` | shared | `id`. `__exclude__ = ["schedule"]` |
+| `AvailabilityForScheduleSpec` | write/read (nested in schedule) | `name`, `slot_type`, `slot_size_in_minutes`, `tokens_per_slot`, `create_tokens`, `reason`, `availability: list[AvailabilityDateTimeSpec]`. Carries the availability + slot-type validators |
+| `AvailabilityCreateSpec` | write · create (standalone) | Adds `schedule: UUID4`; on create, re-checks overlap against all existing availabilities of that schedule |
+| `AvailabilityDateTimeSpec` | nested | `day_of_week` (le=6), `start_time`, `end_time` — the shape of the `availability` JSON field |
+| `AvailabilityExceptionBaseSpec` | shared | `id`, `reason`, `valid_from`, `valid_to`, `start_time`, `end_time`. `__exclude__ = ["resource", "facility"]` |
+| `AvailabilityExceptionWriteSpec` | write · create/upsert | Adds `facility`, `resource_type`, `resource_id`. Validates dates ≥ today and `valid_from ≤ valid_to`; stashes `_resource_type`/`_resource_id` |
+| `AvailabilityExceptionReadSpec` | read · list + detail | Base fields; `perform_extra_serialization` maps `id` → `external_id` |
+| `ChargeItemDefinitionSetSpec` | write (action body) | `charge_item_definition: str \| None`, `re_visit_allowed_days: int`, `re_visit_charge_item_definition: str \| None` — body for `POST .../set_charge_item_definition` (charge fields resolved by `slug` within the facility) |
+
+### Server-maintained behaviour (viewset hooks)
+
+- **Resource resolution.** On schedule/exception create, `get_or_create_resource(resource_type, resource_id, facility)` validates the target is in the facility (practitioner must be a `FacilityOrganizationUser`; location/healthcare-service must belong to the facility) and gets-or-creates the `SchedulableResource`. `facility` is injected from the URL via `clean_create_data`.
+- **Nested availability create.** `ScheduleViewSet.perform_create` (atomic) sets `resource`, saves the schedule, then de-serializes and saves each `availability` linked to it.
+- **Charge items.** `revisit_allowed_days`, `charge_item_definition`, and `revisit_charge_item_definition` are set **only** through the `set_charge_item_definition` detail action — never the create/update body.
+- **Locking + slot guards.** Update/destroy take a `Lock("booking:resource:")`. Deleting a schedule or availability is rejected if future allocated `TokenSlot`s exist; otherwise availabilities and slots are soft-deleted (`deleted=True`).
+- **Exception slot clearing.** Creating an `AvailabilityException` soft-deletes overlapping `TokenSlot`s, but rejects the request if any of them are already allocated ("There are bookings during this exception").
+- **List requires resource.** `Schedule` and `AvailabilityException` list endpoints require `resource_type` and `resource_id` query params and scope results to that `SchedulableResource`.
+
+## Methods & save behaviour
+
+- `Availability.slot_type` drives whether `slot_size_in_minutes` / `tokens_per_slot` survive: only `appointment` keeps them; `open` and `closed` null them out (`validate_for_slot_type`).
+- Schedule validity windows are guarded both ways: they cannot start in the past, and they cannot be edited to a range that excludes already-allocated slots.
+- Slot generation is derived from `Availability` at read time, not stored as rows on the schedule.
+
+## API integration notes
+
+- Schedules, availabilities, and exceptions are exposed through Care's REST API; slot generation is derived from `Availability` rather than stored as rows.
+- A `SchedulableResource` is the join point for booking — clients send `resource_type` + `resource_id`; resolve the underlying `user`, `location`, or `healthcare_service` via `resource_type` rather than assuming one is set.
+- `ChargeItemDefinition` links are `PROTECT` — a definition referenced by a schedule cannot be deleted while the schedule exists.
+- `availability` on `Availability` is a JSON **list** of weekly recurrence windows (`AvailabilityDateTimeSpec`); treat it as the canonical source for which days/times the block covers.
+- `create_tokens` and `tokens_per_slot` drive token/queue behaviour for high-volume, precision-optional workflows; `appointment` slot types are for time-precise booking and must divide each window evenly (≤ `MAX_SLOTS_PER_AVAILABILITY`, default 30).
+
+## Related
+
+- Reference: [Booking](../scheduling/booking.mdx)
+- Reference: [Token](../scheduling/token.mdx)
+- Reference: [Charge item definition](../billing/charge-item-definition.mdx)
+- Reference: [Healthcare service](../facility/healthcare-service.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [User](../access/user.mdx)
+- Source (model): [schedule.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/schedule.py)
+- Source (specs): [schedule/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/schedule/spec.py), [availability_exception/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/availability_exception/spec.py), [resource/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/resource/spec.py)
diff --git a/versioned_docs/version-3.0/references/scheduling/token.mdx b/versioned_docs/version-3.0/references/scheduling/token.mdx
new file mode 100644
index 0000000..c688a1b
--- /dev/null
+++ b/versioned_docs/version-3.0/references/scheduling/token.mdx
@@ -0,0 +1,213 @@
+---
+sidebar_position: 3
+---
+
+# Token
+
+Technical reference for the `Token` module in Care EMR — the token-queue subsystem of scheduling. It models walk-in / queue-based flow (a patient gets a numbered token in a queue, optionally routed to a sub-queue / room) on top of a [schedulable resource](./schedule.mdx) (practitioner, location, or healthcare service).
+
+The Django models are the **storage layer**; several constraints (status enums, the resource binding, queue progression) live only in the Pydantic **resource specs** and the API viewsets. This page documents both.
+
+**Source:**
+- Model: [`care/emr/models/scheduling/token.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/token.py)
+- Specs: [`token/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token/spec.py) · [`token_category/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token_category/spec.py) · [`token_queue/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token_queue/spec.py) · [`token_sub_queue/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token_sub_queue/spec.py)
+- Viewsets: [`api/viewsets/scheduling/token.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/token.py) · [`token_queue.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/token_queue.py) · [`token_sub_queue.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/token_sub_queue.py) · [`token_category.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/token_category.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `TokenQueue` | A queue of tokens for one schedulable resource on one date |
+| `TokenSubQueue` | A sub-queue splitting a resource's tokens (e.g. multiple rooms) |
+| `TokenCategory` | Reusable token categories per facility / resource type |
+| `Token` | A numbered token issued to a patient within a queue |
+
+All models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_by`/`updated_by`/`created_date`/`modified_date`, and soft-delete via `deleted`).
+
+The `resource` FK on `TokenQueue` / `TokenSubQueue` points at `emr.SchedulableResource` — a polymorphic binding to a practitioner (`user`), `location`, or `healthcare_service`, distinguished by `resource_type`. See [Schedule](./schedule.mdx).
+
+## `Token` fields
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → facility.Facility` (CASCADE) | yes (server) | Set server-side from the queue's facility |
+| `patient` | `FK → emr.Patient` (CASCADE) | optional | Nullable; the patient the token belongs to. See [Patient](../../concepts/clinical/patient.mdx) |
+| `queue` | `FK → TokenQueue` (CASCADE) | yes (server) | Set server-side from the URL queue |
+| `category` | `FK → TokenCategory` (CASCADE) | yes | Resolved from the `category` UUID on create. Must be in the same facility as the queue |
+| `sub_queue` | `FK → TokenSubQueue` (CASCADE) | optional | Nullable. Must match the queue's facility **and** resource |
+| `number` | `IntegerField` | yes (server) | Auto-assigned: `count(tokens in queue with same category) + 1` |
+| `status` | `CharField(255)` | yes (server) | `TokenStatusOptions` enum (see below). Defaults to `CREATED` on create |
+| `is_next` | `BooleanField` (default `False`) | server | Queue-progression flag; not set by clients |
+| `note` | `TextField` | optional | Nullable free-text note |
+| `booking` | `FK → emr.TokenBooking` (CASCADE) | optional | Nullable; links the token to a [booking](./booking.mdx). `related_name="booking_token"` |
+
+A patient may hold multiple tokens in a given queue. `number` is **per category within a queue**, not globally unique across the queue.
+
+### `TokenStatusOptions` values
+
+Defined in `token/spec.py`. Bound on `Token.status` via the read/update specs.
+
+| Value | Meaning |
+| --- | --- |
+| `UNFULFILLED` | Token issued but not served |
+| `CREATED` | Default state on creation |
+| `IN_PROGRESS` | Currently being served (set when made the sub-queue's current token) |
+| `FULFILLED` | Service completed |
+| `CANCELLED` | Cancelled |
+| `ENTERED_IN_ERROR` | Set automatically on delete (soft-delete) |
+
+## Related models
+
+### `TokenQueue`
+
+A queue of tokens for one schedulable resource on one date.
+
+```text
+facility → FK Facility (CASCADE)
+resource → FK SchedulableResource (CASCADE)
+name → CharField(255)
+is_primary → BooleanField (default True)
+date → DateField
+system_generated → BooleanField (default False)
+```
+
+- `is_primary` is resolved server-side on create: the first queue for a `(resource, date)` pair becomes primary, later ones do not. Re-pointed via the `set_primary` action.
+- `system_generated` queues are created implicitly by `generate_token` (name `"System Generated"`) when no primary queue exists for the date.
+
+### `TokenSubQueue`
+
+Splits a resource's tokens so the same queue can route tokens to several physical points (e.g. multiple vaccination rooms, each drawing from its own sub-queue).
+
+```text
+facility → FK Facility (CASCADE)
+resource → FK SchedulableResource (CASCADE)
+name → CharField(255)
+status → CharField(255) # TokenSubQueueStatusOptions
+current_token → FK Token (CASCADE, nullable)
+```
+
+`status` is enum-bound in the spec to `TokenSubQueueStatusOptions`:
+
+| Value |
+| --- |
+| `active` |
+| `inactive` |
+
+`current_token` is the token currently being served at that point; maintained server-side by the queue-progression actions (`set_next`, `set_next_token_to_subqueue`).
+
+### `TokenCategory`
+
+Reusable token categories scoped to a facility and resource type (e.g. "General", "Priority").
+
+```text
+facility → FK Facility (CASCADE)
+resource_type → CharField(255) # SchedulableResourceTypeOptions
+name → CharField(255)
+shorthand → CharField(255) # spec limits to max_length 5
+metadata → JSONField (default dict)
+default → BooleanField (default False)
+```
+
+- `resource_type` is enum-bound in the spec to `SchedulableResourceTypeOptions` (`practitioner`, `location`, `healthcare_service`).
+- `shorthand` is constrained to `max_length=5` by the spec (the model column allows 255).
+- `metadata` is an open JSON bag for deployment-specific category config; exposed as `dict | None`.
+- `default` is set exclusively via the `set_default` action (which clears `default` on all other categories of the same facility + resource type); it is not a writable field on the create spec.
+
+### `SchedulableResourceTypeOptions` values
+
+Defined in `scheduling/schedule/spec.py`; used by `TokenCategory.resource_type` and all `*WithQueue` / create specs that bind to a resource.
+
+| Value |
+| --- |
+| `practitioner` |
+| `location` |
+| `healthcare_service` |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`): `serialize` builds the read payload from the model (plus `perform_extra_serialization`), `de_serialize` writes a model from the payload (plus `perform_extra_deserialization`). `id` is always the model's `external_id` (UUID).
+
+### Token
+
+| Spec | Role | Exposed fields |
+| --- | --- | --- |
+| `TokenBaseSpec` | shared | `id` |
+| `TokenGenerateSpec` | write · create (nested under a queue) | `patient?` (UUID), `category` (UUID, required), `note?`, `sub_queue?` (UUID) |
+| `TokenGenerateWithQueueSpec` | write · create (queue resolved/created) | adds `resource_type` (`SchedulableResourceTypeOptions`), `resource_id` (UUID), `date` |
+| `TokenUpdateSpec` | write · update | `status?` (`TokenStatusOptions`), `note?`, `sub_queue` (UUID, nullable) |
+| `TokenMinimalSpec` | read · embedded | `note`, `number`, `status`, `category` (serialized via `TokenCategoryReadSpec`) |
+| `TokenReadSpec` | read · list | `category`, `sub_queue`, `note`, `patient`, `number`, `status`, `queue` (`TokenQueueReadSpec`) |
+| `TokenRetrieveSpec` | read · detail | extends `TokenReadSpec` + `created_by`/`updated_by` (`UserSpec`), `booking`, `resource_type`, `resource`, `encounter?` |
+
+Validation & server behaviour (from `perform_extra_deserialization` and the viewset):
+
+- `TokenGenerateSpec.perform_extra_deserialization`: resolves `patient`, `category`, and `sub_queue` UUIDs to model instances (404 if missing).
+- `TokenUpdateSpec.perform_extra_deserialization`: resolves `sub_queue` UUID, or sets `sub_queue = None` when omitted (so update can clear it).
+- On **create** (`perform_create`): `queue` and `facility` are set from the URL queue; category-vs-queue and sub-queue-vs-queue facility/resource matching is enforced; `number` is computed under a per-queue lock; `status` is forced to `CREATED`.
+- On **update**: changing the sub-queue clears the old sub-queue's `current_token` if it pointed at this token; a sub-queue that already has a current token cannot be reassigned.
+- On **delete** (`perform_destroy`): soft-delete — sets `status = ENTERED_IN_ERROR`, `deleted = True`.
+- `TokenRetrieveSpec` embeds the booking (via `TokenBookingMinimumReadSpec`), the booking's `associated_encounter` ([Encounter](../clinical/encounter.mdx)) when present, and the resolved `resource` (practitioner `UserSpec` / `HealthcareServiceReadSpec` / `FacilityLocationListSpec`) keyed by `resource_type`.
+
+### TokenQueue
+
+| Spec | Role | Exposed fields |
+| --- | --- | --- |
+| `TokenQueueBaseSpec` | shared | `id`, `name` |
+| `TokenQueueCreateSpec` | write · create | adds `resource_type` (`SchedulableResourceTypeOptions`), `resource_id` (UUID), `date` |
+| `TokenQueueUpdateSpec` | write · update | `id`, `name` only |
+| `TokenQueueReadSpec` | read · list | adds `date`, `is_primary`, `system_generated` |
+| `TokenQueueRetrieveSpec` | read · detail | adds `created_by`, `updated_by` |
+
+`TokenQueueCreateSpec.perform_extra_deserialization` stashes `resource_type`/`resource_id` onto the instance; the viewset's `perform_create` resolves/creates the `SchedulableResource`, sets `facility`, and computes `is_primary`.
+
+### TokenSubQueue
+
+| Spec | Role | Exposed fields |
+| --- | --- | --- |
+| `TokenSubQueueBaseSpec` | shared / write · update | `id`, `name`, `status` (`TokenSubQueueStatusOptions`) |
+| `TokenSubQueueCreateSpec` | write · create | adds `resource_type` (`SchedulableResourceTypeOptions`), `resource_id` (UUID) |
+| `TokenSubQueueReadSpec` | read | adds `current_token` (`TokenMinimalSpec`, nullable) |
+
+`TokenSubQueueCreateSpec.perform_extra_deserialization` stashes `resource_type`/`resource_id`; the viewset resolves/creates the resource on create and validates it. `current_token` is serialized only when set.
+
+### TokenCategory
+
+| Spec | Role | Exposed fields |
+| --- | --- | --- |
+| `TokenCategoryBaseSpec` | shared | `id`, `name`, `resource_type` (`SchedulableResourceTypeOptions`), `shorthand` (max 5), `metadata?` (dict) |
+| `TokenCategoryCreateSpec` | write · create | same as base |
+| `TokenCategoryReadSpec` | read · list | adds `default` |
+| `TokenCategoryRetrieveSpec` | read · detail | adds `created_by`, `updated_by` |
+
+`facility` is set from the URL on create; `default` is read-only here and toggled via the `set_default` action.
+
+## Methods & save behaviour
+
+- **Number assignment** — `Token.number` is `count(tokens with same queue + category) + 1`, computed inside a `Lock("booking:token:{queue.id}")` + atomic transaction to avoid duplicates.
+- **Status lifecycle** — create forces `CREATED`; `set_next` / `set_next_token_to_subqueue` set `IN_PROGRESS`; delete sets `ENTERED_IN_ERROR`. There is no separate status-history table; status is a single field.
+- **Queue progression** — `current_token` (on `TokenSubQueue`) and `is_next` are maintained by the platform's custom actions, not by direct client writes:
+ - `Token` action `set_next` (`POST .../{id}/set_next`, body `{ sub_queue }`): points the sub-queue's `current_token` at this token and marks it `IN_PROGRESS`.
+ - `TokenQueue` action `set_next_token_to_subqueue` (`POST .../{queue}/set_next_token_to_subqueue`, body `{ sub_queue, category? }`): picks the oldest `CREATED` token (optionally filtered by category), assigns it to the sub-queue as `current_token`, sets `IN_PROGRESS`.
+ - `TokenQueue` action `set_primary`: makes one queue primary for its `(resource, date)`, clearing the flag on siblings.
+ - `TokenCategory` action `set_default`: makes one category default per `(facility, resource_type)`.
+- **Token generation shortcut** — `TokenQueue` action `generate_token` (`POST .../generate_token`, body = `TokenGenerateWithQueueSpec`) finds or creates the primary (`system_generated`) queue for `(resource, date)` and issues a token in one call.
+- **Queue summary** — `TokenQueue` action `summary` returns per-category, per-status token counts for a queue.
+
+## API integration notes
+
+- Tokens are created **under a queue** (`TokenGenerateSpec` — `queue`/`facility`/`number`/`status` are server-set), or via the queue's `generate_token` action (`TokenGenerateWithQueueSpec`, which also resolves/creates the queue).
+- Send `category` / `patient` / `sub_queue` as **UUIDs** (`external_id`); the server resolves them to FKs. Cross-facility and cross-resource mismatches are rejected with `400`.
+- `status` only accepts `TokenStatusOptions` values via the update spec; `TokenSubQueue.status` only accepts `TokenSubQueueStatusOptions`. The underlying columns are plain `CharField`s, so the constraint is enforced by the spec layer, not the database.
+- Do **not** set `number`, `is_next`, `current_token`, or `is_primary` directly — they are queue-progression state maintained by the viewset actions above.
+- `TokenCategory.metadata` is an open JSON bag for deployment-specific config; `shorthand` is capped at 5 characters by the spec.
+- `booking` links a token back to a [`TokenBooking`](./booking.mdx); on retrieve it expands to the booking and its associated [encounter](../clinical/encounter.mdx).
+
+## Related
+
+- Reference: [Schedule](./schedule.mdx)
+- Reference: [Booking](./booking.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Concept: [Patient](../../concepts/clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Source: [token.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/token.py)
+- Specs: [token](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token/spec.py) · [token_category](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token_category/spec.py) · [token_queue](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token_queue/spec.py) · [token_sub_queue](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token_sub_queue/spec.py)
diff --git a/versioned_docs/version-3.0/references/supply/_category_.json b/versioned_docs/version-3.0/references/supply/_category_.json
new file mode 100644
index 0000000..b86520b
--- /dev/null
+++ b/versioned_docs/version-3.0/references/supply/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Supply & Inventory",
+ "position": 4,
+ "key": "supply-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Supply & Inventory",
+ "description": "Products, product knowledge, stock items, and the supply requests and deliveries that move them between locations."
+ }
+}
diff --git a/versioned_docs/version-3.0/references/supply/inventory-item.mdx b/versioned_docs/version-3.0/references/supply/inventory-item.mdx
new file mode 100644
index 0000000..f8d0b7c
--- /dev/null
+++ b/versioned_docs/version-3.0/references/supply/inventory-item.mdx
@@ -0,0 +1,143 @@
+---
+sidebar_position: 3
+---
+
+# Inventory Item
+
+Technical reference for the `InventoryItem` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/inventory_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/inventory_item.py)
+- Spec: [`care/emr/resources/inventory/inventory_item/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/spec.py)
+- Helpers: [`create_inventory_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/create_inventory_item.py), [`sync_inventory_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/sync_inventory_item.py)
+
+`InventoryItem` represents the availability of a single [`Product`](../supply/product.mdx) at a single [facility location](../facility/location.mdx). It is the on-hand counterpart to the supply workflow: deliveries move product into a location and dispenses move it out, and the resulting balance is recomputed into `net_content`. Inventory rows are **created and maintained automatically by the server** — they are not directly created through a write API; the only client-facing mutation is changing `status` (e.g. marking a line `inactive` when an item is damaged or no longer dispensed).
+
+The Django model is the **storage** layer. The Pydantic **resource specs** (`care/emr/resources/inventory/inventory_item/`) define the `status` enum and the read schema, including the nested `product` / `location` objects that the model exposes only as foreign keys.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `InventoryItem` | Stock of one product at one location, with its current net quantity |
+
+`InventoryItem` extends `EMRBaseModel` (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics). See [Base model](../foundation/base-model.mdx).
+
+## `InventoryItem` fields
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `location` | `FK → FacilityLocation` | yes | — | `on_delete=PROTECT`. The facility location holding the stock (e.g. central store, ward pharmacy). |
+| `product` | `FK → Product` | yes | — | `on_delete=PROTECT`. The product being tracked. |
+| `status` | `CharField(255)` | yes | — | Lifecycle/availability status. Spec constrains values to `InventoryItemStatusOptions` (see below). |
+| `net_content` | `DecimalField` | no | `Decimal(0)` | `max_digits=20`, `decimal_places=6`. Current on-hand quantity. Server-computed (see [`sync()`](#sync_inventory_item)). May go negative if dispenses/outgoing deliveries exceed incoming. |
+
+The pair `(location, product)` is treated as unique — there is at most one inventory item per product per location (enforced in `save()`, see [Methods & save behaviour](#methods--save-behaviour)).
+
+### `InventoryItemStatusOptions` values
+
+Defined in [`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/spec.py). The spec restricts `status` to these values (the model field itself is an unconstrained `CharField`):
+
+| Value | Meaning |
+| --- | --- |
+| `active` | Stock line is live and dispensable. Default applied when the server auto-creates a row. |
+| `inactive` | No longer actively dispensed (e.g. damaged stock, or other concern). |
+| `entered_in_error` | Record created in error. |
+
+## Related models
+
+`InventoryItem` references two models from other domains, plus the transactional records that drive its quantity:
+
+```text
+location → FK FacilityLocation (PROTECT)
+product → FK Product (PROTECT)
+
+drives net_content (read in sync_inventory_item):
+ SupplyDelivery (incoming completed, outgoing in-progress/completed)
+ MedicationDispense (dispensed-out, excluding cancelled statuses)
+```
+
+Both foreign keys use `on_delete=PROTECT`, so a location or product cannot be hard-deleted while inventory rows reference it. See [Supply Delivery](../supply/supply-delivery.mdx) and [Medication Dispense](../medications/medication-dispense.mdx) for the records that adjust `net_content`.
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` / `de_serialize` and the `perform_extra_serialization` hook used to inline the nested `product` and `location` objects.
+
+| Spec class | Role | Fields exposed | Notes |
+| --- | --- | --- | --- |
+| `BaseInventoryItemSpec` | shared | `id`, `status` | `__model__ = InventoryItem`, `__exclude__ = []`. `status` typed as `InventoryItemStatusOptions`. |
+| `InventoryItemWriteSpec` | write | `id`, `status` | Inherits `BaseInventoryItemSpec` unchanged — only `status` is client-writable; `product`, `location`, and `net_content` are server-maintained. |
+| `InventoryItemReadSpec` | read · list | `id`, `status`, `net_content`, `product`, `location` | See serialization below. |
+| `InventoryItemRetrieveSpec` | read · detail | same as `InventoryItemReadSpec` | `pass` subclass — identical shape to the list spec. |
+
+### Read serialization (`InventoryItemReadSpec.perform_extra_serialization`)
+
+| Field | Serialized shape | Source |
+| --- | --- | --- |
+| `id` | `UUID4` | `obj.external_id` (the public id, not the internal pk). |
+| `net_content` | `Decimal` (`max_digits=20`, `decimal_places=0` on the read field) | model `net_content`. |
+| `product` | nested `dict` | `ProductReadSpec.serialize(obj.product).to_json()` — includes nested `product_knowledge` and optional `charge_item_definition`. See [Product](../supply/product.mdx). |
+| `location` | nested `dict` | `FacilityLocationListSpec.serialize(obj.location).to_json()` — includes `parent`, `mode`, `has_children`, `system_availability_status`, and optional `current_encounter`. See [Location](../facility/location.mdx). |
+
+`status` (typed `InventoryItemStatusOptions`) is carried through from `BaseInventoryItemSpec`. There is no separate `CreateSpec` — inventory rows are not created via a client write path.
+
+## Methods & save behaviour
+
+### `save()`
+
+On creation only (when `self.id` is unset), `save()` enforces uniqueness of the `(location, product)` pair:
+
+```text
+if creating and InventoryItem with same (location, product) exists:
+ raise ValueError("Inventory item already exists")
+```
+
+- The check runs **only for new rows**; updates to an existing item skip it.
+- Violations raise a plain `ValueError`, not a database `IntegrityError` — callers creating items through the ORM should handle it.
+
+### `create_inventory_item(product, location)`
+
+Helper in [`create_inventory_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/create_inventory_item.py). Idempotent get-or-create:
+
+- Returns the existing `(product, location)` row if one exists.
+- Otherwise creates one with `status=active`, `net_content=0`, and saves it.
+
+### `sync_inventory_item()`
+
+Helper in [`sync_inventory_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/sync_inventory_item.py) — the source of truth for `net_content`. It runs under `InventoryLock(product, location)` and recomputes the balance from transactional records, then writes it back:
+
+```text
+net_content =
+ Σ incoming completed deliveries (SupplyDelivery.status == completed,
+ order.destination == location,
+ supplied_inventory_item.product == product)
+ - Σ outgoing in-progress + completed deliveries
+ (SupplyDelivery.order.origin is not null,
+ supplied_inventory_item == this item,
+ status in {in_progress, completed})
+ - Σ dispenses (MedicationDispense.item == this item,
+ excluding cancelled statuses)
+```
+
+- If no row exists for `(product, location)`, it is auto-created (`status=active`, `net_content=0`) before computing.
+- `net_content` is the **aggregate sum** of `supplied_item_quantity` / dispense `quantity`; it can be negative.
+- Called whenever a delivery is completed or a dispense changes, keeping availability current.
+
+## API integration notes
+
+- Inventory items are **server-maintained**: created via `create_inventory_item` / `sync_inventory_item`, never through a direct client create. The only meaningful client write is changing `status` (e.g. to `inactive`).
+- One inventory row exists per `(location, product)` pair — `net_content` is adjusted on the existing row rather than by inserting duplicates.
+- `net_content` is recomputed by `sync_inventory_item` under an `InventoryLock`; do not patch it directly — it will be overwritten on the next sync.
+- Read responses inline the full `product` (with `product_knowledge`) and `location` objects, not just their ids.
+- `location` and `product` are `PROTECT` foreign keys — referenced products and locations cannot be hard-deleted while stock rows exist.
+
+## Related
+
+- Reference: [Product](../supply/product.mdx)
+- Reference: [Supply Request](../supply/supply-request.mdx)
+- Reference: [Supply Delivery](../supply/supply-delivery.mdx)
+- Reference: [Medication Dispense](../medications/medication-dispense.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [inventory_item.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/inventory_item.py)
+- Source: [inventory_item spec on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/spec.py)
diff --git a/versioned_docs/version-3.0/references/supply/product-knowledge.mdx b/versioned_docs/version-3.0/references/supply/product-knowledge.mdx
new file mode 100644
index 0000000..d659490
--- /dev/null
+++ b/versioned_docs/version-3.0/references/supply/product-knowledge.mdx
@@ -0,0 +1,250 @@
+---
+sidebar_position: 2
+---
+
+# Product Knowledge
+
+Technical reference for the `ProductKnowledge` module in Care EMR.
+
+`ProductKnowledge` stores the foundational, reusable information about a product that need not be duplicated across individual stock items — for example its coding, base presentation unit, ingredient/nutrient definition, drug characteristics, and storage guidelines. Every concrete `Product` in Care references a `ProductKnowledge` entry, which may be defined 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 `JSONField`s whose real structure is defined by the Pydantic **resource specs** (the API/implementation layer). The enum values, nested JSON shapes, validation, and read/write schemas below all come from those specs.
+
+**Source:**
+- Model: [`care/emr/models/product_knowledge.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/product_knowledge.py)
+- Spec: [`care/emr/resources/inventory/product_knowledge/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product_knowledge/spec.py)
+- Value sets: [`care/emr/resources/inventory/product_knowledge/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product_knowledge/valueset.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ProductKnowledge` | Foundational, reusable definition of a product (medication, nutritional product, or consumable) |
+
+`ProductKnowledge` extends [`SlugBaseModel`](../foundation/base-model.mdx), which itself extends `EMRBaseModel` (shared Care EMR base with `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 slug helpers (`calculate_slug`, `parse_slug`), so a record can be addressed by an instance slug (`i-`) or a facility slug (`f--`).
+
+## `ProductKnowledge` fields
+
+### Identity & scope
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` | `PROTECT`, nullable. Null = instance-level definition; set = facility-scoped. Excluded from the spec base (`__exclude__ = ["facility"]`); set on write via `facility` UUID, surfaced on read as `is_instance_level` |
+| `slug` | `CharField(255)` | Slug value; combined with `facility` to form the addressable slug. Set server-side from `slug_value` on write |
+| `alternate_identifier` | `CharField(255)` | Nullable; secondary external identifier |
+| `status` | `CharField(255)` | Lifecycle status. Spec-constrained to `ProductKnowledgeStatusOptions` (required) |
+| `product_type` | `CharField(255)` | Product category. Spec-constrained to `ProductTypeOptions` (required) |
+| `category` | `FK → emr.ResourceCategory` | `CASCADE`, nullable; classifies the product into a [resource category](../platform/resource-category.mdx). On write supplied as a category **slug** string and resolved via `ResourceCategory.objects.get(slug=...)`; on read serialized as a nested object |
+
+### Naming
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(255)` | Primary display name (required in spec) |
+| `names` | `JSONField` (list) | Nullable; list of `ProductName { name_type: ProductNameTypes, name: str }`. `name_type` is bound to the `ProductNameTypes` enum (not a FHIR value set) |
+| `names_cache` | `CharField(2048)` | Nullable; denormalized space-joined string of `name` + all `names` entries, rebuilt on every `save()` for search. Platform-maintained — not in any spec, do not set from clients |
+
+### Coding & definition (JSON fields)
+
+The model declares these as bare `JSONField`s. Their real shape is enforced by the spec:
+
+| Field | Model type | Spec type / shape | Notes |
+| --- | --- | --- | --- |
+| `code` | `JSONField` (dict) | `Coding \| None` | Nullable; product code as a [`Coding`](#coding) (not value-set bound at the spec layer) |
+| `base_unit` | `JSONField` (dict) | `ValueSetBoundCoding[CARE_UCUM_UNITS]` | **Required** in spec. A `Coding` bound to the UCUM units value set (`system-ucum-units`) |
+| `names` | `JSONField` (list) | `list[ProductName] \| None` | See [ProductName](#productname) |
+| `storage_guidelines` | `JSONField` (list) | `list[StorageGuideline] \| None` | See [StorageGuideline](#storageguideline) |
+| `definitional` | `JSONField` (dict) | `ProductDefinitionSpec \| None` | Type-specific definition payload. See [ProductDefinitionSpec](#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 the structured shapes of the JSON fields, defined as plain Pydantic `BaseModel`s in `spec.py`.
+
+### `ProductName`
+
+`names[]` — alternate names for the product.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `name_type` | `ProductNameTypes` | yes | Enum, see above |
+| `name` | `str` | yes | |
+
+### `StorageGuideline`
+
+`storage_guidelines[]` — storage notes with a stability duration.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `note` | `str` | yes | |
+| `stability_duration` | `DurationSpec { value: int, unit: Coding }` | yes | `value` is an integer count; `unit` is a free `Coding` |
+
+### `ProductDefinitionSpec`
+
+`definitional` — type-specific definition payload.
+
+| Field | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `dosage_form` | `ValueSetBoundCoding[MEDICATION_FORM_CODES] \| None` | required (nullable) | `Coding` bound to medication form codes (`system-medication-form-codes`, SNOMED is-a `736542009`) |
+| `intended_routes` | `list[Coding]` | `[]` | Free `Coding`s (not value-set bound) |
+| `ingredients` | `list[ProductIngredient]` | `[]` | See [ProductIngredient](#productingredient) |
+| `nutrients` | `list[ProductNutrient]` | `[]` | See [ProductNutrient](#productnutrient) |
+| `drug_characteristic` | `list[DrugCharacteristic]` | `[]` | See [DrugCharacteristic](#drugcharacteristic) |
+
+### `ProductIngredient`
+
+`definitional.ingredients[]`.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `is_active` | `bool` | yes | Active vs. inactive ingredient |
+| `substance` | `ValueSetBoundCoding[CARE_SUBSTANCE_VALUSET]` | yes | `Coding` bound to the substance value set (`system-substance`, SNOMED is-a `105590001`) |
+| `strength` | `ProductStrength` | yes | See [ProductStrength](#productstrength) |
+
+### `ProductNutrient`
+
+`definitional.nutrients[]`.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `item` | `ValueSetBoundCoding[CARE_NUTRIENTS_VALUESET]` | yes | `Coding` bound to the nutrients value set (`system-nutrients`, SNOMED is-a `226355009`) |
+| `amount` | `ProductStrength` | yes | See [ProductStrength](#productstrength) |
+
+### `ProductStrength`
+
+Shared by `ProductIngredient.strength` and `ProductNutrient.amount`.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `ratio` | `Ratio { numerator: Quantity, denominator: Quantity }` | yes | |
+| `quantity` | `Quantity` | yes | See [Quantity](#quantity) |
+
+### `DrugCharacteristic`
+
+`definitional.drug_characteristic[]`.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `code` | `DrugCharacteristicCode` | yes | Enum, see above |
+| `value` | `str` | yes | |
+
+## Shared common types
+
+### `Coding`
+
+From `care/emr/resources/common/coding.py` (`extra="forbid"`).
+
+| Field | Type | Required |
+| --- | --- | --- |
+| `system` | `str \| None` | no |
+| `version` | `str \| None` | no |
+| `code` | `str` | yes |
+| `display` | `str \| None` | no |
+
+A `ValueSetBoundCoding[]` 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"`).
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `value` | `Decimal \| None` | max 20 digits, 6 decimal places |
+| `unit` | `Coding \| None` | human-readable unit |
+| `code` | `Coding \| None` | machine-processable unit |
+| `meta` | `dict \| None` | |
+
+`Ratio { numerator: Quantity, denominator: Quantity }` (both required).
+
+## Resource specs (API schema)
+
+All specs build 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` only copies non-FK database fields that are also declared spec fields and not in `__exclude__`.
+
+| Spec class | Role | Adds / behaviour |
+| --- | --- | --- |
+| `BaseProductKnowledgeSpec` | shared base | `__exclude__ = ["facility"]`. Fields: `id`, `alternate_identifier`, `status`, `product_type`, `code`, `base_unit` (required, UCUM-bound), `name`, `names`, `storage_guidelines`, `definitional` |
+| `ProductKnowledgeUpdateSpec` | write · update | Adds `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` |
+| `ProductKnowledgeWriteSpec` | write · create | Extends 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 |
+| `ProductKnowledgeReadSpec` | read · detail/list | Adds `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`; sets `slug_config = parse_slug(slug)` |
+
+### Validation & bound value sets
+
+- `status` ∈ `ProductKnowledgeStatusOptions`; `product_type` ∈ `ProductTypeOptions` — both required.
+- `base_unit` is **required** and bound to `CARE_UCUM_UNITS` (`system-ucum-units`, system `http://unitsofmeasure.org`).
+- `definitional.dosage_form` bound to `MEDICATION_FORM_CODES`; `ingredients[].substance` bound to `CARE_SUBSTANCE_VALUSET`; `nutrients[].item` bound to `CARE_NUTRIENTS_VALUESET`. `code` and `intended_routes` are free `Coding`s.
+- `slug_value` uses `SlugType`: 5–50 chars, URL-safe (`^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$`), must start/end alphanumeric.
+- `category` on write is a slug string; an unknown slug raises (`.get(...)` `DoesNotExist`).
+
+### Server-maintained behaviour
+
+- `facility` is never copied through the generic field loop (it is in `__exclude__`); it is set explicitly from the `facility` UUID only by `ProductKnowledgeWriteSpec` (create).
+- `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` — they are not stored columns.
+
+## Methods & save behaviour
+
+### `save()` side effects
+
+`save()` is overridden to rebuild `names_cache` before persisting:
+
+1. `names_cache` is reset to `" "`.
+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 slug logic inherited from `SlugBaseModel`).
+
+`names_cache` is therefore platform-maintained — clients should not set it directly; write to `name`/`names` instead.
+
+## API integration notes
+
+- Exposed through Care's REST API; aligns with FHIR `MedicationKnowledge` / `NutritionProduct` concepts.
+- `product_type` selects the meaning of `definitional` (`medication`, `nutritional_product`, or `consumable`).
+- `facility` controls scope: omit (instance-wide) vs. set (facility-local). Address records by slug (`i-` or `f--`); the parsed parts are returned as `slug_config`.
+- `code`, `names`, `storage_guidelines`, `definitional`, and `base_unit` are JSON fields whose structure and value-set binding live in the spec, not the model. `base_unit` is required.
+- `names_cache` is maintained by the platform on save — do not set it from clients.
+
+## Related
+
+- Reference: [Product](../supply/product.mdx)
+- Reference: [Inventory Item](../supply/inventory-item.mdx)
+- Reference: [Resource Category](../platform/resource-category.mdx)
+- Base: [Base model](../foundation/base-model.mdx)
+- Source: [model](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/product_knowledge.py) · [spec](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product_knowledge/spec.py) · [value sets](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product_knowledge/valueset.py)
diff --git a/versioned_docs/version-3.0/references/supply/product.mdx b/versioned_docs/version-3.0/references/supply/product.mdx
new file mode 100644
index 0000000..4e2e864
--- /dev/null
+++ b/versioned_docs/version-3.0/references/supply/product.mdx
@@ -0,0 +1,128 @@
+---
+sidebar_position: 1
+---
+
+# Product
+
+Technical reference for the `Product` module in Care EMR.
+
+`Product` is the STORAGE layer (Django model). The API/implementation layer lives in the Pydantic **resource specs**, which define the enums, the structured shape of the model's opaque `JSONField`s, validation rules, and the read/write schemas. Several model fields are opaque `JSONField`s whose real structure is only visible in the specs — see [Resource specs (API schema)](#resource-specs-api-schema).
+
+**Source:**
+- Model: [`care/emr/models/product.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/product.py)
+- Spec: [`care/emr/resources/inventory/product/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Product` | A concrete, batch-level instantiation of a `ProductKnowledge` definition at a facility (medication, nutritional product, or consumable) |
+
+`Product` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+A `Product` carries only the data that is unique to a particular batch — lot number, expiry, purchase price. All catalogue-level detail (name, codes, dosage form) lives on the linked [`ProductKnowledge`](../supply/product-knowledge.mdx); a `Product` is its instantiation at a facility.
+
+## `Product` fields
+
+### Relationships
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` (`PROTECT`) | Facility the product belongs to. Required at storage; not a spec field — set by the viewset from the URL context, not from the request body. |
+| `product_knowledge` | `FK → emr.ProductKnowledge` (`PROTECT`) | Catalogue definition this product instantiates. On write the spec accepts a `slug` string and resolves it; on read it is serialized to the nested [`ProductKnowledge`](../supply/product-knowledge.mdx) read object. |
+| `charge_item_definition` | `FK → emr.ChargeItemDefinition` (`PROTECT`, nullable) | Used to create charge items when the product is billed. Write accepts a `slug` string; read serializes to the nested [`ChargeItemDefinition`](../billing/charge-item-definition.mdx) read object. |
+
+### Classification & status
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(255)` | yes (spec) | Constrained by the spec to [`ProductStatusOptions`](#productstatusoptions-values): `active` / `inactive` / `entered_in_error`. |
+| `product_type` | `CharField(255)` | — | Kind of product. Present at storage but **not exposed by the `Product` resource spec** — `product_type` is carried on the linked [`ProductKnowledge`](../supply/product-knowledge.mdx) (`ProductTypeOptions`: `medication` / `nutritional_product` / `consumable`). |
+
+### Batch & pricing
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `batch` | `JSONField` (nullable) | no | `dict` / `None` | Spec shape: [`ProductBatch`](#productbatch-shape) `{ lot_number: str \| None }`. The model stores an opaque dict; the spec narrows it to a single optional `lot_number`. |
+| `expiration_date` | `DateTimeField` (nullable) | no | `None` | Batch expiry. Spec type `datetime \| None`. |
+| `standard_pack_size` | `IntegerField` (nullable) | no | `None` | Units per standard pack. Spec type `int \| None`. |
+| `purchase_price` | `DecimalField(max_digits=20, decimal_places=6)` (nullable) | no | `None` | Acquisition cost for this batch. Spec type `Decimal \| None`, constrained `max_digits=20, decimal_places=6`. |
+| `extensions` | `JSONField` | yes (spec) | `dict` | Open extension bag. On write it is validated against the registered schemas for `ExtensionResource.product` via `ExtensionValidator` (unknown keys are dropped; invalid data raises). |
+
+## Enums
+
+### `ProductStatusOptions` values
+
+Defined in [`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product/spec.py); bound to the model `status` field.
+
+| Value | Meaning |
+| --- | --- |
+| `active` | Product is in use |
+| `inactive` | Product is no longer in use but retained |
+| `entered_in_error` | Record created in error |
+
+### `ProductTypeOptions` values
+
+Not a field on the `Product` spec — defined in [`product_knowledge/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product_knowledge/spec.py) and carried on the linked [`ProductKnowledge`](../supply/product-knowledge.mdx). Listed here because the model has a `product_type` storage column.
+
+| Value |
+| --- |
+| `medication` |
+| `nutritional_product` |
+| `consumable` |
+
+## Nested JSON shapes
+
+### `ProductBatch` shape
+
+Structure of the `batch` `JSONField` (`care/emr/resources/inventory/product/spec.py`).
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `lot_number` | `str \| None` | no | `None` | Batch/lot identifier |
+
+## Resource specs (API schema)
+
+All specs derive from `EMRResource` ([`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)), which provides `serialize` (DB → pydantic, with the `perform_extra_serialization` hook) and `de_serialize` (pydantic → DB, with the `perform_extra_deserialization` hook).
+
+| Spec class | Role | Fields beyond the base | Behaviour |
+| --- | --- | --- | --- |
+| `BaseProductSpec` | shared | `id`, `status`, `batch`, `expiration_date`, `extensions`, `standard_pack_size`, `purchase_price` | `__model__ = Product`. `__exclude__ = ["product_knowledge", "charge_item_definition"]` (these FKs are handled by the hooks, not by automatic field mapping). `___extension_resource_type__ = ExtensionResource.product`. |
+| `ProductWriteSpec` | write · create | adds `product_knowledge: str` (slug), `charge_item_definition: str \| None` (slug) | Mixes in `ExtensionValidator`. `perform_extra_deserialization` resolves `product_knowledge` via `get_object_or_404(ProductKnowledge, slug=...)` and, if present, `charge_item_definition` via `get_object_or_404(ChargeItemDefinition, slug=...)`. |
+| `ProductUpdateSpec` | write · update | adds `charge_item_definition: str \| None` (slug) | Mixes in `ExtensionValidator`. `perform_extra_deserialization` resolves `charge_item_definition` via `ChargeItemDefinition.objects.get(slug=...)` when supplied. Does **not** re-bind `product_knowledge` (immutable after create). |
+| `ProductReadSpec` | read · list & detail | adds `product_knowledge: dict`, `charge_item_definition: dict \| None` | `perform_extra_serialization` sets `id = external_id`, serializes `product_knowledge` via `ProductKnowledgeReadSpec` and (when set) `charge_item_definition` via `ChargeItemDefinitionReadSpec`, both inlined as JSON. A single read spec serves both list and detail. |
+
+Notes:
+- **`product_type` and `facility` are not request/response fields** of the product spec. `facility` is set server-side from the route; `product_type` lives on `ProductKnowledge`.
+- **Slug-based references**: `product_knowledge` and `charge_item_definition` are written as slug strings and read back as fully serialized nested objects.
+- **Extension validation**: `extensions` on write passes through `ExtensionValidator.validate_extensions` against the JSON schemas registered for `ExtensionResource.product`; keys with no registered handler are silently dropped (current behaviour, marked TODO to become an error).
+- **No status history**: unlike some EMR resources, `Product` does not maintain a server-side `status_history`.
+- The base `de_serialize` dumps with `exclude_defaults=True`, so unset optional fields are not written to the model.
+
+## Methods & save behaviour
+
+- `Product` adds no model methods of its own beyond `EMRBaseModel`; persistence, `external_id`, audit fields, and soft delete are inherited.
+- Write flow: request body → `ProductWriteSpec` / `ProductUpdateSpec` → `de_serialize` → `perform_extra_deserialization` (FK slug resolution) → `obj.save()`.
+- Read flow: `Product` row → `ProductReadSpec.serialize` → `perform_extra_serialization` (inline `product_knowledge` and `charge_item_definition`) → JSON.
+- All foreign keys use `PROTECT`, so a referenced `Facility`, `ProductKnowledge`, or `ChargeItemDefinition` cannot be deleted while products reference it.
+
+## API integration notes
+
+- A `Product` is a batch-level instantiation of [`ProductKnowledge`](../supply/product-knowledge.mdx); catalogue detail (name, codes, dosage form, `product_type`) is read from the linked knowledge record, not duplicated here.
+- On write, send `product_knowledge` and `charge_item_definition` as **slugs**; on read they are returned as full nested objects.
+- `charge_item_definition` links the product to billing, so charge items can be created automatically when the product is billed.
+- `batch` is a structured object (`{ lot_number }`), not a free-form dict, despite the model storing it as a `JSONField`.
+- `extensions` is the supported place for deployment-specific key-value data without schema migrations; values are validated against schemas registered for the `product` extension resource.
+
+## Related
+
+- Reference: [Product knowledge](../supply/product-knowledge.mdx)
+- Reference: [Inventory item](../supply/inventory-item.mdx)
+- Reference: [Supply request](../supply/supply-request.mdx)
+- Reference: [Supply delivery](../supply/supply-delivery.mdx)
+- Reference: [Charge item definition](../billing/charge-item-definition.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [model `product.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/product.py)
+- Source: [spec `inventory/product/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product/spec.py)
+- Source: [base `resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
diff --git a/versioned_docs/version-3.0/references/supply/supply-delivery.mdx b/versioned_docs/version-3.0/references/supply/supply-delivery.mdx
new file mode 100644
index 0000000..cef8727
--- /dev/null
+++ b/versioned_docs/version-3.0/references/supply/supply-delivery.mdx
@@ -0,0 +1,190 @@
+---
+sidebar_position: 5
+---
+
+# Supply Delivery
+
+Technical reference for the `SupplyDelivery` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/supply_delivery.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/supply_delivery.py)
+- Resource specs: [`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_delivery/spec.py) · [`delivery_order.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_delivery/delivery_order.py)
+
+A supply delivery records the movement of a product from one location (or an external supplier) to a destination location. Together with supply dispensing, deliveries are a source of truth for facility inventory: `supply delivered − supply dispensed = current stock`. Deliveries are grouped under a `DeliveryOrder`, which is typically created from a [supply request](./supply-request.mdx).
+
+The Django model is the **storage layer**: `status`, `delivery_type`, and `supplied_item_condition` are plain `CharField`s and `extensions` is an opaque `JSONField`. The **real shape** of those fields — enum values, validation, the read/write schemas — lives in the Pydantic resource specs, documented below.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `SupplyDelivery` | A single line of delivered product (quantity, condition, source product/inventory) |
+| `DeliveryOrder` | Groups deliveries for a shipment between an origin and destination |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_by`/`updated_by`, `created_date`/`modified_date`, soft-delete via `deleted`, and `history`/`meta` JSON).
+
+## `SupplyDelivery` fields
+
+### Delivered item & quantity
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `supplied_item` | `FK → Product (CASCADE)` | Nullable. The product being delivered. Required at the API layer **only when the order has no `origin`** (external delivery). On write, scoped to `order.destination.facility` |
+| `supplied_inventory_item` | `FK → InventoryItem (CASCADE)` | Nullable. Specific inventory item (batch/lot) drawn from. Required at the API layer **when the order has an `origin`** (intra-facility move); scoped to `order.origin.facility` |
+| `supplied_item_quantity` | `DecimalField(max_digits=20, decimal_places=6)` | Nullable in DB. On write a `Decimal` with `decimal_places=0` (whole units); on read serialized as `int`. Auto-derived as `pack_quantity × pack_size` when both are given |
+| `supplied_item_pack_quantity` | `IntegerField` | Nullable; default `None`. Number of packs delivered |
+| `supplied_item_pack_size` | `IntegerField` | Nullable; default `None`. Units per pack |
+| `supplied_item_condition` | `CharField(255)` | Optional. Condition of the delivered item — enum `SupplyDeliveryConditionOptions` (see below) |
+
+### Status & classification
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `status` | `CharField(255)` | Required. Lifecycle state — enum `SupplyDeliveryStatusOptions` (see below) |
+| `delivery_type` | `CharField(255)` | Type of item delivered — enum `SupplyDeliveryTypeOptions` (`product` / `device`). Stored on the model; not exposed on the current Create/Update specs |
+
+### Links & pricing
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `supply_request` | `FK → SupplyRequest (CASCADE)` | Nullable. Request this delivery fulfils. Set by `external_id` on write |
+| `order` | `FK → DeliveryOrder (CASCADE)` | Nullable in DB, but **required on create** (referenced by `external_id`). Optional on update |
+| `total_purchase_price` | `DecimalField(max_digits=20, decimal_places=6)` | Nullable. Total purchase cost. Spec: `Decimal` `max_digits=20, decimal_places=6` |
+| `extensions` | `JSONField` | Default `{}`. Validated per registered extension handler for `ExtensionResource.supply_delivery` (see [Resource specs](#resource-specs-api-schema)) |
+
+### `SupplyDelivery` enum values
+
+These enums are `str` enums in `spec.py` — the stored value equals the member value (snake_case), not a FHIR URI.
+
+#### `SupplyDeliveryStatusOptions` (`status`)
+
+| Value |
+| --- |
+| `in_progress` |
+| `completed` |
+| `abandoned` |
+| `entered_in_error` |
+
+#### `SupplyDeliveryConditionOptions` (`supplied_item_condition`)
+
+| Value |
+| --- |
+| `normal` |
+| `damaged` |
+
+#### `SupplyDeliveryTypeOptions` (`delivery_type`)
+
+| Value |
+| --- |
+| `product` |
+| `device` |
+
+## Related models
+
+### `DeliveryOrder`
+
+Groups one or more `SupplyDelivery` rows into a single logical shipment moving between two facility locations (or from an external supplier into a facility).
+
+```text
+supplier → FK Organization (CASCADE, nullable) -- external/source supplier
+origin → FK FacilityLocation (CASCADE, nullable) -- related_name="origin_delivery_orders"
+destination → FK FacilityLocation (CASCADE) -- related_name="destination_delivery_orders"
+patient → FK Patient (PROTECT, nullable) -- patient the order is dispensed to
+patient_invoice → FK Invoice (PROTECT, nullable) -- linked billing invoice
+```
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(255)` | Required. Human-readable order name |
+| `status` | `CharField(255)` | Required. Order lifecycle — enum `SupplyDeliveryOrderStatusOptions` (see below) |
+| `note` | `TextField` | Nullable. Free-text note |
+| `tags` | `ArrayField[int]` | Default `[]`. Tag IDs; rendered via `SingleFacilityTagManager` on read |
+| `supplier` | `FK → Organization (CASCADE)` | Nullable. On write must be an Organization with `org_type = product_supplier` |
+| `origin` | `FK → FacilityLocation (CASCADE)` | Nullable. Source location; absence means stock is entering from an external `supplier` |
+| `destination` | `FK → FacilityLocation (CASCADE)` | **Required.** Receiving location |
+| `patient` | `FK → Patient (PROTECT)` | Nullable. Patient the order is dispensed to. Cannot be set together with `origin` |
+| `patient_invoice` | `FK → Invoice (PROTECT)` | Nullable. Linked billing invoice; serialized as `patient_invoice_id` on read |
+| `extensions` | `JSONField` | Default `{}`. Validated for `ExtensionResource.supply_delivery_order` |
+
+`patient`/`patient_invoice` use `PROTECT` so referenced patients and invoices cannot be deleted while a delivery order points to them.
+
+#### `SupplyDeliveryOrderStatusOptions` (`status`)
+
+| Value | Notes |
+| --- | --- |
+| `draft` | Allowed on create |
+| `pending` | Allowed on create |
+| `in_progress` | |
+| `completed` | Terminal (in `SUPPLY_DELIVERY_ORDER_COMPLETED_STATUSES`) |
+| `abandoned` | Terminal (in `SUPPLY_DELIVERY_ORDER_COMPLETED_STATUSES`) |
+| `entered_in_error` | Terminal (in `SUPPLY_DELIVERY_ORDER_COMPLETED_STATUSES`) |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` ([`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)): `serialize` builds the read payload from the DB object (`perform_extra_serialization` hook), `de_serialize` builds the DB object from the request (`perform_extra_deserialization` hook). `to_json()` dumps the model excluding `meta`.
+
+### `SupplyDelivery` specs
+
+| Spec | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `BaseSupplyDeliverySpec` | shared | `id`, `status` (required), `supplied_item_condition?`, `total_purchase_price?`. `__exclude__ = ["supplied_item", "supply_request", "supplied_inventory_item"]` (these are resolved by hooks, not direct field copy) |
+| `SupplyDeliveryWriteSpec` | write · create | Adds `supplied_item_pack_quantity?`, `supplied_item_pack_size?`, `supplied_item_quantity` (Decimal, `decimal_places=0`), `supplied_item?`, `supplied_inventory_item?`, `supply_request?`, `order` (**required**), `extensions`. Mixes in `ExtensionValidator` |
+| `SupplyDeliveryUpdateSpec` | write · update | `order?` only (plus shared base fields + `extensions`). Mixes in `ExtensionValidator` |
+| `SupplyDeliveryReadSpec` | read · list | `supplied_item_quantity: int`, `created_date`, `modified_date`, `supplied_item_pack_quantity?`, `supplied_item_pack_size?`, `extensions`, and nested objects `supplied_item`, `supplied_inventory_item`, `supply_request`, `order` (serialized as `dict`). Mixes in `ExtensionListRenderer` |
+| `SupplyDeliveryRetrieveSpec` | read · detail | Extends `SupplyDeliveryReadSpec`; adds `created_by`/`updated_by` (`UserSpec`). Mixes in `ExtensionRetrieveRenderer` |
+
+**Write validation (`SupplyDeliveryWriteSpec`):**
+- `validate_quantity`: when both `supplied_item_pack_quantity` and `supplied_item_pack_size` are set, `supplied_item_quantity` is overwritten with their product.
+- `validate_supplied_item` (resolves `order` from `external_id`):
+ - if `order.origin` is set → `supplied_inventory_item` is **required** (intra-facility stock move);
+ - if `order.origin` is **not** set → `supplied_item` is **required** (external delivery);
+ - `supplied_item` and `supplied_inventory_item` **cannot both** be provided.
+- `perform_extra_deserialization`: resolves `order` by `external_id`; resolves `supplied_item` scoped to `order.destination.facility`; if `order.origin` exists, resolves `supplied_inventory_item` scoped to `order.origin.facility`; resolves `supply_request` by `external_id`.
+
+**Update (`SupplyDeliveryUpdateSpec`):** `perform_extra_deserialization` re-resolves `order` from `external_id` only when `order` is provided.
+
+**Read serialization:** `perform_extra_serialization` sets `id = external_id` and inlines related objects via their read specs — `supplied_item` → `ProductReadSpec`, `supplied_inventory_item` → `InventoryItemReadSpec`, `supply_request` → `SupplyRequestReadSpec`, `order` → `SupplyDeliveryOrderReadSpec`.
+
+### `DeliveryOrder` specs
+
+| Spec | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `BaseSupplyDeliveryOrderSpec` | shared | `id`, `status` (required), `name` (required), `note?`. Mixes in `ExtensionValidator` |
+| `SupplyDeliveryOrderWriteSpec` | write · create/update | Adds `supplier?`, `origin?`, `destination` (**required**), `patient?` (all by `external_id`) |
+| `SupplyDeliveryOrderReadSpec` | read · list | Nested `origin?`, `destination`, `supplier?`, `patient?` (`dict`), `tags`, `patient_invoice_id?`, `created_date`, `modified_date`, `created_by?`, `updated_by?`. Mixes in `ExtensionListRenderer` |
+| `SupplyDeliveryOrderRetrieveSpec` | read · detail | Extends `SupplyDeliveryOrderReadSpec` (no extra fields). Mixes in `ExtensionRetrieveRenderer` |
+
+**Write validation (`SupplyDeliveryOrderWriteSpec.perform_extra_deserialization`):**
+- resolves `destination` (required), `origin?`, `patient?` by `external_id`;
+- resolves `supplier?` constrained to `org_type = product_supplier`;
+- `patient` and `origin` **cannot** be provided together;
+- on create the `status` **must be `draft` or `pending`**.
+
+**Read serialization:** `id = external_id`; inlines `origin`/`destination` via `FacilityLocationListSpec`, `supplier` via `OrganizationReadSpec`, `patient` via `PatientListSpec`; renders `tags` via `SingleFacilityTagManager`; exposes the linked invoice as `patient_invoice_id` (string); attaches audit users.
+
+### Extensions
+
+`extensions` is not free-form JSON. On write, `ExtensionValidator` runs each registered handler for the resource type (`supply_delivery` / `supply_delivery_order`) — unknown keys are dropped, known keys are validated and re-serialized. On read, `ExtensionListRenderer` / `ExtensionRetrieveRenderer` render every registered extension for the resource (list vs retrieve variants).
+
+## API integration notes
+
+- Coded fields carry the **snake_case enum values** above (e.g. `status = "in_progress"`), not FHIR URIs. Validate against the enum, not free text.
+- A delivery is created against a `DeliveryOrder` (`order` is required on create). The order's `origin` decides whether you send `supplied_inventory_item` (intra-facility) or `supplied_item` (external) — never both.
+- Send `supplied_item_pack_quantity` + `supplied_item_pack_size` to have `supplied_item_quantity` computed server-side; otherwise send `supplied_item_quantity` directly (whole units).
+- A `DeliveryOrder` must start in `draft` or `pending`; `completed`/`abandoned`/`entered_in_error` are terminal states.
+- `patient` and `origin` are mutually exclusive on an order (patient dispense vs location-to-location move).
+- `extensions` is the supported place for deployment-specific data — but only **registered** extension keys are persisted.
+
+## Related
+
+- Reference: [Supply Request](./supply-request.mdx)
+- Reference: [Inventory Item](./inventory-item.mdx)
+- Reference: [Product](./product.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Organization](../facility/organization.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Invoice](../billing/invoice.mdx)
+- Source: [supply_delivery.py (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/supply_delivery.py)
+- Source: [spec.py (SupplyDelivery specs)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_delivery/spec.py)
+- Source: [delivery_order.py (DeliveryOrder specs)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_delivery/delivery_order.py)
+- Source: [base.py (EMRResource)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
diff --git a/versioned_docs/version-3.0/references/supply/supply-request.mdx b/versioned_docs/version-3.0/references/supply/supply-request.mdx
new file mode 100644
index 0000000..54d6291
--- /dev/null
+++ b/versioned_docs/version-3.0/references/supply/supply-request.mdx
@@ -0,0 +1,187 @@
+---
+sidebar_position: 4
+---
+
+# Supply Request
+
+Technical reference for the `SupplyRequest` module in Care EMR.
+
+A `SupplyRequest` is a line item asking for a quantity of a catalogue item (`ProductKnowledge`). Each request belongs to a `RequestOrder`, which groups requests describing the movement of items into a destination facility location (optionally from a supplier organization and/or an origin location).
+
+The Django model is the **storage** layer — `status`, `priority`, `intent`, `reason`, `category`, and `supplied_item_condition` are stored as plain `CharField`s with no DB-level constraint. The **real allowed values, validation, and API request/response shapes** live in the Pydantic resource specs (`care/emr/resources/inventory/supply_request/`), documented below.
+
+**Source:**
+
+- Model: [`care/emr/models/supply_request.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/supply_request.py)
+- Spec: [`care/emr/resources/inventory/supply_request/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_request/spec.py)
+- Spec: [`care/emr/resources/inventory/supply_request/request_order.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_request/request_order.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `SupplyRequest` | A request for a quantity of a catalogue item (product knowledge), grouped under an order |
+| `RequestOrder` | An order that groups supply requests for movement of items into a destination facility location |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_date`/`modified_date`/`created_by`/`updated_by`, soft-delete via `deleted`, and `history`/`meta` JSON).
+
+## `SupplyRequest` fields
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `status` | `CharField(255)` | Lifecycle status. Spec-bound to `SupplyRequestStatusOptions` (see below). Required on write. |
+| `quantity` | `DecimalField` | Nullable in DB; `max_digits=20`, `decimal_places=6`. Quantity requested. The write spec accepts a `Decimal` (`max_digits=20`, `decimal_places=0`); the read spec returns it as an `int`. |
+| `supplied_item_condition` | `CharField(255)` | Condition of the supplied item. Not exposed by any current spec (model-only field). |
+| `item` | `FK → ProductKnowledge` | `CASCADE`; the catalogue item being requested. Required on create; resolved server-side from an `external_id`. |
+| `order` | `FK → RequestOrder` | `CASCADE`, nullable in DB. The parent order. Required on both create and update in the spec; resolved server-side from an `external_id`. |
+
+### `SupplyRequestStatusOptions` values
+
+Enum class `SupplyRequestStatusOptions` (`spec.py`). Bound to `SupplyRequest.status`.
+
+| Value |
+| --- |
+| `draft` |
+| `active` |
+| `suspended` |
+| `cancelled` |
+| `processed` |
+| `completed` |
+| `entered_in_error` |
+
+## Related models
+
+### `RequestOrder`
+
+Groups one or more `SupplyRequest` rows into a single order and describes the movement of items into a `destination` facility location, optionally from a `supplier` organization and/or an `origin` location.
+
+```text
+supplier → FK Organization (nullable, CASCADE)
+origin → FK FacilityLocation (nullable, CASCADE, related_name="origin_request_orders")
+destination → FK FacilityLocation (CASCADE, related_name="destination_request_orders")
+```
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(255)` | Order name/label. Required. |
+| `status` | `CharField(255)` | Order lifecycle status. Spec-bound to `SupplyRequestOrderStatusOptions`. |
+| `note` | `TextField` | Nullable; free-text note. Optional (`str \| None`). |
+| `tags` | `ArrayField[int]` | Default `list`; tag IDs attached to the order. Rendered on read via `SingleFacilityTagManager`. |
+| `priority` | `CharField(255)` | Order priority. Spec-bound to `SupplyRequestPriorityOptions`. |
+| `intent` | `CharField(255)` | Intent of the order. Spec-bound to `SupplyRequestIntentOptions`. |
+| `reason` | `CharField(255)` | Reason for the order. Spec-bound to `SupplyRequestReason`. |
+| `category` | `CharField(255)` | Order category. Spec-bound to `SupplyRequestCategoryOptions`. |
+| `supplier` | `FK → Organization` | Nullable `CASCADE`. On write, must be an organization of `org_type == "product_supplier"` (validated server-side). |
+| `origin` | `FK → FacilityLocation` | Nullable `CASCADE`, `related_name="origin_request_orders"`. Sending location. |
+| `destination` | `FK → FacilityLocation` | `CASCADE`, `related_name="destination_request_orders"`. Receiving location. Required. |
+
+`SupplyRequest.order` points back to a `RequestOrder`, so a single order fans out to many supply requests. `origin` and `destination` both target `FacilityLocation` but use distinct `related_name`s, letting a location surface both the orders it sends (`origin_request_orders`) and the orders it receives (`destination_request_orders`).
+
+### `RequestOrder` enum values
+
+Enums from `request_order.py`. Each binds to the matching `RequestOrder` `CharField`.
+
+#### `SupplyRequestOrderStatusOptions` (→ `status`)
+
+| Value |
+| --- |
+| `draft` |
+| `pending` |
+| `in_progress` |
+| `completed` |
+| `abandoned` |
+| `entered_in_error` |
+
+`SUPPLY_REQUEST_ORDER_COMPLETED_STATUSES` = `[abandoned, entered_in_error, completed]` — the terminal/closed statuses.
+
+#### `SupplyRequestIntentOptions` (→ `intent`)
+
+| Value |
+| --- |
+| `proposal` |
+| `plan` |
+| `directive` |
+| `order` |
+| `original_order` |
+| `reflex_order` |
+| `filler_order` |
+| `instance_order` |
+
+#### `SupplyRequestCategoryOptions` (→ `category`)
+
+| Value |
+| --- |
+| `central` |
+| `nonstock` |
+
+#### `SupplyRequestPriorityOptions` (→ `priority`)
+
+| Value |
+| --- |
+| `routine` |
+| `urgent` |
+| `asap` |
+| `stat` |
+
+#### `SupplyRequestReason` (→ `reason`)
+
+| Value |
+| --- |
+| `patient_care` |
+| `ward_stock` |
+
+## Resource specs (API schema)
+
+All specs extend [`EMRResource`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py) (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). Write specs resolve foreign keys from `external_id` values in `perform_extra_deserialization`; read specs expand related objects in `perform_extra_serialization`.
+
+### Supply request specs (`spec.py`)
+
+| Spec class | Role | Fields | Notes |
+| --- | --- | --- | --- |
+| `BaseSupplyRequestSpec` | shared | `id`, `status`, `quantity` | `__model__ = SupplyRequest`, `__exclude__ = ["item"]`. `quantity: Decimal(max_digits=20, decimal_places=0)`. `status: SupplyRequestStatusOptions`. |
+| `SupplyRequestWriteSpec` | write · create | base + `item: UUID4`, `order: UUID4` | Resolves `item` → `ProductKnowledge` and `order` → `RequestOrder` by `external_id` (404 if missing). |
+| `SupplyRequestUpdateSpec` | write · update | base + `order: UUID4` | Resolves `order` → `RequestOrder` by `external_id`. Does **not** accept `item` (item is fixed after create). |
+| `SupplyRequestReadSpec` | read · detail/list | base + `quantity: int`, `item: dict`, `order: dict` | `id` = `external_id`. `item` expanded via `ProductKnowledgeReadSpec`; `order` expanded via `SupplyRequestOrderReadSpec`. |
+
+### Request order specs (`request_order.py`)
+
+| Spec class | Role | Fields | Notes |
+| --- | --- | --- | --- |
+| `BaseSupplyRequestOrderSpec` | shared | `id`, `status`, `name`, `note`, `intent`, `category`, `priority`, `reason` | `__model__ = RequestOrder`. Coded fields bound to their respective enums; `note` optional. |
+| `SupplyRequestOrderWriteSpec` | write · create/update | base + `supplier: UUID4 \| None`, `origin: UUID4 \| None`, `destination: UUID4` | Resolves `supplier`/`origin`/`destination` by `external_id`. Validates `supplier.org_type == "product_supplier"`, else raises `ValidationError`. |
+| `SupplyRequestOrderReadSpec` | read · detail/list | base + `supplier`, `origin: dict \| None`, `destination: dict`, `tags: list[dict]`, `created_date`, `modified_date`, `created_by`, `updated_by` | `id` = `external_id`. `supplier` expanded via `OrganizationReadSpec`; `origin`/`destination` via `FacilityLocationListSpec`; `tags` rendered via `SingleFacilityTagManager`; audit users via `serialize_audit_users`. |
+
+### Validation & server-maintained behaviour
+
+- **FK resolution:** all foreign keys on write are supplied as `external_id` UUIDs and resolved server-side with `get_object_or_404` (`item`, `order`, `supplier`, `origin`, `destination`).
+- **Supplier type guard:** `SupplyRequestOrderWriteSpec` rejects any `supplier` whose `org_type` is not `product_supplier`.
+- **`id` handling:** read specs set `id` to `external_id`; `de_serialize` never writes `id`/`external_id` back to the row.
+- **`quantity`:** write accepts a `Decimal` constrained to `decimal_places=0` (whole numbers) even though the DB column allows `decimal_places=6`; read returns an `int`.
+- **`item` immutability:** create accepts `item`; update (`SupplyRequestUpdateSpec`) omits it, so the requested catalogue item cannot be changed after creation.
+
+## Methods & save behaviour
+
+- Neither model overrides `save()`; standard `EMRBaseModel` audit and soft-delete behaviour applies.
+- Status is free-text at the DB layer; lifecycle is enforced only by the spec enums at the API boundary. There is no automatic `status_history` tracking on these models.
+- Deleting a `RequestOrder` cascades to its `SupplyRequest` rows (`order` FK is `CASCADE`); deleting a referenced `ProductKnowledge` cascades to the request.
+
+## API integration notes
+
+- Field names in API payloads follow the spec field names above; foreign keys are exchanged as `external_id` UUIDs on write and as expanded objects on read.
+- `status`, `priority`, `intent`, `reason`, and `category` are validated against the spec enums — send the exact string values listed above.
+- `supplied_item_condition` exists on the model but is not surfaced by any current spec.
+- `quantity` is sent/returned as a whole number through the specs; the DB column itself retains six decimal places.
+- `tags` on `RequestOrder` is an integer array of tag IDs (not a relational join); on read it is rendered to tag objects via the facility tag manager.
+- `external_id` (from `EMRBaseModel`) is the stable public identifier used in API URLs — not the internal primary key.
+
+## Related
+
+- Reference: [Product Knowledge](../supply/product-knowledge.mdx)
+- Reference: [Supply Delivery](../supply/supply-delivery.mdx)
+- Reference: [Inventory Item](../supply/inventory-item.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Organization](../facility/organization.mdx)
+- Reference: [EMR base model](../foundation/base-model.mdx)
+- Source: [supply_request.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/supply_request.py)
+- Spec: [supply_request/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_request/spec.py)
+- Spec: [supply_request/request_order.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_request/request_order.py)
diff --git a/versioned_docs/version-3.1/references/access/_category_.json b/versioned_docs/version-3.1/references/access/_category_.json
new file mode 100644
index 0000000..1befab6
--- /dev/null
+++ b/versioned_docs/version-3.1/references/access/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Access & Identity",
+ "position": 9,
+ "key": "access-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Access & Identity",
+ "description": "Users and skills, roles, and the permission model that governs who can do what across organizations and facilities."
+ }
+}
diff --git a/versioned_docs/version-3.1/references/access/permission-association.mdx b/versioned_docs/version-3.1/references/access/permission-association.mdx
new file mode 100644
index 0000000..93e870b
--- /dev/null
+++ b/versioned_docs/version-3.1/references/access/permission-association.mdx
@@ -0,0 +1,199 @@
+---
+sidebar_position: 4
+---
+
+# Permission Association
+
+Technical reference for the `RoleAssociation` module in Care EMR.
+
+`RoleAssociation` is the binding that grants a [`RoleModel`](./role.mdx) to a [`User`](./user.mdx) within a specific context (for example, an organization or facility). A role is just a named collection of [permissions](./permission.mdx); this association is what actually scopes that collection to a user inside a context. A user can hold multiple roles across different contexts.
+
+The Django model (`care/security/models/permission_association.py`) is the **storage** layer: the `user`/`role` foreign keys, the generic `context`/`context_id` pair, and `expiry`. `RoleAssociation` has **no resource spec of its own** — it is a platform-internal authorization join, not a client-writable EMR resource. The Pydantic **resource specs** that matter here govern the [`role`](./role.mdx) it points at (`care/emr/resources/role/spec.py`) and the permission-resolution mixins (`care/emr/resources/permissions.py`) that *read* these rows to compute a user's effective permissions in a context.
+
+**Source:**
+- Model: [`care/security/models/permission_association.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/models/permission_association.py)
+- Role spec (the granted role): [`care/emr/resources/role/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py)
+- Permission-resolution mixins: [`care/emr/resources/permissions.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py)
+- Role definitions / `RoleContext`: [`care/security/roles/role.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/roles/role.py)
+- Permission contexts: [`care/security/permissions/constants.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/constants.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `RoleAssociation` | Grants a `RoleModel` to a `User` within a named context, with optional expiry |
+
+`RoleAssociation` extends `BaseModel` — the lightweight Care base providing `external_id` (UUID), `created_date`, `modified_date`, and soft-delete via `deleted` (the overridden `delete()` sets `deleted=True` rather than removing the row). See [Base model](../foundation/base-model.mdx).
+
+It does **not** extend `EMRBaseModel`, so it has no `created_by` / `updated_by` audit fields, no `history`/`meta` JSON, and no `external_id`-routed EMR API. There is no `RoleAssociationCreateSpec`/`...ReadSpec` — rows are created and removed through Care's access-control flows, not via a Pydantic serializer.
+
+## `RoleAssociation` fields
+
+### Subject and role
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `user` | `FK → User` | yes | — | Subject receiving the role. `on_delete=CASCADE`, `null=False`, `blank=False` — deleting the user removes the association |
+| `role` | `FK → RoleModel` | yes | — | Role (set of permissions) being granted. `on_delete=CASCADE`, `null=False`, `blank=False`. Read/write through the role specs — see [Role](./role.mdx) |
+
+### Context
+
+The context identifies *where* the role applies. It is stored as a type/id pair rather than a typed foreign key, so a single association table can scope roles to any kind of context (organization, facility, etc.). There is **no DB-level referential integrity** on this pair.
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `context` | `CharField(1024)` | yes | — | Context type — the name of the entity the role is scoped to. Free-form string at the model layer; not bound to an enum |
+| `context_id` | `BigIntegerField` | yes | — | Integer primary key of the context entity (not a UUID/`external_id`) |
+
+### Lifecycle
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `expiry` | `DateTimeField` | no | `null` | `null=True`, `blank=True`. When set, the role allocation is *intended* to lapse after this time. It is a single timestamp, **not** a `PeriodSpec`/start–end range, and the model does not enforce it |
+
+## Related models
+
+The role granted by an association resolves to permissions through these models. None of them are part of `RoleAssociation` itself, but every effective-permission lookup walks through them.
+
+### `RoleModel`
+
+The granted role ([Role](./role.mdx)). A named, flat set of permissions; `is_system` roles are platform-seeded and cannot be edited via the API.
+
+```text
+name → CharField(1024) # unique among non-deleted rows
+description → TextField (default "")
+is_system → BooleanField (default False)
+is_archived → BooleanField (default False)
+temp_deleted→ BooleanField (default False)
+contexts → ArrayField[CharField(24)] (default []) # bound to RoleContext in specs
+```
+
+### `RolePermission`
+
+Join table linking a `RoleModel` to a single `PermissionModel`. A role accumulates permissions through many `RolePermission` rows; lookups exclude `temp_deleted=True`.
+
+```text
+role → FK RoleModel (CASCADE, required)
+permission → FK PermissionModel (CASCADE, required)
+temp_deleted → BooleanField (default False)
+```
+
+### `PermissionModel`
+
+The atomic permission a role grants ([Permission](./permission.mdx)).
+
+```text
+slug → CharField(1024) unique, indexed
+name → CharField(1024)
+description → TextField (default "")
+context → CharField(1024) # one of the PermissionContext values
+temp_deleted → BooleanField (default False)
+```
+
+## Enum / value tables
+
+`RoleAssociation` stores plain strings/integers, but the role it points at and the permissions that role resolves to are constrained by these enums.
+
+### `RoleContext` values
+
+Each element of the granted role's `contexts` array is validated against this enum (`care/security/roles/role.py`). These are the **organizational boundary** types a role can apply to — distinct from `PermissionContext`. The free-form `RoleAssociation.context` column typically names an entity of one of these boundary types.
+
+| Value | Meaning |
+| --- | --- |
+| `FACILITY` | Role applies within a facility |
+| `GOVT_ORG` | Role applies within a government organization |
+| `ROLE_ORG` | Role applies within a role (user-group) organization |
+
+### `PermissionContext` values
+
+`PermissionModel.context` / `Permission.context` is one of these (`care/security/permissions/constants.py`). The permission-resolution mixins (below) filter a user's grants by these contexts when computing effective permissions from `RoleAssociation` rows.
+
+| Value |
+| --- |
+| `GENERIC` |
+| `FACILITY` |
+| `PATIENT` |
+| `QUESTIONNAIRE` |
+| `ORGANIZATION` |
+| `FACILITY_ORGANIZATION` |
+| `ENCOUNTER` |
+
+### `PermissionEnum` (role write field)
+
+When a role is written via `RoleCreateSpec`, its `permissions` field is typed against `PermissionController.get_enum()` — a `str` enum built dynamically at runtime from every registered permission `name` across all permission handlers (e.g. `can_create_patient`, `can_write_patient`, `can_list_patients`, `can_view_clinical_data`). The set is deployment-dependent (internal handlers + any plugin-registered ones), not a fixed list.
+
+### Built-in (`is_system`) roles
+
+Seeded by `RoleController` (`care/security/roles/role.py`). Associations frequently bind a user to one of these. They cannot be created, updated, or deleted through the API.
+
+| Role | Contexts |
+| --- | --- |
+| `Doctor` | `FACILITY`, `GOVT_ORG` |
+| `Nurse` | `FACILITY`, `GOVT_ORG` |
+| `Staff` | `FACILITY`, `GOVT_ORG` |
+| `Volunteer` | `FACILITY`, `GOVT_ORG` |
+| `Pharmacist` | `FACILITY` |
+| `Administrator` | `FACILITY`, `GOVT_ORG` |
+| `Facility Admin` | `FACILITY` |
+| `Admin` | `FACILITY`, `GOVT_ORG` |
+| `Admin` (role org) | `ROLE_ORG` |
+| `Manager` (role org) | `ROLE_ORG` |
+| `Member` (role org) | `ROLE_ORG` |
+
+## Resource specs (API schema)
+
+`RoleAssociation` has **no dedicated `...CreateSpec`/`...UpdateSpec`/`...ListSpec`/`...RetrieveSpec`** — it is not exposed as an EMR resource. The Pydantic surface relevant to associations is twofold: the specs that define the **role** an association grants, and the mixins that **consume** associations to expose a user's effective permissions on another resource. All extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB → Pydantic via `perform_extra_serialization`) and `de_serialize` (Pydantic → DB via `perform_extra_deserialization`).
+
+### Specs for the granted role
+
+| Spec class | Role | Fields |
+| --- | --- | --- |
+| `RoleBaseSpec` | shared base (`__exclude__ = ["permissions"]`) | `id`, `name`, `description`, `is_system`, `is_archived`, `contexts` |
+| `RoleCreateSpec` | write · create & update | base fields + `permissions: list[PermissionEnum]` (≥ 1, de-duplicated) |
+| `RoleReadSpec` | read · detail/list | base fields + `permissions: list[PermissionSpec]` |
+| `RoleReadMinimalSpec` | read · minimal/embedded | base fields only |
+| `PermissionSpec` | nested (read) | `name`, `description`, `slug` (`SlugType`), `context` |
+
+`RoleBaseSpec.contexts` is `list[RoleContext]` — bound to the `RoleContext` enum at the API layer even though the model column is an open string array. `PermissionSpec.slug` is a `SlugType`: `str`, length 5–50, pattern `^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$`. Key role validation (`RoleCreateSpec.validate_role`, mode="after"): non-blank unique name (`name__iexact`), reject `is_system=True`, reject updating a system role, require ≥ 1 permission. See [Role](./role.mdx) for the full spec breakdown.
+
+### Permission-resolution mixins (`care/emr/resources/permissions.py`)
+
+These mixins are inherited by *other* resource specs (patient, encounter, facility). When `serialize` is called with an authenticated `user`, `perform_extra_user_serialization` walks the user's `RoleAssociation` rows for that object, resolves the granted roles to permissions, and writes them into `mapping["permissions"]`. This is the read path that turns stored associations into a permission list on the wire.
+
+| Mixin | Resolves (from the user's associations) | Context filter | Extra fields |
+| --- | --- | --- | --- |
+| `PermissionsMixin` | base — adds `permissions: list[str]` | — | — |
+| `PatientPermissionsMixin` | roles the user holds on a patient | `permission__context in ("PATIENT", "FACILITY")` | — |
+| `EncounterPermissionsMixin` | roles the user holds on an encounter | `permission__context in ("ENCOUNTER", "PATIENT")` | — |
+| `FacilityPermissionsMixin` | roles on facility root + sub-orgs | none (root) / child set excludes `can_update_facility` | `root_org_permissions`, `child_org_permissions` |
+
+Permission slugs are looked up via `RolePermission.objects.filter(role_id__in=roles, …).values_list("permission__slug", flat=True)`, so only active (non-`temp_deleted`) grants on the resolved roles count.
+
+## Methods & save behaviour
+
+`RoleAssociation` defines no model methods, validators, or `save()`/`delete()` overrides of its own. It inherits soft-delete from `BaseModel`: calling `delete()` sets `deleted=True` and persists with `save(update_fields=["deleted"])`, and the default manager filters out soft-deleted rows.
+
+A source `TODO` notes that a composite index on `user`, `context`, and `context_id` is planned but not yet present; lookups by those fields are not index-backed today.
+
+`expiry` is a stored timestamp only — the model does not enforce it. Expiry-based revocation is the responsibility of the authorization layer that reads these associations.
+
+There is no `perform_extra_serialization` / `perform_extra_deserialization` on `RoleAssociation` (it has no spec). The serialization hooks that matter live on the role specs and the permission mixins described above.
+
+## API integration notes
+
+- `RoleAssociation` is a platform-maintained authorization record, not a client-writable EMR resource. It is created and removed through Care's access-control flows, not via FHIR, a Pydantic spec, or direct REST writes.
+- The `context` / `context_id` pair is a generic (non-FK) reference. `context` is a free-form string (typically naming an entity in one of the `RoleContext` boundary types) and `context_id` is the entity's **integer PK** — not its `external_id` UUID. Integrators must pair the right type string with the matching PK; there is no database-level referential integrity on the context.
+- A user may have many `RoleAssociation` rows — one per (role, context) grant. Effective permissions in a given context are computed by the `permissions` mixins, which union the active permission slugs of all resolved roles, filtered by `PermissionContext`.
+- `expiry` is advisory at the model layer; do not assume the row is automatically deactivated when it passes.
+- The granted `role` is written/read through the role specs (`RoleCreateSpec` / `RoleReadSpec`), and each role's `contexts` is bound to the `RoleContext` enum — not an open string at the API layer.
+
+## Related
+
+- Reference: [Role](./role.mdx)
+- Reference: [Permission](./permission.mdx)
+- Reference: [User](./user.mdx)
+- Reference: [Organization](../facility/organization.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [permission_association.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/security/models/permission_association.py)
+- Source: [role/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py)
+- Source: [permissions.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py)
diff --git a/versioned_docs/version-3.1/references/access/permission.mdx b/versioned_docs/version-3.1/references/access/permission.mdx
new file mode 100644
index 0000000..27d9646
--- /dev/null
+++ b/versioned_docs/version-3.1/references/access/permission.mdx
@@ -0,0 +1,163 @@
+---
+sidebar_position: 3
+---
+
+# Permission
+
+Technical reference for the `Permission` module in Care EMR.
+
+A permission is the ability to perform a single action in a given context — usually expressed as "Action on Resource" (for example, "Can Create Patient" in the `PATIENT` context). Permissions are the atomic building blocks of Care's security system: they are declared in code, synced into the database, grouped into [roles](./role.mdx), and bound to resources via [permission associations](./permission-association.mdx).
+
+The Django `PermissionModel` is the **storage** layer. The real implementation lives in two other places: the **permission registry** (`care/security/permissions/`), which declares every permission as a Python `enum` member with a `name`, `description`, `context`, and the default `roles` that hold it; and the Pydantic **resource specs** (`care/emr/resources/role/spec.py`), which define the read/write API schema. This doc covers all three.
+
+**Source:**
+
+- Model: [`care/security/models/permission.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/models/permission.py)
+- Spec: [`care/emr/resources/role/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py) (`PermissionSpec`)
+- Computed-permission mixins: [`care/emr/resources/permissions.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py)
+- Registry: [`care/security/permissions/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/base.py), [`care/security/permissions/constants.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/constants.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `PermissionModel` | A single, named permission (action) that can be granted to a user in a context |
+
+`PermissionModel` extends [`BaseModel`](../foundation/base-model.mdx) — the lowest-level Care base. It does **not** use `EMRBaseModel`, so it has no `created_by`/`updated_by`, `history`, or `meta` columns. From `BaseModel` it inherits:
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `external_id` | `UUIDField` | `default=uuid4`, `unique`, indexed. Opaque public identifier — never expose the integer `pk` |
+| `created_date` | `DateTimeField` | `auto_now_add`; nullable, indexed |
+| `modified_date` | `DateTimeField` | `auto_now`; nullable, indexed |
+| `deleted` | `BooleanField` | `default=False`, indexed. Soft-delete flag; the default manager hides `deleted=True` rows |
+
+## `PermissionModel` fields
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `slug` | `CharField(1024)` | yes | — | `unique`, indexed. Stable machine identifier for the permission (the `enum` member name, e.g. `can_create_patient`). The slug — not `external_id` — is how code, specs, and the API reference a permission. On the API it is the `lookup_field` |
+| `name` | `CharField(1024)` | yes | — | Human-readable display name (e.g. `"Can Create Patient"`). Sourced from the registry `Permission.name` |
+| `description` | `TextField` | no | `""` | Free-text explanation. Sourced from the registry `Permission.description`; many declared permissions ship with an empty description |
+| `context` | `CharField(1024)` | yes | — | The resource context the permission applies to. No DB-level `choices`, but values are always one of the `PermissionContext` enum strings below (set by the sync command from `Permission.context.value`) |
+| `temp_deleted` | `BooleanField` | no | `False` | Staging flag used by the sync command to mark permissions no longer present in the declared set before they are hard-deleted; distinct from the inherited `deleted` soft-delete flag |
+
+### `context` values (`PermissionContext`)
+
+`context` is a free-form `CharField` at the DB level, but the registry only ever writes one of these enum values ([`constants.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/constants.py)):
+
+| Value | Meaning |
+| --- | --- |
+| `GENERIC` | Not scoped to a specific resource type |
+| `FACILITY` | Scoped to a facility |
+| `PATIENT` | Scoped to a patient |
+| `QUESTIONNAIRE` | Scoped to a questionnaire |
+| `ORGANIZATION` | Scoped to a (govt/role) organization |
+| `FACILITY_ORGANIZATION` | Scoped to a facility organization |
+| `ENCOUNTER` | Scoped to an encounter |
+
+The context drives which permissions are considered when resolving access for a serialized resource — see the mixins below, which filter on `permission__context__in=[...]`.
+
+## Related models
+
+### Registry declaration — `Permission` dataclass
+
+Permissions are not authored in the database. Each is a member of a `*Permissions` `enum` (one per resource area, e.g. `PatientPermissions`, `EncounterPermissions`), where the member **name** becomes the `slug` and the member **value** is a `Permission` dataclass ([`constants.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/constants.py)):
+
+| Field | Type | Maps to `PermissionModel` | Notes |
+| --- | --- | --- | --- |
+| `name` | `str` | `name` | Display name |
+| `description` | `str` | `description` | Free text |
+| `context` | `PermissionContext` | `context` (`.value`) | One of the enum values above |
+| `roles` | `list[Role]` | — (drives `RolePermission`) | Default system roles that receive this permission on sync |
+
+Example (`care/security/permissions/patient.py`):
+
+```python
+can_create_patient = Permission(
+ "Can Create Patient", "", PermissionContext.PATIENT,
+ [STAFF_ROLE, DOCTOR_ROLE, NURSE_ROLE, ADMINISTRATOR, ADMIN_ROLE, FACILITY_ADMIN_ROLE],
+)
+```
+
+`PermissionController` ([`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/base.py)) aggregates every handler enum:
+
+- `get_permissions() → dict[slug, Permission]` — the full declared registry (cached).
+- `get_enum() → Enum` — a dynamically built `str` enum of all permission slugs, used by [`RoleCreateSpec`](./role.mdx) to constrain the `permissions` write field to known slugs.
+
+### Sync command (`sync_permissions_roles`)
+
+The management command [`sync_permissions_roles`](https://github.com/ohcnetwork/care/blob/develop/care/security/management/commands/sync_permissions_roles.py) is the only writer of this table. It is idempotent and Redis-locked, and runs in a single transaction:
+
+1. Mark every `PermissionModel` `temp_deleted=True`.
+2. For each declared permission, upsert by `slug`, setting `name`, `description`, `context` (from `Permission.context.value`) and clearing `temp_deleted`.
+3. Hard-delete any row still `temp_deleted=True` (i.e. no longer declared).
+4. Upsert system roles, then rebuild `RolePermission` rows from each `Permission.roles` list (same temp-delete-then-prune pattern).
+
+### `RolePermission`
+
+Permissions reach users only through roles. `RolePermission` is the join table (`role` FK, `permission` FK, `temp_deleted`) connecting a [`RoleModel`](./role.mdx) to a `PermissionModel`. Effective access is the union of permissions across a user's role bindings, filtered to active grants (`rolepermission__temp_deleted=False`).
+
+## Resource specs (API schema)
+
+The Pydantic specs build on `EMRResource` ([`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)), whose `serialize` (DB → spec) and `de_serialize` (spec → DB) drive read/write, with `perform_extra_serialization`/`perform_extra_deserialization` hooks for side effects.
+
+| Spec class | Role | File | Notes |
+| --- | --- | --- | --- |
+| `PermissionSpec` | read · list + detail | `role/spec.py` | The only permission-facing spec. Served read-only by `PermissionViewSet` |
+| `PermissionsMixin` | read augmentation | `permissions.py` | Adds a computed `permissions` list to other resources' serialized output for the requesting user |
+
+There is **no** `PermissionCreateSpec`/`PermissionUpdateSpec` — permissions are reference data and never client-writable. (Write specs exist for roles instead; see [Role](./role.mdx).)
+
+### `PermissionSpec`
+
+`__model__ = PermissionModel`. A flat read schema with no extra serialization hooks (it inherits the base, which sets `id = external_id`):
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `str` | From `PermissionModel.name` |
+| `description` | `str` | From `PermissionModel.description` |
+| `slug` | `SlugType` | `str`, `min_length=5`, `max_length=50`, must match `^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$` (URL-safe; starts and ends alphanumeric). See [`slug_type.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/utils/slug_type.py) |
+| `context` | `str` | One of the `PermissionContext` values above |
+| `id` | `UUID4` | Added by `serialize` as `external_id` (inherited base behaviour) |
+
+Served by [`PermissionViewSet`](https://github.com/ohcnetwork/care/blob/develop/care/security/api/viewsets/permissions.py) — an `EMRModelReadOnlyViewSet` (list + retrieve only), `lookup_field="slug"`, filterable by `name` (`icontains`). There is no create/update/delete endpoint.
+
+### Computed `permissions` on other resources (`PermissionsMixin`)
+
+[`permissions.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py) defines mixins that other read specs inherit so a serialized resource carries the **current user's** effective permission slugs for that object. `PermissionsMixin.perform_extra_user_serialization` calls `add_permissions(mapping, user, obj)` for authenticated users, populating a `permissions: list[str]` field:
+
+| Mixin | Output fields | Resolution |
+| --- | --- | --- |
+| `PatientPermissionsMixin` | `permissions` | Roles on the patient; permission slugs where `context in ["PATIENT", "FACILITY"]` |
+| `EncounterPermissionsMixin` | `permissions` | Roles on the encounter; permission slugs where `context in ["ENCOUNTER", "PATIENT"]` |
+| `FacilityPermissionsMixin` | `permissions`, `root_org_permissions`, `child_org_permissions` | Union of root-org and sub-org role permissions; `child_org_permissions` excludes the `can_update_facility` slug |
+
+These lists let the frontend gate UI by capability without a separate authorization round-trip.
+
+## Methods & save behaviour
+
+`PermissionModel` defines no custom `save()`, `delete()` override, validators, or signals beyond what it inherits from `BaseModel`. Soft-delete behaviour (`delete()` setting `deleted=True`) and the soft-delete-filtering default manager come from the base. The lifecycle is driven externally by the `sync_permissions_roles` command (see above), which uses `temp_deleted` as a staging flag and **hard-deletes** undeclared rows.
+
+`PermissionSpec` performs no extra serialization or deserialization; serialization is the base `serialize` flow plus the inherited `id = external_id` mapping.
+
+## API integration notes
+
+- Permissions are **platform-maintained reference data**, declared in the permission registry and synced into this table by `sync_permissions_roles` — they are read-only over the API (`PermissionViewSet` exposes list + retrieve only).
+- Reference a permission by its `slug`, which is stable, `unique`, and the API `lookup_field`; treat `external_id` as the opaque public ID and never expose the integer `pk`.
+- Permissions are not granted directly to users. They are collected into a [role](./role.mdx) (via `RolePermission`), and the role is bound to a resource (organization, patient, encounter, …) through a [permission association](./permission-association.mdx). Effective access is the union of active permissions across a user's role bindings.
+- `context` is a `CharField` with no DB `choices`, but is always one of the `PermissionContext` enum strings; the mixins filter resource permissions by context, so the value is load-bearing.
+- When writing a role, the `permissions` field is constrained to known slugs via `PermissionController.get_enum()` — submitting an unknown slug fails validation. See [Role](./role.mdx).
+
+## Related
+
+- Reference: [Role](./role.mdx)
+- Reference: [Permission association](./permission-association.mdx)
+- Reference: [User](./user.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Source: [permission.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/security/models/permission.py)
+- Source: [role/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py) (`PermissionSpec`)
+- Source: [resources/permissions.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py)
diff --git a/versioned_docs/version-3.1/references/access/role.mdx b/versioned_docs/version-3.1/references/access/role.mdx
new file mode 100644
index 0000000..d5ca011
--- /dev/null
+++ b/versioned_docs/version-3.1/references/access/role.mdx
@@ -0,0 +1,220 @@
+---
+sidebar_position: 2
+---
+
+# Role
+
+Technical reference for the `Role` module in Care EMR.
+
+A role is an arbitrary, flat collection of permissions that can be assigned to a user within a given organizational boundary (it is **not** a job title or a type — "Doctor", "Doctor Read Only" and "Doctor Scheduler" are all just distinct permission sets). A user may hold multiple roles across different organizations, but only one role per organization chain. Roles are resolved into effective permissions through `RolePermission` join rows.
+
+The Django model (`care/security/models/role.py`) is the **storage** layer: `name`, `description`, flags, and the `contexts` array. The Pydantic **resource specs** (`care/emr/resources/role/spec.py`, `care/emr/resources/permissions.py`) define the API read/write schemas, the `contexts` enum binding, the permission enum, and all create/update validation.
+
+**Source:**
+- Model: [`care/security/models/role.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/models/role.py)
+- Spec: [`care/emr/resources/role/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py)
+- Permissions mixins: [`care/emr/resources/permissions.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py)
+- Role definitions: [`care/security/roles/role.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/roles/role.py)
+- Permission registry: [`care/security/permissions/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/base.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `RoleModel` | A named role grouping a set of permissions |
+| `RolePermission` | Join table linking a `RoleModel` to a `PermissionModel` |
+
+Both models extend `BaseModel` (see [Base model](../foundation/base-model.mdx)) — they get `external_id`, `created_date`/`modified_date`, and soft-delete via the `deleted` flag, but **not** the richer `created_by`/`updated_by`/history/meta fields of `EMRBaseModel`.
+
+## `RoleModel` fields
+
+A role comprises multiple permissions. Roles can be created on the fly: system roles (`is_system=True`) cannot be deleted, and the API also blocks creating or updating them; user-created roles can be removed by users with the appropriate permission.
+
+### Identity & description
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `name` | `CharField(1024)` | yes | — | Role name. Unique across non-deleted rows (see Meta). Spec rejects empty/blank on create and rejects a name that already exists case-insensitively |
+| `description` | `TextField` | no | `""` | Free text |
+| `contexts` | `ArrayField[CharField(24)]` | yes | `[]` | The boundary types this role applies to. The model stores opaque strings; the spec constrains each element to the **`RoleContext`** enum (see [RoleContext values](#rolecontext-values)) |
+
+### Status flags
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `is_system` | `BooleanField` | no | `False` | `True` when created by the platform. Such roles cannot be deleted; the create/update spec rejects any request with `is_system=True` and rejects updates to existing system roles |
+| `temp_deleted` | `BooleanField` | no | `False` | Soft-marks a role for staged removal independent of `deleted` |
+| `is_archived` | `BooleanField` | no | `False` | Hides the role without deleting it. Exposed in the API specs |
+
+### Meta / constraints
+
+A partial unique constraint enforces a unique `name` only among non-deleted rows:
+
+```text
+UniqueConstraint(fields=["name"], condition=Q(deleted=False), name="unique_name_if_not_deleted")
+```
+
+The `RoleCreateSpec` validator additionally enforces uniqueness case-insensitively (`name__iexact`) before the DB constraint is reached.
+
+## Related models
+
+### `RolePermission`
+
+Join table connecting a role to a single permission. A role accumulates permissions through many `RolePermission` rows.
+
+```text
+role → FK RoleModel (CASCADE, required)
+permission → FK PermissionModel (CASCADE, required)
+temp_deleted → BooleanField (default False)
+```
+
+`temp_deleted` lets a permission grant be excluded from a role without removing the row — permission lookups filter on `rolepermission__temp_deleted=False`.
+
+### `PermissionModel`
+
+The permission a `RolePermission` points at ([Permission](../access/permission.mdx)).
+
+```text
+slug → CharField(1024) unique, indexed
+name → CharField(1024)
+description → TextField (default "")
+context → CharField(1024) # one of the PermissionContext values
+temp_deleted → BooleanField (default False)
+```
+
+The available permissions are not stored as a fixed list in the DB; they are registered in code via `PermissionController` (`care/security/permissions/base.py`), which aggregates per-domain `enum.Enum` handlers (e.g. `PatientPermissions`, `FacilityPermissions`). Each member's value is a `Permission(name, description, context, roles)` dataclass.
+
+## Enum / value tables
+
+### `RoleContext` values
+
+Each element of `contexts` is validated against this enum (`care/security/roles/role.py`). These are the **organizational boundary** types a role can apply to — distinct from `PermissionContext`.
+
+| Value | Meaning |
+| --- | --- |
+| `FACILITY` | Role applies within a facility |
+| `GOVT_ORG` | Role applies within a government organization |
+| `ROLE_ORG` | Role applies within a role (user-group) organization |
+
+### `PermissionContext` values
+
+`PermissionModel.context` / `Permission.context` is one of these (`care/security/permissions/constants.py`). The `permissions` mixins (below) filter grants by these contexts when resolving a user's effective permissions.
+
+| Value |
+| --- |
+| `GENERIC` |
+| `FACILITY` |
+| `PATIENT` |
+| `QUESTIONNAIRE` |
+| `ORGANIZATION` |
+| `FACILITY_ORGANIZATION` |
+| `ENCOUNTER` |
+
+### `PermissionEnum` (write field for `permissions`)
+
+`RoleCreateSpec.permissions` is typed against `PermissionController.get_enum()` — a `str` enum built dynamically at runtime from every registered permission `name` across all permission handlers. Values are the permission identifiers (slugs), e.g. `can_create_patient`, `can_write_patient`, `can_list_patients`, `can_view_clinical_data`. The full set is the union of all handlers listed in `PermissionController.internal_permission_handlers` plus any registered via plugins, so the enum is deployment-dependent rather than fixed.
+
+### Built-in (`is_system`) roles
+
+Seeded by `RoleController` (`care/security/roles/role.py`). These are created with `is_system=True` and cannot be created, updated, or deleted through the API.
+
+| Role | Contexts |
+| --- | --- |
+| `Doctor` | `FACILITY`, `GOVT_ORG` |
+| `Nurse` | `FACILITY`, `GOVT_ORG` |
+| `Staff` | `FACILITY`, `GOVT_ORG` |
+| `Volunteer` | `FACILITY`, `GOVT_ORG` |
+| `Pharmacist` | `FACILITY` |
+| `Administrator` | `FACILITY`, `GOVT_ORG` |
+| `Facility Admin` | `FACILITY` |
+| `Admin` | `FACILITY`, `GOVT_ORG` |
+| `Admin` (role org) | `ROLE_ORG` |
+| `Manager` (role org) | `ROLE_ORG` |
+| `Member` (role org) | `ROLE_ORG` |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB → Pydantic, via `perform_extra_serialization`) and `de_serialize` (Pydantic → DB, via `perform_extra_deserialization`). `RoleBaseSpec` sets `__exclude__ = ["permissions"]` so the `permissions` field is handled explicitly by each subclass rather than copied field-by-field.
+
+| Spec class | Role | Fields |
+| --- | --- | --- |
+| `RoleBaseSpec` | shared base | `id`, `name`, `description`, `is_system`, `is_archived`, `contexts` |
+| `RoleCreateSpec` | write · create & update | base fields + `permissions: list[PermissionEnum]` |
+| `RoleReadSpec` | read · detail/list | base fields + `permissions: list[PermissionSpec]` |
+| `RoleReadMinimalSpec` | read · minimal/embedded | base fields only |
+| `PermissionSpec` | nested (read) | `name`, `description`, `slug` (`SlugType`), `context` |
+
+### Field shapes & bindings
+
+| Field | Spec type | Notes |
+| --- | --- | --- |
+| `id` | `UUID4 \| None` | The role's `external_id` on read (set in `perform_extra_serialization`); ignored on write |
+| `name` | `str \| None` | Required & non-blank on create; case-insensitive uniqueness enforced |
+| `description` | `str \| None` | Optional |
+| `is_system` | `bool \| None` (default `False`) | Must be falsy on write — `True` is rejected |
+| `is_archived` | `bool \| None` (default `False`) | — |
+| `contexts` | `list[RoleContext]` | Bound to the `RoleContext` enum |
+| `permissions` (write) | `list[PermissionEnum]` (default `[]`) | Dynamic `str` enum of every registered permission name; must contain ≥ 1 entry |
+| `permissions` (read) | `list[PermissionSpec]` | Resolved server-side from the role's active grants |
+| `PermissionSpec.slug` | `SlugType` | `str`, length 5–50, pattern `^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$` |
+
+### Validation (`RoleCreateSpec.validate_role`, mode="after")
+
+Runs on both create and update (the `is_update` flag and target `object` are read from the serializer context):
+
+- On create, `name` must be present and non-blank (`"Role name cannot be empty"`).
+- `name` must be unique case-insensitively (`name__iexact`); on update the current row is excluded. Duplicate → `"Role with this name already exists"`.
+- Updating a role whose `is_system=True` → `"Cannot update system roles"`.
+- `is_system=True` in the payload → `"Cannot create system roles"`.
+- `permissions` must be non-empty → `"At least one permission must be assigned to the role"`.
+- `permissions` is de-duplicated (`list(set(...))`).
+
+### Server-side serialization behaviour
+
+- **Write** (`RoleCreateSpec.perform_extra_deserialization`): the validated `permissions` list is attached to the model instance as `obj.permissions` (a transient attribute, since `permissions` is in `__exclude__` and is not a model column). The view/service layer materializes these into `RolePermission` rows.
+- **Read detail** (`RoleReadSpec.perform_extra_serialization`): sets `id = obj.external_id` and fills `permissions` from `obj.get_permissions_for_role()` — the cached list of `{name, slug, context, description}` for every active (non-`temp_deleted`) grant.
+- **Read minimal** (`RoleReadMinimalSpec.perform_extra_serialization`): sets `id = obj.external_id` only; omits `permissions` entirely.
+
+### Permission resolution mixins
+
+`care/emr/resources/permissions.py` defines mixins that other resource specs inherit to expose the **calling user's** effective permissions on an object (not the role's own list). They populate `mapping["permissions"]` in `perform_extra_user_serialization` only when an authenticated `user` is passed to `serialize`:
+
+| Mixin | Resolves | Context filter |
+| --- | --- | --- |
+| `PermissionsMixin` | base — adds `permissions: list[str]` | — |
+| `PatientPermissionsMixin` | roles the user holds on a patient | `permission__context in ("PATIENT", "FACILITY")` |
+| `EncounterPermissionsMixin` | roles the user holds on an encounter | `permission__context in ("ENCOUNTER", "PATIENT")` |
+| `FacilityPermissionsMixin` | roles on facility root + sub-orgs; also exposes `root_org_permissions` and `child_org_permissions` (child set excludes `can_update_facility`) | none (root) / excludes `can_update_facility` (child) |
+
+## Methods & save behaviour
+
+`RoleModel` resolves its effective permissions through two cached helpers backed by the Django cache (Redis), with a 7-day TTL:
+
+- `get_permission_sk_for_role() → list[str]` — returns the `slug` of every active permission on the role. Cache key `role_permissions_cache:{id}`.
+- `get_permissions_for_role() → list[dict]` — returns full permission detail (`name`, `slug`, `context`, `description`) for every active permission. Cache key `role_permissions:{id}`. Used by `RoleReadSpec`.
+
+Both queries join through `RolePermission` and exclude `temp_deleted=True` grants.
+
+### Cache invalidation signal
+
+A `@receiver([post_save, post_delete], sender=RolePermission)` handler (`invalidate_role_permissions_cache`) clears both cache keys for the affected `role_id` whenever a `RolePermission` is created, updated, or deleted. Integrators mutating role-permission links through the ORM get automatic cache invalidation; raw SQL writes bypass the signal and leave stale caches.
+
+## API integration notes
+
+- Roles are referenced from access-control models (for example organization memberships and patient/encounter associations) to resolve a user's effective permissions in a given context.
+- `is_system` roles are platform-maintained: the spec rejects creating one (`is_system=True`) and rejects any update to an existing system role.
+- Writes go through `RoleCreateSpec` (create and update). `permissions` is required (≥ 1) and is de-duplicated; the resolved set is attached to the model as `obj.permissions` for the service layer to translate into `RolePermission` rows.
+- Reads use `RoleReadSpec` (full, with nested `PermissionSpec[]`) or `RoleReadMinimalSpec` (no permissions) depending on the endpoint.
+- Each element of `contexts` is bound to the `RoleContext` enum (`FACILITY`, `GOVT_ORG`, `ROLE_ORG`) — not an open string at the API layer, even though the model column is.
+- Permission membership is managed through `RolePermission` rows, not by editing `RoleModel` directly — prefer ORM writes so the cache-invalidation signal fires.
+
+## Related
+
+- Reference: [Permission](../access/permission.mdx)
+- Reference: [Permission association](../access/permission-association.mdx)
+- Reference: [User](../access/user.mdx)
+- Reference: [Organization](../facility/organization.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [role.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/security/models/role.py)
+- Source: [role/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py)
+- Source: [permissions.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py)
diff --git a/versioned_docs/version-3.1/references/access/user.mdx b/versioned_docs/version-3.1/references/access/user.mdx
new file mode 100644
index 0000000..e8d4ab9
--- /dev/null
+++ b/versioned_docs/version-3.1/references/access/user.mdx
@@ -0,0 +1,268 @@
+---
+sidebar_position: 1
+---
+
+# User & Skills
+
+Technical reference for the `User` module in Care EMR. The Django model is the **storage** layer; the Pydantic resource specs in `care/emr/resources/user/` and `care/emr/resources/mfa/` are the **API/implementation** layer that define enums, validation, the shape of opaque `JSONField`s, and the read/write request/response schemas.
+
+**Source:** [`care/users/models.py`](https://github.com/ohcnetwork/care/blob/develop/care/users/models.py), [`care/facility/models/patient.py`](https://github.com/ohcnetwork/care/blob/develop/care/facility/models/patient.py), [`care/emr/resources/user/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/user/spec.py), [`care/emr/resources/mfa/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/mfa/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `User` | Care account: authentication, profile, clinician credentials, and notification/MFA data |
+| `Skill` | Instance-wide skill/competency definition |
+| `UserSkill` | Through model linking a `User` to a `Skill` |
+| `UserFlag` | Feature flag scoped to a single `User` |
+| `PlugConfig` | Slug-keyed JSON configuration for installed plugs |
+| `MobileOTP` | One-time password issued against a phone number |
+
+Base classes vary by model:
+
+- `User` extends Django's `AbstractUser` (adds `external_id` and a soft-delete `deleted` flag of its own; not an `EMRBaseModel`).
+- `Skill`, `UserSkill`, and `MobileOTP` extend [`BaseModel`](../foundation/base-model.mdx) (`external_id`, `created_date`, `modified_date`, soft-delete via `deleted`).
+- `UserFlag` extends `BaseFlag` (a `BaseModel` subclass that adds a `flag` field plus cache-invalidating `save()`).
+- `PlugConfig` extends plain `django.db.models.Model` (no audit or soft-delete fields).
+
+## `User` fields
+
+`User` extends `AbstractUser`, so it inherits `password`, `email`, `first_name`, `last_name`, `is_staff`, `is_active`, `is_superuser`, `last_login`, and `date_joined` from Django. The fields below are Care additions or overrides. Queried through `CustomUserManager`, which filters out `deleted=True` rows by default (`get_entire_queryset()` bypasses the filter).
+
+### Identity & profile
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `external_id` | `UUIDField` | Unique, indexed; stable public identifier. Surfaced as `id` in every read spec |
+| `username` | `CharField(150)` | Unique; model-validated by `UsernameValidator`. API additionally enforces `^[a-zA-Z0-9_-]{3,}$` and global uniqueness (incl. deleted users) on create |
+| `user_type` | `CharField(100)` | Nullable; free-text role label (e.g. `administrator`). Not exposed by any user spec — set internally / by `create_superuser` |
+| `prefix` | `CharField(10)` | Name prefix (e.g. Dr.); spec caps length at 10. Optional in specs |
+| `suffix` | `CharField(50)` | Name suffix; spec caps length at 50. Optional in specs |
+| `gender` | `CharField(100)` | Nullable in DB. Write specs constrain it to `GenderChoices` (required); read specs return it as a plain string. See [GenderChoices values](#genderchoices-values) |
+| `old_gender` | `IntegerField` | Legacy `GENDER_CHOICES` (`1` Male, `2` Female, `3` Non-binary); nullable. Not exposed by any user spec |
+| `date_of_birth` | `DateField` | Nullable. Read-only in `CurrentUserRetrieveSpec` (string, nullable) |
+| `profile_picture_url` | `CharField(500)` | Storage key. Read specs return the **resolved** URL via `read_profile_picture_url()`, not the raw key |
+| `created_by` | `FK → User (self, SET_NULL)` | Account creator; `related_name="users_created"`. Serialized in `UserRetrieveSpec` as a cached nested `UserSpec` dict (nullable) |
+| `deleted` | `BooleanField` | Soft-delete flag (default `False`); exposed read-only in `UserSpec` |
+| `verified` | `BooleanField` | Default `False`; read-only in `CurrentUserRetrieveSpec` |
+| `is_service_account` | `BooleanField` | Default `False`; marks machine/integration accounts. Settable on create, read in `UserRetrieveSpec` |
+
+### Contact
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `phone_number` | `CharField(14)` | Model: `mobile_or_landline_number_validator`. Specs cap at 14 chars and required; on create, must be globally unique (rejects existing) |
+| `alt_phone_number` | `CharField(14)` | Nullable; model `mobile_validator`. Read-only string in `CurrentUserRetrieveSpec` |
+| `video_connect_link` | `URLField` | Nullable; tele-consult link. Not exposed by any user spec |
+
+### Clinician credentials
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `qualification` | `TextField` | Nullable. Read-only (nullable string) in `CurrentUserRetrieveSpec` |
+| `doctor_experience_commenced_on` | `DateField` | Nullable; used to derive experience. Read-only (nullable string) in `CurrentUserRetrieveSpec` |
+| `doctor_medical_council_registration` | `CharField(255)` | Nullable; council registration number. Read-only (nullable string) in `CurrentUserRetrieveSpec` |
+| `weekly_working_hours` | `IntegerField` | Nullable; model-validated `0`–`168`. Read-only (nullable string) in `CurrentUserRetrieveSpec` |
+
+### Organization & facility
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `geo_organization` | `FK → emr.Organization (SET_NULL)` | Geographic/administrative org. Write specs accept it as a `UUID4` and resolve to an `Organization` with `org_type="govt"` (404 otherwise). Read in `UserRetrieveSpec` as a nested `OrganizationReadSpec` dict. Listed in `UserBaseSpec.__exclude__`, so it never round-trips through the generic field copy |
+| `home_facility` | `FK → facility.Facility (PROTECT)` | Primary facility. Not exposed by any user spec |
+| `skills` | `ManyToManyField → Skill` | Through `UserSkill` |
+| `cached_role_orgs` | `JSONField` | Nullable; cached role/organization map. Lazily populated by `get_cached_role_orgs()`; surfaced as `role_orgs` in read specs. Platform-maintained — do not write directly |
+
+### Notifications, MFA & preferences
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `pf_endpoint` | `TextField` | Web-push endpoint; nullable. Read-only in `CurrentUserRetrieveSpec` |
+| `pf_p256dh` | `TextField` | Web-push key; nullable. Read-only in `CurrentUserRetrieveSpec` |
+| `pf_auth` | `TextField` | Web-push auth secret; nullable. Read-only in `CurrentUserRetrieveSpec` |
+| `totp_secret` | `TextField` | Nullable; TOTP seed. Never serialized; written via the MFA setup/verify flow |
+| `mfa_settings` | `JSONField` | Default `{}`. Shape: `{ "totp": { "enabled": bool, ... } }`. `is_mfa_enabled()` reads `mfa_settings["totp"]["enabled"]`; surfaced as the boolean `mfa_enabled` in `UserSpec`. Not written directly by clients |
+| `preferences` | `JSONField` | Default `{}`; open per-user UI/app preferences bag. Read-only `dict` in `CurrentUserRetrieveSpec` |
+
+`REQUIRED_FIELDS = ["email"]` and the model is managed by `CustomUserManager`.
+
+## Enum values
+
+### GenderChoices values
+
+Bound to `gender` on every **write** spec (`UserUpdateSpec`, `UserCreateSpec`). Defined in [`care/emr/resources/patient/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/patient/spec.py) and reused here (read specs return `gender` as a free string).
+
+| Value |
+| --- |
+| `male` |
+| `female` |
+| `non_binary` |
+| `transgender` |
+
+Note: `create_superuser()` sets `gender="non_binary"`, which matches this enum. The legacy integer `GENDER_CHOICES` on the model (`1` Male, `2` Female, `3` Non-binary) is separate and unrelated to the API.
+
+### LoginMethod values
+
+Used by `MFALoginRequest.method` in [`care/emr/resources/mfa/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/mfa/spec.py).
+
+| Value | Meaning |
+| --- | --- |
+| `totp` | Authenticator app one-time code |
+| `backup` | Single-use backup recovery code |
+
+## Resource specs (API schema)
+
+All user specs build on `EMRResource` ([`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)), which provides `serialize` (DB → pydantic) and `de_serialize` (pydantic → DB) plus the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `UserBaseSpec` sets `__model__ = User` and `__exclude__ = ["geo_organization"]`.
+
+| Spec class | Role | Fields / behaviour |
+| --- | --- | --- |
+| `UserBaseSpec` | shared base | `id`, `first_name`, `last_name`, `phone_number` (max 14), `prefix` (max 10, optional), `suffix` (max 50, optional) |
+| `UserUpdateSpec` | write · update | Base + `gender` (`GenderChoices`, required), `phone_number` (max 14), `geo_organization` (`UUID4`, optional). On de-serialize, resolves `geo_organization` to an `Organization` with `org_type="govt"` (404 if missing) |
+| `UserCreateSpec` | write · create | Extends `UserUpdateSpec` + `password` (optional), `username`, `email`, `is_service_account` (default `False`), `role_orgs: list[UserRoleOrgCreateSpec]`. Validates username pattern/uniqueness, phone uniqueness, email format/uniqueness, password strength. On de-serialize, stashes `role_orgs` on the instance and calls `set_password()` |
+| `UserRoleOrgCreateSpec` | write · nested | `organization: UUID4`, `role: UUID4` — a single role-in-organization assignment supplied at user creation |
+| `UserSpec` | read · list/summary | Base + `last_login`, `profile_picture_url`, `gender` (string), `username`, `mfa_enabled` (default `False`), `phone_number`, `deleted` (default `False`), `role_orgs: dict`. `@cacheable(use_base_manager=True)` (cached, incl. deleted users). Sets `id` from `external_id`, resolves picture URL, computes `mfa_enabled`, loads `role_orgs` |
+| `UserRetrieveSpec` | read · detail | Extends `UserSpec` + `geo_organization: dict`, `created_by: dict \| None`, `email`, `flags: list[str]`, `is_service_account`. Serializes `created_by` (cached `UserSpec`), `geo_organization` (`OrganizationReadSpec`), and `flags` (`get_all_flags()`) |
+| `CurrentUserRetrieveSpec` | read · self (`/users/getcurrentuser`-style) | Extends `UserRetrieveSpec` + `is_superuser`, `qualification`, `doctor_experience_commenced_on`, `doctor_medical_council_registration`, `weekly_working_hours`, `alt_phone_number`, `date_of_birth` (all nullable strings), `verified`, `pf_endpoint`/`pf_p256dh`/`pf_auth`, `organizations: list[dict]`, `facilities: list[dict]`, `permissions: list[str]`, `preferences: dict`. Computes the caller's organizations, facilities (excluding deleted), and resolved permission slugs |
+| `PublicUserReadSpec` | read · public | Base + `last_login`, `profile_picture_url`, `gender`, `username`, `role_orgs: list[dict]`. Minimal unauthenticated projection |
+
+Plain-`BaseModel` request/response DTOs (not `EMRResource`, no model binding) for the password-reset flow, all in `user/spec.py`:
+
+| DTO | Shape |
+| --- | --- |
+| `ResetPasswordCheckRequest` | `{ token: str }` |
+| `ResetPasswordConfirmRequest` | `{ token: str, password: str }` |
+| `ResetPasswordResponse` | `{ detail: str }` |
+| `ResetPasswordRequestTokenRequest` | `{ username: str }` |
+
+### MFA specs
+
+Plain-`BaseModel` DTOs in [`care/emr/resources/mfa/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/mfa/spec.py) (no model binding; drive the TOTP/backup-code flow that writes `totp_secret` and `mfa_settings`):
+
+| DTO | Direction | Shape |
+| --- | --- | --- |
+| `TOTPSetupResponse` | response | `{ uri: str, secret_key: str }` |
+| `TOTPVerifyRequest` | request | `{ code: str }` |
+| `TOTPVerifyResponse` | response | `{ backup_codes: list[str] }` |
+| `PasswordVerifyRequest` | request | `{ password: str }` |
+| `MFALoginRequest` | request | `{ method: LoginMethod, code: str, temp_token: str }` |
+| `MFALoginResponse` | response | `{ access: str, refresh: str }` (JWT pair) |
+
+### Validation rules (write specs)
+
+| Field | Rule | Source |
+| --- | --- | --- |
+| `username` | `^[a-zA-Z0-9_-]{3,}$`; must not already exist (checked against the **entire** queryset, incl. deleted) | `UserCreateSpec.validate_username` |
+| `phone_number` | Must not already exist on any user | `UserCreateSpec.validate_phone_number` |
+| `email` | Must not already exist; must pass Django `validate_email` | `UserCreateSpec.validate_user_email` |
+| `password` | If provided, must pass Django `validate_password` (else "Password is too weak"); `None` is allowed | `UserCreateSpec.validate_password` |
+| `gender` | Must be a `GenderChoices` member | type annotation |
+| `geo_organization` | Must reference an existing `Organization` with `org_type="govt"` | `UserUpdateSpec.perform_extra_deserialization` |
+
+### Server-maintained behaviour
+
+- **`role_orgs` on create** — `UserCreateSpec.perform_extra_deserialization` copies `role_orgs` onto `obj._role_orgs` for the view to apply role/organization assignments after the user is saved; it is **not** a DB column.
+- **`set_password`** — create hashes the supplied password (or sets an unusable password when `None`) via `obj.set_password(self.password)`.
+- **Cache** — `UserSpec` is `@cacheable(use_base_manager=True)`; saving a `User` invalidates the cache (via `post_save`), and `created_by` is read through `model_from_cache(UserSpec, ...)`. `use_base_manager=True` lets cached lookups resolve soft-deleted users.
+- **Computed read fields** — `id` (from `external_id`), `profile_picture_url` (resolved), `mfa_enabled`, `role_orgs`, `flags`, `geo_organization`, `created_by`, and the current-user `organizations`/`facilities`/`permissions` are all populated in `perform_extra_serialization`, never copied straight from columns.
+
+## Shared spec types
+
+Referenced by the resource layer (defined under `care/emr/resources/base.py` and `care/emr/resources/common/`). User specs do not bind any coded fields, but the base period/contact types are part of the shared vocabulary:
+
+| Type | Shape | Source |
+| --- | --- | --- |
+| `PeriodSpec` | `{ start: datetime \| None, end: datetime \| None }` — both must be **timezone-aware**; `start ≤ end` enforced | [`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py) |
+| `Period` | `{ id: str?, start: datetime?, end: datetime? }`, `extra="forbid"` | [`common/period.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/period.py) |
+| `ContactPoint` | `{ system: ContactPointSystemChoices, value: str, use: ContactPointUseChoices }` | [`common/contact_point.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/contact_point.py) |
+| `PhoneNumber` | E.164 string (`PhoneNumberValidator`) | [`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py) |
+
+`ContactPointSystemChoices`: `phone`, `fax`, `email`, `pager`, `url`, `sms`, `other`. `ContactPointUseChoices`: `home`, `work`, `temp`, `old`, `mobile`.
+
+## Related models
+
+### `Skill`
+
+Instance-wide competency definition. Extends [`BaseModel`](../foundation/base-model.mdx).
+
+```text
+name → CharField(255), unique
+description → TextField (nullable, default "")
+```
+
+### `UserSkill`
+
+Through model joining `User` and `Skill`. Extends [`BaseModel`](../foundation/base-model.mdx).
+
+```text
+user → FK User (CASCADE, nullable)
+skill → FK Skill (CASCADE, nullable)
+```
+
+A unique constraint (`unique_user_skill`) prevents duplicate `(skill, user)` pairs among non-deleted rows.
+
+### `UserFlag`
+
+Feature flag attached to a single user. Extends `BaseFlag` (a [`BaseModel`](../foundation/base-model.mdx) subclass with a `flag` field).
+
+```text
+user → FK User (CASCADE)
+flag → CharField(1024) (inherited from BaseFlag)
+```
+
+`flag_type = FlagType.USER`. A unique constraint (`unique_user_flag`) prevents duplicate `(user, flag)` pairs among non-deleted rows. Flags are read through `UserFlag.check_user_has_flag(user_id, flag_name)` and `get_all_flags(user_id)`, both cache-backed (TTL 1 day). `UserRetrieveSpec.flags` surfaces these names as a `list[str]`.
+
+### `PlugConfig`
+
+Slug-keyed configuration for installed plugs. Extends plain `models.Model` (no audit/soft-delete fields).
+
+```text
+slug → CharField(255), unique
+meta → JSONField (default {})
+```
+
+### `MobileOTP`
+
+Defined in `care/facility/models/patient.py`. One-time password issued against a phone number for verification flows. Extends [`BaseModel`](../foundation/base-model.mdx).
+
+```text
+is_used → BooleanField (default False)
+phone_number → CharField(14) (mobile_or_landline_number_validator)
+otp → CharField(10)
+```
+
+## Methods & save behaviour
+
+`User` overrides no `save()`/`delete()` but exposes notable helpers:
+
+- `get_cached_role_orgs()` — returns `cached_role_orgs` if set; otherwise loads from `OrganizationUser.get_cached_role_orgs(self.id)` and persists it via `save(update_fields=["cached_role_orgs"])`. Read specs surface the result as `role_orgs`.
+- `read_profile_picture_url()` — resolves `profile_picture_url` against `FACILITY_CDN` or the S3 bucket endpoint; returns `None` when unset. Used by every read spec's `profile_picture_url`.
+- `is_mfa_enabled()` — `True` when `mfa_settings["totp"]["enabled"]` is set; surfaced as `mfa_enabled`.
+- `full_name` (property) — joins `prefix`, `get_full_name()`, and `suffix`.
+- `check_username_exists(username)` (static) — checks across the entire (including deleted) queryset; used by `UserCreateSpec.validate_username`.
+- `get_all_flags()` — delegates to `UserFlag.get_all_flags(self.id)`; surfaced as `flags` in `UserRetrieveSpec`.
+
+`CustomUserManager` adds:
+
+- `get_queryset()` filters `deleted=False`; `get_entire_queryset()` returns all rows.
+- `create_superuser()` forces `phone_number="+919696969696"`, `gender="non_binary"`, and `user_type="administrator"`.
+- `make_random_password()` generates a secure password guaranteeing lower/upper/digit composition.
+
+`UserFlag.save()` (via `BaseFlag`) validates the flag name against the registry and invalidates the per-flag and all-flags cache keys on every write.
+
+## API integration notes
+
+- `User` is exposed through Care's REST API; `external_id` (returned as `id`) is the public identifier, never the integer PK or `username` alone.
+- `deleted` is honoured by `CustomUserManager` — soft-deleted users are excluded from normal queries, but cached `UserSpec` lookups use the base manager (`use_base_manager=True`) so they can still resolve deleted users (e.g. as `created_by`).
+- Authorization is not stored on `User`. Access is resolved through the roles/permissions/organization system; `cached_role_orgs` is a platform-maintained cache — do not write it directly from clients. New users supply `role_orgs` (organization + role UUIDs) at create time.
+- `mfa_settings`, `preferences`, and `PlugConfig.meta` are open JSON bags for evolving config without migrations. `mfa_settings` is mutated by the MFA flow (`mfa/spec.py`), not by user create/update.
+- `is_service_account` distinguishes machine/integration accounts from human users.
+- Write paths use `UserCreateSpec` / `UserUpdateSpec`; read paths use `UserSpec` (list), `UserRetrieveSpec` (detail), `CurrentUserRetrieveSpec` (self), and `PublicUserReadSpec` (public). Picture URLs, MFA status, roles, flags, and nested org/creator objects are computed server-side.
+
+## Related
+
+- Reference: [Role](./role.mdx), [Permission](./permission.mdx), [Permission association](./permission-association.mdx)
+- Reference: [Organization](../facility/organization.mdx), [Facility](../facility/facility.mdx)
+- Concept: [Patient](../../concepts/clinical/patient.mdx) (`GenderChoices` is shared with the patient resource)
+- Base: [Base model](../foundation/base-model.mdx)
+- Source: [users/models.py](https://github.com/ohcnetwork/care/blob/develop/care/users/models.py), [facility/models/patient.py](https://github.com/ohcnetwork/care/blob/develop/care/facility/models/patient.py), [emr/resources/user/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/user/spec.py), [emr/resources/mfa/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/mfa/spec.py), [emr/resources/base.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
diff --git a/versioned_docs/version-3.1/references/billing/_category_.json b/versioned_docs/version-3.1/references/billing/_category_.json
new file mode 100644
index 0000000..512129e
--- /dev/null
+++ b/versioned_docs/version-3.1/references/billing/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Billing",
+ "position": 6,
+ "key": "billing-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Billing",
+ "description": "Patient accounts, charge items and their definitions, invoices, and payment reconciliation."
+ }
+}
diff --git a/versioned_docs/version-3.1/references/billing/account.mdx b/versioned_docs/version-3.1/references/billing/account.mdx
new file mode 100644
index 0000000..894bbf2
--- /dev/null
+++ b/versioned_docs/version-3.1/references/billing/account.mdx
@@ -0,0 +1,218 @@
+---
+sidebar_position: 1
+---
+
+# Account
+
+Technical reference for the `Account` module in Care EMR.
+
+`Account` is a financial bucket aggregating all transactions made against a patient within a facility. The Django model is the **storage layer** — several fields are opaque `JSONField`s whose real structure lives in the Pydantic **resource specs** (`care/emr/resources/account/`). This doc enriches the model fields with the spec-defined enums, JSON shapes, validation, and the API read/write schemas.
+
+**Source:**
+- Model: [`care/emr/models/account.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/account.py)
+- Resource spec: [`care/emr/resources/account/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/spec.py)
+- Default account helper: [`account/default_account.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/default_account.py)
+- Balance sync: [`account/sync_items.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/sync_items.py)
+- Base resource: [`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Account` | A financial bucket for all transactions made against a patient in a facility, aggregating charges and balances across encounters |
+
+`Account` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+An account is perpetual by default: a charge item is placed on the default account for a patient within a facility, and to start with only one active+open account exists per patient per facility (see [`get_default_account`](#default-account)). Accounts can be balanced and closed once a patient is discharged, and invoices are created against accounts or items within them.
+
+## `Account` fields
+
+### Identity & scope
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → facility.Facility` (PROTECT) | yes | Facility where the account is created. Server-set; not exposed on write specs |
+| `patient` | `FK → emr.Patient` (PROTECT) | yes | The entity that incurred the expenses. On create, supplied as a patient `external_id` (UUID) and resolved server-side |
+| `name` | `CharField(255)` | yes | Human-readable label. Default account auto-names as `"{patient.name} {YYYY-MM-DD}"` |
+| `description` | `TextField` | no | Nullable; explanation of purpose/use |
+| `primary_encounter` | `FK → emr.Encounter` (SET_NULL, nullable) | no | Optional link to associate the account with a single encounter for reports, summaries, and insurance paperwork. Settable only via the update spec (supplied as an encounter `external_id`) |
+
+### Status
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(255)` | yes | Account lifecycle status — `AccountStatusOptions` enum (see [values](#accountstatusoptions-values)) |
+| `billing_status` | `CharField(255)` | yes | Billing lifecycle status — `AccountBillingStatusOptions` enum (see [values](#accountbillingstatusoptions-values)) |
+| `service_period` | `JSONField` (default `{}`) | yes (on spec) | Transaction window. Shape = `PeriodSpec { start, end }` — see [service_period shape](#service_period-shape) |
+
+### Balances & totals
+
+These decimal totals are stored with `max_digits=20, decimal_places=6` and default to `0`. They are **platform-maintained aggregates** computed by [`sync_account_items`](#balance-sync), not client-writable. None appear on the write specs.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `total_net` | `DecimalField` | Net total before adjustments. Currently **not populated** by `sync_account_items` (logic commented out); stays at default `0` |
+| `total_gross` | `DecimalField` | Sum of `total_price` over charge items in `paid`/`billed` status |
+| `total_paid` | `DecimalField` | Sum of `active`+`complete` payment reconciliations minus credit-note reconciliations |
+| `total_balance` | `DecimalField` | `total_gross − total_paid` |
+| `total_billable_charge_items` | `DecimalField` | Sum of `total_price` over charge items in `billable` status |
+| `total_price_components` | `JSONField` (default `{}`) | Intended breakdown of price components (taxes, discounts, etc.); shape = list of `MonetaryComponent` (see [below](#monetarycomponent-shape)). Currently **not populated** by `sync_account_items` (logic commented out) |
+| `cached_items` | `JSONField` (default `{}`) | Denormalized snapshot of the account's charge items. Currently **not populated** by `sync_account_items` (logic commented out). Serialized as a `list` on retrieve |
+| `calculated_at` | `DateTimeField` (nullable) | Timestamp the balances were last calculated (set to `care_now()` on each sync) |
+
+### Tags & extensions
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `tags` | `ArrayField[int]` (default `[]`) | Tag IDs applied to the account; rendered via `SingleFacilityTagManager` on read |
+| `extensions` | `JSONField` (default `{}`) | Open extension bag for deployment-specific metadata. Validated by `ExtensionValidator` against `ExtensionResource.account`; surfaced on the retrieve spec |
+
+## Enum values
+
+### `AccountStatusOptions` values
+
+Lifecycle status of the account. Stored as the raw enum value string in `status`.
+
+| Value | Meaning |
+| --- | --- |
+| `active` | Account is open and in use (default-account state) |
+| `inactive` | Account is no longer actively used |
+| `entered_in_error` | Account was created in error |
+| `on_hold` | Account is temporarily paused |
+
+Note: values use underscores (e.g. `entered_in_error`, `on_hold`), not the FHIR-style hyphenated forms.
+
+### `AccountBillingStatusOptions` values
+
+Billing lifecycle status of the account. Stored as the raw enum value string in `billing_status`.
+
+| Value | Meaning |
+| --- | --- |
+| `open` | Billing open / accruing (default-account state) |
+| `carecomplete_notbilled` | Care complete, not yet billed |
+| `billing` | Billing in progress |
+| `closed_baddebt` | Closed as bad debt |
+| `closed_voided` | Closed and voided |
+| `closed_completed` | Closed, billing completed |
+| `closed_combined` | Closed, combined into another account |
+
+## JSON field shapes
+
+### `service_period` shape
+
+`service_period` stores a `PeriodSpec` (from [`resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)):
+
+```text
+PeriodSpec {
+ start: datetime | null # ISO 8601, timezone-aware
+ end: datetime | null # ISO 8601, timezone-aware
+}
+```
+
+Validation (`PeriodSpec.validate_period`):
+- `start`, if present, **must be timezone-aware** (naive datetimes raise `"Start Date must be timezone aware"`).
+- `end`, if present, **must be timezone-aware** (`"End Date must be timezone aware"`).
+- If both present, `start <= end` (`"Start Date cannot be greater than End Date"`).
+
+The default account is created with only `start` set, formatted as `"%Y-%m-%dT%H:%M:%S.%fZ"` (`care_now()`).
+
+The FHIR-style [`Period`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/period.py) common type (`{ id?, start?, end? }`, `extra="forbid"`) is a related shape but `Account` binds `service_period` to `PeriodSpec`, not `Period`.
+
+### `MonetaryComponent` shape
+
+`total_price_components` is intended to hold a list of [`MonetaryComponent`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py) entries (the same shape used across billing):
+
+```text
+MonetaryComponent {
+ monetary_component_type: base | surcharge | discount | tax | informational
+ code: Coding | null
+ factor: Decimal(max_digits=20, decimal_places=6) | null
+ amount: Decimal(max_digits=20, decimal_places=6) | null
+ tax_included_amount: Decimal(max_digits=20, decimal_places=6) | null
+ global_component: bool = false
+ conditions: EvaluatorConditionSpec[] = []
+}
+```
+
+Key `MonetaryComponent` validation: `tax_included_amount` only allowed on a `base` component; a `base` component must have an `amount`, no `factor`, and no `conditions`; `amount` and `factor` are mutually exclusive; exactly one of `amount`/`factor` is required unless it is a `global_component` with a `code`.
+
+## Resource specs (API schema)
+
+The API layer is defined in [`resources/account/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/spec.py). All specs build on `EMRResource` (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). `AccountSpec` sets `__exclude__ = ["patient"]` and binds `___extension_resource_type__ = ExtensionResource.account`.
+
+| Spec class | Role | Fields exposed (beyond base) |
+| --- | --- | --- |
+| `AccountSpec` | shared base | `id`, `status`, `billing_status`, `name`, `service_period` (`PeriodSpec`), `description` |
+| `AccountCreateSpec` | write · create | base + `patient` (UUID, required) |
+| `AccountUpdateSpec` | write · update | base + `primary_encounter` (UUID, optional) |
+| `AccountMinimalReadSpec` | read · minimal | base + `total_gross`, `total_paid`, `total_balance`, `total_billable_charge_items` (all `Decimal` 20/6), `calculated_at`, `created_date`, `modified_date` |
+| `AccountReadSpec` | read · list | minimal + `patient` (serialized `PatientListSpec`), `tags` (rendered tag dicts) |
+| `AccountRetrieveSpec` | read · detail | read + `patient` (serialized `PatientRetrieveSpec`), `primary_encounter` (serialized `EncounterRetrieveSpec` when set), `cached_items` (`list`), `total_price_components` (`dict`), `extensions` (`dict`) |
+
+### Validation & server-side behaviour
+
+- **Create** (`AccountCreateSpec`): mixes in `ExtensionValidator` (validates `extensions`) and requires `patient` as a UUID. `perform_extra_deserialization` resolves it via `get_object_or_404(Patient, external_id=...)` and assigns `obj.patient`. `patient` is otherwise excluded from the base serialize loop.
+- **Update** (`AccountUpdateSpec`): mixes in `ExtensionValidator`; `primary_encounter` is optional. If supplied, `perform_extra_deserialization` resolves it via `get_object_or_404(Encounter, external_id=...)` and assigns `obj.primary_encounter`.
+- **Status / billing_status** are validated against `AccountStatusOptions` / `AccountBillingStatusOptions` — invalid values are rejected by the Pydantic enums.
+- **Read serialization**: `perform_extra_serialization` sets `mapping["id"] = obj.external_id`; `AccountReadSpec` additionally serializes the patient (`PatientListSpec`) and renders tags via `SingleFacilityTagManager`; `AccountRetrieveSpec` serializes the full patient (`PatientRetrieveSpec`, scoped to `obj.facility`) and the primary encounter (`EncounterRetrieveSpec`) when present.
+- **Totals are read-only**: balance/aggregate fields appear only on read specs; they are recomputed by `sync_account_items`, never set from client payloads.
+
+
+### Default account
+
+`get_default_account(patient, facility)` ([`default_account.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/default_account.py)) returns the first account for that `patient` + `facility` with `status = active` and `billing_status = open`. If none exists it creates one with:
+
+- `status = active`, `billing_status = open`
+- `service_period = { "start": care_now() }` (start only)
+- `name = "{patient.name} {YYYY-MM-DD}"`
+
+This is how the default account is materialized when the first charge item is added — clients normally do not create accounts directly.
+
+
+### Balance sync
+
+`sync_account_items(account)` ([`sync_items.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/sync_items.py)) recomputes the totals under an `AccountLock`:
+
+- `total_billable_charge_items` = `Σ total_price` of charge items with status `billable`
+- `total_gross` = `Σ total_price` of charge items with status `paid` or `billed`
+- `total_paid` = `Σ amount` of `active` + `complete` non-credit-note payment reconciliations − the same for credit notes
+- `total_balance` = `total_gross − total_paid`
+- `calculated_at` = `care_now()`
+
+All sums use `care_round`. `total_net`, `cached_items`, and `total_price_components` are currently **not** updated (their logic is commented out), so they remain at their defaults. `rebalance_account_task(account_id)` is the Celery task wrapper that runs the sync and saves.
+
+## Related models
+
+The `Account` model is the only class defined in `account.py`. Its relationships are:
+
+```text
+facility → FK facility.Facility (PROTECT)
+patient → FK emr.Patient (PROTECT)
+primary_encounter → FK emr.Encounter (SET_NULL, nullable)
+```
+
+[Charge items](./charge-item.mdx) refer back to the account they belong to and drive its totals; [invoices](./invoice.mdx) and [payment reconciliations](./payment-reconciliation.mdx) are created against the account or its items.
+
+## API integration notes
+
+- Account records are exposed through Care's REST API and align to the FHIR `Account` resource; spec field names may differ from FHIR (e.g. enum values use underscores).
+- The default account is created automatically via `get_default_account` when the first charge item is added for a patient in a facility — clients normally do not create accounts directly.
+- On **create**, supply `patient` as the patient's `external_id` (UUID); `facility` is server-scoped.
+- On **update**, `primary_encounter` accepts an encounter `external_id` (UUID); only the base fields plus `primary_encounter` are mutable.
+- Balance/aggregate fields (`total_net`, `total_gross`, `total_paid`, `total_balance`, `total_billable_charge_items`, `total_price_components`, `cached_items`, `calculated_at`) are platform-maintained and appear only on read specs — never set them from clients.
+- `service_period` requires **timezone-aware** datetimes with `start <= end`.
+- `extensions` is the supported place for custom key-value data without schema migrations; it is validated by `ExtensionValidator` and surfaced only on the retrieve spec.
+- Account data should be gated behind permissions that restrict access to users with rights to financial information.
+
+## Related
+
+- Reference: [Charge item](./charge-item.mdx)
+- Reference: [Charge item definition](./charge-item-definition.mdx)
+- Reference: [Invoice](./invoice.mdx)
+- Reference: [Payment reconciliation](./payment-reconciliation.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Patient concept: [Patient](../../concepts/clinical/patient.mdx)
+- Source: [account.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/account.py)
+- Spec: [account/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/spec.py)
diff --git a/versioned_docs/version-3.1/references/billing/charge-item-definition.mdx b/versioned_docs/version-3.1/references/billing/charge-item-definition.mdx
new file mode 100644
index 0000000..5ad9739
--- /dev/null
+++ b/versioned_docs/version-3.1/references/billing/charge-item-definition.mdx
@@ -0,0 +1,212 @@
+---
+sidebar_position: 3
+---
+
+# Charge Item Definition
+
+Technical reference for the `ChargeItemDefinition` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/charge_item_definition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/charge_item_definition.py)
+- Resource spec: [`care/emr/resources/charge_item_definition/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/charge_item_definition/spec.py)
+- Shared spec: [`care/emr/resources/common/monetary_component.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
+
+A charge item definition is the billing template that answers "how much does resource X cost". It binds a billable resource to a set of price components — base rate, surcharges, discounts, and taxes — that are evaluated when a [charge item](../billing/charge-item.mdx) is generated during data entry.
+
+Two layers describe this resource:
+
+- The **Django model** (`care/emr/models/charge_item_definition.py`) is the storage layer. `price_components` and `discount_configuration` are opaque `JSONField`s — their real structure is not visible in the model.
+- The **Pydantic resource specs** (`care/emr/resources/charge_item_definition/`) are the API layer. They define the enum for `status`, the structured shape of those JSON fields (via `MonetaryComponent` / `DiscountConfiguration`), validation rules, and the read/write schemas. See [Resource specs (API schema)](#resource-specs-api-schema).
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ChargeItemDefinition` | Facility-scoped pricing template defining the price components applied to a billable resource |
+
+`ChargeItemDefinition` extends [`SlugBaseModel`](../foundation/base-model.mdx) — the slug-aware Care EMR base. `SlugBaseModel` extends `EMRBaseModel`, so the model inherits `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and the `history`/`meta` JSON fields, and adds facility-scoped slug helpers (see [Methods & save behaviour](#methods--save-behaviour)).
+
+## `ChargeItemDefinition` fields
+
+### Identity & versioning
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → facility.Facility` | yes | `PROTECT`. The facility that owns this definition |
+| `version` | `IntegerField` | — | Default `1`. Revision number; surfaced read-only by the spec |
+| `status` | `CharField(255)` | yes | Lifecycle status — constrained by `ChargeItemDefinitionStatusOptions` (`draft`, `active`, `retired`). See [Status values](#status-values) |
+| `title` | `CharField(255)` | yes | Human-readable name for the definition |
+| `slug` | `CharField(255)` | yes | Stored slug (see [slug behaviour](#methods--save-behaviour)). On write the client sends `slug_value`; the server prefixes it |
+| `derived_from_uri` | `TextField` | no | Nullable, default `None`. URI of an upstream definition this was derived from |
+
+### Descriptive
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `description` | `TextField` | no | Nullable, default `None`. Natural-language description |
+| `purpose` | `TextField` | no | Nullable, default `None`. Why the definition exists |
+
+### Pricing & rules
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `price_components` | `JSONField` | yes | Default `[]`. List of `MonetaryComponent` — see [Price components shape](#price-components-shape) |
+| `discount_configuration` | `JSONField` | no | Nullable, default `None`. Shaped as `DiscountConfiguration` — see [Discount configuration shape](#discount-configuration-shape) |
+| `can_edit_charge_item` | `BooleanField` | yes | Default `True`. Whether a charge item generated from this definition may be edited after creation |
+| `category` | `FK → emr.ResourceCategory` | no | `CASCADE`, nullable. Categorizes the definition. On write supplied as an `ExtendedSlugType` slug; resolved server-side |
+| `tags` | `ArrayField[int]` | — | Default `[]`. Tag IDs; rendered to objects on read |
+
+## Enum values
+
+### Status values
+
+`ChargeItemDefinitionStatusOptions` (`spec.py`) — `status` is constrained to these values. Note the model `CharField` is unconstrained at the DB level; the spec is the source of truth.
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Not yet active; excluded from live billing |
+| `active` | In use; only these should drive new charge items |
+| `retired` | Withdrawn; excluded from live billing |
+
+### Monetary component type values
+
+`MonetaryComponentType` (`common/monetary_component.py`) — the `monetary_component_type` of each price component.
+
+| Value | Meaning |
+| --- | --- |
+| `base` | The base rate. Exactly one allowed; must carry an `amount`; may not have `conditions` or a `factor` |
+| `surcharge` | An additive charge layered on the base |
+| `discount` | A reduction layered on the base |
+| `tax` | A tax component |
+| `informational` | Non-priced informational component |
+
+### Discount applicability values
+
+`DiscountApplicability` (`common/monetary_component.py`) — `discount_configuration.applicability_order`.
+
+| Value | Meaning |
+| --- | --- |
+| `total_asc` | Apply discounts in ascending order of total |
+| `total_desc` | Apply discounts in descending order of total |
+
+## JSON field shapes
+
+### Price components shape
+
+`price_components` is a `list[MonetaryComponent]`. Each entry (`common/monetary_component.py`):
+
+```text
+MonetaryComponent {
+ monetary_component_type : MonetaryComponentType # required; see enum above
+ code : Coding | null # billing code (system/version/code/display); code required if present
+ factor : Decimal | null # max_digits=20, decimal_places=6
+ amount : Decimal | null # max_digits=20, decimal_places=6
+ tax_included_amount : Decimal | null # max_digits=20, decimal_places=6; base-only
+ global_component : bool # default false
+ conditions : list[EvaluatorConditionSpec] # default []
+}
+```
+
+`Coding` (`common/coding.py`, `extra="forbid"`): `{ system: str|null, version: str|null, code: str (required), display: str|null }`.
+
+`EvaluatorConditionSpec` (`common/condition_evaluator.py`): `{ metric: str, operation: str, value: dict|str }` — `metric` is validated against `EvaluatorMetricsRegistry`, and `operation`/`value` are validated by that metric's evaluator.
+
+**Per-component validation (`MonetaryComponent`):**
+
+| Rule | Detail |
+| --- | --- |
+| `tax_included_amount` base-only | Allowed only when `monetary_component_type == base` |
+| base no conditions | A `base` component must have no `conditions` |
+| base requires amount | A `base` component must have an `amount` |
+| amount xor factor | `amount` and `factor` cannot both be present |
+| amount or factor required | One of `amount`/`factor` must be present — except when `global_component` is true and a `code` is set |
+
+### Discount configuration shape
+
+`discount_configuration` is a `DiscountConfiguration | null` (`common/monetary_component.py`):
+
+```text
+DiscountConfiguration {
+ max_applicable : int # required; >= 0
+ applicability_order : DiscountApplicability # required; total_asc | total_desc
+}
+```
+
+## Resource specs (API schema)
+
+The API does not serialize the Django model directly; it goes through Pydantic specs built on `EMRResource` (`resources/base.py`), which provide `serialize` (DB → API) and `de_serialize` (API → DB).
+
+| Spec class | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `ChargeItemDefinitionSpec` | shared base | `id`, `status`, `title`, `derived_from_uri`, `description`, `purpose`, `price_components`, `can_edit_charge_item`, `discount_configuration`. `__exclude__ = []` |
+| `ChargeItemDefinitionWriteSpec` | write · create + update | Adds `slug_value: SlugType` and `category: ExtendedSlugType | null`. Validates `price_components`; deserialization side effects below |
+| `ChargeItemDefinitionReadSpec` | read · list + detail | Adds read-only `version`, `category: dict`, `slug_config: dict`, `tags: list[dict]`, `slug: str`, `created_by`, `updated_by`, `created_date`, `updated_date` |
+
+Nested specs used by these: `MonetaryComponent` (price component), `DiscountConfiguration` (discount rules), `Coding` (component code), `EvaluatorConditionSpec` (component conditions).
+
+### Write-spec validation & side effects
+
+- **Duplicate (code, type) check** (`check_components_with_duplicate_codes` field validator): no two `price_components` may share the same `(code.code, monetary_component_type)` pair (only components that carry a `code` are checked).
+- **`perform_extra_deserialization`** (`ChargeItemDefinitionWriteSpec`):
+ - If `category` (slug) is supplied, resolves it via `ResourceCategory.objects.get(slug=category)` and sets `obj.category`.
+ - Sets `obj.slug = self.slug_value` (the unprefixed value; the model's slug helpers prefix it — see below).
+
+### Read-spec serialization
+
+- **`perform_extra_serialization`** (`ChargeItemDefinitionReadSpec`):
+ - `id` ← `obj.external_id`.
+ - `category` ← `ResourceCategoryReadSpec.serialize(obj.category)` when set.
+ - `slug_config` ← `obj.parse_slug(obj.slug)` (decomposes the stored slug into `{facility, slug_value}` or `{slug_value}`).
+ - `tags` ← `SingleFacilityTagManager().render_tags(obj)`.
+ - `created_by` / `updated_by` ← serialized via `serialize_audit_users`.
+
+### Slug type constraints
+
+| Type | Constraints |
+| --- | --- |
+| `SlugType` (`slug_value`) | `min_length=5`, `max_length=50`; pattern `^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$` (URL-safe; alphanumeric start/end) |
+| `ExtendedSlugType` (`category`) | `min_length=7`, `max_length=88`; same pattern, and must start with `f-` or `i-` |
+
+## Related models
+
+`ChargeItemDefinition` references two other models by foreign key:
+
+```text
+facility → FK facility.Facility (PROTECT)
+category → FK emr.ResourceCategory (CASCADE, nullable)
+```
+
+- `facility` is protected: a facility cannot be deleted while definitions reference it.
+- `category` cascades: deleting the [resource category](../platform/resource-category.mdx) removes the link.
+
+## Methods & save behaviour
+
+`ChargeItemDefinition` adds no overrides of its own, but inherits slug behaviour from `SlugBaseModel` (`FACILITY_SCOPED = True`):
+
+| Method | Behaviour |
+| --- | --- |
+| `calculate_slug()` | Returns `f-{facility.external_id}-{slug}` when facility-scoped with a facility, otherwise `i-{slug}` |
+| `calculate_slug_from_facility(facility_external_id, slug)` | Classmethod — builds a facility-scoped slug string |
+| `calculate_slug_from_instance(slug)` | Classmethod — builds an instance-scoped slug string |
+| `parse_slug(slug)` | Splits a stored slug back into `{facility, slug_value}` (facility-scoped, `f-` prefix) or `{slug_value}` (instance-scoped, `i-` prefix); validates the embedded facility UUID; raises on length `<= 2` or an unknown prefix |
+
+Because slugs are namespaced by the owning facility's `external_id`, the same human-readable `slug_value` (for example `consultation-fee`) can coexist across facilities without collision.
+
+## API integration notes
+
+- Charge item definitions are FHIR-aligned (`ChargeItemDefinition`); `price_components` mirror the FHIR `MonetaryComponent` shape. API field names differ from Django model names (e.g. the client sends `slug_value`, not `slug`; `category` is a slug string on write but an object on read).
+- `status` is enum-gated by the spec (`draft`/`active`/`retired`); only `active` definitions should drive new charge items.
+- `price_components` and `discount_configuration` are validated by Pydantic on write — well-formedness is enforced server-side, not left to the client.
+- `can_edit_charge_item` is a platform-enforced flag controlling whether a derived charge item is mutable, not the definition itself.
+- `slug_config`, `tags`, `category`, and audit users are computed server-side and appear only on read.
+
+## Related
+
+- Source: [charge_item_definition.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/charge_item_definition.py)
+- Spec: [charge_item_definition/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/charge_item_definition/spec.py)
+- Spec: [common/monetary_component.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
+- Reference: [Charge Item](../billing/charge-item.mdx)
+- Reference: [Invoice](../billing/invoice.mdx)
+- Reference: [Account](../billing/account.mdx)
+- Reference: [Resource Category](../platform/resource-category.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
diff --git a/versioned_docs/version-3.1/references/billing/charge-item.mdx b/versioned_docs/version-3.1/references/billing/charge-item.mdx
new file mode 100644
index 0000000..57aeff7
--- /dev/null
+++ b/versioned_docs/version-3.1/references/billing/charge-item.mdx
@@ -0,0 +1,266 @@
+---
+sidebar_position: 2
+---
+
+# Charge Item
+
+Technical reference for the `ChargeItem` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/charge_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/charge_item.py)
+- Resource specs: [`care/emr/resources/charge_item/`](https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/charge_item)
+ ([`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/charge_item/spec.py),
+ [`sync_charge_item_costs.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/charge_item/sync_charge_item_costs.py),
+ [`apply_charge_item_definition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/charge_item/apply_charge_item_definition.py),
+ [`handle_charge_item_cancel.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/charge_item/handle_charge_item_cancel.py))
+- Viewset: [`care/emr/api/viewsets/charge_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/charge_item.py)
+
+A charge item tracks the financial cost of a service or product supplied to a patient. Charge items are self-descriptive — they record what was charged, the quantity, the unit and total price components, and any applied discounts or override reasons. Every charge item belongs to an [`Account`](./account.mdx) and is created against a patient (optionally an encounter).
+
+The Django model is the **storage** layer: several fields are opaque `JSONField`s whose real structure lives only in the Pydantic resource specs (`care/emr/resources/charge_item/`). Those specs define the enums, the JSON shapes, validation, and the read/write API schemas, and they drive server-side cost calculation and status side effects. This page documents both layers.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ChargeItem` | A single billable line item for a service or product rendered to a patient |
+
+`ChargeItem` extends `EMRBaseModel` (shared Care EMR base with `external_id`, audit fields — `created_by`/`updated_by`/`created_date`/`modified_date` — and soft-delete semantics — see [Base model](../foundation/base-model.mdx)).
+
+## `ChargeItem` fields
+
+### Context & relationships
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` (`PROTECT`) | Facility where the charge is created. Set server-side from the URL, not the request body |
+| `patient` | `FK → emr.Patient` (`CASCADE`) | Patient associated with the charge. Derived from `encounter` when an encounter is supplied |
+| `encounter` | `FK → emr.Encounter` (`CASCADE`) | Nullable; encounter the charge relates to |
+| `charge_item_definition` | `FK → emr.ChargeItemDefinition` (`CASCADE`) | Nullable; template the charge was applied from. Not writable via the standard create/update specs (set only by `apply_charge_item_defs`) |
+| `account` | `FK → emr.Account` (`CASCADE`) | Account this charge is placed on. Defaults to the patient's default account when omitted |
+
+### Description & status
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `title` | `CharField(255)` | Required; display title for the charge |
+| `description` | `TextField` | Nullable; longer description |
+| `status` | `CharField(255)` | Required; lifecycle status — constrained to `ChargeItemStatusOptions`, see [Status values](#status-values) |
+| `code` | `JSONField` | Nullable; a `Coding` that identifies the charge (e.g. a billing code) — shape `Coding { system?, version?, code, display? }`, see [Coding shape](#coding-shape). No bound value set |
+| `note` | `TextField` | Nullable; free-text (markdown) comments about the charge item |
+
+### Pricing
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `quantity` | `DecimalField(20, 6)` | Required on write; quantity serviced. Multiplies the base/per-unit components during cost sync |
+| `unit_price_components` | `JSONField` | Required on write; list of `MonetaryComponent` — see [MonetaryComponent shape](#monetarycomponent-shape). At most one `base` component; duplicate component codes rejected |
+| `total_price_components` | `JSONField` | Nullable; **server-computed** list of `MonetaryComponent` (as dicts) — the resolved breakdown after applying base × quantity, surcharges, discounts, and taxes. Not client-writable |
+| `total_price` | `DecimalField(20, 6)` | Nullable; **server-computed** resolved total amount. Must be ≥ 0 unless the charge is a reversal |
+| `override_reason` | `JSONField` | Nullable; `ChargeItemOverrideReason { text: str, code?: Coding }` explaining a list-price/factor override. No bound value set |
+| `discount_configuration` | `JSONField` | Nullable (default `None`); `DiscountConfiguration { max_applicable: int (≥0), applicability_order: total_asc \| total_desc }`. Populated from the definition/facility config when applied from a definition |
+
+### Service source
+
+These record why the charge was rendered — the originating resource that produced this charge.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `service_resource` | `CharField(255)` | Nullable (default `None`); resource type — constrained to `ChargeItemResourceOptions`, see [Service resource types](#service-resource-types) |
+| `service_resource_id` | `CharField(255)` | Nullable (default `None`); external id of the originating resource. Required when `service_resource` is set |
+| `performer_actor` | `FK → users.User` (`CASCADE`) | Nullable (default `None`); user who performed the service. Must belong to the facility's organization |
+
+### Invoicing & tags
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `paid_invoice` | `FK → emr.Invoice` (`CASCADE`) | Nullable (default `None`); denormalized link to the invoice that settled this charge. Platform-maintained; cleared on cancel |
+| `paid_on` | `DateTimeField` | Nullable (default `None`); timestamp the charge was paid. Platform-maintained; cleared on cancel |
+| `tags` | `ArrayField[int]` | Tag IDs, defaults to `[]`. Managed through the tag mixin (`SingleFacilityTagManager`), serialized as objects on read |
+
+## Enums
+
+### Status values
+
+`status` is a plain `CharField` in storage, but every write goes through `ChargeItemStatusOptions` (`care/emr/resources/charge_item/spec.py`):
+
+| Value | Meaning |
+| --- | --- |
+| `billable` | Ready to be invoiced |
+| `not_billable` | Will not be billed |
+| `aborted` | Charge was cancelled before billing |
+| `billed` | Included on an invoice |
+| `paid` | Settled (links to `paid_invoice` / `paid_on`) |
+| `entered_in_error` | Recorded in error |
+
+(`planned` exists in the source as a commented-out option and is **not** currently selectable.)
+
+`not_billable`, `aborted`, and `entered_in_error` form the cancelled set (`CHARGE_ITEM_CANCELLED_STATUS`). Transitioning into this set on update triggers the cancel side effect (see [Methods & save behaviour](#methods--save-behaviour)). `billed` and `paid` cannot be set manually — the viewset rejects manual changes into them.
+
+### Service resource types
+
+`service_resource` is constrained by `ChargeItemResourceOptions`:
+
+| Value |
+| --- |
+| `service_request` |
+| `medication_dispense` |
+| `appointment` |
+| `bed_association` |
+
+On create the viewset validates the referenced resource: `service_request` must exist (and not be completed) for the patient/encounter, and `bed_association` must reference a `FacilityLocationEncounter` on a non-completed encounter in the facility.
+
+### MonetaryComponentType values
+
+Used by every entry in `unit_price_components` / `total_price_components` (`care/emr/resources/common/monetary_component.py`):
+
+| Value | Role in cost sync |
+| --- | --- |
+| `base` | Per-unit base price. Exactly one allowed; must carry `amount` (not `factor`); no `conditions`. `base.amount × quantity` seeds the running total |
+| `surcharge` | Added on top of base; `amount` or `factor` (% of base) |
+| `discount` | Subtracted from the net price; `amount` or `factor` (% of net). Filtered/limited by `discount_configuration` |
+| `tax` | Added on the post-discount taxable price; `amount` or `factor` (% of taxable) |
+| `informational` | Carried through without affecting the computed total |
+
+### DiscountApplicability values
+
+`discount_configuration.applicability_order` (`care/emr/resources/common/monetary_component.py`):
+
+| Value | Effect |
+| --- | --- |
+| `total_asc` | Sort candidate discounts by amount ascending before applying `max_applicable` |
+| `total_desc` | Sort by amount descending before applying `max_applicable` |
+
+`max_applicable = 0` drops all discounts.
+
+## JSON field shapes
+
+### Coding shape
+
+`code` and the `code` field inside components / `override_reason` (`care/emr/resources/common/coding.py`, `extra="forbid"`):
+
+```text
+Coding {
+ system?: str
+ version?: str
+ code: str # required
+ display?: str
+}
+```
+
+### MonetaryComponent shape
+
+Each entry of `unit_price_components` and `total_price_components` (`care/emr/resources/common/monetary_component.py`):
+
+```text
+MonetaryComponent {
+ monetary_component_type: base | surcharge | discount | tax | informational # required
+ code?: Coding
+ factor?: Decimal(20,6) # percentage; mutually exclusive with amount
+ amount?: Decimal(20,6) # absolute; mutually exclusive with factor
+ tax_included_amount?: Decimal(20,6) # base component only
+ global_component: bool = False # resolved against facility monetary config
+ conditions: [EvaluatorConditionSpec] = [] # not allowed on base
+}
+```
+
+Validation: exactly one of `amount`/`factor` (unless `global_component` with a `code`); `base` must have `amount`, no `factor`, no `conditions`; `tax_included_amount` only on `base`; duplicate component `code`s are rejected.
+
+`EvaluatorConditionSpec { metric: str, operation: str, value: dict | str }` — conditions are evaluated against patient/facility/encounter context when a definition is applied; a component whose conditions fail is dropped.
+
+### ChargeItemOverrideReason shape
+
+`override_reason` (`care/emr/resources/charge_item/spec.py`):
+
+```text
+ChargeItemOverrideReason {
+ text: str # required
+ code?: Coding # no bound value set
+}
+```
+
+### DiscountConfiguration shape
+
+`discount_configuration` (`care/emr/resources/common/monetary_component.py`):
+
+```text
+DiscountConfiguration {
+ max_applicable: int (>= 0) # 0 drops all discounts
+ applicability_order: total_asc | total_desc
+}
+```
+
+## Resource specs (API schema)
+
+Charge items are exposed through `ChargeItemViewSet` (create / retrieve / update / upsert / list / tag actions, plus the `apply_charge_item_defs` and `change_account` custom actions). The Pydantic specs in `care/emr/resources/charge_item/spec.py` build on `EMRResource` (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks).
+
+| Spec | Role | Notes |
+| --- | --- | --- |
+| `ChargeItemSpec` | shared base | `__model__ = ChargeItem`, `__exclude__ = ["encounter", "account"]`. Fields: `id`, `title`, `description`, `status`, `code`, `quantity`, `unit_price_components`, `note`, `override_reason`. Validators: no duplicate component codes, ≤1 `base` component |
+| `ChargeItemWriteSpec` | write · create | Extends base with `encounter`, `patient`, `account`, `service_resource`, `service_resource_id`, `performer_actor` (all `UUID4`/enum, optional). Requires `encounter` **or** `patient`; requires `service_resource_id` when `service_resource` set |
+| `ChargeItemUpdateSpec` | write · update | Base fields + `performer_actor`. Does not re-bind patient/encounter/account |
+| `ChargeItemReadSpec` | read · list & detail | Base fields + server-side: `total_price_components`, `total_price`, `charge_item_definition` (nested), `paid_invoice` (nested), `tags` (objects), `service_resource(_id)`, `created_date`, `modified_date`, `paid_on`, `performer_actor`, `created_by`, `updated_by`, `discount_configuration` |
+
+Notable validation / binding:
+
+- `code`, `override_reason.code`, and component `code`s are FHIR-aligned `Coding`s with **no bound value set**.
+- `ChargeItemWriteSpec.perform_extra_deserialization` resolves `patient`/`encounter`/`account`/`performer_actor` from external ids; supplying `encounter` overrides `patient` with the encounter's patient, and `account` is looked up scoped to that patient.
+- `ChargeItemReadSpec.perform_extra_serialization` nests the full `ChargeItemDefinitionReadSpec` and `InvoiceReadSpec`, renders tags, and resolves `performer_actor` / `created_by` / `updated_by` from the user cache.
+
+### Nested / related specs
+
+| Spec | Used by | Shape |
+| --- | --- | --- |
+| `Coding` | `code`, component & override codes | see [Coding shape](#coding-shape) |
+| `MonetaryComponent` | `unit_price_components`, `total_price_components` | see [MonetaryComponent shape](#monetarycomponent-shape) |
+| `ChargeItemOverrideReason` | `override_reason` | `{ text, code? }` |
+| `DiscountConfiguration` | `discount_configuration` | `{ max_applicable, applicability_order }` |
+| `ChargeItemDefinitionReadSpec` | `charge_item_definition` (read) | full nested definition — see [Charge Item Definition](./charge-item-definition.mdx) |
+| `InvoiceReadSpec` | `paid_invoice` (read) | full nested invoice — see [Invoice](./invoice.mdx) |
+| `UserSpec` | `performer_actor`, audit users (read) | see [User](../access/user.mdx) |
+
+## Related models
+
+`ChargeItem` is a leaf model — it holds foreign keys rather than owning child rows. Its relationships:
+
+```text
+facility → FK facility.Facility (PROTECT)
+patient → FK emr.Patient (CASCADE)
+encounter → FK emr.Encounter (CASCADE, nullable)
+charge_item_definition → FK emr.ChargeItemDefinition (CASCADE, nullable)
+account → FK emr.Account (CASCADE)
+paid_invoice → FK emr.Invoice (CASCADE, nullable)
+performer_actor → FK users.User (CASCADE, nullable)
+```
+
+## Methods & save behaviour
+
+Cost resolution and status side effects are applied in the spec helpers and the viewset, not in the model's `save()`.
+
+- **Cost sync (`sync_charge_item_costs`)** — runs on every create and on update (unless suppressed). Iterates `unit_price_components`: multiplies the `base` amount by `quantity` to seed `total_price`/`base`, adds `surcharge`s, applies `discount`s (filtered by `discount_configuration` via `apply_discount_configuration`), then adds `tax`es on the taxable price. Writes the resolved breakdown to `total_price_components` and the sum to `total_price`. Raises `ValidationError` if `total_price < 0` and the charge is not a reversal.
+- **Apply from definition (`apply_charge_item_definition` / `POST apply_charge_item_defs`)** — builds a `ChargeItem` from a `ChargeItemDefinition`: copies `title`/`description`, merges category + global components, evaluates per-component `conditions` against patient/facility/encounter context (dropping unmet ones), sets `status = billable`, resolves `discount_configuration`, defaults the account to the patient's default account, and runs cost sync. `reverse` negates component amounts to produce a credit.
+- **Cancel (`handle_charge_item_cancel`)** — fires on update when `status` moves into `CHARGE_ITEM_CANCELLED_STATUS` (`not_billable` / `aborted` / `entered_in_error`). If the charge sits on a **draft** invoice it is removed from the invoice, the invoice is rebalanced and saved, and `paid_invoice` / `paid_on` are cleared. Cancelling a charge on a non-draft invoice raises `ValidationError`. Cost sync is skipped for cancellations.
+- **Update guards (`validate_data` / `perform_update`)** — no updates allowed once the charge is in a cancelled status; updates blocked if the linked invoice is `balanced`/`issued`; `status` cannot be manually moved into `billed`/`paid`; if the source definition has `can_edit_charge_item = False`, pricing fields (`unit_price_components`, `total_price_components`, `total_price`, `quantity`) are reset from the previous object and cost sync is skipped. After update, a draft `paid_invoice` is re-synced.
+- **Cancel authorization (`authorize_cancel`)** — cancellation is free within `CHARGE_ITEM_FREE_CANCEL_PERIOD_MINUTES` of creation; afterwards it requires the `can_cancel_charge_item_in_facility` permission.
+- **Change account (`POST change_account`)** — bulk-moves up to 100 `billable` charge items to a target account, then queues `rebalance_account_task` for every affected account.
+
+## API integration notes
+
+- The viewset sets `facility` from the URL and ignores any body value; `patient` is derived from `encounter` when supplied.
+- A charge item must resolve to an `account` — when none is supplied the patient's default account is used. `encounter` and `charge_item_definition` are optional.
+- `total_price`, `total_price_components`, `paid_invoice`, and `paid_on` are server-maintained; do not set them from clients.
+- Create/update enforce that `performer_actor` (and, when applying definitions, the actor) belongs to the facility's organization.
+- `discount_configuration` and `tags` are structured but deployment-tunable: `discount_configuration` follows `DiscountConfiguration`, and tags are resolved via the single-facility tag manager.
+
+## Related
+
+- Reference: [Account](./account.mdx)
+- Reference: [Charge Item Definition](./charge-item-definition.mdx)
+- Reference: [Invoice](./invoice.mdx)
+- Reference: [Payment Reconciliation](./payment-reconciliation.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [User](../access/user.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [charge_item.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/charge_item.py)
+- Source: [charge_item resource specs on GitHub](https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/charge_item)
diff --git a/versioned_docs/version-3.1/references/billing/invoice.mdx b/versioned_docs/version-3.1/references/billing/invoice.mdx
new file mode 100644
index 0000000..580d4c6
--- /dev/null
+++ b/versioned_docs/version-3.1/references/billing/invoice.mdx
@@ -0,0 +1,177 @@
+---
+sidebar_position: 4
+---
+
+# Invoice
+
+Technical reference for the `Invoice` module in Care EMR.
+
+The Django model is the **storage** layer: it holds the columns and several opaque `JSONField`s (`charge_items_copy`, `total_price_components`, `lock_history`) whose real structure lives in the Pydantic **resource specs**. The specs define the API request/response schemas, the `InvoiceStatusOptions` enum, the shape of the JSON fields, validation, and the server-side totalling/snapshot behaviour. Read both layers together.
+
+**Source:**
+- Model: [`care/emr/models/invoice.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/invoice.py)
+- Resource specs: [`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/invoice/spec.py) · [`sync_items.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/invoice/sync_items.py) · [`return_items_invoice.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/invoice/return_items_invoice.py) · [`default_expression_evaluator.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/invoice/default_expression_evaluator.py)
+- Shared types: [`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py) · [`monetary_component.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Invoice` | A billable grouping of charge items for one account, with snapshotted line items, totals, and status |
+
+`Invoice` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+An invoice groups [charge items](../billing/charge-item.mdx) raised against a single [account](../billing/account.mdx). Invoice `status` drives the charge item lifecycle. On issue, the server computes net/gross totals and snapshots each charge item, so the invoice stays stable even if the underlying charge items change. [Payment reconciliation](../billing/payment-reconciliation.mdx) records (payments and credit notes) are reported against the invoice on the retrieve view.
+
+## `Invoice` fields
+
+### References
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` (PROTECT) | Facility where the invoice is created |
+| `patient` | `FK → emr.Patient` (PROTECT) | Entity that incurred the charges; set server-side from `account.patient` on write |
+| `account` | `FK → emr.Account` (PROTECT) | Account being balanced by this invoice |
+
+### Status & metadata
+
+| Field | Type | Spec / values | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(100)` | `InvoiceStatusOptions` enum — required on write | Lifecycle status. See [values](#invoicestatusoptions-values) |
+| `title` | `CharField(1024)` | `str \| None`, default `None` | Optional human-readable invoice title |
+| `cancelled_reason` | `TextField` | `str \| None` | Optional reason recorded when cancelled |
+| `payment_terms` | `TextField` | `str \| None` | Optional payment details (markdown) |
+| `note` | `TextField` | `str \| None` | Optional free-text comments (markdown) |
+| `number` | `CharField(1000)` | `str \| None`, default `None` | Invoice number. Auto-generated for return invoices via the facility's `invoice_number_expression` (see [Methods](#methods--save-behaviour)) |
+| `issue_date` | `DateTimeField` | `datetime \| None` (tz-aware), default `None` | When the invoice was issued |
+| `is_refund` | `BooleanField` | `bool`, default `False` | Flags a refund/return invoice; required for negative totals (see validation) |
+
+### Charge items
+
+| Field | Type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `charge_items` | `ArrayField[int]`, default `[]` | write: `list[UUID4]`; storage: list of integer PKs | Live references to the grouped `ChargeItem` rows. On write the client sends external UUIDs; resolved to PKs server-side |
+| `charge_items_copy` | `JSONField`, default `[]` | `list[dict]` — array of serialized `ChargeItemReadSpec` | Point-in-time snapshot of the line items, written by `sync_invoice_items`. Used as the retrieve `charge_items` for any non-`draft` invoice |
+
+### Totals (server-maintained)
+
+These are computed by `sync_invoice_items` / `calculate_charge_items_summary` — never set them from a client.
+
+| Field | Type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `total_price_components` | `JSONField`, default `{}` | retrieve: `list[dict]` of `MonetaryComponent` | Aggregated monetary components across all charge items (one per type+code). See [MonetaryComponent shape](#monetarycomponent-shape) |
+| `total_net` | `DecimalField(20, 6)`, default `0` | `Decimal(max_digits=20, decimal_places=6)` | Net total = base + surcharge − discount (tax **excluded**). Rounded with `INVOICE_FINAL_AMOUNT_PRECISION` / `..._ROUNDING_METHOD` |
+| `total_gross` | `DecimalField(20, 6)`, default `0` | `Decimal(max_digits=20, decimal_places=6)` | Gross total = net + tax (tax **included**). Same rounding |
+
+On the read specs, if `locked` is `True`, both `total_net` and `total_gross` are serialized as `0`.
+
+### Locking
+
+| Field | Type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `locked` | `BooleanField`, default `False` | `bool` (read-only on read specs) | When set, the invoice is frozen against further edits and totals serialize as `0` |
+| `lock_history` | `JSONField`, default `[]` | `list[dict]` — each entry `{ user, ... }` | Audit trail of lock/unlock events. On retrieve, each entry's `user` id is hydrated into a `UserSpec` dict |
+
+## Enums
+
+### `InvoiceStatusOptions` values
+
+`care/emr/resources/invoice/spec.py`. Note the API value is `entered_in_error` (underscore), not the FHIR-style hyphen.
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | In progress; retrieve serializes live charge items (not the snapshot) |
+| `issued` | Issued to the patient/payer |
+| `balanced` | Fully settled |
+| `cancelled` | Cancelled |
+| `entered_in_error` | Recorded in error |
+
+`INVOICE_CANCELLED_STATUS = ["cancelled", "entered_in_error"]` — the two terminal/void states.
+
+### `MonetaryComponentType` values
+
+`care/emr/resources/common/monetary_component.py`. Drives how each component contributes to net/gross during totalling.
+
+| Value | Effect on totals |
+| --- | --- |
+| `base` | Added to net (one base component per item; must carry an `amount`) |
+| `surcharge` | Added to net |
+| `discount` | Subtracted from net |
+| `tax` | Added to gross only (excluded from net) |
+| `informational` | Carried for display; not summed |
+
+## Resource specs (API schema)
+
+All specs subclass `EMRResource` (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). `BaseInvoiceSpec.__model__ = Invoice` and `__exclude__ = ["account", "charge_items"]` (these are handled by the hooks, not the generic mapper).
+
+| Spec | Role | Fields beyond base | Server behaviour |
+| --- | --- | --- | --- |
+| `BaseInvoiceSpec` | shared | `id`, `title`, `status` (required), `cancelled_reason`, `payment_terms`, `note`, `issue_date`, `number` | Base for all specs |
+| `InvoiceWriteSpec` | write · create + update | `account: UUID4` (required), `charge_items: list[UUID4]` (default `[]`) | `perform_extra_deserialization`: resolves `account` from `external_id`, sets `patient = account.patient`, stages `charge_items` (rewritten in `perform_create`) |
+| `InvoiceReadSpec` | read · list | `total_net`, `total_gross`, `locked`, `created_date`, `modified_date`, `account: dict`, `is_refund` | `perform_extra_serialization`: `id = external_id`; `account` → `AccountMinimalReadSpec`; if `locked`, zero out `total_net`/`total_gross` |
+| `InvoiceRetrieveSpec` | read · detail | (all read fields) + `charge_items: list[dict]`, `total_price_components: list[dict]`, `created_by`, `updated_by`, `payments: list[dict]`, `total_payments`, `credit_notes: list[dict]`, `total_credit_notes`, `lock_history: list[dict]` | See retrieve serialization below |
+
+`InvoiceWriteSpec` is used for both create and update (no separate Update spec). `InvoiceRetrieveSpec` extends `InvoiceReadSpec`.
+
+### `InvoiceRetrieveSpec.perform_extra_serialization`
+
+- `id = external_id`; `account` serialized via `AccountReadSpec` (full account on detail).
+- `charge_items`: if `status == draft`, live `ChargeItemReadSpec.serialize(...)` over `ChargeItem.objects.filter(id__in=charge_items)` (`select_related` `paid_invoice`, `charge_item_definition`); otherwise the stored `charge_items_copy` snapshot.
+- `created_by` / `updated_by`: hydrated via `serialize_audit_users`.
+- Reconciliations against this invoice with `outcome = complete` and `status = active` are split into:
+ - `payments` (+ `total_payments`) where `is_credit_note` is false,
+ - `credit_notes` (+ `total_credit_notes`) where `is_credit_note` is true,
+ - each serialized via `PaymentReconciliationRetrieveSpec`.
+- `lock_history`: each entry's `user` id replaced with a cached `UserSpec`.
+
+### `MonetaryComponent` shape
+
+Stored inside `total_price_components` and `charge_items_copy`. Source: `care/emr/resources/common/monetary_component.py`.
+
+```text
+MonetaryComponent {
+ monetary_component_type: MonetaryComponentType # required (base|surcharge|discount|tax|informational)
+ code: Coding | None # { system?, version?, code, display? }
+ factor: Decimal(20,6) | None
+ amount: Decimal(20,6) | None
+ tax_included_amount: Decimal(20,6) | None # only valid on base
+ global_component: bool = False
+ conditions: list[EvaluatorConditionSpec] = []
+}
+```
+
+Validation (per component): `base` must carry an `amount` and no `conditions`; `tax_included_amount` is allowed only on `base`; `amount` and `factor` are mutually exclusive; at least one of `amount`/`factor` is required (unless `global_component` with a `code`). Collection-level rules (`MonetaryComponents`) forbid duplicate codes, require a single `base`, and reconcile `tax_included_amount` against the sum of tax components.
+
+## Related models
+
+- [`ChargeItem`](../billing/charge-item.mdx) — line items grouped by the invoice; snapshotted into `charge_items_copy` via `ChargeItemReadSpec`.
+- [`Account`](../billing/account.mdx) — the billing account; resolved on write, rebalanced after return-invoice generation/cancellation.
+- [`PaymentReconciliation`](../billing/payment-reconciliation.mdx) — payments and credit notes reported on the retrieve view (filtered to `outcome=complete`, `status=active`).
+- [`ChargeItemDefinition`](../billing/charge-item-definition.mdx) — applied when building return-invoice charge items.
+
+## Methods & save behaviour
+
+- **`sync_invoice_items(invoice)`** (`sync_items.py`) — recomputes `total_net`, `total_gross`, `total_price_components`, and `charge_items_copy` from the linked charge items, then rounds totals using `INVOICE_FINAL_AMOUNT_PRECISION` / `INVOICE_FINAL_AMOUNT_ROUNDING_METHOD`. **Validation:** if `is_refund` is false and either total is negative, raises `ValidationError("A Refund Ivoice is required for negative values")`.
+- **`calculate_charge_items_summary(charge_items)`** — net = Σ base + Σ surcharge − Σ discount; gross = net + Σ tax; components aggregated per `type + code` (key = `system + code`, else `No-Code`).
+- **`evaluate_invoice_identifier_default_expression(facility)`** (`default_expression_evaluator.py`) — generates `number` from the facility's `invoice_number_expression` with context `{ invoice_count, current_year_yyyy, current_year_yy }`; returns `""` if unset. `evaluate_invoice_dummy_expression` previews an expression with sample context.
+- **`generate_return_invoice(delivery_order)`** (`return_items_invoice.py`) — creates a `draft` refund invoice (`is_refund=True`), reverses each completed `SupplyDelivery`'s charge item via `apply_charge_item_definition(reverse=True)`, marks items `billed`, runs `sync_invoice_items`, sets status `issued`, then triggers `rebalance_account_task`. Raises if any delivery is still `in_progress`.
+- **`cancel_return_invoice(delivery_order)`** — sets the invoice `cancelled`, marks its charge items `entered_in_error` (clears `paid_invoice`/`paid_on`), voids the supply deliveries, rebalances the account, and resyncs inventory.
+
+## API integration notes
+
+- `status` is the control surface for the billing lifecycle. Drive transitions through the API rather than editing rows directly; values are `draft`, `issued`, `balanced`, `cancelled`, `entered_in_error`.
+- On write (`InvoiceWriteSpec`): send `account` and `charge_items` as **external UUIDs**. `patient` is derived from the account server-side and must not be sent. `account` and `charge_items` are excluded from the generic field mapper and handled in `perform_extra_deserialization`.
+- `charge_items_copy`, `total_price_components`, `total_net`, and `total_gross` are platform-maintained snapshots derived from the linked charge items — do not set them directly.
+- List view (`InvoiceReadSpec`) returns a minimal `account`; detail view (`InvoiceRetrieveSpec`) returns the full account plus charge items, aggregated price components, payments, credit notes, and lock history.
+- When `locked` is `True`, the read specs serialize `total_net` and `total_gross` as `0`.
+
+## Related
+
+- Reference: [Account](../billing/account.mdx)
+- Reference: [Charge Item](../billing/charge-item.mdx)
+- Reference: [Charge Item Definition](../billing/charge-item-definition.mdx)
+- Reference: [Payment Reconciliation](../billing/payment-reconciliation.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
+- Source: [invoice.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/invoice.py)
+- Source: [invoice resource specs on GitHub](https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/invoice)
diff --git a/versioned_docs/version-3.1/references/billing/payment-reconciliation.mdx b/versioned_docs/version-3.1/references/billing/payment-reconciliation.mdx
new file mode 100644
index 0000000..12e3f30
--- /dev/null
+++ b/versioned_docs/version-3.1/references/billing/payment-reconciliation.mdx
@@ -0,0 +1,200 @@
+---
+sidebar_position: 5
+---
+
+# Payment Reconciliation
+
+Technical reference for the `PaymentReconciliation` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/payment_reconciliation.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/payment_reconciliation.py)
+- Resource spec: [`care/emr/resources/payment_reconciliation/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/payment_reconciliation/spec.py)
+
+`PaymentReconciliation` records a payment made against an `Account` — and optionally a specific `Invoice`. It captures how, when, by whom, and how much was paid, mirroring the FHIR [`PaymentReconciliation`](https://build.fhir.org/paymentreconciliation.html) resource.
+
+The Django **model** is the storage layer: several constrained fields are stored as plain `CharField`s, and `extensions` is an opaque `JSONField`. The real shape — enum values, amount computation, validation, and the read/write API schemas — lives in the Pydantic **resource specs** (`care/emr/resources/payment_reconciliation/spec.py`), built on `EMRResource`. This page documents both layers.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `PaymentReconciliation` | A single payment (or credit note / adjustment) recorded against an account or invoice |
+
+The model extends `EMRBaseModel` (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics — see [Base model](../foundation/base-model.mdx)).
+
+## `PaymentReconciliation` fields
+
+### Relationships
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → Facility` (PROTECT) | Facility where the payment is recorded |
+| `target_invoice` | `FK → Invoice` (PROTECT) | Nullable (`default=None`); the invoice this payment is allocated to. A single allocation is supported. On write, supplied as the invoice `external_id` (UUID) |
+| `account` | `FK → Account` (PROTECT) | The account being paid against. Required even when `target_invoice` is set. On write, supplied as the account `external_id` (UUID) |
+| `location` | `FK → FacilityLocation` (PROTECT) | Nullable; physical location (e.g. counter) where payment was taken. On write, supplied as the location `external_id` (UUID) |
+
+### Classification
+
+These string columns are bound to fixed enums defined in the resource spec. The model stores them as free-form `CharField(100)`; the enum constraint is enforced at the schema/API layer, not by the database. See [enum values](#enum-values) for the full lists.
+
+| Field | Type | Spec enum | Notes |
+| --- | --- | --- | --- |
+| `reconciliation_type` | `CharField(100)` | `PaymentReconciliationTypeOptions` | Required. `payment` \| `adjustment` \| `advance` |
+| `status` | `CharField(100)` | `PaymentReconciliationStatusOptions` | Required. `active` \| `cancelled` \| `draft` \| `entered_in_error` |
+| `kind` | `CharField(100)` | `PaymentReconciliationKindOptions` | Required. `deposit` \| `periodic_payment` \| `online` \| `kiosk` |
+| `issuer_type` | `CharField(100)` | `PaymentReconciliationIssuerTypeOptions` | Required. `patient` \| `insurer` |
+| `outcome` | `CharField(100)` | `PaymentReconciliationOutcomeOptions` | Required. `queued` \| `complete` \| `error` \| `partial` |
+| `method` | `CharField(100)` | `PaymentReconciliationPaymentMethodOptions` | Required. HL7 [v2-0570](https://terminology.hl7.org/6.2.0/ValueSet-v2-0570.html) payment-method codes — `cash` \| `ccca` \| `cchk` \| `cdac` \| `chck` \| `ddpo` \| `debc` |
+
+### Payment details
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `payment_datetime` | `DateTimeField` → `datetime \| None` | Optional (default `None`); when the payment was issued |
+| `reference_number` | `CharField(1024)` → `str \| None` | Optional; cheque number or payment reference |
+| `authorization` | `CharField(1024)` → `str \| None` | Optional; authorization number |
+| `disposition` | `TextField` → `str \| None` | Optional; disposition message describing the outcome |
+| `note` | `TextField` → `str \| None` | Optional; free-text note |
+
+### Amounts
+
+All amounts are `DecimalField(max_digits=20, decimal_places=6)` (spec: `Decimal`, `max_digits=20`, `decimal_places=6`). Six decimal places give sub-currency precision.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `tendered_amount` | `Decimal(20, 6)` | **Required on write.** Amount offered by the issuer |
+| `returned_amount` | `Decimal(20, 6)` | **Required on write.** Amount returned by the receiver (e.g. change). Validated `returned_amount < tendered_amount` |
+| `amount` | `Decimal(20, 6)` | **Server-computed.** `amount = tendered_amount - returned_amount`; any client-supplied value is overwritten by the write validator |
+
+### Flags & extensions
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `is_credit_note` | `BooleanField` → `bool` | Defaults to `False`; marks the record as a credit note rather than an inbound payment |
+| `extensions` | `JSONField` → `dict` | Defaults to `{}`. Open extension bag. On write, each key is validated against the registered extension handler for resource type `payment_reconciliation` (unknown keys are currently dropped); on read (retrieve), rendered through the registered handlers |
+
+## Enum values
+
+All enums are `str, Enum` classes in [`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/payment_reconciliation/spec.py).
+
+### `PaymentReconciliationTypeOptions` (`reconciliation_type`)
+
+| Value |
+| --- |
+| `payment` |
+| `adjustment` |
+| `advance` |
+
+### `PaymentReconciliationStatusOptions` (`status`)
+
+| Value |
+| --- |
+| `active` |
+| `cancelled` |
+| `draft` |
+| `entered_in_error` |
+
+### `PaymentReconciliationKindOptions` (`kind`)
+
+| Value |
+| --- |
+| `deposit` |
+| `periodic_payment` |
+| `online` |
+| `kiosk` |
+
+### `PaymentReconciliationIssuerTypeOptions` (`issuer_type`)
+
+| Value |
+| --- |
+| `patient` |
+| `insurer` |
+
+### `PaymentReconciliationOutcomeOptions` (`outcome`)
+
+| Value |
+| --- |
+| `queued` |
+| `complete` |
+| `error` |
+| `partial` |
+
+### `PaymentReconciliationPaymentMethodOptions` (`method`)
+
+HL7 v2-0570 payment-method codes.
+
+| Value | HL7 meaning |
+| --- | --- |
+| `cash` | Cash |
+| `ccca` | Credit card |
+| `cchk` | Cashier's check |
+| `cdac` | Credit/debit account |
+| `chck` | Check |
+| `ddpo` | Direct deposit |
+| `debc` | Debit card |
+
+## Resource specs (API schema)
+
+Defined in [`care/emr/resources/payment_reconciliation/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/payment_reconciliation/spec.py). All extend `EMRResource` (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). See [Base model](../foundation/base-model.mdx).
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BasePaymentReconciliationSpec` | shared base | Holds `id`, the six enum fields, `disposition`, `payment_datetime`, `method`, `reference_number`, `authorization`, `note`. `__exclude__ = ["target_invoice", "account"]` (FKs handled in hooks). `___extension_resource_type__ = payment_reconciliation` |
+| `PaymentReconciliationWriteSpec` | write · create & update | Adds `target_invoice` (UUID, optional), `account` (UUID, required), `tendered_amount`, `returned_amount`, `amount` (optional), `is_credit_note` (default `False`), `location` (UUID, optional). Mixes in `ExtensionValidator` |
+| `PaymentReconciliationMinimalReadSpec` | read · list | Adds `amount`, `tendered_amount`, `returned_amount`, `is_credit_note`, `created_date`, `modified_date` |
+| `PaymentReconciliationReadSpec` | read · detail (nested) | Extends minimal read; adds `account: dict` (serialized via `AccountReadSpec`) and `target_invoice: dict \| None` (serialized via `InvoiceReadSpec`) |
+| `PaymentReconciliationRetrieveSpec` | read · full retrieve | Extends read; adds `location: dict \| None` (via `FacilityLocationListSpec`), `extensions: dict`, `created_by` / `updated_by`. Also injects `account.patient` via `PatientRetrieveSpec` |
+
+### Write validation & side effects
+
+`PaymentReconciliationWriteSpec`:
+
+- **Amount validator** (`model_validator(mode="after")`, `check_amount_or_factor`): raises `"Returned amount cannot be greater than tendered amount"` when `returned_amount >= tendered_amount`, then sets `amount = tendered_amount - returned_amount`. The `amount` field on the write spec is therefore advisory only — always recomputed.
+- **`perform_extra_deserialization(is_update, obj)`** resolves the FK UUIDs to model instances:
+ - `target_invoice` (if provided) → `Invoice.objects.get(external_id=...)`
+ - `account` (required) → `Account.objects.get(external_id=...)`
+ - `location` (if provided) → `FacilityLocation.objects.get(external_id=...)`
+- **`extensions`** are validated by `ExtensionValidator.validate_extensions` against the `payment_reconciliation` extension registry before save.
+
+### Read serialization side effects
+
+- `PaymentReconciliationMinimalReadSpec.perform_extra_serialization` maps `id` ← `obj.external_id`.
+- `PaymentReconciliationReadSpec` serializes the related `account` (`AccountReadSpec`) and, when present, `target_invoice` (`InvoiceReadSpec`) into nested objects.
+- `PaymentReconciliationRetrieveSpec` additionally nests the account's `patient` (`PatientRetrieveSpec`, scoped to the account's facility), the `location` (`FacilityLocationListSpec`), the raw `extensions`, and audit users (`created_by` / `updated_by` via `serialize_audit_users`).
+
+## Related models
+
+`PaymentReconciliation` is a single, self-contained model. Its foreign keys reach into the broader billing and facility domains:
+
+```text
+facility → FK Facility (PROTECT)
+target_invoice → FK Invoice (PROTECT, nullable)
+account → FK Account (PROTECT)
+location → FK FacilityLocation (PROTECT, nullable)
+```
+
+All foreign keys use `on_delete=PROTECT`, so a payment record blocks deletion of the facility, invoice, account, or location it references.
+
+## Methods & save behaviour
+
+- **No custom model `save()`** — `PaymentReconciliation` inherits `EMRBaseModel` behaviour (audit fields, `external_id`, soft delete).
+- **Amount is derived, not stored as sent**: on every create/update the write spec computes `amount = tendered_amount - returned_amount` and rejects `returned_amount >= tendered_amount`. Clients should send `tendered_amount` and `returned_amount`; `amount` is informational.
+- **FK resolution** happens in `perform_extra_deserialization`: clients pass `external_id` UUIDs for `account`, `target_invoice`, and `location`, which are resolved to instances at de-serialization time.
+- **Extensions lifecycle**: validated on write against the `payment_reconciliation` extension registry; rendered on retrieve through registered handlers.
+
+## API integration notes
+
+- Exposed through Care's REST API. Write requests use `PaymentReconciliationWriteSpec` for both create and update; list responses use `PaymentReconciliationMinimalReadSpec`, detail/retrieve responses use `PaymentReconciliationReadSpec` / `PaymentReconciliationRetrieveSpec`.
+- `account` is always required; `target_invoice` is optional and represents a single allocation of the payment to one invoice. Both, plus `location`, are passed as `external_id` UUIDs.
+- Classification fields (`reconciliation_type`, `status`, `kind`, `issuer_type`, `outcome`, `method`) are constrained to the fixed enums above by the Pydantic schema — invalid values are rejected at the API even though the column is a plain `CharField`.
+- Send `tendered_amount` and `returned_amount` (both required, `Decimal(20, 6)`); do **not** rely on a client-supplied `amount` — the server overwrites it with `tendered_amount - returned_amount`.
+- `extensions` is the supported place for custom key-value data without schema migrations, validated against registered extension handlers.
+
+## Related
+
+- Reference: [Account](../billing/account.mdx)
+- Reference: [Invoice](../billing/invoice.mdx)
+- Reference: [Charge item](../billing/charge-item.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source (model): [payment_reconciliation.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/payment_reconciliation.py)
+- Source (spec): [payment_reconciliation/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/payment_reconciliation/spec.py)
diff --git a/versioned_docs/version-3.1/references/clinical/_category_.json b/versioned_docs/version-3.1/references/clinical/_category_.json
index 61229f3..30a1cc9 100644
--- a/versioned_docs/version-3.1/references/clinical/_category_.json
+++ b/versioned_docs/version-3.1/references/clinical/_category_.json
@@ -1,5 +1,10 @@
{
"label": "Clinical",
- "position": 1,
- "key": "clinical-references"
+ "position": 2,
+ "key": "clinical-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Clinical",
+ "description": "Patient-centred clinical records — encounters, conditions, allergies, observations, diagnostics, specimens, service requests, and consent."
+ }
}
diff --git a/versioned_docs/version-3.1/references/clinical/activity-definition.mdx b/versioned_docs/version-3.1/references/clinical/activity-definition.mdx
new file mode 100644
index 0000000..a46b8f2
--- /dev/null
+++ b/versioned_docs/version-3.1/references/clinical/activity-definition.mdx
@@ -0,0 +1,163 @@
+---
+sidebar_position: 13
+---
+
+# Activity Definition
+
+Technical reference for the `ActivityDefinition` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/activity_definition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/activity_definition.py)
+- Resource specs: [`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/spec.py) · [`valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/valueset.py) · [`service_request.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/service_request.py)
+
+An Activity Definition is a FHIR-aligned, facility-scoped **template** for clinical activities. It captures the defaults needed to instantiate downstream resources (currently a [Service Request](./service-request.mdx)) when the definition is "applied" against an encounter — combining stored defaults with the encounter context.
+
+The Django model is the **storage layer**: several fields are opaque `JSONField`s or integer `ArrayField`s whose real structure lives in the Pydantic **resource specs** (the API/implementation layer). The specs define the enums, the JSON shapes, the validation rules, and the read/write schemas. Read both layers together.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ActivityDefinition` | Versioned, facility-scoped template describing a clinical activity and the defaults used to create the resources it produces |
+
+`ActivityDefinition` extends `SlugBaseModel` (a Care EMR base that adds a facility-scoped `slug` on top of `EMRBaseModel` — which provides `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and the `history`/`meta` JSON fields). See [Base model](../foundation/base-model.mdx).
+
+## `ActivityDefinition` fields
+
+Each field below shows the **storage type** (Django model) and, where the spec constrains it, the **API shape/values** enforced by the Pydantic specs.
+
+### Identity & versioning
+
+| Field | Storage type | API shape / notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` (PROTECT) | Owning facility; definitions are facility-scoped. Spec-`__exclude__`d — set server-side, never accepted in the payload. |
+| `slug` | `CharField(255)` | Facility-scoped identifier. On write it is supplied as `slug_value` (`SlugType`) and assigned to `obj.slug` in `perform_extra_deserialization`. On read it is returned as `slug` plus a parsed `slug_config` dict. |
+| `version` | `IntegerField` | Default `1`. Read-only in specs (`version: int \| None`), serialized on list/retrieve. Models an append-only version chain. |
+| `latest` | `BooleanField` | Default `True`; marks the current version among a slug's version chain. Not exposed in the specs (server-maintained). |
+| `derived_from_uri` | `TextField` | Nullable. URI this definition was derived from. Optional in specs (`str \| None = None`). |
+
+### Descriptive metadata
+
+| Field | Storage type | API shape / notes |
+| --- | --- | --- |
+| `title` | `CharField(1024)` | Required (`str`). Human-friendly name. |
+| `status` | `CharField(255)` | Required enum — `ActivityDefinitionStatusOptions` (`draft` / `active` / `retired` / `unknown`). |
+| `classification` | `CharField(100)` | Required enum — `ActivityDefinitionCategoryOptions` (`laboratory` / `imaging` / `counselling` / `surgical_procedure` / `education`). |
+| `kind` | `CharField(100)` | Required enum — `ActivityDefinitionKindOptions` (only `service_request`). Determines the resource produced when applied. |
+| `description` | `TextField` | Optional in specs; default `""`. |
+| `usage` | `TextField` | Optional in specs; default `""`. |
+
+### Coded clinical attributes (JSON)
+
+Stored as `JSONField`. The spec binds each to a value set via `ValueSetBoundCoding[]`, which validates the submitted code against that value set. Each coding is shaped `{ system?: str, version?: str, code: str (required), display?: str }` (`extra="forbid"`).
+
+| Field | Storage type | API shape / notes |
+| --- | --- | --- |
+| `code` | `JSONField` (nullable) | **Required** `Coding` bound to value set `activity-definition-procedure-code` (SNOMED CT, `concept is-a 71388002` "Procedure"). |
+| `body_site` | `JSONField` (nullable) | Optional `Coding \| None = None` bound to value set `system-body-site-observation` (SNOMED CT body sites). |
+| `diagnostic_report_codes` | `JSONField` (nullable) | `list[Coding]`, default `[]`. Each `Coding` bound to value set `system-observation` (LOINC). |
+
+### Requirement & linkage arrays
+
+Stored as integer-ID arrays (`ArrayField(IntegerField, default=list)`) holding primary keys — not Django FK relations. The specs accept/return them by **external slug or UUID**, not raw integer PKs:
+
+| Field | Storage type | Write shape | Read (retrieve) shape |
+| --- | --- | --- | --- |
+| `specimen_requirements` | `ArrayField[int]` | `list[ExtendedSlugType]` | `list[dict]` — serialized [Specimen Definitions](./specimen-definition.mdx) (`SpecimenDefinitionReadSpec`) |
+| `observation_result_requirements` | `ArrayField[int]` | `list[ExtendedSlugType]` | `list[dict]` — serialized [Observation Definitions](./observation-definition.mdx) (`ObservationDefinitionReadSpec`) |
+| `locations` | `ArrayField[int]` | `list[UUID4]` | `list[dict]` — serialized [Locations](../facility/location.mdx) (`FacilityLocationListSpec`) |
+| `charge_item_definitions` | `ArrayField[int]` | `list[ExtendedSlugType]` | `list[dict]` — serialized [Charge Item Definitions](../billing/charge-item-definition.mdx) (`ChargeItemDefinitionReadSpec`) |
+| `tags` | `ArrayField[int]` | not written via spec | `list[dict]` — rendered via `SingleFacilityTagManager().render_tags(obj)` |
+
+> `ExtendedSlugType`: 7–88 chars, URL-safe, must start with `f-` or `i-`. `SlugType` (used for `slug_value`): 5–50 chars, URL-safe. Referential integrity for the stored integer arrays is the caller's responsibility — retrieve serialization skips IDs that no longer resolve.
+
+### Service relationships
+
+| Field | Storage type | API shape / notes |
+| --- | --- | --- |
+| `healthcare_service` | `FK → emr.HealthcareService` (PROTECT) | Nullable; default `None`. Write: `UUID4 \| None` (resolved by `external_id` in `perform_extra_deserialization`). Retrieve: serialized via `HealthcareServiceReadSpec`. See [Healthcare Service](../facility/healthcare-service.mdx). |
+| `category` | `FK → emr.ResourceCategory` (CASCADE) | Nullable. Write: `ExtendedSlugType \| None` (resolved by `slug`). Read: serialized via `ResourceCategoryReadSpec`. Deleting the [Resource Category](../platform/resource-category.mdx) cascades. |
+
+## Enum values
+
+### `ActivityDefinitionStatusOptions` (`status`)
+
+| Value |
+| --- |
+| `draft` |
+| `active` |
+| `retired` |
+| `unknown` |
+
+### `ActivityDefinitionKindOptions` (`kind`)
+
+| Value | Notes |
+| --- | --- |
+| `service_request` | Only supported kind; applying the definition produces a [Service Request](./service-request.mdx) |
+
+### `ActivityDefinitionCategoryOptions` (`classification`)
+
+| Value |
+| --- |
+| `laboratory` |
+| `imaging` |
+| `counselling` |
+| `surgical_procedure` |
+| `education` |
+
+## Bound value sets
+
+| Field | Value set slug | Source system / filter |
+| --- | --- | --- |
+| `code` | `activity-definition-procedure-code` | SNOMED CT, `concept is-a 71388002` (Procedure) |
+| `body_site` | `system-body-site-observation` | SNOMED CT body-site concepts |
+| `diagnostic_report_codes[]` | `system-observation` | LOINC (`http://loinc.org`) |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (see [Base model](../foundation/base-model.mdx)), which provides `serialize` (DB → Pydantic) and `de_serialize` (Pydantic → DB) plus the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__model__ = ActivityDefinition`, `__exclude__ = ["facility"]`.
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BaseActivityDefinitionSpec` | shared base | `id`, `title`, `derived_from_uri`, `status`, `description`, `usage`, `classification`, `kind`, `code`, `body_site`, `diagnostic_report_codes`. |
+| `ActivityDefinitionWriteSpec` | write · create + update | Adds `locations` (`list[UUID4]`), `specimen_requirements` / `observation_result_requirements` / `charge_item_definitions` (`list[ExtendedSlugType]`), `healthcare_service` (`UUID4 \| None`), `category` (`ExtendedSlugType \| None`), `slug_value` (`SlugType`). |
+| `ActivityDefinitionReadSpec` | read · list | Base fields plus `version`, `tags`, `category` (serialized dict), `slug`, `slug_config`. |
+| `ActivityDefinitionRetrieveSpec` | read · detail | Extends `ReadSpec`; expands `specimen_requirements`, `observation_result_requirements`, `locations`, `healthcare_service`, `charge_item_definitions` from ID arrays into fully serialized nested objects. |
+
+### Server-side behaviour
+
+- **`ActivityDefinitionWriteSpec.perform_extra_deserialization`** (write path):
+ - Resolves `healthcare_service` by `external_id` (or clears it to `None` when absent).
+ - Resolves `category` by `slug` (only when provided).
+ - Assigns `obj.slug = self.slug_value`.
+- **`ActivityDefinitionReadSpec.perform_extra_serialization`** (read path): sets `id = external_id`, renders `tags`, serializes `category` (if present) via `ResourceCategoryReadSpec`, and parses `slug_config = obj.parse_slug(obj.slug)`.
+- **`ActivityDefinitionRetrieveSpec.perform_extra_serialization`**: calls the parent, then resolves each ID array (`specimen_requirements`, `observation_result_requirements`, `locations`, `charge_item_definitions`) against its model — silently dropping IDs that no longer exist — and serializes `healthcare_service`.
+- The base `de_serialize` writes only model-mapped fields (`exclude_defaults=True`), so unset optional fields fall back to model defaults.
+
+## Methods & save behaviour
+
+- **Apply → Service Request** ([`service_request.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/service_request.py)): `convert_ad_to_sr(activity_definition, encounter)` builds a draft [`ServiceRequest`](./service-request.mdx) carrying over `title`, `category`, `code`, `body_site`, `locations`, `healthcare_service`, and binding `patient`/`encounter` from the encounter. The created request defaults to `status=draft`, `intent=proposal`, `priority=routine`, `do_not_perform=False`.
+- **Charge items on apply**: `apply_ad_charge_definitions(activity_definition, encounter, service_request)` loads every linked [Charge Item Definition](../billing/charge-item-definition.mdx), instantiates a Charge Item per definition (via `apply_charge_item_definition`), tags it to the service request (`service_resource = service_request`, `service_resource_id = external_id`), copies the creator, and sets `performer_actor` to the request's `requester` when set.
+- `version` / `latest` model an append-only version chain — clients select the active definition via `latest`, while older versions remain referenceable.
+
+## API integration notes
+
+- Payload field names follow the Pydantic specs and differ from the Django model: write `slug` as `slug_value`; reference `specimen_requirements` / `observation_result_requirements` / `charge_item_definitions` / `category` by **external slug** (`ExtendedSlugType`, `f-`/`i-` prefixed), `locations` / `healthcare_service` by **UUID**.
+- `code` is required and must validate against the `activity-definition-procedure-code` value set; `body_site` and each entry of `diagnostic_report_codes` validate against their bound value sets.
+- The "apply" operation instantiates the target `kind` (currently only `service_request`) by merging stored defaults with the encounter; linked `charge_item_definitions` are instantiated at the same time.
+- Stored requirement/linkage fields are **plain integer ID arrays** — retrieve serialization resolves them to nested objects and skips IDs that no longer resolve.
+
+## Related
+
+- Reference: [Service Request](./service-request.mdx)
+- Reference: [Specimen Definition](./specimen-definition.mdx)
+- Reference: [Observation Definition](./observation-definition.mdx)
+- Reference: [Charge Item Definition](../billing/charge-item-definition.mdx)
+- Reference: [Healthcare Service](../facility/healthcare-service.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Resource Category](../platform/resource-category.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [`activity_definition.py` (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/activity_definition.py)
+- Source: [`spec.py` (resource specs)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/spec.py)
+- Source: [`valueset.py` (procedure-code value set)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/valueset.py)
+- Source: [`service_request.py` (apply logic)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/service_request.py)
diff --git a/versioned_docs/version-3.1/references/clinical/allergy-intolerance.mdx b/versioned_docs/version-3.1/references/clinical/allergy-intolerance.mdx
new file mode 100644
index 0000000..5d29229
--- /dev/null
+++ b/versioned_docs/version-3.1/references/clinical/allergy-intolerance.mdx
@@ -0,0 +1,175 @@
+---
+sidebar_position: 4
+---
+
+# Allergy Intolerance
+
+Technical reference for the `AllergyIntolerance` module in Care EMR. Care aligns this resource to FHIR `AllergyIntolerance`.
+
+The Django model is the **storage layer** — several fields are opaque `JSONField`s whose real structure is defined only in the Pydantic **resource specs** (the API/implementation layer). The specs define the enums, the JSON-field shapes, validation, the bound value set, and the read/write API schemas. This page documents both.
+
+**Source:**
+- Model: [`care/emr/models/allergy_intolerance.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/allergy_intolerance.py)
+- Specs: [`care/emr/resources/allergy_intolerance/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/allergy_intolerance/spec.py) · [`valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/allergy_intolerance/valueset.py)
+- Base resource: [`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `AllergyIntolerance` | Records a patient's allergy or intolerance to a substance, scoped to an encounter |
+
+`AllergyIntolerance` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `AllergyIntolerance` fields
+
+### Clinical classification
+
+| Field | Type (storage) | Spec type / values | Notes |
+| --- | --- | --- | --- |
+| `clinical_status` | `CharField(100)`, null | `ClinicalStatusChoices` | Active / inactive / resolved (FHIR `clinical-status`). Required on write. |
+| `verification_status` | `CharField(100)`, null | `VerificationStatusChoices` | Confirmation level. Required on write. |
+| `category` | `CharField(100)`, null | `CategoryChoices` | Allergy category. Required on **create** only (not exposed on update). |
+| `criticality` | `CharField(100)`, null | `CriticalityChoices` | Potential clinical harm. Required on write. |
+| `allergy_intolerance_type` | `CharField(20)`, default `"allergy"` | `AllergyIntoleranceTypeOptions` | Allergy vs intolerance. Defaults to `allergy`. |
+
+### Substance & coding
+
+| Field | Type (storage) | Spec type / shape | Notes |
+| --- | --- | --- | --- |
+| `code` | `JSONField` (`default=dict`, not null/blank) | `Coding` bound to value set `system-allergy-code` | The allergen/substance. On write it is a `ValueSetBoundCoding` validated against the bound value set; on read it is a plain `Coding`. Required. |
+
+`Coding` shape: `{ system?: str, version?: str, code: str (required), display?: str }` (`extra="forbid"`).
+
+### Timing
+
+| Field | Type (storage) | Spec type / shape | Notes |
+| --- | --- | --- | --- |
+| `onset` | `JSONField` (`default=dict`) | `AllergyIntoleranceOnSetSpec` | Structured onset. Defaults to `{}`. Only exposed on **create** (write). See shape below. |
+| `recorded_date` | `DateTimeField`, null | `datetime \| None` | When first asserted. Optional, write-on-**create** only. |
+| `last_occurrence` | `DateTimeField`, null | `datetime \| None` | Most recent known reaction. Optional on create and update. |
+
+`AllergyIntoleranceOnSetSpec` shape:
+
+| Sub-field | Type | Required | Default |
+| --- | --- | --- | --- |
+| `onset_datetime` | `datetime` | No | `None` |
+| `onset_age` | `int` | No | `None` |
+| `onset_string` | `str` | No | `None` |
+| `note` | `str` | **Yes** | — |
+
+### Context & notes
+
+| Field | Type (storage) | Spec type | Notes |
+| --- | --- | --- | --- |
+| `patient` | `FK → Patient` (`CASCADE`) | excluded from specs | Subject of the allergy. Not client-settable — derived server-side from the encounter on create. |
+| `encounter` | `FK → Encounter` (`CASCADE`) | `UUID4` | Encounter the allergy was recorded in. Sent as `external_id`; required on write; validated to exist. |
+| `note` | `TextField`, null | `str \| None` | Free-text clinical note. Optional. |
+| `copied_from` | `BigIntegerField`, null (`default=None`) | not exposed | Source record ID when this entry is a maintained copy. Platform-maintained — never set from clients. |
+
+## Enum values
+
+### `ClinicalStatusChoices`
+
+| Value |
+| --- |
+| `active` |
+| `inactive` |
+| `resolved` |
+
+### `VerificationStatusChoices`
+
+| Value |
+| --- |
+| `unconfirmed` |
+| `presumed` |
+| `confirmed` |
+| `refuted` |
+| `entered_in_error` |
+
+### `CategoryChoices`
+
+| Value |
+| --- |
+| `food` |
+| `medication` |
+| `environment` |
+| `biologic` |
+
+### `CriticalityChoices`
+
+| Value |
+| --- |
+| `low` |
+| `high` |
+| `unable_to_assess` |
+
+### `AllergyIntoleranceTypeOptions`
+
+| Value |
+| --- |
+| `allergy` (default) |
+| `intolerance` |
+
+## Bound value set
+
+| Value set | Slug | System | Status |
+| --- | --- | --- | --- |
+| `CARE_ALLERGY_CODE_VALUESET` ("Allergy") | `system-allergy-code` | SNOMED CT (`http://snomed.info/sct`) | `active` |
+
+The `code` field on write binds to this value set via `ValueSetBoundCoding[...slug]`, which validates the submitted `Coding` against the value set (`validate_valueset`). The compose includes SNOMED CT concepts that are `is-a` descendants of: `105590001`, `418038007`, `267425008`, `29736007`, `340519003`, `190753003`, `413427002`, `716186003`. The value set is also registered as a system (`register_as_system()`).
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB object → pydantic, via `model_construct`) and `de_serialize` (pydantic → DB object), plus the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `BaseAllergyIntoleranceSpec` sets `__model__ = AllergyIntolerance` and `__exclude__ = ["patient", "encounter"]` (these are not auto-mapped; they are handled by the hooks). `meta` is carried on every spec via the base.
+
+| Spec class | Role | Fields exposed |
+| --- | --- | --- |
+| `BaseAllergyIntoleranceSpec` | shared base | `id: UUID4` |
+| `AllergyIntoleranceWriteSpec` | write · create | `clinical_status`, `verification_status`, `category`, `criticality`, `last_occurrence?`, `recorded_date?`, `encounter` (UUID4), `code` (bound Coding), `onset` (default `{}`), `allergy_intolerance_type` (default `allergy`), `note?` |
+| `AllergyIntoleranceUpdateSpec` | write · update | `clinical_status`, `verification_status`, `criticality`, `last_occurrence?`, `note?`, `encounter` (UUID4), `allergy_intolerance_type` (default `allergy`) |
+| `AllergyIntoleranceReadSpec` | read · list & detail | `id`, `clinical_status`, `verification_status`, `category`, `criticality`, `code` (Coding), `encounter` (UUID4), `onset`, `last_occurrence?`, `recorded_date?`, `note?`, `allergy_intolerance_type`, `created_by?`, `updated_by?`, `created_date`, `modified_date` |
+| `AllergyIntoleranceOnSetSpec` | nested (shape of `onset` JSON) | `onset_datetime?`, `onset_age?`, `onset_string?`, `note` (required) |
+
+Notes on the spec set:
+
+- **Create vs update surface differs.** `category`, `code`, `onset`, and `recorded_date` are write-only on **create** (`AllergyIntoleranceWriteSpec`) and are **not** present on `AllergyIntoleranceUpdateSpec` — they cannot be changed after creation through the update path.
+- **Read uses relaxed types.** `AllergyIntoleranceReadSpec` types the coded fields as plain `str` and `code` as `Coding` (no value-set re-validation on read, for performance — it relies on `model_construct`).
+
+### Validation
+
+- `encounter` (both write specs): `validate_encounter_exists` — rejects with `"Encounter not found"` if no `Encounter` has that `external_id`.
+- `code` (create): validated against value set `system-allergy-code` (must be a permitted SNOMED CT allergy concept).
+- `onset.note` is required whenever an `onset` object is supplied.
+- Any `datetime` fields are stored as-is; tz-aware handling for period-style values is enforced by `PeriodSpec` in the base (start ≤ end, both tz-aware) — `AllergyIntolerance` itself uses scalar datetimes, not a `PeriodSpec`.
+
+### Server-side side effects
+
+- **Create** (`AllergyIntoleranceWriteSpec.perform_extra_deserialization`): resolves `encounter` by `external_id` and sets `obj.encounter`, then sets `obj.patient = obj.encounter.patient`. The client never sends `patient` directly.
+- **Update** (`AllergyIntoleranceUpdateSpec.perform_extra_deserialization`): if `encounter` is present, re-resolves and sets `obj.encounter`.
+- **Read** (`AllergyIntoleranceReadSpec.perform_extra_serialization`): sets `mapping["id"] = obj.external_id`, sets `encounter` to `obj.encounter.external_id`, and serializes audit users (`created_by` / `updated_by`) via `serialize_audit_users`.
+
+## Methods & save behaviour
+
+- `serialize` / `de_serialize` are inherited from `EMRResource`. `de_serialize` maps only DB fields not in `__exclude__` and not `id`/`external_id`, then calls `perform_extra_deserialization` for the encounter/patient wiring described above.
+- `patient` is **always** derived from the encounter on create — there is no path to set it from a client payload.
+- `copied_from` is platform-maintained for propagated/copied records and is not part of any spec.
+- `patient` and `encounter` are both `CASCADE`-deleted, so an allergy record never outlives its patient or encounter.
+
+## API integration notes
+
+- Exposed through Care's REST API and aligned to the FHIR `AllergyIntolerance` resource; API field names follow the spec classes above (e.g. `encounter` is sent/returned as a UUID `external_id`, not a numeric FK).
+- Submit `code` as a `Coding` from the bound value set `system-allergy-code` rather than free text, so allergies can be matched against medications and other business logic.
+- The coded status fields (`clinical_status`, `verification_status`, `category`, `criticality`) accept only their enum values listed above; invalid values are rejected on write.
+- Do not send `patient` or `copied_from`; both are server-maintained.
+
+## Related
+
+- Reference: [Patient](./patient.mdx)
+- Reference: [Encounter](./encounter.mdx)
+- Reference: [Condition](./condition.mdx)
+- Reference: [Observation](./observation.mdx)
+- Base: [EMRBaseModel](../foundation/base-model.mdx)
+- Source (model): [allergy_intolerance.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/allergy_intolerance.py)
+- Source (spec): [resources/allergy_intolerance/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/allergy_intolerance/spec.py)
+- Source (value set): [resources/allergy_intolerance/valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/allergy_intolerance/valueset.py)
diff --git a/versioned_docs/version-3.1/references/clinical/condition.mdx b/versioned_docs/version-3.1/references/clinical/condition.mdx
new file mode 100644
index 0000000..1722574
--- /dev/null
+++ b/versioned_docs/version-3.1/references/clinical/condition.mdx
@@ -0,0 +1,194 @@
+---
+sidebar_position: 3
+---
+
+# Condition
+
+Technical reference for the `Condition` module in Care EMR. A condition captures a clinical problem, diagnosis, or symptom recorded for a patient. In the Care product UI conditions are also surfaced as **Symptoms** (the FHIR `Condition` resource).
+
+**Source:**
+- Model: [`care/emr/models/condition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/condition.py)
+- Resource spec: [`care/emr/resources/condition/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/condition/spec.py)
+- Value set: [`care/emr/resources/condition/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/condition/valueset.py)
+
+Two layers define a `Condition`:
+
+- The **Django model** (`care/emr/models/condition.py`) is the storage layer. Coded/timing fields (`code`, `body_site`, `onset`, `abatement`) are opaque `JSONField`s — their real structure lives in the spec layer, not the model.
+- The **Pydantic resource specs** (`care/emr/resources/condition/spec.py`) are the API layer. They define the enums, the structured shape of the JSON fields, field validation, the value-set binding for `code`, and the read/write schemas.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Condition` | A clinical condition, problem, diagnosis, or symptom recorded for a patient |
+
+`Condition` extends [`EMRBaseModel`](../foundation/base-model.mdx), the shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON.
+
+## `Condition` fields
+
+### Status & classification
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `clinical_status` | `CharField(100)`, nullable | Clinical state. Spec binds to [`ClinicalStatusChoices`](#clinicalstatuschoices-values); optional on write. |
+| `verification_status` | `CharField(100)`, nullable | Level of certainty. Spec binds to [`VerificationStatusChoices`](#verificationstatuschoices-values); **required** on write (`ConditionSpec`/`ConditionUpdateSpec`). |
+| `category` | `CharField(100)`, nullable | Classification. Spec binds to [`CategoryChoices`](#categorychoices-values); **required** on create (`ConditionSpec`). |
+| `severity` | `CharField(100)`, nullable | Subjective severity. Spec binds to [`SeverityChoices`](#severitychoices-values); optional on write. |
+
+### Coded concepts
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `code` | `JSONField` (`default=dict`, not null/blank) | The condition itself. On write a single [`Coding`](#coding-shape) bound to the [`CARE_CODITION_CODE_VALUESET`](#code-value-set-binding) value set (SNOMED CT clinical findings). On read serialized as a plain `Coding`. |
+| `body_site` | `JSONField` (`default=dict`, not null/blank) | Anatomical location(s). Present in the model but **not exposed by any current spec** — not written or read through the standard `Condition` API schemas. |
+
+### Timing
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `onset` | `JSONField` (`default=dict`) | Choice-of-type onset. Shape = [`ConditionOnSetSpec`](#conditiononsetspec-onset-shape). Defaults to `{}`. |
+| `abatement` | `JSONField` (`default=dict`) | When the condition resolved / went into remission. Shape = [`ConditionAbatementSpec`](#conditionabatementspec-abatement-shape). Defaults to `{}`. |
+| `recorded_date` | `DateTimeField`, nullable | When the condition was first recorded. Not exposed by the current specs. |
+
+### Context & notes
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `patient` | `FK → Patient`, `on_delete=CASCADE` | Subject of the condition. Server-set from the encounter on create — never accepted directly from clients (`__exclude__`). |
+| `encounter` | `FK → Encounter`, nullable, `on_delete=CASCADE` | Encounter the condition was recorded in. Server-set on create (`__exclude__`); supplied as a UUID in the spec and validated to exist. |
+| `note` | `TextField`, nullable | Free-text clinical note. |
+
+## Enums
+
+All enum classes are defined in `care/emr/resources/condition/spec.py` as `str, Enum`. The stored/serialized value is the string on the right.
+
+### `ClinicalStatusChoices` values
+
+| Value |
+| --- |
+| `active` |
+| `recurrence` |
+| `relapse` |
+| `inactive` |
+| `remission` |
+| `resolved` |
+| `unknown` |
+
+### `VerificationStatusChoices` values
+
+| Value |
+| --- |
+| `unconfirmed` |
+| `provisional` |
+| `differential` |
+| `confirmed` |
+| `refuted` |
+| `entered_in_error` |
+
+### `CategoryChoices` values
+
+| Value |
+| --- |
+| `problem_list_item` |
+| `encounter_diagnosis` |
+| `chronic_condition` |
+
+### `SeverityChoices` values
+
+| Value |
+| --- |
+| `mild` |
+| `moderate` |
+| `severe` |
+
+## Nested JSON shapes
+
+These spec classes (all extend `EMRResource`) define the real structure of the model's JSON fields.
+
+### `ConditionOnSetSpec` (`onset` shape)
+
+| Field | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `onset_datetime` | `datetime \| None` | `None` | Coerced to timezone-aware (`make_aware`) if naive. Validated: **cannot be in the future** (`> care_now()` raises). |
+| `onset_age` | `int \| None` | `None` | Age at onset. |
+| `onset_string` | `str \| None` | `None` | Free-text onset description. |
+| `note` | `str \| None` | `None` | Note about the onset. |
+
+### `ConditionAbatementSpec` (`abatement` shape)
+
+| Field | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `abatement_datetime` | `datetime \| None` | `None` | No future-date validation (unlike `onset_datetime`). |
+| `abatement_age` | `int \| None` | `None` | Age at abatement. |
+| `abatement_string` | `str \| None` | `None` | Free-text abatement description. |
+| `note` | `str \| None` | `None` | Note about the abatement. |
+
+### `Coding` shape
+
+`code` is a single [`Coding`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py) (not a `CodeableConcept`). `extra="forbid"`.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `system` | `str \| None` | Code system URI (e.g. `http://snomed.info/sct`). |
+| `version` | `str \| None` | Code system version. |
+| `code` | `str` | **Required.** The code value. |
+| `display` | `str \| None` | Human-readable label. |
+
+### `code` value-set binding
+
+On write, `code` is typed `ValueSetBoundCoding[CARE_CODITION_CODE_VALUESET.slug]` — a `Coding` validated against the **Condition code** value set (`care/emr/resources/condition/valueset.py`, slug `system-condition-code`). The value set includes SNOMED CT concepts that are `is-a` `404684003` (*Clinical finding*). Codes outside the value set are rejected on `ConditionSpec` / `ConditionUpdateSpec` / `ChronicConditionUpdateSpec`. The read spec uses a plain `Coding` (no value-set validation, for read performance).
+
+## Resource specs (API schema)
+
+All specs extend `BaseConditionSpec` → `EMRResource` and convert via `serialize` (DB → Pydantic) / `de_serialize` (Pydantic → DB). `BaseConditionSpec` sets `__model__ = Condition`, `__exclude__ = ["patient", "encounter"]` (these are server-maintained, never trusted from the client), and exposes `id: UUID4`.
+
+| Spec class | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `BaseConditionSpec` | shared base | `id`; excludes `patient` and `encounter` from direct mapping. |
+| `ConditionSpec` | write · create | `clinical_status?`, `verification_status` (**required**), `severity?`, `code` (value-set bound, **required**), `encounter` (UUID4, **required**), `onset` (`={}`), `abatement` (`={}`), `note?`, `category` (**required**). Validates the encounter exists; on create sets `obj.encounter` and `obj.patient = encounter.patient`. |
+| `ConditionUpdateSpec` | write · update | `clinical_status?`, `verification_status` (**required**), `severity?`, `code` (value-set bound, **required**), `onset` (`={}`), `abatement` (`={}`), `note?`. Does **not** accept `encounter`, `category`, `patient`. |
+| `ChronicConditionUpdateSpec` | write · update (chronic) | Extends `ConditionUpdateSpec` and adds `encounter` (UUID4). On deserialize, if `encounter` is set, resolves it (`get_object_or_404`) and assigns `obj.encounter`. |
+| `ConditionReadSpec` | read · detail/list | `clinical_status`, `verification_status`, `category`, `criticality`, `severity` (all plain `str`), `code` (plain `Coding`), `encounter` (UUID4), `onset`, `abatement`, `created_by?`, `updated_by?`, `note?`, `created_date`, `modified_date`. |
+
+### Validation & server-side behaviour
+
+- **Encounter required and verified on create.** `ConditionSpec.validate_encounter_exists` rejects unknown encounter UUIDs; `perform_extra_deserialization` (create only) loads the encounter and derives `patient` from it. Clients never set `patient`/`encounter` fields directly.
+- **`verification_status` is mandatory** on both `ConditionSpec` and `ConditionUpdateSpec`; `category` is mandatory only on create (`ConditionSpec`).
+- **`code` must be in the bound value set** (SNOMED CT clinical findings) on all write specs.
+- **`onset_datetime` cannot be in the future** and is forced timezone-aware; `abatement_datetime` has no such constraint.
+- **`ConditionReadSpec` exposes `criticality`**, a string that is present in the read schema but has no backing column on the `Condition` model — it is not populated by the standard serializer and is effectively unset on read.
+- **Read serialization** (`perform_extra_serialization`) maps `id = external_id`, replaces `encounter` with its `external_id`, and expands `created_by`/`updated_by` via `serialize_audit_users` (cached `UserSpec`).
+- The Django model carries `body_site` and `recorded_date`, but **no current spec reads or writes them** — they are storage-only fields not part of the standard `Condition` API surface.
+
+## Related models
+
+`Condition` links to two clinical records:
+
+```text
+patient → FK Patient (CASCADE, server-derived from encounter)
+encounter → FK Encounter (CASCADE, nullable; required on create)
+```
+
+Deleting a `Patient` or `Encounter` cascades to its `Condition` rows. Although the model allows a null `encounter`, the standard create flow always derives `patient` from a supplied encounter.
+
+## Methods & save behaviour
+
+- `serialize(obj)` / `de_serialize(obj)` (from `EMRResource`) convert between the `Condition` model and the spec. Field mapping uses the model's non-FK column names; `__exclude__` (`patient`, `encounter`) and `id`/`external_id` are skipped during deserialization.
+- `ConditionSpec.perform_extra_deserialization(is_update=False, obj)` — create path: resolves the encounter and sets `obj.patient = obj.encounter.patient`.
+- `ChronicConditionUpdateSpec.perform_extra_deserialization` — resolves and assigns `encounter` when provided.
+- `ConditionReadSpec.perform_extra_serialization` — sets `id`/`encounter` external ids and serializes audit users.
+- `external_id`, audit fields, `meta`/`history`, and soft-delete (`deleted`) are platform-maintained via [`EMRBaseModel`](../foundation/base-model.mdx).
+
+## API integration notes
+
+- `Condition` aligns with the FHIR `Condition` resource and is surfaced in the product as **Symptoms**.
+- Write `code` as a structured `Coding` (`{system, code, display}`) drawn from the SNOMED CT clinical-finding value set — not free text. `verification_status` must always be provided; `category` and `encounter` are required when creating.
+- `onset` / `abatement` are structured choice-of-type objects (`*_datetime`, `*_age`, `*_string`, `note`) — not arbitrary JSON. `onset_datetime` must be timezone-aware and not in the future.
+- Do not send `patient`, `external_id`, audit fields, or `deleted` from clients — the server maintains them.
+
+## Related
+
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [condition.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/condition.py) · [spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/condition/spec.py) · [valueset.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/condition/valueset.py)
diff --git a/versioned_docs/version-3.1/references/clinical/consent.mdx b/versioned_docs/version-3.1/references/clinical/consent.mdx
new file mode 100644
index 0000000..0ddb166
--- /dev/null
+++ b/versioned_docs/version-3.1/references/clinical/consent.mdx
@@ -0,0 +1,175 @@
+---
+sidebar_position: 8
+---
+
+# Consent
+
+Technical reference for the `Consent` module in Care EMR.
+
+The Django model is the **storage layer**: it persists `status`, `category`, `decision` as bare `CharField`s and `period` / `verification_details` as opaque `JSONField`s. The real constraints — allowed enum values, the nested shape of those JSON fields, validation, and the read/write API schemas — live in the **Pydantic resource specs** (`care/emr/resources/consent/spec.py`), all built on `EMRResource`.
+
+**Source:**
+
+- Model: [`care/emr/models/consent.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/consent.py)
+- Resource spec: [`care/emr/resources/consent/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/consent/spec.py)
+- Base resource: [`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Consent` | Records a patient's consent decision (permit/deny) for a category of activity, scoped to an encounter |
+
+`Consent` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `Consent` fields
+
+Storage types come from the Django model; the **Spec type** column gives the real shape enforced at the API layer.
+
+| Field | Storage type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(50)` | `ConsentStatusChoices` (enum) | Required. Consent state. See [enum](#consentstatuschoices-values) |
+| `category` | `CharField(50)` | `CategoryChoice` (enum) | Required. Classification of the consent. See [enum](#categorychoice-values) |
+| `date` | `DateTimeField` | `datetime` | Required. When the consent was recorded |
+| `period` | `JSONField` (default `dict`) | `PeriodSpec { start, end }` | Validity window. Both ends tz-aware; `start ≤ end`; and `start ≥ date` (enforced on create). See [PeriodSpec](#periodspec-nested-shape) |
+| `encounter` | `FK → Encounter` (`CASCADE`, `related_name="consents"`) | `UUID4` (`external_id`) | Required. Encounter the consent applies to; deleting the encounter cascades to its consents. Excluded from the auto DB mapping (`__exclude__`) and resolved/assigned manually — see [Methods](#methods--save-behaviour) |
+| `decision` | `CharField(10)` | `DecisionType` (enum) | Required. `permit` / `deny`. See [enum](#decisiontype-values) |
+| `verification_details` | `JSONField` (default `list`) | `list[ConsentVerificationSpec]` | Read-only on the API; list of verification records. See [ConsentVerificationSpec](#consentverificationspec-nested-shape) |
+| `note` | `TextField` (null, blank) | `str \| None` | Optional free-text note |
+
+> Note: `source_attachments` is **not** a model field. It is derived at serialize time from `FileUpload` rows (see [Resource specs](#resource-specs-api-schema)).
+
+## Enums
+
+### `ConsentStatusChoices` values
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Consent is being drafted, not yet in force |
+| `active` | Consent is in force |
+| `inactive` | Consent is no longer in force |
+| `not_done` | Consent activity did not occur |
+| `entered_in_error` | Consent was recorded in error |
+
+### `CategoryChoice` values
+
+| Value | Meaning |
+| --- | --- |
+| `research` | Consent for research participation |
+| `patient_privacy` | Patient privacy / information disclosure consent |
+| `treatment` | Consent to treatment |
+| `dnr` | Do-not-resuscitate directive |
+| `comfort_care` | Comfort / palliative care directive |
+| `acd` | Advance care directive |
+| `adr` | Advance directive (other) |
+
+> A `consent_document` category (LOINC 59284-0) exists in migrations only and is **not** an accepted API value.
+
+### `DecisionType` values
+
+| Value | Meaning |
+| --- | --- |
+| `permit` | Consent is granted |
+| `deny` | Consent is refused |
+
+### `VerificationType` values
+
+Used inside `ConsentVerificationSpec.verification_type`.
+
+| Value | Meaning |
+| --- | --- |
+| `family` | Verified by a family member |
+| `validation` | Verified through validation |
+
+## Nested JSON shapes
+
+### `PeriodSpec` nested shape
+
+The `period` JSON field deserializes to `PeriodSpec` (from `care/emr/resources/base.py`). The Django model default is `dict`, so an absent period stores `{}`.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `start` | `datetime \| None` | Default `None`. Must be **timezone-aware** if set |
+| `end` | `datetime \| None` | Default `None`. Must be **timezone-aware** if set |
+
+Validation (`PeriodSpec.validate_period`, mode `after`):
+
+- `start` must be tz-aware (else `"Start Date must be timezone aware"`).
+- `end` must be tz-aware (else `"End Date must be timezone aware"`).
+- If both set, `start ≤ end` (else `"Start Date cannot be greater than End Date"`).
+
+### `ConsentVerificationSpec` nested shape
+
+Each entry in `verification_details` follows this shape (write side). On read, `verified_by` is expanded — see below.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `verified` | `bool` | Required. Whether the consent was verified |
+| `verified_by` | `UUID4 \| None` | Default `None`. User `external_id` who verified (write). On read, replaced by a full `UserSpec` object |
+| `verification_date` | `datetime \| None` | Default `None` |
+| `verification_type` | `VerificationType` | Required. `family` / `validation` |
+| `note` | `str \| None` | Default `None` |
+
+## Resource specs (API schema)
+
+All specs subclass `EMRResource` via `ConsentBaseSpec`. `serialize` builds the read schema from the model; `de_serialize` writes a model from the payload (skipping `id`/`external_id` and `__exclude__` fields). `ConsentBaseSpec` sets `__exclude__ = ["encounter"]`, so the encounter FK is never round-tripped through the generic mapping and is handled by the deserialization hooks instead.
+
+| Spec class | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `ConsentBaseSpec` | shared base | `id`, `status`, `category`, `date`, `period`, `encounter` (UUID4), `decision`, `note`. `__model__ = Consent`, `__exclude__ = ["encounter"]` |
+| `ConsentCreateSpec` | write · create | Same fields as base (all required except `id`, `note`). Validates `period.start ≥ date`; resolves `encounter` from `external_id` → `Encounter` instance |
+| `ConsentUpdateSpec` | write · update | All fields optional (`status`, `category`, `date`, `period`, `encounter`, `decision`, `note` → `None`). Keeps the existing `encounter` (ignores any supplied value) |
+| `ConsentListSpec` | read · list | Base fields plus derived `source_attachments: list` and expanded `verification_details: list` |
+| `ConsentRetrieveSpec` | read · detail | Identical to `ConsentListSpec` (`pass`) |
+
+Nested specs used by the schema: [`PeriodSpec`](#periodspec-nested-shape) (the `period` field) and [`ConsentVerificationSpec`](#consentverificationspec-nested-shape) (entries of `verification_details`).
+
+### Validation
+
+- **Create only** (`ConsentCreateSpec.validate_period_and_date`, mode `after`): if `period.start` is set and `period.start < date`, raises `"Start of the period cannot be before than the Consent date"`.
+- **All specs** (via `PeriodSpec`): tz-aware `start`/`end`, and `start ≤ end`.
+- Enum fields (`status`, `category`, `decision`) reject any value outside their respective enums; on `ConsentUpdateSpec` they may be omitted (`None`).
+
+### Server-maintained behaviour
+
+- **Encounter resolution (create):** `ConsentCreateSpec.perform_extra_deserialization` sets `obj.encounter = Encounter.objects.get(external_id=self.encounter)` when not an update.
+- **Encounter immutability (update):** `ConsentUpdateSpec.perform_extra_deserialization` copies `self.encounter = obj.encounter` on update, so the encounter cannot be reassigned via the API.
+- **Source attachments (read):** `ConsentListSpec.perform_extra_serialization` sets `source_attachments` to the serialized `FileUploadListSpec` of every `FileUpload` where `associating_id == consent.external_id`, `file_category == consent_attachment`, and `file_type == consent`.
+- **Verifier expansion (read):** each `verification_details[i].verified_by` is replaced with a full `UserSpec` (`User.objects.get(external_id=...)`).
+- **Identifiers (read):** `id` is set to `external_id`; `encounter` is serialized as `obj.encounter.external_id`.
+
+## Related models
+
+`Consent` links to a single `Encounter`; an encounter can carry many consents via the `consents` reverse relation:
+
+```text
+encounter → FK Encounter (CASCADE, related_name="consents")
+```
+
+The owning patient is reached transitively through `encounter`. Coded values (`status`, `category`, `decision`) and structured JSON (`period`, `verification_details`) are validated at the API layer (Pydantic specs), not by database constraints.
+
+Read schemas also reach into `FileUpload` (consent attachments) and `User` (verifiers) at serialize time.
+
+## Methods & save behaviour
+
+- `EMRResource.serialize(obj)` → builds the read object: copies non-FK model fields present on the spec, runs `perform_extra_serialization` (id, encounter `external_id`, `source_attachments`, expanded `verification_details`), and stamps `version`.
+- `EMRResource.de_serialize(obj=None)` → builds/updates the model: dumps the spec (`exclude_defaults=True`), assigns matching DB fields (skipping `id`/`external_id` and `__exclude__`), then runs `perform_extra_deserialization`.
+- `encounter` is in `__exclude__`, so it is never set by the generic loop — create resolves it from `external_id`, update preserves the existing FK.
+- Status is a free field: changing `status` to `entered_in_error` (or any other value) is a plain field write — there is no server-side status-history side effect for consent.
+
+## API integration notes
+
+- `Consent` is exposed through Care's REST API using the spec classes above; payload field names match the spec (`encounter` is a UUID `external_id`, not a numeric PK).
+- The model aligns with the FHIR `Consent` resource — `status`, `category`, `decision`, and `period` map to their FHIR counterparts.
+- `period` accepts `{ start, end }` (tz-aware datetimes); `verification_details` is read-only on the consent payload and returns expanded user objects.
+- Allowed values for `status`, `category`, and `decision` are enforced by the Pydantic enums at the API layer, not by the database column definitions.
+- `note` is optional free text and may be null.
+
+## Related
+
+- Reference: [Encounter](./encounter.mdx)
+- Reference: [Patient](./patient.mdx)
+- Reference: [File upload](../platform/file-upload.mdx)
+- Reference: [User](../access/user.mdx)
+- Source: [consent.py (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/consent.py)
+- Source: [spec.py (resource)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/consent/spec.py)
diff --git a/versioned_docs/version-3.1/references/clinical/diagnostic-report.mdx b/versioned_docs/version-3.1/references/clinical/diagnostic-report.mdx
new file mode 100644
index 0000000..c4ddd47
--- /dev/null
+++ b/versioned_docs/version-3.1/references/clinical/diagnostic-report.mdx
@@ -0,0 +1,143 @@
+---
+sidebar_position: 7
+---
+
+# Diagnostic Report
+
+Technical reference for the `DiagnosticReport` module in Care EMR.
+
+The Django model (`care/emr/models/diagnostic_report.py`) is the **storage** layer: it persists `category` and `code` as opaque `JSONField`s and `status` as a free `CharField`. The real structure of those fields — the status enum, the coded-concept shape, the bound value sets, and the read/write API schemas — lives in the Pydantic **resource specs** (`care/emr/resources/diagnostic_report/`), which build on `EMRResource` and serialize/deserialize between the API and the model.
+
+**Source:**
+- Model: [`care/emr/models/diagnostic_report.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/diagnostic_report.py)
+- Spec: [`care/emr/resources/diagnostic_report/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/diagnostic_report/spec.py)
+- Valueset: [`care/emr/resources/diagnostic_report/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/diagnostic_report/valueset.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `DiagnosticReport` | Results and interpretation of a diagnostic test or investigation, tied to a `ServiceRequest` |
+
+`DiagnosticReport` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `DiagnosticReport` fields
+
+### Classification
+
+| Field | Model type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(255)` | `DiagnosticReportStatusChoices` (enum, required) | Lifecycle status of the report. Constrained to the enum below by the spec. Optional on update. |
+| `category` | `JSONField` (null) | `Coding { system, version?, code, display? }`, **required** | Coded service section the report belongs to. Bound to the **Diagnostic Service Sections** value set (`code` is validated against it). |
+| `code` | `JSONField` (null) | `Coding { system, version?, code, display? }`, optional | Coded name/type of the report (test or panel performed). Bound to the **Observation** value set (LOINC). Nullable. |
+
+The `Coding` shape (from `care/emr/resources/common/coding.py`, `extra="forbid"`):
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `system` | `str` | optional | Code system URI (e.g. `http://terminology.hl7.org/CodeSystem/v2-0074`, `http://loinc.org`) |
+| `version` | `str` | optional | Code system version |
+| `code` | `str` | **required** | The code; validated against the bound value set |
+| `display` | `str` | optional | Human-readable label |
+
+### Narrative
+
+| Field | Model type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `note` | `TextField` (null) | `str \| None` | Free-text comments about the report. Not parsed by the platform. |
+| `conclusion` | `TextField` (null) | `str \| None` | Clinical interpretation/summary of the results. Not parsed by the platform. |
+
+### Relationships
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` | `PROTECT`, nullable. Facility the report is scoped to. Set server-side; not a writable spec field. |
+| `patient` | `FK → emr.Patient` | `CASCADE`. Subject of the report. Set server-side; not a writable spec field. |
+| `encounter` | `FK → emr.Encounter` | `CASCADE`. Encounter the report was produced under. Set server-side; not a writable spec field. |
+| `service_request` | `FK → emr.ServiceRequest` | `CASCADE`. The order/request this report fulfils. Write-only on create (resolved from `external_id`); excluded from base serialization via `__exclude__`. |
+
+## Enum values
+
+### `DiagnosticReportStatusChoices`
+
+From `spec.py`. (`modified` is defined in source but commented out and is **not** an active value.)
+
+| Value | Meaning |
+| --- | --- |
+| `registered` | Report exists / has been registered |
+| `partial` | Some results available, report incomplete |
+| `preliminary` | Provisional results pending verification |
+| `final` | Verified, complete report |
+
+## Bound value sets
+
+The coded fields validate their `code` against a registered Care value set (`ValueSetBoundCoding`).
+
+| Field | Value set | Slug | Included system(s) |
+| --- | --- | --- | --- |
+| `category` | Diagnostic Service Sections | `system-diagnostic-service-sections-code` | `http://terminology.hl7.org/CodeSystem/v2-0074` |
+| `code` | Observation | `system-observation` | `http://loinc.org` |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`) via `DiagnosticReportSpecBase`. `serialize` builds the read payload from the model (mapping `external_id` → `id`); `de_serialize` writes spec fields back to the model, with `perform_extra_serialization`/`perform_extra_deserialization` hooks for server-side behaviour.
+
+| Spec class | Role | Fields / behaviour |
+| --- | --- | --- |
+| `DiagnosticReportSpecBase` | shared base | `id?`, `status` (enum, required), `category` (bound Coding, required), `code?` (bound Coding), `note?`, `conclusion?`. `__model__ = DiagnosticReport`, `__exclude__ = ["service_request"]`. |
+| `DiagnosticReportCreateSpec` | write · create | Base + `service_request: UUID4` (required, the report's `external_id`). `perform_extra_deserialization` resolves it via `get_object_or_404(ServiceRequest, external_id=...)` and sets `obj.service_request`. |
+| `DiagnosticReportUpdateSpec` | write · update | Base, but `status` is optional (`DiagnosticReportStatusChoices \| None`). `service_request` is **not** writable on update. |
+| `DiagnosticReportListSpec` | read · list | Base + `created_date`, `modified_date`, `service_request: dict \| None`. `perform_extra_serialization` sets `id = external_id` and embeds the linked request via `BaseServiceRequestSpec.serialize(...)`. |
+| `DiagnosticReportRetrieveSpec` | read · detail | Extends `ListSpec` + `observations: list[dict]`, `encounter: dict`, `created_by?`, `updated_by?`, `requester?`. See serialization below. |
+
+### `DiagnosticReportRetrieveSpec.perform_extra_serialization` (server-side, read)
+
+- Calls the `ListSpec` hook (sets `id`, embeds `service_request`).
+- `serialize_audit_users` → populates `created_by` / `updated_by` from cache (`UserSpec`).
+- `observations` → all `Observation` rows where `diagnostic_report == obj`, each via `ObservationRetrieveSpec.serialize(...)`.
+- `requester` → if `service_request.requester_id` is set, loaded from cache via `UserSpec`.
+- `encounter` → `EncounterListSpec.serialize(obj.encounter)`, with `encounter["patient"]` set to `PatientRetrieveSpec.serialize(obj.encounter.patient, facility=obj.facility)`.
+
+These embedded fields (`service_request`, `observations`, `encounter`, `requester`, audit users) are **read-only**; they are not accepted on create/update.
+
+## Related models
+
+`DiagnosticReport` is the only model defined in this file. It sits downstream of a `ServiceRequest` and is anchored to a patient and encounter:
+
+```text
+diagnostic_report
+ facility → FK facility.Facility (PROTECT, nullable)
+ patient → FK emr.Patient (CASCADE)
+ encounter → FK emr.Encounter (CASCADE)
+ service_request → FK emr.ServiceRequest (CASCADE)
+```
+
+Deleting a `Patient`, `Encounter`, or `ServiceRequest` cascades to its diagnostic reports; the `facility` link is `PROTECT`ed and only soft-deletes apply through `EMRBaseModel`. On retrieve, related `Observation` records are pulled in via the reverse FK (`Observation.diagnostic_report`).
+
+## Methods & save behaviour
+
+- **Create** (`DiagnosticReportCreateSpec.de_serialize`): writes `status`, `category`, `code`, `note`, `conclusion` onto the model; `perform_extra_deserialization` resolves `service_request` from its `external_id` (404 if missing). `facility`, `patient`, and `encounter` are set by the view layer from the request context, not from the payload.
+- **Update** (`DiagnosticReportUpdateSpec`): same base fields; `status` is optional so a partial status change is allowed. `service_request` cannot be reassigned.
+- **Read** (`List`/`Retrieve`): no model writes; all enrichment happens in `perform_extra_serialization` (see above). User and service-request lookups go through the serializer cache (`model_from_cache`).
+- Coded fields are validated at deserialization: `category.code` must be in the Diagnostic Service Sections value set; `code.code` (when present) must be in the Observation value set. `status` must be a `DiagnosticReportStatusChoices` value.
+
+## API integration notes
+
+- Diagnostic reports are exposed through Care's REST API and align with the FHIR `DiagnosticReport` resource; payload field names follow the spec classes above.
+- `category` and `code` are coded concepts (`Coding`: `system`/`code`/`display`), not free strings — `code` is validated against the bound value set, so populate from the relevant value set. `Coding` forbids extra keys.
+- `status` is stored as a free `CharField` but the spec restricts writes to the `DiagnosticReportStatusChoices` enum (`registered`, `partial`, `preliminary`, `final`).
+- A report fulfils exactly one `service_request`; create the [Service Request](./service-request.mdx) first, then pass its `external_id` as `service_request` on create.
+- `note` and `conclusion` carry human-readable narrative and are not parsed by the platform.
+- The detail (`Retrieve`) payload embeds the linked service request, its requester, the encounter (with patient), audit users, and all child observations — none of which are writable.
+
+## Related
+
+- Reference: [Service Request](./service-request.mdx)
+- Reference: [Observation](./observation.mdx)
+- Reference: [Specimen](./specimen.mdx)
+- Reference: [Encounter](./encounter.mdx)
+- Reference: [Patient](./patient.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
+- Source: [diagnostic_report.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/diagnostic_report.py)
+- Source: [resources/diagnostic_report/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/diagnostic_report/spec.py)
+- Source: [resources/diagnostic_report/valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/diagnostic_report/valueset.py)
diff --git a/versioned_docs/version-3.1/references/clinical/encounter.mdx b/versioned_docs/version-3.1/references/clinical/encounter.mdx
new file mode 100644
index 0000000..53f4acd
--- /dev/null
+++ b/versioned_docs/version-3.1/references/clinical/encounter.mdx
@@ -0,0 +1,267 @@
+---
+sidebar_position: 2
+---
+
+# Encounter
+
+Technical reference for the `Encounter` module in Care EMR. An `Encounter` is a single interaction between a patient and a facility (visit, admission, consultation) and aligns with the FHIR `Encounter` resource.
+
+The Django model is the **storage** layer — several fields are opaque `JSONField`s whose real structure lives in the Pydantic **resource specs**. This page documents both: the model fields and the API request/response schemas (enums, JSON-field shapes, validation, server-side side effects).
+
+**Source:**
+- Model: [`care/emr/models/encounter.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/encounter.py)
+- Specs: [`care/emr/resources/encounter/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/encounter/spec.py) · [`constants.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/encounter/constants.py) · [`valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/encounter/valueset.py) · [`enum_display_names.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/encounter/enum_display_names.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Encounter` | A single interaction between a patient and a facility (visit, admission, consultation) |
+| `EncounterOrganization` | Links an `Encounter` to a `FacilityOrganization` for access control |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+## `Encounter` fields
+
+### Classification & status
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `status` | `CharField(100)` | Nullable in DB; **required** in API. One of `StatusChoices` (see below) |
+| `status_history` | `JSONField` | Default `{}`. Server-maintained. Shape: `{ "history": [{ "status": str, "moved_at": str (ISO datetime) }] }` |
+| `encounter_class` | `CharField(100)` | Nullable in DB; **required** in API. One of `ClassChoices` (see below) |
+| `encounter_class_history` | `JSONField` | Default `{}`. Server-maintained. Shape: `{ "history": [{ "status": str, "moved_at": str (ISO datetime) }] }` |
+| `priority` | `CharField(100)` | Nullable in DB; **required** in API. One of `EncounterPriorityChoices` (see below) |
+| `external_identifier` | `CharField(100)` | Nullable. Identifier from an external/source system |
+
+### Subject & facility
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `patient` | `FK → Patient` | `CASCADE`. The patient this encounter is for. Set on create only |
+| `facility` | `FK → Facility` | `PROTECT`. The facility where the encounter takes place. Set on create only |
+| `appointment` | `FK → TokenBooking` | `SET_NULL`, nullable. Originating [scheduling booking](../scheduling/booking.mdx), if any |
+| `current_location` | `FK → FacilityLocation` | `SET_NULL`, nullable. Cached current [location](../facility/location.mdx) for easier querying |
+
+### Timing & disposition
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `period` | `JSONField` | Default `{}`. `PeriodSpec { start: datetime (tz-aware, optional), end: datetime (tz-aware, optional) }`. If both set, `start ≤ end` |
+| `hospitalization` | `JSONField` | Default `{}`. `HospitalizationSpec` (see shape below). Nullable |
+| `discharge_summary_advice` | `TextField` | Nullable. Free-text discharge advice. Explicitly cleared to `None` on update when omitted |
+
+### Care team
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `care_team` | `JSONField` | Default `{}`. Stored as a list of `{ "user_id": int, "role": Coding }` entries. **Not** writable on create/update (excluded); managed via the dedicated care-team write spec |
+| `care_team_users` | `ArrayField[int]` | Platform-maintained denormalized cache of user IDs derived from `care_team` via `sync_care_team_users_cache()` on every save |
+
+### Organization cache, tags & extensions
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility_organization_cache` | `ArrayField[int]` | Platform-maintained cache of facility-[organization](../facility/organization.mdx) IDs (incl. parent chains). Rebuilt by `sync_organization_cache()` |
+| `tags` | `ArrayField[int]` | Tag IDs applied to the encounter. Serialized via `SingleFacilityTagManager().render_tags()` |
+| `extensions` | `JSONField` | Default `{}`. Open extension bag for deployment-specific metadata. Validated by `ExtensionValidator` against `ExtensionResource.encounter` |
+
+## Nested JSON shapes
+
+### `PeriodSpec` (`period`)
+
+Defined in [`resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py).
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `start` | `datetime \| None` | Must be timezone-aware if provided |
+| `end` | `datetime \| None` | Must be timezone-aware if provided |
+
+Validation (`validate_period`): naive datetimes are rejected; if both `start` and `end` are set, `start` must not be greater than `end`.
+
+### `HospitalizationSpec` (`hospitalization`)
+
+Defined in `spec.py`. All fields optional/nullable.
+
+| Field | Type | Values |
+| --- | --- | --- |
+| `re_admission` | `bool \| None` | — |
+| `admit_source` | `AdmitSourcesChoices \| None` | see [Admit source values](#admit-source-values-admitsourceschoices) |
+| `discharge_disposition` | `DischargeDispositionChoices \| None` | see [Discharge disposition values](#discharge-disposition-values-dischargedispositionchoices) |
+| `diet_preference` | `DietPreferenceChoices \| None` | see [Diet preference values](#diet-preference-values-dietpreferencechoices) |
+
+## Enum values
+
+### Status values (`StatusChoices`)
+
+| Value | |
+| --- | --- |
+| `planned` | |
+| `in_progress` | |
+| `on_hold` | |
+| `discharged` | |
+| `completed` | terminal (in `COMPLETED_CHOICES`) |
+| `cancelled` | terminal (in `COMPLETED_CHOICES`) |
+| `discontinued` | terminal (in `COMPLETED_CHOICES`) |
+| `entered_in_error` | terminal (in `COMPLETED_CHOICES` and `ERROR_CHOICES`) |
+| `unknown` | |
+
+`COMPLETED_CHOICES = [completed, cancelled, entered_in_error, discontinued]`; `ERROR_CHOICES = [entered_in_error]`.
+
+### Class values (`ClassChoices`)
+
+| Value | Meaning |
+| --- | --- |
+| `imp` | inpatient |
+| `amb` | ambulatory |
+| `obsenc` | observation |
+| `emer` | emergency |
+| `vr` | virtual |
+| `hh` | home health |
+
+### Priority values (`EncounterPriorityChoices`)
+
+| Value |
+| --- |
+| `ASAP` |
+| `callback_results` |
+| `callback_for_scheduling` |
+| `elective` |
+| `emergency` |
+| `preop` |
+| `as_needed` |
+| `routine` |
+| `rush_reporting` |
+| `stat` |
+| `timing_critical` |
+| `use_as_directed` |
+| `urgent` |
+
+### Admit source values (`AdmitSourcesChoices`)
+
+| Value | Display (`get_admit_source_display`) |
+| --- | --- |
+| `hosp_trans` | Transferred from other hospital |
+| `emd` | From accident/emergency department |
+| `outp` | From outpatient department |
+| `born` | Born in hospital |
+| `gp` | General Practitioner referral |
+| `mp` | Medical Practitioner/physician referral |
+| `nursing` | From nursing home |
+| `psych` | From psychiatric hospital |
+| `rehab` | From rehabilitation facility |
+| `other` | Other |
+
+### Discharge disposition values (`DischargeDispositionChoices`)
+
+| Value | Display (`get_discharge_disposition_display`) |
+| --- | --- |
+| `home` | Home |
+| `alt_home` | Alternate Home |
+| `other_hcf` | Other Health Care Facility |
+| `hosp` | Hospital |
+| `long` | Long-term Care Facility |
+| `aadvice` | Against Medical Advice |
+| `exp` | Expired |
+| `psy` | Psychiatric Hospital |
+| `rehab` | Rehabilitation Facility |
+| `snf` | Skilled Nursing Facility |
+| `oth` | Other |
+
+### Diet preference values (`DietPreferenceChoices`)
+
+| Value |
+| --- |
+| `vegetarian` |
+| `dairy_free` |
+| `nut_free` |
+| `gluten_free` |
+| `vegan` |
+| `halal` |
+| `kosher` |
+| `none` |
+
+## Resource specs (API schema)
+
+All specs build on `EMRResource` (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). `EncounterSpecBase` sets `__exclude__ = [patient, organizations, facility, appointment, current_location, care_team]` — those fields are handled by hooks or dedicated endpoints rather than direct assignment.
+
+| Spec | Role | Notes |
+| --- | --- | --- |
+| `EncounterSpecBase` | shared | Fields: `id`, `status`, `encounter_class`, `period` (`PeriodSpec`, default `{}`), `hospitalization` (`HospitalizationSpec \| None`, default `{}`), `priority`, `external_identifier`, `discharge_summary_advice`. `__model__ = Encounter` |
+| `EncounterCreateSpec` | write · create | Extends base + `ExtensionValidator`. Adds `patient: UUID4`, `facility: UUID4`, `organizations: list[UUID4] = []`, `appointment: UUID4 \| None`. Resolves FKs by `external_id`; validates appointment belongs to the patient and facility |
+| `EncounterUpdateSpec` | write · update | Extends base + `ExtensionValidator`. Does not accept `patient`/`facility`/`organizations`/`appointment` — only mutable fields. Appends to status/class history on change |
+| `EncounterListSpec` | read · list | Adds serialized `patient` ([`PatientListSpec`](./patient.mdx)), `facility` (`FacilityBareMinimumSpec`), `status_history`, `encounter_class_history`, `created_date`, `modified_date`, `tags`, `current_location` (`FacilityLocationMinimalListSpec \| None`), `care_team` (`[{ member, role }]`) |
+| `EncounterRetrieveSpec` | read · detail | Extends list + `EncounterPermissionsMixin`. Adds `appointment` (`TokenBookingReadSpec`), `created_by`/`updated_by` ([`UserSpec`](../access/user.mdx)), `organizations` (`FacilityOrganizationReadSpec[]`), `location_history` (`FacilityLocationEncounter[]`, newest first), `extensions`; patient via `PatientRetrieveSpec` |
+| `HospitalizationSpec` | nested | Plain `BaseModel`; see shape above |
+| `EncounterCareTeamMemberSpec` | nested (write) | `{ user_id: UUID4, role: Coding }`, `role` bound to the `system-practitioner-role-code` value set |
+| `EncounterCareTeamMemberWriteSpec` | write · care team | `{ members: list[EncounterCareTeamMemberSpec] }`. Dedicated payload for replacing the care team |
+
+### Server-maintained behaviour
+
+- **Status history**: on create, `status_history` is initialized to `{ "history": [{ status, moved_at }] }` with the initial status; on update, a new entry is appended only when `status` changes. Same pattern for `encounter_class_history` / `encounter_class`.
+- **FK resolution (create)**: `patient`, `facility`, and `appointment` are resolved from `external_id` (404 if not found). The appointment must belong to the same patient and to a slot whose resource is in the same facility.
+- **Organizations (create)**: `organizations` (a deduplicated list of UUIDs) is stashed on `obj._organizations`; `EncounterOrganization` rows drive `facility_organization_cache`.
+- **Discharge advice (update)**: when `discharge_summary_advice` is omitted on update, it is explicitly set to `None`.
+- **Care team**: `role` binds to the `PRACTITIONER_ROLE_VALUESET` (slug `system-practitioner-role-code`) — a SNOMED CT value set composed of descendants of `223366009` (healthcare professional) and `224930009` (healthcare-related occupation).
+
+## Related models
+
+### `EncounterOrganization`
+
+Associates an encounter with a facility-scoped [organization](../facility/organization.mdx). Used to drive organization-based access control on encounters.
+
+```text
+encounter → FK Encounter (CASCADE)
+organization → FK FacilityOrganization (CASCADE)
+```
+
+Saving an `EncounterOrganization` calls `encounter.sync_organization_cache()`, which rebuilds the encounter's `facility_organization_cache`.
+
+## Methods & save behaviour
+
+### `sync_care_team_users_cache()`
+
+When `care_team` is a list, populates `care_team_users` with `int(entry["user_id"])` for each entry (defaulting to `-1` when a `user_id` is missing). Provides a flat integer array for fast querying without JSON traversal.
+
+### `sync_organization_cache()`
+
+Recomputes `facility_organization_cache` as the union of:
+
+- every linked `EncounterOrganization` organization plus its `parent_cache` chain, and
+- the facility's `default_internal_organization_id`.
+
+Persists the result with `super().save(update_fields=["facility_organization_cache"])`.
+
+### `save()` side effects
+
+On every save:
+
+1. `sync_care_team_users_cache()` runs, refreshing `care_team_users`.
+2. If the record has no `pk`, it is flagged as newly `created`.
+3. Inside a single `transaction.atomic()` block, the base `save()` runs; for newly created encounters, `evaluate_patient_facility_default_values(patient, facility)` runs to generate facility-scoped patient identifier defaults.
+4. After the transaction, `sync_organization_cache()` runs, which performs a **second write** (`update_fields=["facility_organization_cache"]`).
+
+Integrators should expect **two write passes** when creating or updating encounters through the ORM, plus identifier-default generation on creation.
+
+## API integration notes
+
+- Encounters are exposed through Care's REST API and align with the FHIR `Encounter` resource. Reads use `EncounterListSpec` / `EncounterRetrieveSpec`; writes use `EncounterCreateSpec` / `EncounterUpdateSpec`.
+- `status`, `encounter_class`, and `priority` are **required** on write and validated against their enums even though the DB columns are nullable.
+- `status_history`, `encounter_class_history`, `care_team_users`, and `facility_organization_cache` are **platform-maintained** — do not set them directly from clients. Status/class history is appended server-side on create/update; the caches are derived from `care_team` and `EncounterOrganization`.
+- The care team is **not** writable through create/update; use the dedicated care-team endpoint with `EncounterCareTeamMemberWriteSpec`. Each member's `role` must resolve within the `system-practitioner-role-code` value set.
+- `period`, `hospitalization` carry structured JSON; use the nested-spec shapes above. `period.start`/`end` must be timezone-aware.
+- `current_location` is a cached convenience field; authoritative location history is `location_history` (from `FacilityLocationEncounter`).
+- `extensions` is the supported place for custom key-value data without schema migrations and is validated by `ExtensionValidator`.
+
+## Related
+
+- Reference: [Patient](./patient.mdx)
+- Reference: [Service request](./service-request.mdx)
+- Reference: [Booking](../scheduling/booking.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Organization](../facility/organization.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [User](../access/user.mdx)
+- Concept: [Patient](../../concepts/clinical/patient.mdx)
+- Source (model): [encounter.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/encounter.py)
+- Source (specs): [resources/encounter on GitHub](https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/encounter)
diff --git a/versioned_docs/version-3.1/references/clinical/notes.mdx b/versioned_docs/version-3.1/references/clinical/notes.mdx
new file mode 100644
index 0000000..7e7d74b
--- /dev/null
+++ b/versioned_docs/version-3.1/references/clinical/notes.mdx
@@ -0,0 +1,136 @@
+---
+sidebar_position: 9
+---
+
+# Notes
+
+Technical reference for the `NoteThread` and `NoteMessage` modules in Care EMR.
+
+The Django models (`care/emr/models/notes.py`) are the **storage layer**: `NoteThread`
+groups messages for a patient (optionally scoped to an encounter), and `NoteMessage`
+stores each message body plus an opaque `message_history` `JSONField`. The structure of
+`message_history`, the read/write schemas, and the server-side edit-history behaviour are
+defined by the **Pydantic resource specs** (`care/emr/resources/notes/`), all built on
+`EMRResource`.
+
+**Source:**
+
+- Model: [`care/emr/models/notes.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/notes.py)
+- Specs: [`resources/notes/thread_spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/notes/thread_spec.py), [`resources/notes/notes_spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/notes/notes_spec.py)
+- Base: [`resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `NoteThread` | A discussion thread attached to a patient (optionally scoped to an encounter) |
+| `NoteMessage` | An individual message posted within a `NoteThread` |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `NoteThread` fields
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `title` | `CharField(255)` | Nullable, blank-allowed at the storage layer; the spec layer requires it on write (`title: str`, `max_length=255`) |
+| `patient` | `FK → Patient` | `on_delete=CASCADE`; thread is removed when the [patient](./patient.mdx) is deleted. Excluded from spec serialization (`__exclude__`); set from the URL/viewset context, not the request body |
+| `encounter` | `FK → Encounter` | Nullable, blank-allowed; `on_delete=CASCADE`. Scopes the thread to a specific [encounter](./encounter.mdx) when set. Excluded from spec serialization; on create it is resolved from a UUID in the request body (see specs below) |
+
+A `NoteThread` groups related messages for a patient. Threads with no `encounter` are patient-level; threads with an `encounter` are scoped to that visit. A `TODO` in the source notes that organization-based access restriction is planned but not yet implemented.
+
+## Related models
+
+### `NoteMessage`
+
+An individual message within a thread.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `thread` | `FK → NoteThread` | `on_delete=CASCADE`; messages are removed when the thread is deleted. Excluded from spec serialization (`__exclude__`); set from the URL/viewset context |
+| `message` | `TextField` | Free-text message body. Required on write (`message: str`) |
+| `message_history` | `JSONField` | Defaults to `{}`. Opaque at the model layer; the spec layer gives it the shape below and maintains it server-side on edit. Exposed read-only |
+
+#### `message_history` shape
+
+`message_history` is not a free-form blob — the update spec writes a fixed structure. On
+each update, the **previous** message body is appended to a `history` list:
+
+```jsonc
+{
+ "history": [
+ {
+ "message": "string", // the prior message body (pre-edit)
+ "created_by": {
+ "username": "string",
+ "external_id": "uuid" // editor of the prior version
+ },
+ "edited_at": "datetime str", // timezone.now() at the time of this edit
+ "created_at": "datetime str" // the prior version's modified_date
+ }
+ // ... one entry per edit, oldest first
+ ]
+}
+```
+
+Clients must not set or overwrite `message_history`; it is read-only on the API and
+maintained entirely server-side (see `NoteMessageUpdateSpec` below).
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`resources/base.py`), which provides `serialize`
+(DB object → pydantic, via `perform_extra_serialization`) and `de_serialize`
+(pydantic → DB object, via `perform_extra_deserialization`). Fields listed in
+`__exclude__` are skipped by both directions and handled manually.
+
+### Thread specs
+
+| Spec | Role | Fields exposed | Notes |
+| --- | --- | --- | --- |
+| `NoteThreadSpec` | shared base | `id` (UUID, read), `title` (str, required, ≤255) | `__model__ = NoteThread`; `__exclude__ = ["patient", "encounter"]` |
+| `NoteThreadCreateSpec` | write · create | base + `encounter` (UUID, optional) | Validates `encounter` exists (raises `Encounter not found`); resolves the UUID to an `Encounter` FK in `perform_extra_deserialization` |
+| `NoteThreadUpdateSpec` | write · update | base (`title`) | No extra behaviour; `encounter`/`patient` cannot be reassigned via update |
+| `NoteThreadReadSpec` | read · detail/list | base + `created_by`, `updated_by` (dict, nullable), `created_date`, `modified_date` (datetime) | `perform_extra_serialization` sets `id = external_id` and serializes audit users via `serialize_audit_users` |
+
+Validation / server behaviour:
+
+- `title` is **required** on write at the spec layer (`Field(..., max_length=255)`), even though the column is nullable.
+- `NoteThreadCreateSpec.encounter` is validated with `validate_encounter_exists`: a non-null value must match an existing `Encounter.external_id`, otherwise `ValueError("Encounter not found")`.
+- On create, `perform_extra_deserialization` looks up the `Encounter` by `external_id` and assigns it to the FK. `patient` is not part of any thread spec — it is assigned from the request context.
+
+### Message specs
+
+| Spec | Role | Fields exposed | Notes |
+| --- | --- | --- | --- |
+| `NoteMessageSpec` | shared base | `id` (UUID, read), `message` (str, required) | `__model__ = NoteMessage`; `__exclude__ = ["thread"]` |
+| `NoteMessageCreateSpec` | write · create | base (`message`) | No extra behaviour |
+| `NoteMessageUpdateSpec` | write · update | base (`message`) | `perform_extra_deserialization` appends the pre-edit message to `message_history["history"]` (see below) |
+| `NoteMessageReadSpec` | read · detail/list | base + `message_history` (dict), `created_by`, `updated_by` (dict, nullable), `created_date`, `modified_date` (datetime) | `perform_extra_serialization` sets `id = external_id` and serializes audit users |
+
+Validation / server behaviour:
+
+- `message` is **required** on create and update.
+- `thread` is never in the request body (`__exclude__`); it is taken from the URL/viewset context.
+- **Edit history is maintained server-side.** On update, `NoteMessageUpdateSpec.perform_extra_deserialization` fetches the existing `NoteMessage` by `external_id`, initializes `message_history["history"] = []` if empty, and appends an entry capturing the prior `message`, the prior author (`username` + `external_id`), `edited_at` (`timezone.now()`), and `created_at` (the prior `modified_date`). Clients cannot write `message_history` directly.
+
+## Methods & save behaviour
+
+- `NoteThread` / `NoteMessage` inherit save/audit behaviour from [`EMRBaseModel`](../foundation/base-model.mdx): `external_id`, `created_by`/`updated_by`, `created_date`/`modified_date`, and soft delete via `deleted`.
+- Audit users in read specs are populated by `EMRResource.serialize_audit_users`, which resolves `created_by`/`updated_by` to cached `UserSpec` dicts.
+- `message_history` is mutated only by `NoteMessageUpdateSpec.perform_extra_deserialization` — never by client input.
+- Cascade deletes: deleting a patient removes its threads; deleting a thread removes its messages.
+
+## API integration notes
+
+- Note threads and messages are exposed through Care's REST API; field names in API payloads follow the spec classes above, which may differ from Django model names.
+- Threads are always anchored to a `patient` (set from request context); the `encounter` link is optional and narrows a thread to a single visit. On create, send `encounter` as the encounter's `external_id` (UUID) — it is validated to exist.
+- `title` and `message` are required on write even though the underlying columns are permissive.
+- `message_history` is read-only and platform-maintained: each edit appends the previous body and authorship to `history`. Do not send it from clients.
+- Authorship and timestamps come from the inherited `EMRBaseModel` audit fields (`created_by`, `updated_by`, `created_date`, `modified_date`); soft deletes are handled via `deleted`.
+
+## Related
+
+- Source: [notes.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/notes.py)
+- Specs: [thread_spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/notes/thread_spec.py), [notes_spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/notes/notes_spec.py)
+- Reference: [Patient](./patient.mdx)
+- Reference: [Encounter](./encounter.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
diff --git a/versioned_docs/version-3.1/references/clinical/observation-definition.mdx b/versioned_docs/version-3.1/references/clinical/observation-definition.mdx
new file mode 100644
index 0000000..e4f493d
--- /dev/null
+++ b/versioned_docs/version-3.1/references/clinical/observation-definition.mdx
@@ -0,0 +1,269 @@
+---
+sidebar_position: 6
+---
+
+# Observation Definition
+
+Technical reference for the `ObservationDefinition` module in Care EMR.
+
+An observation definition is master data that describes *how* a particular observation is captured — its code, permitted data type, unit, method, body site, and (interpretation) reference ranges. Questionnaires reference observation definitions so the same observation (e.g. blood pressure) is collected consistently across forms, at either the instance level or a single facility. A single observation code can have multiple definitions. The model is loosely based on the [FHIR ObservationDefinition](https://build.fhir.org/observationdefinition.html) resource.
+
+The Django model is the **storage layer**: `code`, `body_site`, `method`, `permitted_unit`, `component`, and `qualified_ranges` are opaque `JSONField`s. Their real structure, the enums, the value-set bindings, and the read/write contracts live in the **Pydantic resource specs** (`care/emr/resources/observation_definition/spec.py`), all built on the `EMRResource` base.
+
+**Source:**
+
+- Model: [`care/emr/models/observation_definition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/observation_definition.py)
+- Resource spec: [`care/emr/resources/observation_definition/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation_definition/spec.py)
+- Helper: [`care/emr/resources/observation_definition/observation.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation_definition/observation.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ObservationDefinition` | Master-data definition of how an observation is coded, typed, and measured |
+
+`ObservationDefinition` extends [`SlugBaseModel`](../foundation/base-model.mdx), which adds facility-scoped slug helpers on top of `EMRBaseModel`. From `EMRBaseModel` it inherits `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and the `history`/`meta` JSON fields.
+
+## `ObservationDefinition` fields
+
+### Identity & scope
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → Facility` (PROTECT) | Nullable, `default=None`. When set, the definition is scoped to that facility; when `null` it is instance-wide. Excluded from the base spec (`__exclude__ = ["facility"]`) and set server-side from the `facility` UUID on create |
+| `version` | `IntegerField` | Defaults to `1`; assigned version number for the definition. Read-only in the API (`...ReadSpec` only) |
+| `slug` | `CharField(255)` | Set server-side from `slug_value`; namespaced. See [slug behaviour](#slug-behaviour) |
+| `title` | `CharField(1024)` | Required. Human-friendly name for this definition |
+| `status` | `CharField(255)` | Required. Lifecycle code from `ObservationStatusChoices` — see [Status values](#status-values). Definitions are not destroyed — they are moved to `retired` instead |
+| `description` | `TextField` | Required. Natural-language (markdown) description |
+| `derived_from_uri` | `TextField` | External URI this definition was derived from. Optional in the API (`str \| None`, default `None`) |
+
+### Coding & measurement
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `category` | `CharField(255)` | Nullable in the model, but **required** in the API as `ObservationCategoryChoices` — see [Category values](#category-values) |
+| `code` | `JSONField` | Required. `Coding` bound to the `system-observation` value set (LOINC). What is being observed — see [Coding shape](#coding-shape) |
+| `permitted_data_type` | `CharField(100)` | Required. `QuestionType` enum (questionnaire data types) — see [Permitted data type values](#permitted-data-type-values). `group`, `display`, and `url` are rejected |
+| `body_site` | `JSONField` | Nullable. `Coding` bound to the `system-body-site-observation` value set (SNOMED CT) |
+| `method` | `JSONField` | Nullable. `Coding` bound to the `system-collection-method` value set (SNOMED CT) |
+| `permitted_unit` | `JSONField` | Nullable. `Coding` bound to the `system-ucum-units` value set (UCUM). Care does not convert between units when displaying data |
+
+### Components & ranges
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `component` | `JSONField` | Nullable. `list[ObservationDefinitionComponentSpec] \| None`. Multiple measured values for one definition (e.g. systolic + diastolic), each with its own `code`, `permitted_data_type`, `permitted_unit`, and `qualified_ranges` — see [ObservationDefinitionComponentSpec](#observationdefinitioncomponentspec) |
+| `qualified_ranges` | `JSONField` | Model default `list`. **Required** in the API as `list[QualifiedRangeSpec]` (may be empty). Condition-dependent reference ranges / interpretations — see [QualifiedRangeSpec](#qualifiedrangespec) |
+
+## Enum values
+
+### Status values
+
+`ObservationStatusChoices` — bound to the `status` field.
+
+| Value |
+| --- |
+| `draft` |
+| `active` |
+| `retired` |
+| `unknown` |
+
+### Category values
+
+`ObservationCategoryChoices` — bound to the `category` field.
+
+| Value |
+| --- |
+| `social_history` |
+| `vital_signs` |
+| `imaging` |
+| `laboratory` |
+| `procedure` |
+| `survey` |
+| `exam` |
+| `therapy` |
+| `activity` |
+
+### Permitted data type values
+
+`QuestionType` (from `questionnaire/spec.py`) — bound to `permitted_data_type` on both the definition and each component. The values `group`, `display`, and `url` are **rejected** by `validate_question_type` ("Cannot create a definition with this type").
+
+| Value | Allowed for a definition |
+| --- | --- |
+| `group` | No (rejected) |
+| `boolean` | Yes |
+| `decimal` | Yes |
+| `integer` | Yes |
+| `string` | Yes |
+| `text` | Yes |
+| `display` | No (rejected) |
+| `date` | Yes |
+| `dateTime` | Yes |
+| `time` | Yes |
+| `choice` | Yes |
+| `url` | No (rejected) |
+| `quantity` | Yes |
+| `structured` | Yes |
+
+## JSON field shapes (nested specs)
+
+### Coding shape
+
+The coded fields (`code`, `body_site`, `method`, `permitted_unit`) store a `Coding` object. The spec binds each to a value set via `ValueSetBoundCoding[]`, so the submitted `code` is validated against that value set on write.
+
+```text
+Coding {
+ system: str | null
+ version: str | null
+ code: str # required
+ display: str | null
+} # extra="forbid" — unknown keys rejected
+```
+
+| Field | Bound value set (slug) | Code system |
+| --- | --- | --- |
+| `code` | `system-observation` | LOINC (`http://loinc.org`) |
+| `body_site` | `system-body-site-observation` | SNOMED CT (`http://snomed.info/sct`) |
+| `method` | `system-collection-method` | SNOMED CT |
+| `permitted_unit` | `system-ucum-units` | UCUM (`http://unitsofmeasure.org`) |
+
+### ObservationDefinitionComponentSpec
+
+Element of the `component` list. Lets one definition describe several measured values.
+
+```text
+ObservationDefinitionComponentSpec {
+ code: ValueSetBoundCoding[system-observation] # required
+ permitted_data_type: QuestionType # required; group/display/url rejected
+ permitted_unit: ValueSetBoundCoding[system-ucum-units] | null = null
+ qualified_ranges: list[QualifiedRangeSpec] # required
+}
+```
+
+### QualifiedRangeSpec
+
+Element of `qualified_ranges` (on the definition and on each component). A qualified range is either **numeric** (`ranges`) or **categorical** (coded value sets) — never both.
+
+```text
+QualifiedRangeSpec {
+ title: str | null = null
+ conditions: list[EvaluatorConditionSpec] = []
+ ranges: list[NumericRangeSpec] = []
+ default_interpretation: InterpretationSpec | null = null
+ normal_coded_value_set: str | null = ""
+ critical_coded_value_set: str | null = ""
+ abnormal_coded_value_set: str | null = ""
+ valueset_interpretation: list[ValueSetInterpretationSpec] = []
+}
+```
+
+Validation (`validate_categorical_or_numeric`):
+
+- Must provide either `ranges` (numeric) **or** at least one coded value set / `valueset_interpretation` (categorical) — at least one is required.
+- Cannot specify both numeric `ranges` and coded value sets.
+- Numeric `ranges` must not overlap (sorted by `min`, treating missing `min`/`max` as -∞/+∞).
+- Coded value-set slugs (`normal_coded_value_set`, `critical_coded_value_set`, `abnormal_coded_value_set`, and each `valueset_interpretation.valuset`) must be unique — duplicates are rejected.
+
+### NumericRangeSpec
+
+```text
+NumericRangeSpec {
+ interpretation: InterpretationSpec # required
+ min: Decimal(max_digits=20, decimal_places=6) | null = null
+ max: Decimal(max_digits=20, decimal_places=6) | null = null
+}
+```
+
+At least one of `min`/`max` must be provided (`validate_range`).
+
+### InterpretationSpec
+
+```text
+InterpretationSpec {
+ display: str # required
+ icon: str | null = ""
+ color: str | null = ""
+ highlight: bool | null = false
+ code: Coding | null = {}
+}
+```
+
+### ValueSetInterpretationSpec
+
+```text
+ValueSetInterpretationSpec {
+ interpretation: InterpretationSpec # required
+ valuset: str # value-set slug (note: spelling "valuset")
+}
+```
+
+### EvaluatorConditionSpec
+
+Element of `QualifiedRangeSpec.conditions`. Validated against `EvaluatorMetricsRegistry` — an unknown `metric` raises, and the registered evaluator validates `operation`/`value`.
+
+```text
+EvaluatorConditionSpec {
+ metric: str # must resolve in EvaluatorMetricsRegistry
+ operation: str
+ value: dict | str
+}
+```
+
+## Resource specs (API schema)
+
+All specs subclass `BaseObservationDefinitionSpec` (which subclasses `EMRResource`, `__model__ = ObservationDefinition`, `__exclude__ = ["facility"]`). `EMRResource` provides `serialize`/`de_serialize` plus the `perform_extra_serialization` / `perform_extra_deserialization` hooks.
+
+| Spec class | Role | Notable fields / behaviour |
+| --- | --- | --- |
+| `BaseObservationDefinitionSpec` | Shared base | `id`, `title`, `status`, `description`, `category`, `code`, `permitted_data_type`, `component`, `body_site`, `method`, `permitted_unit`, `derived_from_uri`, `qualified_ranges`. Runs `validate_data_type` and `validate_qualified_ranges_consistency` |
+| `ObservationDefinitionCreateSpec` | Write · create | Adds `facility: UUID4 \| null` and `slug_value: SlugType`. Validates the facility exists; `perform_extra_deserialization` resolves the facility and sets `obj.slug = slug_value` |
+| `ObservationDefinitionUpdateSpec` | Write · update | Adds `slug_value: SlugType`; `perform_extra_deserialization` sets `obj.slug = slug_value` (facility is not re-bound on update) |
+| `ObservationDefinitionReadSpec` | Read · list & detail | Adds `version: int \| null`, `facility: dict \| null`, `slug_config: dict`, `slug: str`. `perform_extra_serialization` sets `id = external_id`, serializes `facility` via `FacilityBareMinimumSpec`, and sets `slug_config = parse_slug(slug)` |
+
+Notes:
+
+- **Value-set bindings** — `code`, `body_site`, `method`, `permitted_unit` (and component equivalents) are `ValueSetBoundCoding[...]`; the submitted coding is validated against the bound value set (`system-observation`, `system-body-site-observation`, `system-collection-method`, `system-ucum-units`).
+- **Data-type guard** — `permitted_data_type` (definition and component) rejects `group`, `display`, `url`.
+- **Range consistency** — `validate_qualified_ranges_consistency`: all `qualified_ranges` on a definition must be the same kind (all numeric `ranges` or all coded value sets); mixing raises.
+- **`slug_value`** — `SlugType`: 5–50 chars, URL-safe (`[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]`), must start and end alphanumeric. Stored namespaced as `obj.slug` (see below).
+- **No metadata store** — `__store_metadata__` is `False`, so spec fields map directly to model columns rather than into `meta`.
+
+## Methods & save behaviour
+
+`ObservationDefinition` defines no overrides of its own; slug handling is inherited from `SlugBaseModel`, and the API specs perform the create/update side effects.
+
+### Slug behaviour
+
+`SlugBaseModel` is `FACILITY_SCOPED = True`, so the stored `slug` is namespaced:
+
+```text
+facility-scoped: f--
+instance-scoped: i-
+```
+
+- `calculate_slug()` returns the `f-…` form when `facility` is set, otherwise the `i-…` form.
+- `parse_slug(slug)` validates the prefix and returns a dict: `{"facility": , "slug_value": }` for `f-` slugs, or `{"slug_value": }` for `i-` slugs. `ObservationDefinitionReadSpec` exposes this as `slug_config`.
+
+The create/update specs receive the bare `slug_value` and store it on `obj.slug`; namespacing is applied via the model's slug helpers.
+
+## API integration notes
+
+- Observation definitions are master data: created at the instance level or per-facility, and referenced by questionnaires rather than embedded into them.
+- The facility binding is write-once via the create spec (`facility` UUID → resolved FK); the update spec only re-sets `slug_value`.
+- Definitions are append-only by convention — retire via `status` (`retired`) instead of hard deletion; soft-delete via the inherited `deleted` flag still applies at the ORM level.
+- `code`, `body_site`, `method`, `permitted_unit`, and component codings are FHIR `Coding` objects validated against bound value sets, so the data stays translatable to FHIR.
+- Units follow UCUM principles; Care does not currently convert between units at display time.
+- `qualified_ranges` supports condition-dependent numeric ranges or categorical (coded) interpretations, but a single definition must commit to one kind across all its ranges.
+- `convert_od_to_observation` (`observation.py`) builds an [`Observation`](./observation.mdx) from a definition for a given encounter, carrying over `category` and `code` and defaulting `status` to `final`.
+
+## Related
+
+- Reference: [Observation](./observation.mdx)
+- Reference: [Service Request](./service-request.mdx)
+- Reference: [Questionnaire](../forms/questionnaire.mdx)
+- Reference: [Value Set](../forms/valueset.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [observation_definition.py (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/observation_definition.py)
+- Source: [observation_definition/spec.py (resource spec)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation_definition/spec.py)
+- Source: [observation/valueset.py (bound value sets)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation/valueset.py)
diff --git a/versioned_docs/version-3.1/references/clinical/observation.mdx b/versioned_docs/version-3.1/references/clinical/observation.mdx
new file mode 100644
index 0000000..56d3abe
--- /dev/null
+++ b/versioned_docs/version-3.1/references/clinical/observation.mdx
@@ -0,0 +1,271 @@
+---
+sidebar_position: 5
+---
+
+# Observation
+
+Technical reference for the `Observation` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/observation.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/observation.py)
+- Resource spec: [`care/emr/resources/observation/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation/spec.py)
+- Value sets: [`care/emr/resources/observation/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation/valueset.py)
+
+An observation is a single named, coded data point recorded about a subject — almost always a patient — such as a blood pressure reading, a temperature, or a coded answer captured through a questionnaire. The `main_code` says *what* was observed (typically bound to LOINC), and `value` holds the recorded result.
+
+The Django model is the **storage** layer: several fields are opaque `JSONField`s whose real structure lives in the Pydantic resource specs. The specs (`care/emr/resources/observation/`) are the **API/implementation** layer — they define the enums, the nested shape of those JSON fields, validation, and the read/write schemas. Read both layers together: the table notes below give the storage column, then the spec-enforced shape.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Observation` | A single coded measurement or finding about a patient, captured within an encounter |
+
+`Observation` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `Observation` fields
+
+### Classification & coding
+
+| Field | Storage type | Spec shape / notes |
+| --- | --- | --- |
+| `status` | `CharField(255)` | Enum `ObservationStatus` — `final`, `amended`, `entered_in_error`. Required in specs. Observations created from questionnaires are `final` |
+| `is_group` | `BooleanField` | Default `False`. Marks a grouping/panel observation that holds `component`/child observations rather than a single value. Not exposed in the resource specs |
+| `category` | `JSONField` (default `{}`) | Spec: a single `Coding { system?, version?, code, display? }`, optional. FHIR observation category derived from the questionnaire |
+| `main_code` | `JSONField` (default `{}`) | Spec: `Coding`, optional. Primary code for what is being observed, typically bound to LOINC (`CARE_OBSERVATION_VALUSET`, system `http://loinc.org`) |
+| `alternate_coding` | `JSONField` (default `[]`) | Spec: a single `CodeableConcept { id?, coding?: Coding[], text }`, optional. Additional codings for the same concept across other code systems |
+
+### Subject & context
+
+| Field | Storage type | Spec shape / notes |
+| --- | --- | --- |
+| `subject_type` | `CharField(255)` | Enum `SubjectType` — `patient`, `encounter`. Spec default `encounter` |
+| `subject_id` | `UUIDField` | Identifier of the subject entity. Not exposed in the specs (set server-side) |
+| `patient` | `FK → Patient` | `CASCADE`. The patient the observation belongs to. Set server-side, not in the spec body |
+| `encounter` | `FK → Encounter` | `CASCADE`. Spec field is a `UUID4 \| None`. The encounter in which the observation was recorded |
+| `effective_datetime` | `DateTimeField` | Nullable in storage. Spec: required `datetime` (tz-aware) on create; optional on update. When the observation was effective/recorded |
+
+### Author & performer
+
+| Field | Storage type | Spec shape / notes |
+| --- | --- | --- |
+| `data_entered_by` | `FK → users.User` | `CASCADE`, nullable. `related_name="observations_entered"`. Write specs take `data_entered_by_id: int`; read specs serialize a nested `UserSpec` |
+| `performer` | `JSONField` (default `{}`) | Spec: `Performer { type: PerformerType (related_person\|user), id: str }`, optional. Who performed the observation when not the data-entry user |
+
+### Value
+
+| Field | Storage type | Spec shape / notes |
+| --- | --- | --- |
+| `value_type` | `CharField(255)` | Enum `QuestionType` (from the questionnaire module) — discriminates how `value` is interpreted. See values below. Required in specs |
+| `value` | `JSONField` | Spec: `QuestionnaireSubmitResultValue { value?: str, unit?: Coding, coding?: Coding }`. Required on create, optional on update. `value` carries the raw/string value; `unit` for quantities; `coding` for coded answers |
+| `note` | `TextField` | Spec: `str \| None`. Free-text note about the observation |
+
+### Clinical qualifiers
+
+| Field | Storage type | Spec shape / notes |
+| --- | --- | --- |
+| `body_site` | `JSONField` (default `{}`) | Spec: `Coding` bound to value set `CARE_BODY_SITE_VALUESET` (slug `system-body-site-observation`, SNOMED CT), optional. FHIR `Observation.bodySite` |
+| `method` | `JSONField` (default `{}`) | Spec: `Coding` bound to value set `CARE_OBSERVATION_COLLECTION_METHOD` (slug `system-collection-method`, SNOMED CT), optional. FHIR `Observation.method` |
+| `reference_range` | `JSONField` (default `[]`) | Spec: `ReferenceRange[]` (see shape below), default `[]`. Reference ranges for the value, typically derived from the observation definition |
+| `interpretation` | `JSONField` (default `{}`) | Spec: free `dict`, default `{}`. Coded interpretation relative to the reference range (high/low/normal). Bound value sets exist for normal/abnormal/critical codings (see below) |
+| `interpretation_old` | `CharField(255)` | Nullable. Legacy free-text interpretation retained for backward compatibility. Not in the specs |
+
+### Structure & relationships
+
+| Field | Storage type | Spec shape / notes |
+| --- | --- | --- |
+| `parent` | `UUIDField` | Nullable. Spec: `UUID4 \| None` — `external_id` of a parent observation, used to nest related observations |
+| `component` | `JSONField` (default `[]`) | Spec: `Component[]` (see shape below), default `[]`. Sub-observations that share a context but carry their own codes/values (FHIR `Observation.component`) |
+| `questionnaire_response` | `FK → QuestionnaireResponse` | `CASCADE`, nullable. Spec field is `UUID4 \| None`. The questionnaire submission that produced this observation |
+| `diagnostic_report` | `FK → DiagnosticReport` | `CASCADE`, nullable. The report this observation belongs to, if any. Not in the write specs |
+| `observation_definition` | `FK → ObservationDefinition` | `CASCADE`, nullable. The definition that templates this observation. Serialized (as `BaseObservationDefinitionSpec`) only in `ObservationRetrieveSpec` |
+
+## Enum & value-set reference
+
+### `ObservationStatus` values
+
+| Value | Meaning |
+| --- | --- |
+| `final` | Recorded, complete and verified. Default for questionnaire-sourced observations |
+| `amended` | Previously final, subsequently corrected |
+| `entered_in_error` | Recorded in error; should be disregarded |
+
+### `PerformerType` values (`performer.type`)
+
+| Value |
+| --- |
+| `related_person` |
+| `user` |
+
+### `SubjectType` values (`subject_type`)
+
+| Value |
+| --- |
+| `patient` |
+| `encounter` (spec default) |
+
+### `QuestionType` values (`value_type`)
+
+Discriminator borrowed from the questionnaire module. Tells the client how to read `value`.
+
+| Value | Value sourced from `QuestionnaireSubmitResultValue` |
+| --- | --- |
+| `group` | grouping node (no direct value) |
+| `boolean` | `value` (`"true"`/`"false"`) |
+| `decimal` | `value` |
+| `integer` | `value` |
+| `string` | `value` |
+| `text` | `value` |
+| `display` | display-only |
+| `date` | `value` |
+| `dateTime` | `value` |
+| `time` | `value` |
+| `choice` | `coding` |
+| `url` | `value` |
+| `quantity` | `value` + `unit` |
+| `structured` | structured payload |
+
+### Bound value sets
+
+Coded fields validate against registered Care value sets. A `body_site` or `method` coding that is not a member of its value set is rejected on deserialization.
+
+| Field | Value set | Slug | System(s) |
+| --- | --- | --- | --- |
+| `main_code` | `CARE_OBSERVATION_VALUSET` | `system-observation` | `http://loinc.org` |
+| `body_site` | `CARE_BODY_SITE_VALUESET` | `system-body-site-observation` | `http://snomed.info/sct` (curated concept list + descendants of `442083009`) |
+| `method` | `CARE_OBSERVATION_COLLECTION_METHOD` | `system-collection-method` | `http://snomed.info/sct` (descendants of `272394005`, `129264002`, `386053000`) |
+
+Additional interpretation value sets are registered for use by the FE / observation definitions (not directly enforced on the `interpretation` dict):
+
+| Value set | Slug | Concepts (system `http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation`) |
+| --- | --- | --- |
+| Normal | `system-normal-coded-valueset` | `N` Normal, `ND` Not detected, `NEG` Negative |
+| Abnormal | `system-abnormal-coded-valueset` | `A` Abnormal, `AA` Critically abnormal, `H` High, `HH` Critically high, `L` Low, `LL` Critically low, `POS` Positive, `DET` Detected |
+| Critical | `system-critical-coded-valueset` | `C` Critical, `CC` Critical high, `CL` Critical low, `CV` Critical value |
+| UCUM units | `system-ucum-units` | `http://unitsofmeasure.org` (all UCUM units) |
+
+## Nested JSON shapes (from the spec)
+
+These are the real structures behind the model's `JSONField`s.
+
+### `Coding`
+
+```text
+Coding {
+ system: str | null # e.g. http://loinc.org
+ version: str | null
+ code: str # required
+ display: str | null
+} # extra keys forbidden
+```
+
+### `CodeableConcept` (`alternate_coding`)
+
+```text
+CodeableConcept {
+ id: str | null
+ coding: Coding[] | null
+ text: str | null # field present, required key
+} # extra keys forbidden
+```
+
+### `QuestionnaireSubmitResultValue` (`value`)
+
+```text
+QuestionnaireSubmitResultValue {
+ value: str | null # raw/string value
+ unit: Coding | null # for quantity values
+ coding: Coding | null # for coded values
+}
+```
+
+### `Performer` (`performer`)
+
+```text
+Performer {
+ type: PerformerType # related_person | user
+ id: str
+}
+```
+
+### `ReferenceRange` (`reference_range[]`)
+
+```text
+ReferenceRange {
+ min: float | null
+ max: float | null
+ unit: str | null
+ interpretation: str # required
+ value: str | null
+}
+```
+
+### `Component` (`component[]`)
+
+```text
+Component {
+ value: QuestionnaireSubmitResultValue # required
+ interpretation: str | dict = {}
+ reference_range: ReferenceRange[] = []
+ code: Coding | null = null
+ note: str = ""
+}
+```
+
+## Resource specs (API schema)
+
+The Pydantic specs build on [`EMRResource`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py) (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). All concrete specs inherit from `BaseObservationSpec`.
+
+| Spec | Role | Notes |
+| --- | --- | --- |
+| `BaseObservationSpec` | shared base | Defines all common fields: `id`, `status`, `category`, `main_code`, `alternate_coding`, `subject_type`, `encounter`, `effective_datetime`, `performer`, `value_type`, `value`, `note`, `body_site` (bound), `method` (bound), `reference_range`, `interpretation`, `parent`, `questionnaire_response`, `component` |
+| `ObservationSpec` | write · create | Adds `data_entered_by_id`, `created_by_id`, `updated_by_id` (all `int`). Used internally when materializing observations from a questionnaire submission |
+| `ObservationUpdateSpec` | write · update | Same as base but relaxes `effective_datetime` and `value` to optional (`None`) for partial updates |
+| `ObservationReadSpec` | read · list | Replaces user IDs with nested `created_by` / `updated_by` / `data_entered_by` `UserSpec` objects |
+| `ObservationRetrieveSpec` | read · detail | Extends `ObservationReadSpec`; additionally serializes `observation_definition` via `BaseObservationDefinitionSpec` when present |
+
+### Server-side behaviour
+
+- `ObservationSpec.perform_extra_deserialization` (create/update): sets `obj.external_id = self.id`, copies `data_entered_by_id`, `created_by_id`, `updated_by_id` onto the model, drops `data_entered_by_id` from `meta`, and on **create** (`is_update` is false) clears `obj.id` so a new row is inserted.
+- `ObservationReadSpec.perform_extra_serialization`: maps `id` from `external_id`; deliberately sets `encounter`, `patient`, and `questionnaire_response` to `None` in the serialized output to avoid extra DB queries; resolves audit users (`created_by`, `updated_by`) and `data_entered_by` from cache (`model_from_cache`).
+- `ObservationRetrieveSpec.perform_extra_serialization`: calls the parent, then inlines the full `observation_definition` when the FK is set.
+- `body_site` / `method` are `ValueSetBoundCoding[...]`: the supplied `Coding` is validated against its bound value set during deserialization and rejected if the code is not a member.
+- `de_serialize` only writes fields present in the model's column mapping; non-default values are persisted (`model_dump(exclude_defaults=True)`), and unknown spec-only fields would be stored in `meta` when `__store_metadata__` is set (it is not, by default, for observations).
+
+## Related models
+
+`Observation` is a single self-contained model; its relationships are expressed through foreign keys rather than secondary tables.
+
+```text
+patient → FK Patient (CASCADE)
+encounter → FK Encounter (CASCADE)
+data_entered_by → FK users.User (CASCADE, nullable)
+questionnaire_response → FK QuestionnaireResponse (CASCADE, nullable)
+diagnostic_report → FK DiagnosticReport (CASCADE, nullable)
+observation_definition → FK ObservationDefinition (CASCADE, nullable)
+```
+
+`parent` is a soft, UUID-based self-reference (not a Django FK) to another observation's `external_id`, allowing observations to be nested without enforcing a database constraint.
+
+## API integration notes
+
+- Observations are exposed through Care's REST API and align with the FHIR `Observation` resource. API/FHIR field names (e.g. `effectiveDateTime`, `bodySite`) may differ from the Django model names; the resource specs above are the authoritative request/response schemas.
+- Most observations are created as a side effect of submitting a questionnaire — `value` and `value_type` mirror the questionnaire submission types (`QuestionnaireSubmitResultValue` / `QuestionType`), and the linked `questionnaire_response` records provenance.
+- `effective_datetime` must be timezone-aware. It is required on create and optional on update.
+- `status` is constrained to `final` / `amended` / `entered_in_error`; questionnaire-sourced observations are `final`.
+- Coded fields `body_site` and `method` are validated against their bound value sets; `main_code` is expected to bind to LOINC via `CARE_OBSERVATION_VALUSET`.
+- On read, the list/detail specs blank out `encounter`, `patient`, and `questionnaire_response` to avoid extra queries — clients should not rely on those being populated in observation list payloads.
+- Prefer **structured** observations: storing arbitrary unstructured data is discouraged, since business logic (e.g. allergy/medication checks) relies on coded, structured values rather than free text.
+
+## Related
+
+- Reference: [Observation Definition](../clinical/observation-definition.mdx)
+- Reference: [Diagnostic Report](../clinical/diagnostic-report.mdx)
+- Reference: [Questionnaire](../forms/questionnaire.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
+- Source: [observation.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/observation.py)
+- Source: [observation/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation/spec.py)
+- Source: [observation/valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation/valueset.py)
+- Source: [resources/base.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
diff --git a/versioned_docs/version-3.1/references/clinical/service-request.mdx b/versioned_docs/version-3.1/references/clinical/service-request.mdx
new file mode 100644
index 0000000..5cbde59
--- /dev/null
+++ b/versioned_docs/version-3.1/references/clinical/service-request.mdx
@@ -0,0 +1,199 @@
+---
+sidebar_position: 12
+---
+
+# Service Request
+
+Technical reference for the `ServiceRequest` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/service_request.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/service_request.py)
+- Resource spec: [`care/emr/resources/service_request/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/service_request/spec.py)
+
+A service request is an order or proposal to perform some action — clinical or otherwise — for a patient (FHIR `ServiceRequest`). It can drive the creation of specimens, diagnostic reports, observations, and procedures, all of which link back to the originating request. Care implements a minimal subset of the FHIR spec.
+
+The Django model is the **storage** layer: several columns are opaque `JSONField`s and free-text `CharField`s whose real structure and allowed values live in the **Pydantic resource specs** (`care/emr/resources/service_request/spec.py`). The specs define the enums, the bound value sets, the coded-field shapes, and the read/write API schemas. This page documents both.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ServiceRequest` | An order or proposal to perform an action for a patient |
+
+`ServiceRequest` extends `EMRBaseModel` (shared Care EMR base with `external_id`, audit fields `created_by`/`updated_by`, `created_date`/`modified_date`, `meta`, and soft-delete semantics — see [Base model](../foundation/base-model.mdx)).
+
+## `ServiceRequest` fields
+
+### Request definition
+
+| Field | Model type | Spec type / shape | Notes |
+| --- | --- | --- | --- |
+| `title` | `CharField(1024)` | `str` | Human-readable summary. Required on create; optional on update. |
+| `category` | `CharField(255)` | `ActivityDefinitionCategoryOptions` enum | Coded category. Required on create. See [category values](#category-values). |
+| `status` | `CharField(255)` | `ServiceRequestStatusChoices` enum | Lifecycle status. Required on create. See [status values](#status-values). |
+| `intent` | `CharField(255)` | `ServiceRequestIntentChoices` enum | Required on create. See [intent values](#intent-values). |
+| `priority` | `CharField(255)` | `ServiceRequestPriorityChoices` enum | Required on create. See [priority values](#priority-values). |
+| `do_not_perform` | `BooleanField` | `bool \| None` | `True` if the service/procedure should **not** be performed. Model default `False`; spec default `None` (omitted unless set). |
+| `code` | `JSONField` (nullable) | `Coding` bound to `activity-definition-procedure-code` | The requested service/procedure. **Required** on create (single `Coding`, not `CodeableConcept`). Value validated against the bound value set. |
+| `body_site` | `JSONField` (nullable) | `Coding` bound to `system-body-site-observation`, optional | Coded location on the body. Single `Coding`, validated against the bound value set. |
+
+A bound `Coding` (`ValueSetBoundCoding`) has the shape:
+
+```json
+{
+ "system": "",
+ "version": "",
+ "code": "", // required; must resolve in the bound value set
+ "display": ""
+}
+```
+
+### Clinical detail
+
+| Field | Model type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `note` | `TextField` (nullable) | `str \| None` | Free-text comments. |
+| `patient_instruction` | `TextField` (nullable) | `str \| None` | Instructions surfaced to the patient. |
+| `occurance` | `DateTimeField` (nullable) | `datetime \| None` | When the request should take place. Spelled `occurance` in both model and spec (note the typo). |
+
+### Relationships
+
+| Field | Model type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → facility.Facility` | (not in write spec) | Owning facility. `PROTECT`. Set server-side from request context. |
+| `patient` | `FK → emr.Patient` | (derived) | Subject of the request. `CASCADE`. Not accepted from clients — set server-side from `encounter.patient` on create. |
+| `encounter` | `FK → emr.Encounter` (nullable) | `UUID4` (create only) | Encounter the request was created in. `CASCADE`. Required (UUID) on `Create`; resolved via `get_object_or_404`. Read back as a serialized encounter dict. |
+| `healthcare_service` | `FK → emr.HealthcareService` (nullable) | `UUID4 \| None` | Service fulfilling the request. `PROTECT`. Write accepts `external_id`; resolved server-side. Excluded from base serialize; returned only on `Retrieve`. |
+| `activity_definition` | `FK → emr.ActivityDefinition` (nullable) | (read only) | Template the request was created from. `PROTECT`. Serialized only on `Retrieve`. |
+| `requester` | `FK → users.User` (nullable) | `UUID4 \| None` (write) / `dict \| None` (read) | User who placed the request. `CASCADE`. Write accepts `external_id`; read returns a cached `UserSpec` dict. |
+| `locations` | `ArrayField[int]` | `list[UUID4]` (write) / `list[dict]` (read) | Facility-location IDs the request applies to. Model default `[]`. Write accepts location `external_id`s (stored on `obj._locations` for the manager to persist); serialized as `FacilityLocationListSpec` dicts only on `Retrieve`. |
+| `tags` | `ArrayField[int]` | `list[dict]` (read) | Tag IDs. Model default `[]`. Rendered via `SingleFacilityTagManager().render_tags(obj)` on read; not set directly from the write spec. |
+
+## Enums
+
+### Status values
+
+`ServiceRequestStatusChoices` — note values are **snake_case**, not hyphenated FHIR codes.
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Request is being drafted, not yet actionable |
+| `active` | Request is active |
+| `on_hold` | Request is paused |
+| `entered_in_error` | Request was entered in error |
+| `ended` | Request has ended |
+| `completed` | Request has been fulfilled |
+| `revoked` | Request was revoked/cancelled |
+
+Derived groupings in `spec.py`:
+- `SERVICE_REQUEST_COMPLETED_CHOICES` = `completed`, `revoked`, `ended`, `entered_in_error` (terminal states).
+- `SERVICE_REQUEST_CANCELLED_CHOICES` = `revoked`, `entered_in_error`.
+
+There is **no** `unknown` status.
+
+### Intent values
+
+`ServiceRequestIntentChoices`
+
+| Value |
+| --- |
+| `proposal` |
+| `plan` |
+| `directive` |
+| `order` |
+
+### Priority values
+
+`ServiceRequestPriorityChoices`
+
+| Value |
+| --- |
+| `routine` |
+| `urgent` |
+| `asap` |
+| `stat` |
+
+### Category values
+
+`category` reuses `ActivityDefinitionCategoryOptions` (from `care/emr/resources/activity_definition/spec.py`).
+
+| Value |
+| --- |
+| `laboratory` |
+| `imaging` |
+| `counselling` |
+| `surgical_procedure` |
+| `education` |
+
+## Bound value sets
+
+Coded `Coding` fields validate their `code` against a registered Care value set (`ValueSetBoundCoding[...]`).
+
+| Field | Value set slug | Composition |
+| --- | --- | --- |
+| `code` | `activity-definition-procedure-code` | SNOMED CT `is-a 71388002` (Procedure). See [`activity_definition/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/activity_definition/valueset.py). |
+| `body_site` | `system-body-site-observation` | SNOMED CT — explicit limb/joint concepts plus `is-a 442083009`. See [`observation/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/observation/valueset.py). |
+
+## Resource specs (API schema)
+
+All specs subclass `EMRResource` (`serialize` / `de_serialize`, `perform_extra_serialization` / `perform_extra_deserialization` hooks — see [Base model](../foundation/base-model.mdx)). `__model__ = ServiceRequest`; `__exclude__ = ["encounter", "healthcare_service", "locations"]` on the base, so those three are handled only by the explicit per-spec hooks.
+
+| Spec class | Role | Adds / overrides |
+| --- | --- | --- |
+| `BaseServiceRequestSpec` | shared | `id`, `title`, `status`, `intent`, `priority`, `category`, `do_not_perform`, `note`, `code` (required), `body_site`, `occurance`, `patient_instruction` |
+| `ServiceRequestWriteSpec` | write · shared | Adds `healthcare_service: UUID4?`, `locations: list[UUID4]`, `requester: UUID4?`. Resolves `healthcare_service` and `requester` from `external_id`; stashes `locations` on `obj._locations`. |
+| `ServiceRequestCreateSpec` | write · create | Adds `encounter: UUID4` (**required**). Resolves the encounter and sets `obj.patient = obj.encounter.patient` server-side. |
+| `ServiceRequestUpdateSpec` | write · update | Makes `title`, `status`, `intent`, `priority`, `category`, `code` all optional (partial update). |
+| `ServiceRequestReadSpec` | read · list | Adds `created_date`, `modified_date`, `encounter` (serialized via `EncounterListSpec`), `tags` (rendered), `requester` (cached `UserSpec`). Sets `id = external_id`. |
+| `ServiceRequestRetrieveSpec` | read · detail | Extends Read with `locations` (`FacilityLocationListSpec`), `healthcare_service` (`HealthcareServiceReadSpec`), `activity_definition` (`ActivityDefinitionReadSpec`), `specimens` (`SpecimenReadSpec`), `diagnostic_reports` (`DiagnosticReportListSpec`), embedded `encounter.patient` (`PatientRetrieveSpec`), and audit `created_by`/`updated_by`. |
+
+### Write-side behaviour (`perform_extra_deserialization`)
+
+- `healthcare_service` (when provided): looked up by `external_id` and assigned.
+- `requester` (when provided): looked up by `external_id` via `get_object_or_404(User, ...)`.
+- `locations`: the list of UUIDs is stored on `obj._locations`; the persisting view/manager translates them to integer IDs for the `locations` array.
+- **Create only:** `encounter` is required and fetched via `get_object_or_404(Encounter, ...)`, then `obj.patient` is forced to `encounter.patient` — clients cannot set `patient` directly.
+
+### Read-side behaviour (`perform_extra_serialization`)
+
+- `id` is set to `external_id`; `encounter` is serialized with `EncounterListSpec`; `tags` come from `SingleFacilityTagManager().render_tags(obj)`; `requester` (if set) is a cached `UserSpec`.
+- **Retrieve only:** queries `FacilityLocation` for `obj.locations`; serializes `healthcare_service` and `activity_definition` if present; fetches related `Specimen` and `DiagnosticReport` rows that reference this request; embeds the patient (`PatientRetrieveSpec`, scoped to `obj.facility`) under `encounter.patient`; and attaches audit users.
+
+## Methods & save behaviour
+
+- `ServiceRequest` inherits `save`, soft-delete, and audit behaviour from `EMRBaseModel` (see [Base model](../foundation/base-model.mdx)). No custom `save` override is defined on the model.
+- All write validation (enums, required `code`, bound value sets, encounter requirement) is enforced by the Pydantic write specs during `de_serialize`, not by Django field choices — the DB columns are plain `CharField`/`JSONField`.
+- `patient` is never accepted from the client; it is derived from `encounter.patient` on create.
+- Related `Specimen` and `DiagnosticReport` records reference the request via their own `service_request` FK; the request's detail view (`Retrieve`) reads them back rather than storing them.
+
+## API integration notes
+
+- `ServiceRequest` is exposed through Care's REST API and aligns with FHIR `ServiceRequest`, but field/enum spellings follow Care conventions: status/intent/priority are **snake_case** enum values and `occurance` keeps its model typo.
+- `code` is **required** and `body_site` optional; both are single `Coding` objects validated against their bound value sets — not `CodeableConcept`.
+- `category`, `status`, `intent`, and `priority` are validated as enums by the spec even though the columns are free `CharField`s.
+- Write `healthcare_service`, `requester`, and `locations` as `external_id`/UUIDs; the server resolves them. `encounter` (UUID) is required on create and determines the patient.
+- `locations` and `tags` store ID arrays for fast filtering — drive them through the location/tag managers rather than setting the raw arrays from clients.
+
+## Related models
+
+- [Activity Definition](./activity-definition.mdx) — template a request can be created from; supplies the `category` enum and `code` value set
+- [Encounter](./encounter.mdx) — required context on create; supplies the patient
+- [Patient](./patient.mdx) — subject, derived from the encounter
+- [Specimen](./specimen.mdx) — specimens collected for a request
+- [Diagnostic Report](./diagnostic-report.mdx) — reports produced for a request
+- [Observation](./observation.mdx) — shares the body-site value set
+- [Healthcare Service](../facility/healthcare-service.mdx) — service fulfilling the request
+- [Location](../facility/location.mdx) — facility locations the request applies to
+- [User](../access/user.mdx) — requester / audit users
+
+## Related
+
+- Reference: [Activity Definition](./activity-definition.mdx)
+- Reference: [Specimen](./specimen.mdx)
+- Reference: [Diagnostic Report](./diagnostic-report.mdx)
+- Reference: [Observation](./observation.mdx)
+- Reference: [Encounter](./encounter.mdx)
+- Reference: [Patient](./patient.mdx)
+- Reference: [Healthcare Service](../facility/healthcare-service.mdx)
+- Source: [service_request.py (model) on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/service_request.py)
+- Source: [service_request/spec.py (resource spec) on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/service_request/spec.py)
diff --git a/versioned_docs/version-3.1/references/clinical/specimen-definition.mdx b/versioned_docs/version-3.1/references/clinical/specimen-definition.mdx
new file mode 100644
index 0000000..4f41293
--- /dev/null
+++ b/versioned_docs/version-3.1/references/clinical/specimen-definition.mdx
@@ -0,0 +1,163 @@
+---
+sidebar_position: 11
+---
+
+# Specimen Definition
+
+Technical reference for the `SpecimenDefinition` module in Care EMR.
+
+A `SpecimenDefinition` is a reusable, facility-scoped template describing a *kind* of specimen — what material to collect, how to prepare the patient, how to collect it, and how it is held in a container for testing. It lets a lab maintain a repository of specimen kinds, and an [Activity Definition](activity-definition.mdx) / [Service Request](service-request.mdx) can reference it so the same container/handling rules are applied consistently. A concrete [Specimen](specimen.mdx) can be instantiated from a definition (the link is preserved, and the specimen keeps a copy of the definition's data for history/integrity).
+
+The Django model is the **storage** layer: `type_collected`, `patient_preparation`, `collection`, and `type_tested` are opaque `JSONField`s. Their real structure, the enums, value-set bindings, and the read/write API schemas live in the **Pydantic resource specs** documented below.
+
+**Source:**
+
+- Model: [`care/emr/models/specimen_definition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/specimen_definition.py)
+- Resource spec: [`care/emr/resources/specimen_definition/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen_definition/spec.py)
+- Value sets: [`care/emr/resources/specimen_definition/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen_definition/valueset.py)
+- Conversion helper: [`care/emr/resources/specimen_definition/specimen.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen_definition/specimen.py)
+- ViewSet: [`care/emr/api/viewsets/specimen_definition.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/specimen_definition.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `SpecimenDefinition` | Reusable definition of a kind of specimen to be collected and processed — what to collect, how to prepare the patient, how to collect it, and how it is contained and tested |
+
+`SpecimenDefinition` extends [`SlugBaseModel`](../foundation/base-model.mdx) (the Care EMR base augmented with `FACILITY_SCOPED = True`, a facility-scoped `slug`, plus `external_id`, `history`/`meta` JSON, audit fields, and soft-delete semantics).
+
+## `SpecimenDefinition` fields
+
+### Identity & status
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → facility.Facility` (PROTECT) | model: optional | `null=True`, `blank=True`, `default=None`. In the API it is **never client-supplied** — the ViewSet sets it server-side from the `facility_external_id` URL kwarg, so every API-created definition is facility-scoped. Excluded from both spec schemas (`__exclude__ = ["facility"]`) |
+| `version` | `IntegerField` | default `1` | Definition version. Read-only over the API (exposed by `SpecimenDefinitionReadSpec` only, never accepted on write) |
+| `slug` | `CharField(255)` | yes (server-built) | Stored as the **fully-qualified** slug `f--` (see [Methods & save behaviour](#methods--save-behaviour)). Clients send the bare `slug_value`, not this |
+| `title` | `CharField(1024)` | yes | Human-readable name |
+| `derived_from_uri` | `TextField` | optional | `null=True`, `blank=True`. URI of the canonical/external definition this was derived from |
+| `status` | `CharField(255)` | yes | Lifecycle status. Constrained by `SpecimenDefinitionStatusOptions` — see [enum](#specimendefinitionstatusoptions-values) |
+| `description` | `TextField` | yes | Natural-language description (markdown) |
+
+### Collection & testing detail (JSON fields)
+
+The model stores these as raw JSON; the spec defines their true shape and value-set bindings.
+
+| Field | Model type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `type_collected` | `JSONField` (`null=True`) | `Coding` bound to **Specimen Type Code** value set | Required on write. The kind of material collected. See [value sets](#bound-value-sets) |
+| `patient_preparation` | `JSONField` (`default=list`) | `list[Coding]` bound to **Prepare Patient Prior Specimen Code** value set | Defaults to `[]`. Preparation steps the patient follows before collection |
+| `collection` | `JSONField` (`null=True`) | `Coding` bound to **Specimen Collection Code** value set, optional | The specimen collection procedure |
+| `type_tested` | `JSONField` (`default=dict`) | `TypeTestedSpec` (nested), optional | The container + handling + testing detail. See [`TypeTestedSpec`](#typetestedspec) |
+
+> Each bound coded field accepts a `Coding` object (`{ system, version?, code, display? }`); the `code` is validated against the bound value set at de-serialization time via `ValueSetBoundCoding`.
+
+## Related models
+
+| Type | Where | Shape |
+| --- | --- | --- |
+| `Coding` | [`resources/common/coding.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py) | `{ system: str?, version: str?, code: str (required), display: str? }`, `extra="forbid"` |
+| `QuantitySpec` | spec.py | `{ value: Decimal (max_digits=20, decimal_places=0), unit: Coding }` — note `decimal_places=0`, i.e. integer-valued |
+| `MinimumVolumeSpec` | spec.py | `{ quantity: QuantitySpec?, string: str? }`; validator forbids supplying **both** |
+| `ContainerSpec` | spec.py | `{ description: str?, capacity: QuantitySpec?, minimum_volume: MinimumVolumeSpec?, cap: Coding? (bound to Container Cap), preparation: str? }` |
+| `DurationSpec` | spec.py | `{ value: Decimal (max_digits=20, decimal_places=0), unit: Coding }` (`unit` not yet restricted to datetime units) |
+| `RangeSpec` | spec.py | `{ low: QuantitySpec?, high: QuantitySpec? }` |
+| `HandlingSpec` | spec.py | `{ temperature_qualifier: HandlingConditionOptions?, temperature_range: RangeSpec?, max_duration: DurationSpec?, instruction: str? }` |
+| `TypeTestedSpec` | spec.py | see [`TypeTestedSpec`](#typetestedspec) below |
+
+### `TypeTestedSpec`
+
+The structured shape of the `type_tested` JSON field. Care currently supports **a single container per definition** — for multiple containers per test, repeat the definition in the [Activity Definition](activity-definition.mdx) spec.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `is_derived` | `bool` | yes | Primary (`false`) vs secondary/derived (`true`) specimen |
+| `preference` | `PreferenceOptions` | yes | `preferred` / `alternate` — see [enum](#preferenceoptions-values) |
+| `container` | `ContainerSpec` | optional | The specimen's container (description, capacity, minimum volume, cap, preparation) |
+| `requirement` | `str` | optional | Requirements for delivery / special handling (markdown) |
+| `retention_time` | `DurationSpec` | optional | Usual time this kind of specimen is retained |
+| `single_use` | `bool` | optional | Specimen for single use only |
+| `handling` | `HandlingSpec` | optional | Temperature qualifier/range, max duration, instruction |
+
+## Enums
+
+### `SpecimenDefinitionStatusOptions` values
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Definition is being authored, not yet in use |
+| `active` | Definition is in use |
+| `retired` | Definition is withdrawn from use |
+
+### `PreferenceOptions` values
+
+| Value | Meaning |
+| --- | --- |
+| `preferred` | Preferred specimen for the test |
+| `alternate` | Acceptable alternative specimen |
+
+### `HandlingConditionOptions` values
+
+| Value | Meaning |
+| --- | --- |
+| `room` | Room temperature |
+| `refrigerated` | Refrigerated |
+| `frozen` | Frozen |
+
+## Resource specs (API schema)
+
+All specs build on `EMRResource` (`serialize`/`de_serialize`, `perform_extra_serialization`/`perform_extra_deserialization`). `__exclude__ = ["facility"]` keeps `facility` out of both directions. The ViewSet wires `pydantic_model = SpecimenDefinitionWriteSpec` (create/update) and `pydantic_read_model = SpecimenDefinitionReadSpec` (list/retrieve).
+
+| Spec class | Role | Fields / behaviour |
+| --- | --- | --- |
+| `BaseSpecimenDefinitionSpec` | shared base | `id`, `title`, `derived_from_uri`, `status`, `description`, `type_collected`, `patient_preparation`, `collection`, `type_tested`. Holds the coded-field value-set bindings and the nested `type_tested` shape |
+| `SpecimenDefinitionWriteSpec` | write · create & update | Base fields **plus** `slug_value: SlugType`. `perform_extra_deserialization` copies `slug_value` → `obj.slug` (ViewSet then fully-qualifies it). `facility`, `version`, and the stored `slug` are never client-supplied |
+| `SpecimenDefinitionReadSpec` | read · list & detail | Base fields **plus** `version: int?`, `slug: str`, `slug_config: dict`. `perform_extra_serialization` sets `id = obj.external_id` and `slug_config = obj.parse_slug(obj.slug)` (decomposes the qualified slug into `{ facility, slug_value }`) |
+
+Nested specs used by the schemas: `TypeTestedSpec`, `ContainerSpec`, `MinimumVolumeSpec`, `QuantitySpec`, `DurationSpec`, `RangeSpec`, `HandlingSpec` (see [Related models](#related-models)).
+
+### Validation rules
+
+- `slug_value` (`SlugType`): string, length 5–50, must match `^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$` (URL-safe; starts and ends alphanumeric).
+- `type_collected`, `patient_preparation[*]`, `collection`, `container.cap`: each `Coding.code` is validated against its bound value set by `ValueSetBoundCoding` during de-serialization; an unknown code is rejected.
+- `MinimumVolumeSpec`: `quantity` and `string` are mutually exclusive — supplying both raises `"Only one of quantity or string should be provided"`.
+- `QuantitySpec` / `DurationSpec` `value`: `Decimal`, `max_digits=20`, `decimal_places=0` (integer-valued).
+- Slug uniqueness (ViewSet `validate_data`): the fully-qualified `f--` must be unique within the facility (case-insensitive); otherwise `"Specimen Definition with this slug already exists."` On update, the current record is excluded from the check.
+
+### Bound value sets
+
+| Field | Value set | Slug | Source system(s) |
+| --- | --- | --- | --- |
+| `type_collected` | Specimen Type Code | `system-specimen_type-code` | HL7 v2-0487 (unbounded include) |
+| `patient_preparation[*]` | Prepare Patient Prior Specimen Code | `system-prepare_patient_prior_specimen_code` | SNOMED CT, `is-a 703763000` |
+| `collection` | Specimen Collection Code | `system-specimen_collection_code` | SNOMED CT (curated list: aspiration, biopsy, puncture, excision, scraping, clean-catch/timed/catheterized urine, coughed sputum, finger-prick) |
+| `container.cap` | Container Cap | `system-container_cap-code` | HL7 `container-cap` code system |
+
+## Methods & save behaviour
+
+`SpecimenDefinition` defines no overridden `save()`/`delete()` or custom methods. Slug helpers, audit fields, and soft-delete are inherited from [`SlugBaseModel`](../foundation/base-model.mdx). The side effects below live in the resource spec and the ViewSet, not the model:
+
+- **Slug qualification (write).** Clients send a bare `slug_value`. `SpecimenDefinitionWriteSpec.perform_extra_deserialization` sets `obj.slug = slug_value`, then the ViewSet's `perform_create`/`perform_update` rewrites it to `SpecimenDefinition.calculate_slug_from_facility(facility.external_id, slug)` → `f--`.
+- **Facility binding (create).** `perform_create` sets `instance.facility` from the `facility_external_id` URL kwarg before saving.
+- **Slug decomposition (read).** `SpecimenDefinitionReadSpec.perform_extra_serialization` exposes `slug_config = obj.parse_slug(obj.slug)`, splitting the stored slug back into `{ facility, slug_value }`.
+- **Specimen instantiation.** `convert_sd_to_specimen` ([`specimen.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen_definition/specimen.py)) builds a [`Specimen`](specimen.mdx) from a definition with `status="available"`, `specimen_type = definition.type_collected`, and `specimen_definition = ` — the link and a copy of the type are preserved.
+
+## API integration notes
+
+- **Endpoints are facility-scoped.** The ViewSet supports create, retrieve, update, list, and upsert; `lookup_field = "slug"`. All operations require `facility_external_id` in the URL.
+- **Authorization.** Writes require `can_write_facility_specimen_definition`; list/retrieve require `can_list_facility_specimen_definition` on the facility — otherwise `403 PermissionDenied`. The queryset is filtered to the URL facility.
+- **Filtering / ordering.** `status` (iexact) and `title` (icontains) filters; ordering by `created_date` / `modified_date`.
+- **Write payload** (`SpecimenDefinitionWriteSpec`): send `slug_value`, `title`, `status`, `description`, coded fields (`type_collected`, `patient_preparation`, `collection`) as `Coding` objects, and `type_tested`. Do **not** send `facility`, `version`, or the qualified `slug` — they are server-managed.
+- **Read payload** (`SpecimenDefinitionReadSpec`): adds `id` (= `external_id`), `version`, the qualified `slug`, and `slug_config`.
+- The model maps to the FHIR `SpecimenDefinition` resource. Care's spec is intentionally minimal (single container per definition; `version` bump rather than mutation of published records when behaviour changes).
+
+## Related
+
+- Reference: [Specimen](specimen.mdx)
+- Reference: [Service Request](service-request.mdx)
+- Reference: [Observation Definition](observation-definition.mdx)
+- Reference: [Activity Definition](activity-definition.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Reference: [Valueset](../forms/valueset.mdx)
+- Source: [model on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/specimen_definition.py) · [spec on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen_definition/spec.py) · [value sets on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen_definition/valueset.py)
diff --git a/versioned_docs/version-3.1/references/clinical/specimen.mdx b/versioned_docs/version-3.1/references/clinical/specimen.mdx
new file mode 100644
index 0000000..2664574
--- /dev/null
+++ b/versioned_docs/version-3.1/references/clinical/specimen.mdx
@@ -0,0 +1,209 @@
+---
+sidebar_position: 10
+---
+
+# Specimen
+
+Technical reference for the `Specimen` module in Care EMR.
+
+The Django model (`care/emr/models/specimen.py`) is the **storage layer** — several fields are opaque `JSONField`s whose real structure is defined only in the Pydantic **resource specs** (`care/emr/resources/specimen/`). The specs define the enums, the nested JSON shapes, coded value-set bindings, validation, and the read/write API schemas. This page documents both layers.
+
+**Source:**
+- Model: [`care/emr/models/specimen.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/specimen.py)
+- Resource specs: [`care/emr/resources/specimen/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen/spec.py), [`valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen/valueset.py)
+- Viewset: [`care/emr/api/viewsets/specimen.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/specimen.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Specimen` | A physical sample (blood, tissue, swab, etc.) collected from a patient for laboratory analysis |
+
+`Specimen` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, soft-delete via `deleted`, and `history`/`meta` JSON).
+
+## `Specimen` fields
+
+### Identity & status
+
+| Field | Model type | Spec shape / values | Notes |
+| --- | --- | --- | --- |
+| `accession_identifier` | `CharField(255)`, `db_index=True` | `str`, default `""` | Lab accession/barcode identifier; indexed for lookup. Spec default is empty string (not required on write). |
+| `status` | `CharField(20)` | `SpecimenStatusOptions` enum | Specimen status. Required on base/create; optional on update. See [status values](#specimenstatusoptions-values). |
+| `specimen_type` | `JSONField` | `Coding` bound to value set `system-specimen_type-code` | Kind of material collected (single coding). Required on base/create; optional on update. |
+
+### Clinical links
+
+| Field | Model type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → Facility` | `PROTECT`, nullable; facility the specimen belongs to. Excluded from all specs (`__exclude__`). |
+| `patient` | `FK → Patient` | `CASCADE`; the patient the specimen was collected from. Set on create via `subject_patient` (UUID). |
+| `encounter` | `FK → Encounter` | `CASCADE`, nullable; encounter during collection. Set on create via `subject_encounter` (UUID). |
+| `service_request` | `FK → ServiceRequest` | `CASCADE`, nullable; the order this specimen fulfills. Set on create via `request` (UUID); excluded from base/read field copy. |
+| `specimen_definition` | `FK → SpecimenDefinition` | `CASCADE`, nullable; the definition/template this specimen was derived from. |
+
+### Collection & processing
+
+| Field | Model type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `collection` | `JSONField` `default=dict` | `CollectionSpec \| None`, default `None` | Single collection event (collector, method, body site, quantity, fasting). See [CollectionSpec](#collectionspec). |
+| `received_time` | `DateTimeField`, nullable | `str \| None`, default `None` | When the lab received the specimen (ISO 8601 string in the spec). |
+| `condition` | `JSONField` `default=list` | `list[Coding]` bound to `system-specimen-condition-code`, default `[]` | Coded conditions/state of the specimen on receipt. |
+| `processing` | `JSONField` `default=list` | `list[ProcessingSpec]`, default `[]` | Processing steps applied. See [ProcessingSpec](#processingspec). |
+| `note` | `TextField`, nullable | `str \| None`, default `None` | Free-text annotation. |
+
+## Enums
+
+### `SpecimenStatusOptions` values
+
+`status` enum (`str`). Note: `entered_in_error` uses an underscore (not the FHIR hyphen), and `draft` is a Care-specific addition not in FHIR.
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Care-specific; not from FHIR |
+| `available` | Specimen available for testing |
+| `unavailable` | Specimen no longer available |
+| `unsatisfactory` | Specimen unsuitable for testing |
+| `entered_in_error` | Recorded in error |
+
+## Nested JSON specs
+
+These define the real structure of the model's `JSONField`s.
+
+### `CollectionSpec`
+
+Shape of the `collection` JSON field (a single collection event).
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `collector` | `UUID4 \| None` | optional | External ID of collecting `User`. Validated to exist (`User.objects.filter(external_id=...)`), else `"Collector user not found"`. Serialized output adds `collector_object` (resolved `UserSpec` from cache). |
+| `collected_date_time` | `datetime \| None` | optional | When the specimen was collected. |
+| `quantity` | `QuantitySpec \| None` | optional | Amount collected. See [QuantitySpec](#quantityspec). |
+| `method` | `Coding \| None` bound to `system-collection-method-code` | optional | Collection method (SNOMED). See [collection method values](#collection-method-value-set). |
+| `procedure` | `UUID4 \| None` | optional | Reference to a procedure. |
+| `body_site` | `Coding \| None` bound to `system-body-site-observation` | optional | Anatomical site collected from. |
+| `fasting_status_codeable_concept` | `Coding \| None` bound to `system-fasting-status-code` | optional | Patient fasting status (HL7 v2-0916). |
+| `fasting_status_duration` | `DurationSpec \| None` | optional | Fasting duration. See [DurationSpec](#durationspec). |
+
+### `ProcessingSpec`
+
+One element of the `processing` list.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `description` | `str` | required | Description of the processing step. |
+| `method` | `Coding \| None` bound to `system-specimen-processing-method-code` | optional | Processing method (SNOMED, descendants of `9265001`). |
+| `performer` | `UUID4 \| None` | optional | External ID of performing `User`. Validated to exist, else `"Performer user not found"`. Serialized output adds `performer_object` (resolved `UserSpec` from cache). |
+| `time_date_time` | `str` | required | When the step was performed. |
+
+### `QuantitySpec`
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `value` | `Decimal` | `max_digits=20`, `decimal_places=0` (integer-valued). |
+| `unit` | `Coding` | Required unit coding. |
+
+Note: this is a specimen-local quantity spec, distinct from the shared [`Quantity`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/quantity.py) common type (which allows `decimal_places=6`, optional `value`/`unit`, plus `code`/`meta`).
+
+### `DurationSpec`
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `value` | `int` | Duration magnitude. |
+| `unit` | `Coding` | Required unit coding. |
+
+### `Coding` (shared common type)
+
+All coded fields above use [`Coding`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py) (`extra="forbid"`):
+
+| Field | Type | Required |
+| --- | --- | --- |
+| `system` | `str \| None` | optional |
+| `version` | `str \| None` | optional |
+| `code` | `str` | required |
+| `display` | `str \| None` | optional |
+
+## Bound value sets
+
+Coded fields bind to Care value sets via `ValueSetBoundCoding[]`, which validates the submitted `code` against the value set at write time.
+
+| Field | Value set | Slug | Source system |
+| --- | --- | --- | --- |
+| `specimen_type` | Specimen Type Code | `system-specimen_type-code` | HL7 v2-0487 |
+| `condition[]` | Specimen Condition | `system-specimen-condition-code` | HL7 v2-0493 (v2.0.0) |
+| `collection.method` | Collection Method | `system-collection-method-code` | SNOMED CT (enumerated, see below) |
+| `collection.body_site` | Body Site | `system-body-site-observation` | (observation body-site value set) |
+| `collection.fasting_status_codeable_concept` | Fasting Status | `system-fasting-status-code` | HL7 v2-0916 (v2.0.0) |
+| `processing[].method` | Specimen Processing Method | `system-specimen-processing-method-code` | SNOMED CT (`< 9265001`) |
+
+### Collection Method value set
+
+The `system-collection-method-code` value set enumerates these SNOMED CT codes:
+
+| Code | Display |
+| --- | --- |
+| `129316008` | Aspiration - action |
+| `129314006` | Biopsy - action |
+| `129300006` | Puncture - action |
+| `129304002` | Excision - action |
+| `129323009` | Scraping - action |
+| `73416001` | Urine specimen collection, clean catch |
+| `225113003` | Timed urine collection |
+| `70777001` | Urine specimen collection, catheterized |
+| `386089008` | Collection of coughed sputum |
+| `278450005` | Finger-prick sampling |
+
+Fasting Status (`http://terminology.hl7.org/CodeSystem/v2-0916`), Specimen Condition (`v2-0493`), Specimen Type (`v2-0487`), Body Site, and Specimen Processing Method (SNOMED `< 9265001`) are included by code-system reference/filter rather than an enumerated concept list.
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB object → pydantic, via `get_database_mapping`) and `de_serialize` (pydantic → DB object), plus `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__exclude__ = ["facility", "request"]` on the base means those are never copied field-for-field.
+
+| Spec class | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `BaseSpecimenSpec` | shared base | `id`, `accession_identifier`, `status` (required), `specimen_type` (required), `received_time`, `collection`, `processing`, `condition`, `note`. `__model__ = Specimen`, `__exclude__ = ["facility", "request"]`. |
+| `SpecimenCreateSpec` | write · create | Adds `subject_patient: UUID4` (required), `subject_encounter: UUID4` (required), `request: UUID4 \| None`. Used to wire FK links on creation. |
+| `SpecimenUpdateSpec` | write · update | Relaxes `status` and `specimen_type` to optional (`\| None`) for partial updates. |
+| `SpecimenReadSpec` | read · list | Adds `specimen_definition: dict \| None`. `perform_extra_serialization` sets `id = external_id` and, when present, embeds the linked specimen definition via `SpecimenDefinitionReadSpec.serialize(...).to_json()`. |
+| `SpecimenRetrieveSpec` | read · detail | Extends `SpecimenReadSpec`, adds `service_request: dict \| None`; serialization additionally embeds the linked service request via `ServiceRequestReadSpec.serialize(...).to_json()`. |
+
+Nested specs (defined in the same module): `CollectionSpec`, `ProcessingSpec`, `QuantitySpec`, `DurationSpec` — see [Nested JSON specs](#nested-json-specs).
+
+### Validation & server behaviour
+
+- **User references resolved & validated**: `collection.collector` and `processing[].performer` must reference existing users (by `external_id`); serialization injects fully resolved `collector_object` / `performer_object` (cached `UserSpec`).
+- **Coded fields validated against value sets** at write time (`ValueSetBoundCoding`) — an unbound/invalid `code` is rejected.
+- **Read specs embed related resources** (specimen definition on list/detail; service request on detail) as nested JSON rather than bare IDs.
+- The base spec does **not** override `perform_extra_deserialization`, so no `status_history`-style server-side history is appended on write for this resource (unlike some other clinical resources).
+
+## API integration notes
+
+- Aligned with the FHIR `Specimen` resource; Care API field names differ in places (e.g. `subject_patient` / `subject_encounter` / `request` on create map to the `patient` / `encounter` / `service_request` FKs).
+- The viewset (`SpecimenViewSet`) mixes in **retrieve** and **update** only (`EMRRetrieveMixin`, `EMRUpdateMixin`) — it exposes read/detail/list and update, with `pydantic_model = BaseSpecimenSpec`, `pydantic_update_model = SpecimenUpdateSpec`, `pydantic_read_model = SpecimenReadSpec`, `pydantic_retrieve_model = SpecimenRetrieveSpec`.
+- Filtering: `accession_identifier` (`icontains`); ordering on `created_date` / `modified_date`.
+- Custom action `POST retrieve_by_accession_identifier` looks a specimen up by `accession_identifier` (must be ≥ 5 chars); raises a validation error on multiple/no matches and returns a `SpecimenRetrieveSpec`.
+- Authorization: read via `can_read_specimen`, write via `can_write_specimen`, both keyed off the linked `service_request`.
+- `accession_identifier` is `db_index=True` for fast barcode/accession lookup.
+- A specimen links back to its originating `service_request`; one service request may have multiple specimens.
+
+## Related models
+
+```text
+facility → FK Facility (PROTECT, nullable) [excluded from specs]
+patient → FK Patient (CASCADE) [set via subject_patient]
+encounter → FK Encounter (CASCADE, nullable) [set via subject_encounter]
+service_request → FK ServiceRequest (CASCADE, nullable) [set via request]
+specimen_definition → FK SpecimenDefinition (CASCADE, nullable)
+```
+
+A `Specimen` is typically created to fulfill a [`ServiceRequest`](./service-request.mdx) (a lab order) and may be shaped by a [`SpecimenDefinition`](./specimen-definition.mdx) describing the expected sample. Results derived from a specimen surface through a [`DiagnosticReport`](./diagnostic-report.mdx).
+
+## Related
+
+- Reference: [Specimen Definition](./specimen-definition.mdx)
+- Reference: [Service Request](./service-request.mdx)
+- Reference: [Diagnostic Report](./diagnostic-report.mdx)
+- Reference: [Patient](./patient.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
+- Source (model): [specimen.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/specimen.py)
+- Source (spec): [specimen/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen/spec.py)
+- Source (value sets): [specimen/valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/specimen/valueset.py)
diff --git a/versioned_docs/version-3.1/references/facility/_category_.json b/versioned_docs/version-3.1/references/facility/_category_.json
new file mode 100644
index 0000000..8cb96ba
--- /dev/null
+++ b/versioned_docs/version-3.1/references/facility/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Facility & Administration",
+ "position": 7,
+ "key": "facility-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Facility & Administration",
+ "description": "Facilities, organizations, locations, healthcare services, and devices — the administrative backbone of a deployment."
+ }
+}
diff --git a/versioned_docs/version-3.1/references/facility/device.mdx b/versioned_docs/version-3.1/references/facility/device.mdx
new file mode 100644
index 0000000..1fb808d
--- /dev/null
+++ b/versioned_docs/version-3.1/references/facility/device.mdx
@@ -0,0 +1,231 @@
+---
+sidebar_position: 7
+---
+
+# Device
+
+Technical reference for the `Device` module in Care EMR.
+
+The Django model is the **storage layer** — several fields are typed only loosely (`CharField` for status enums, `JSONField` for `contact`/`metadata`). The **Pydantic resource specs** under `care/emr/resources/device/` are the API/implementation layer: they define the enums those `CharField`s are constrained to, the structured shape of the JSON fields, validation rules, the read/write schemas, and server-maintained side effects.
+
+**Source:**
+- Model: [`care/emr/models/device.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/device.py)
+- Spec: [`care/emr/resources/device/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/device/spec.py)
+- History spec: [`care/emr/resources/device/history_spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/device/history_spec.py)
+- Viewset: [`care/emr/api/viewsets/device.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/device.py)
+- Device-type registry: [`care/emr/registries/device_type/device_registry.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/registries/device_type/device_registry.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Device` | A physical or logical device tracked within a facility |
+| `DeviceEncounterHistory` | Records each period a device was associated with an `Encounter` |
+| `DeviceLocationHistory` | Records each period a device was placed at a `FacilityLocation` |
+| `DeviceServiceHistory` | Records servicing/maintenance events for a device |
+
+All models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+## `Device` fields
+
+### Device data
+
+| Field | Type | Spec constraint | Notes |
+| --- | --- | --- | --- |
+| `identifier` | `CharField(1024)` | `str \| None` | Optional; free-form device identifier. Filterable via `?identifier=` (case-insensitive exact match) |
+| `status` | `CharField(16)` | `DeviceStatusChoices` (**required**) | Lifecycle status — see [enum](#devicestatuschoices-values) |
+| `availability_status` | `CharField(14)` | `DeviceAvailabilityStatusChoices` (**required**) | Physical availability — see [enum](#deviceavailabilitystatuschoices-values) |
+| `manufacturer` | `CharField(1024)` | `str \| None` | Optional in spec (model column itself is non-null) |
+| `manufacture_date` | `DateTimeField` | `datetime \| None` | Optional, tz-aware |
+| `expiration_date` | `DateTimeField` | `datetime \| None` | Optional, tz-aware |
+| `lot_number` | `CharField(1024)` | `str \| None` | Optional |
+| `serial_number` | `CharField(1024)` | `str \| None` | Optional |
+| `registered_name` | `CharField(1024)` | `str` (**required**) | Formal/registered device name; required by all write specs. Searchable via `?search=` |
+| `user_friendly_name` | `CharField(1024)` | `str \| None` | Display name. Searchable via `?search=` |
+| `model_number` | `CharField(1024)` | `str \| None` | Optional |
+| `part_number` | `CharField(1024)` | `str \| None` | Optional |
+| `contact` | `JSONField` (default `{}`) | `list[ContactPoint]` (default `[]`) | NOT a free-form dict — a list of structured contact points. See [`ContactPoint` shape](#contactpoint-shape) |
+| `care_type` | `CharField(1024)` (default `None`) | `str \| None` (create only) | Discriminator selecting a registered device-type plugin. Validated against `DeviceTypeRegistry` on create (must be a registered key, e.g. `"camera"`, or `null`). Filterable via `?care_type=` |
+| `metadata` | `JSONField` (default `{}`) | not exposed directly | Excluded from all specs. Written server-side by the `care_type` plugin's `handle_create`/`handle_update`. Read back through the virtual `care_metadata` field (below) |
+
+#### Virtual / spec-only field
+
+| Field | Spec type | Notes |
+| --- | --- | --- |
+| `care_metadata` | `dict` (default `{}`) | Not a DB column. On read, populated by the `care_type` plugin: `DeviceListSpec` uses the plugin's `list(obj)`, `DeviceRetrieveSpec` uses `retrieve(obj)`. On write it carries plugin-specific data consumed by `handle_create`/`handle_update` (which typically persist into the model's `metadata` column). Empty `{}` when no `care_type` or the plugin lookup fails |
+
+### Relations
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → Facility` | `CASCADE`; owning facility (required). Set server-side from the URL (`facility_external_id`), not from the request body |
+| `managing_organization` | `FK → FacilityOrganization` | `SET_NULL`, nullable; org responsible for the device. Excluded from create/update body — managed via dedicated endpoints (see [API integration notes](#api-integration-notes)) |
+| `current_location` | `FK → FacilityLocation` | `SET_NULL`, nullable; where the device is currently placed. Excluded from create/update body — managed via `associate_location` |
+| `current_encounter` | `FK → Encounter` | `SET_NULL`, nullable; encounter the device is currently attached to. Excluded from create/update body — managed via `associate_encounter` |
+
+### Access cache
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility_organization_cache` | `ArrayField[int]` | **Denormalized cache** of facility-organization IDs, rebuilt on every `save()`. Do not set directly. Used by `get_queryset` to scope which devices a user may see |
+
+## Enum values
+
+### `DeviceStatusChoices` values
+
+`status` field. Defined in `spec.py`.
+
+| Value | Meaning |
+| --- | --- |
+| `active` | Device is in service |
+| `inactive` | Device is not currently in service |
+| `entered_in_error` | Record created in error |
+
+### `DeviceAvailabilityStatusChoices` values
+
+`availability_status` field. Defined in `spec.py`.
+
+| Value | Meaning |
+| --- | --- |
+| `lost` | Device cannot be located |
+| `damaged` | Device is damaged |
+| `destroyed` | Device is destroyed |
+| `available` | Device is available for use |
+
+### `ContactPoint` shape
+
+`contact` is `list[ContactPoint]`. Each item (from `care/emr/resources/common/contact_point.py`):
+
+```text
+ContactPoint {
+ system: ContactPointSystemChoices # required
+ value: str # required
+ use: ContactPointUseChoices # required
+}
+```
+
+| `system` values | `use` values |
+| --- | --- |
+| `phone`, `fax`, `email`, `pager`, `url`, `sms`, `other` | `home`, `work`, `temp`, `old`, `mobile` |
+
+## Related models
+
+### `DeviceEncounterHistory`
+
+Audit trail of device-to-encounter associations. A new row is created when a device is attached to an encounter; `end` is stamped when it detaches. Read-only via the API (`EMRModelReadOnlyViewSet`), ordered by `-end`.
+
+```text
+device → FK Device (CASCADE)
+encounter → FK Encounter (CASCADE)
+start → DateTimeField
+end → DateTimeField (nullable)
+```
+
+### `DeviceLocationHistory`
+
+Audit trail of device placements. A new row is created when a device moves to a location; `end` is stamped when it leaves. Read-only via the API, ordered by `-end`.
+
+```text
+device → FK Device (CASCADE)
+location → FK FacilityLocation (CASCADE)
+start → DateTimeField
+end → DateTimeField (nullable)
+```
+
+### `DeviceServiceHistory`
+
+Servicing/maintenance log for a device. Writable via the API (create + update + list + retrieve).
+
+```text
+device → FK Device (PROTECT, required)
+serviced_on → DateTimeField (nullable, default None)
+note → TextField (default "")
+edit_history → JSONField (default [])
+```
+
+`device` uses `PROTECT`, so a `Device` with service-history rows cannot be hard-deleted while they exist. `edit_history` is an append-only audit list maintained server-side on every update (see [Methods & save behaviour](#deviceservicehistory-edit-history)).
+
+## Resource specs (API schema)
+
+The viewset wires these specs as: create = `DeviceCreateSpec`, update = `DeviceUpdateSpec`, list = `DeviceListSpec`, retrieve = `DeviceRetrieveSpec`. All extend `EMRResource` (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks).
+
+### Device specs
+
+| Spec class | Role | Adds / overrides | Excludes |
+| --- | --- | --- | --- |
+| `DeviceSpecBase` | shared base | `id`, all device-data fields, `status`/`availability_status` enums, `contact: list[ContactPoint]`, `registered_name` required | `facility`, `managing_organization`, `current_location`, `current_encounter`, `care_metadata` |
+| `DeviceCreateSpec` | write · create | `care_type` (validated against `DeviceTypeRegistry`), `care_metadata: dict` | (inherits base excludes) |
+| `DeviceUpdateSpec` | write · update | `care_metadata: dict` — note: **no `care_type`**, so `care_type` is immutable after create | (inherits base excludes) |
+| `DeviceListSpec` | read · list | `id = external_id`; `care_metadata` from plugin `list(obj)` | (inherits) |
+| `DeviceRetrieveSpec` | read · detail | full nested objects: `current_location` (`FacilityLocationListSpec`), `current_encounter` (`EncounterListSpec`), `managing_organization` (`FacilityOrganizationReadSpec`), `created_by`/`updated_by` (audit users); `care_metadata` from plugin `retrieve(obj)` | (inherits) |
+
+### Device history specs
+
+| Spec class | Role | Fields | Excludes |
+| --- | --- | --- | --- |
+| `DeviceLocationHistoryListSpec` | read · list | `id`, `location` (nested `FacilityLocationListSpec`), `created_by`, `start`, `end?` | `device`, `location` (raw FK) |
+| `DeviceEncounterHistoryListSpec` | read · list | `id`, `encounter` (nested `EncounterListSpec`), `created_by`, `start`, `end?` | `device`, `encounter` (raw FK) |
+| `DeviceServiceHistorySpecBase` | shared base | `id` | `device`, `edit_history` |
+| `DeviceServiceHistoryWriteSpec` | write | `serviced_on: datetime` (required), `note: str` (required) | (inherits) |
+| `DeviceServiceHistoryListSpec` | read · list | adds `created_date`, `modified_date`; `id = external_id` | (inherits) |
+| `DeviceServiceHistoryRetrieveSpec` | read · detail | adds `edit_history: list[dict]` (each entry's `updated_by` hydrated to a `UserSpec` from cache), `created_by`, `updated_by` | (inherits) |
+
+### Validation rules
+
+- `status` and `availability_status` must be one of their enum values (request rejected otherwise).
+- `registered_name` is required on every write.
+- `care_type` (create only) must be a registered device-type key or `null`; `DeviceCreateSpec.validate_care_type` calls `DeviceTypeRegistry.get_care_device_class(value)`, which raises for unknown types.
+- `care_type` cannot be changed on update (`DeviceUpdateSpec` does not expose it).
+- `DeviceServiceHistory.serviced_on` and `note` are required on write; updates are blocked once `edit_history` reaches 50 entries (`"Cannot Edit instance anymore"`).
+- `ContactPoint.system`, `value`, and `use` are all required for each contact entry.
+
+## Methods & save behaviour
+
+### `Device.save()` — organization cache
+
+On every save the `facility_organization_cache` is rebuilt:
+
+1. Look up the `FacilityOrganization` with `org_type="root"` for the device's `facility`; its ID seeds the cache.
+2. If `managing_organization` is set, add its `id` and its `parent_cache` (full ancestor chain).
+3. Persist the resulting set as `facility_organization_cache`.
+
+This cache lets the device be filtered by organization hierarchy without traversing joins. Treat it as platform-maintained and rely on `managing_organization` / `facility` as the writable inputs.
+
+### `care_type` plugin hooks
+
+When `care_type` is set, the matching `DeviceTypeBase` subclass from `DeviceTypeRegistry` is invoked inside the create/update/delete transaction:
+
+| Stage | Hook | Effect |
+| --- | --- | --- |
+| create | `handle_create(request_data, obj)` | May mutate `obj` / `obj.metadata` and persist (e.g. the bundled `camera` plugin stores `request_data["some_data"]` into `metadata`) |
+| update | `handle_update(request_data, obj)` | Same, on update |
+| destroy | `handle_delete(obj)` | Validation/cleanup before the device is soft-deleted (`deleted=True`) |
+| list | `list(obj)` | Supplies `care_metadata` for `DeviceListSpec` |
+| retrieve | `retrieve(obj)` | Supplies `care_metadata` for `DeviceRetrieveSpec` |
+
+### `DeviceServiceHistory` edit history
+
+On `perform_update`, the viewset snapshots the **current** DB row and appends `{serviced_on, note, updated_by}` to `edit_history` before saving the new values — so `edit_history` is a server-maintained, append-only trail of previous states. Updates are refused once it holds 50 entries.
+
+## API integration notes
+
+- `facility` is taken from the URL (`facility_external_id`) on create — not from the body.
+- `current_location`, `current_encounter`, and `managing_organization` are **not** writable through the device create/update body. They are mutated through dedicated detail actions, each of which closes the open history row (stamps `end`) and opens a new one:
+ - `POST associate_encounter` — `{ encounter: uuid | null }`; closes the open `DeviceEncounterHistory`, sets `current_encounter`, opens a new history row (returns it). `null` detaches.
+ - `POST associate_location` — `{ location: uuid | null }`; same pattern against `DeviceLocationHistory`.
+ - `POST add_managing_organization` — `{ managing_organization: uuid }`.
+ - `POST remove_managing_organization` — clears `managing_organization`.
+- When an encounter reaches a completed status, `disassociate_device_from_encounter` clears `current_encounter` on all attached devices and stamps `end` on their open `DeviceEncounterHistory` rows.
+- `metadata` (model column) is plugin-owned; clients read deployment-specific data back through the `care_metadata` virtual field, not `metadata`.
+- `facility_organization_cache` is maintained by the platform — do not set it directly. The list endpoint scopes results to devices whose cache overlaps the requesting user's facility-organization memberships (or, when `?location=` is supplied, by location permission / `parent_cache` with `?include_children=`).
+
+## Related
+
+- Reference: [Facility](./facility.mdx)
+- Reference: [Organization](./organization.mdx)
+- Reference: [Location](./location.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Source: [device.py model](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/device.py)
+- Source: [device spec](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/device/spec.py)
+- Source: [device history spec](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/device/history_spec.py)
+- Source: [ContactPoint](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/contact_point.py)
diff --git a/versioned_docs/version-3.1/references/facility/facility-config.mdx b/versioned_docs/version-3.1/references/facility/facility-config.mdx
new file mode 100644
index 0000000..0ea691c
--- /dev/null
+++ b/versioned_docs/version-3.1/references/facility/facility-config.mdx
@@ -0,0 +1,229 @@
+---
+sidebar_position: 2
+---
+
+# Facility Config
+
+Technical reference for the per-facility monetary/billing configuration in Care EMR. The Django model `FacilityMonetoryConfig` is the **storage layer** — several of its fields are opaque `JSONField`s whose real structure lives in the Pydantic **resource specs** (the API/implementation layer). This page documents both: the model fields and the spec-defined shapes, enums, and validation that constrain them.
+
+**Sources:**
+
+- Model: [`care/emr/models/facility_config.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/facility_config.py)
+- Spec: [`care/emr/resources/facility/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py)
+- Monetary types: [`care/emr/resources/common/monetary_component.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
+- Coding: [`care/emr/resources/common/coding.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py)
+- Expression evaluator: [`care/emr/resources/invoice/default_expression_evaluator.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/invoice/default_expression_evaluator.py)
+- API viewset: [`care/emr/api/viewsets/facility.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/facility.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `FacilityMonetoryConfig` | Per-facility billing/monetary configuration: facility-scoped discount codes, discount monetary component definitions, a discount-stacking rule, and the invoice-number expression |
+
+`FacilityMonetoryConfig` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `FacilityMonetoryConfig` fields
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `facility` | `OneToOneField → Facility` | yes | — | `on_delete=CASCADE`, `unique=True`. Exactly one config per facility |
+| `discount_codes` | `JSONField` → `list[Coding]` | no | `[]` | Catalog of facility-defined discount codes. Each entry is a [`Coding`](#coding-shape). Max 100. No bound value set — codes are free-form but must not duplicate or redefine instance `settings.DISCOUNT_CODES` |
+| `discount_monetary_components` | `JSONField` → `list[MonetaryComponentDefinition]` | no | `[]` | Definitions of selectable discount line items. Each entry is a [`MonetaryComponentDefinition`](#monetarycomponentdefinition-shape). Max 100. Every `code` referenced here must resolve to either an instance discount code or a facility `discount_codes` entry |
+| `discount_configuration` | `JSONField` → `DiscountConfiguration \| null` | no | `{}` | Discount-stacking rule: see [`DiscountConfiguration`](#discountconfiguration-shape). `null`/empty is treated as `{}` |
+| `invoice_number_expression` | `CharField(1000)` | no | `None` | Nullable/blank. Expression template used to generate invoice numbers. Validated by dry-running it against a dummy context (see [Invoice expression](#invoice-number-expression)) |
+
+> The Django model declares `discount_codes`, `discount_monetary_components`, and `discount_configuration` as plain `JSONField`s. Their real structure is defined by the Pydantic specs below and is **only enforced at the API layer** — the database does not constrain them.
+
+## JSON field shapes
+
+### `Coding` shape
+
+`discount_codes[]` entries are `Coding` objects (`model_config = extra="forbid"`).
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `system` | `str \| null` | no | Code system URI |
+| `version` | `str \| null` | no | Code system version |
+| `code` | `str` | **yes** | The code value |
+| `display` | `str \| null` | no | Human-readable label |
+
+### `MonetaryComponentDefinition` shape
+
+`discount_monetary_components[]` entries. Subclasses `MonetaryComponent` and adds `title`. Definition-mode validators relax some base rules (duplicate-code and amount-or-factor checks are overridden/disabled), but **a `base`-typed component is rejected** in a definition.
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `title` | `str` | **yes** | — | Display title (added by the definition subclass) |
+| `monetary_component_type` | `MonetaryComponentType` enum | **yes** | — | See [enum values](#monetarycomponenttype-values). `base` is **not allowed** in a definition |
+| `code` | `Coding \| null` | no | `null` | Must resolve against allowed codes (instance + facility) per `FacilityMonetaryCodeSpec` validation |
+| `factor` | `Decimal \| null` | no | `null` | `max_digits=20, decimal_places=6`. Mutually exclusive with `amount` |
+| `amount` | `Decimal \| null` | no | `null` | `max_digits=20, decimal_places=6`. Mutually exclusive with `factor` |
+| `tax_included_amount` | `Decimal \| null` | no | `null` | `max_digits=20, decimal_places=6`. On the base `MonetaryComponent` this is only allowed when type is `base`; irrelevant for definitions since `base` is rejected |
+| `global_component` | `bool` | no | `false` | Whether the component applies globally |
+| `conditions` | `list[EvaluatorConditionSpec]` | no | `[]` | Applicability conditions; each has `metric`, `operation`, `value` validated against the evaluator-metrics registry |
+
+`EvaluatorConditionSpec` (each `conditions[]` entry):
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `metric` | `str` | **yes** | Must be a registered evaluator metric, else "Invalid metric" |
+| `operation` | `str` | **yes** | Validated by the metric's `validate_rule` |
+| `value` | `dict \| str` | **yes** | Validated by the metric's `validate_rule` |
+
+### `DiscountConfiguration` shape
+
+`discount_configuration` object.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `max_applicable` | `int` (`ge=0`) | **yes** | Maximum number of discounts that may stack on a charge |
+| `applicability_order` | `DiscountApplicability` enum | **yes** | Order discounts are applied in; see [enum values](#discountapplicability-values) |
+
+## Enum values
+
+### `MonetaryComponentType` values
+
+From `care/emr/resources/common/monetary_component.py`. String enum.
+
+| Value | Meaning |
+| --- | --- |
+| `base` | Base price (not allowed in a `MonetaryComponentDefinition`) |
+| `surcharge` | Additional charge |
+| `discount` | Discount line item |
+| `tax` | Tax line item |
+| `informational` | Informational-only component |
+
+### `DiscountApplicability` values
+
+From `care/emr/resources/common/monetary_component.py`. String enum.
+
+| Value | Meaning |
+| --- | --- |
+| `total_asc` | Apply discounts in ascending order of total |
+| `total_desc` | Apply discounts in descending order of total |
+
+## Resource specs (API schema)
+
+These Pydantic specs (built on [`EMRResource`](../foundation/base-model.mdx) — `serialize`/`de_serialize`, with `perform_extra_serialization`/`perform_extra_deserialization` hooks, `__model__`, `__exclude__`) define how monetary config is written and read. The monetary config is **not** exposed via the standard facility CRUD; it is written through dedicated facility detail actions and read back inside the facility retrieve payload.
+
+| Spec | Role | Bound to | Notes |
+| --- | --- | --- | --- |
+| `FacilityMonetaryCodeSpec` | write · set monetary config | `FacilityMonetoryConfig` | Validates and persists `discount_codes`, `discount_monetary_components`, `discount_configuration` in one payload. `de_serialize`d onto the get-or-created config row |
+| `FacilityInvoiceExpressionSpec` | write · set invoice expression | (plain `BaseModel`) | Single field `invoice_number_expression`; validated by dry-run before save |
+| `FacilityRetrieveSpec` | read · facility detail | `Facility` | Surfaces the facility's monetary config plus instance-level catalogs in `perform_extra_serialization` |
+| `MonetaryComponentDefinition` | nested (write) | — | Shape of each `discount_monetary_components[]` entry |
+| `DiscountConfiguration` | nested (write) | — | Shape of `discount_configuration` |
+| `Coding` | nested (write) | — | Shape of each `discount_codes[]` entry |
+
+### `FacilityMonetaryCodeSpec`
+
+`__model__ = FacilityMonetoryConfig`, `__exclude__ = []`. Write schema for the `set_monetary_config` action.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `discount_codes` | `list[Coding]` | Facility-defined codes |
+| `discount_monetary_components` | `list[MonetaryComponentDefinition]` | Selectable discount definitions |
+| `discount_configuration` | `DiscountConfiguration \| null` | Stacking rule |
+
+**Validation (model validators):**
+
+- **Count limits** — `discount_codes` and `discount_monetary_components` must each be **fewer than 100** (`DISCOUNT_CODE_COUNT_LIMIT` / `DISCOUNT_MONETARY_COMPONENT_COUNT_LIMIT = 100`); the check uses `>= 100`.
+- **No duplicate codes** — the `code` values in `discount_codes` must be unique.
+- **No redefining system codes** — a facility code may not reuse a `[code, system]` pair already present in instance `settings.DISCOUNT_CODES`.
+- **All component codes must be defined** — every `discount_monetary_components[i].code` (when present) must match a `[code, system]` pair from either `settings.DISCOUNT_CODES` (instance) or the facility's `discount_codes`.
+
+### `FacilityInvoiceExpressionSpec`
+
+Plain `BaseModel` (not an `EMRResource`). Write schema for the `set_invoice_expression` action.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `invoice_number_expression` | `str` | Validated via `evaluate_invoice_dummy_expression(v)`; any exception raises `"Invalid Expression"`. Empty value skips validation |
+
+The dummy context supplies `invoice_count=1234`, `current_year_yyyy=2025`, `current_year_yy=25`; at runtime `evaluate_invoice_identifier_default_expression(facility)` evaluates the stored expression against the live invoice count and current year.
+
+### `FacilityRetrieveSpec` (read path)
+
+`FacilityRetrieveSpec` (extends `FacilityReadSpec` + `FacilityPermissionsMixin`) injects the monetary config into the facility detail response in `perform_extra_serialization`, calling `FacilityMonetoryConfig.get_monetory_config(obj.id)` (get-or-create). Monetary-config-derived fields on the read payload:
+
+| Field | Source |
+| --- | --- |
+| `invoice_number_expression` | config row |
+| `discount_codes` | config row (`list[dict]`) |
+| `discount_monetary_components` | config row (`list[dict]`) |
+| `discount_configuration` | config row (`dict \| null`) |
+| `instance_discount_codes` | `settings.DISCOUNT_CODES` |
+| `instance_discount_monetary_components` | `settings.DISCOUNT_MONETARY_COMPONENT_DEFINITIONS` |
+| `instance_tax_codes` | `settings.TAX_CODES` |
+| `instance_tax_monetary_components` | `settings.TAX_MONETARY_COMPONENT_DEFINITIONS` |
+| `instance_informational_codes` | `settings.INFORMATIONAL_MONETARY_CODES` |
+| `flags` | `obj.get_facility_flags()` |
+| `patient_instance_identifier_configs` | `PatientIdentifierConfigCache.get_instance_config()` |
+| `patient_facility_identifier_configs` | `PatientIdentifierConfigCache.get_facility_config(obj.id)` |
+
+> Facility codes are layered on top of instance-level catalogs: the read payload returns both the facility's own `discount_*` fields and the `instance_*` settings-derived catalogs, while write-time validation forbids facilities from redefining instance codes.
+
+## API integration
+
+Both write operations are detail actions on the facility viewset and require update authorization on the facility.
+
+| Action | Method | Request spec | Behaviour |
+| --- | --- | --- | --- |
+| `set_monetary_config` | `POST` (detail) | `FacilityMonetaryCodeSpec` | `model_validate` with context `{is_update: True, object: }`, then `de_serialize` onto the get-or-created config, set `updated_by`, `save()`. Returns the facility retrieve payload |
+| `set_invoice_expression` | `POST` (detail) | `FacilityInvoiceExpressionSpec` | Sets `invoice_number_expression` on the get-or-created config, set `updated_by`, `save()`. Returns the facility retrieve payload |
+
+## Invoice number expression
+
+`invoice_number_expression` is a string template evaluated by `care.emr.utils.expression_evaluator.evaluate_expression`. Available context variables:
+
+| Variable | Source |
+| --- | --- |
+| `invoice_count` | count of `Invoice` rows for the facility |
+| `current_year_yyyy` | four-digit current year |
+| `current_year_yy` | two-digit current year (`year % 100`) |
+
+A `null`/empty expression evaluates to `""` (no generated number).
+
+## Methods & save behaviour
+
+`FacilityMonetoryConfig` caches derived data in the Django cache and rebuilds it on write.
+
+### Cache keys
+
+| Key | Built by |
+| --- | --- |
+| `facility:{facility_id}:monetory_component` | `get_monetory_component_cache_key(facility_id)` |
+| `facility:{facility_id}:discount_configuration` | `get_discount_configuration_cache_key(facility_id)` |
+
+### Class methods
+
+- `get_monetory_config(facility_id)` — returns the facility's config, **creating an empty one if none exists**. The get-or-create entry point used by both write actions and the retrieve serializer.
+- `get_component_key(component)` — derives a component's lookup key as `code.system + "/" + code.code`.
+- `calculate_monetory_components(components)` — folds a component list into a dict keyed by `get_component_key`.
+- `get_monetory_component(facility_id)` — returns the cached component dict, computing it from `discount_monetary_components` and caching it on a miss.
+- `get_discount_configuration(facility_id)` — returns the cached `discount_configuration` (defaulting to `{}`), caching it on a miss.
+
+### `save()` side effects
+
+`save()` deletes both cache keys for the facility **before** calling `super().save()`, so the next read of `get_monetory_component()` / `get_discount_configuration()` recomputes from the persisted row. Integrators updating monetary config should expect the cache to be invalidated and rebuilt lazily on the next access.
+
+## API integration notes
+
+- One config exists per facility; use `get_monetory_config(facility_id)` to read or auto-create it rather than querying the table directly. The `set_monetary_config` / `set_invoice_expression` actions and `FacilityRetrieveSpec` all rely on this get-or-create.
+- `discount_codes`, `discount_monetary_components`, and `discount_configuration` are opaque `JSONField`s at the DB layer; their real shape and validation come from `FacilityMonetaryCodeSpec` and the nested `Coding` / `MonetaryComponentDefinition` / `DiscountConfiguration` specs.
+- Monetary components are keyed by a FHIR-style `code.system`/`code.code` pair (`get_component_key`); consumers should match on that derived key.
+- `get_monetory_component()` / `get_discount_configuration()` results are cache-backed and platform-maintained; do not write to the cache keys directly — write the model and let `save()` invalidate.
+- `invoice_number_expression` drives invoice number generation and ties this config to the billing domain.
+
+## Related
+
+- Reference: [Facility](./facility.mdx)
+- Reference: [Invoice](../billing/invoice.mdx)
+- Reference: [Charge Item](../billing/charge-item.mdx)
+- Reference: [Charge Item Definition](../billing/charge-item-definition.mdx)
+- Reference: [Payment Reconciliation](../billing/payment-reconciliation.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [facility_config.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/facility_config.py)
+- Source: [facility/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py)
+- Source: [common/monetary_component.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
diff --git a/versioned_docs/version-3.1/references/facility/facility-flag.mdx b/versioned_docs/version-3.1/references/facility/facility-flag.mdx
new file mode 100644
index 0000000..bacc630
--- /dev/null
+++ b/versioned_docs/version-3.1/references/facility/facility-flag.mdx
@@ -0,0 +1,161 @@
+---
+sidebar_position: 3
+---
+
+# Facility Flag
+
+Technical reference for the `FacilityFlag` module in Care EMR.
+
+`FacilityFlag` is a server-side feature-gating primitive: it associates a registered flag name with one `Facility` so behaviour can be toggled per facility. It is **not** a FHIR resource and has **no dedicated Pydantic resource spec** — there is no `FacilityFlag` API endpoint, serializer, or `...CreateSpec`/`...ReadSpec`. The sections below are documented entirely from the Django storage model. Flags reach API clients only as a derived, read-only array on the Facility resource (see [Resource specs](#resource-specs-api-schema)).
+
+**Source:**
+- Model: [`care/facility/models/facility_flag.py`](https://github.com/ohcnetwork/care/blob/develop/care/facility/models/facility_flag.py)
+- Base/flag machinery: [`care/utils/models/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/utils/models/base.py)
+- Flag registry & types: [`care/utils/registries/feature_flag.py`](https://github.com/ohcnetwork/care/blob/develop/care/utils/registries/feature_flag.py)
+- Surfaced via Facility spec: [`care/emr/resources/facility/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `FacilityFlag` | Associates a registered feature flag with a single `Facility`, gating behaviour per facility |
+
+`FacilityFlag` extends `BaseFlag`, which extends `BaseModel` — the lightweight Care EMR base providing `external_id` (UUID), `created_date`/`modified_date` audit timestamps, and soft-delete via `deleted`. Unlike `EMRBaseModel` (see [Base model](../foundation/base-model.mdx)), `BaseModel` does **not** add `created_by`/`updated_by`, `history`, or `meta` JSON. `BaseFlag` adds the `flag` value and the per-entity flag machinery (cache invalidation, validation against the flag registry).
+
+## `FacilityFlag` fields
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `flag` | `CharField(max_length=1024)` | yes | — | Inherited from `BaseFlag`. The registered flag name; validated against the `FACILITY` flag registry on every save (see [validation](#save-inherited-from-baseflag)). Value space is **not** an enum — it is whatever has been registered at runtime via `FlagRegistry`, so it is open-ended but constrained to registered strings |
+| `facility` | `FK → facility.Facility` | yes | — | `on_delete=CASCADE`, `null=False`, `blank=False`. The facility the flag applies to. Drives the `entity` / `entity_id` properties via `entity_field_name="facility"` |
+| `external_id` | `UUIDField` | auto | `uuid4` | Inherited from `BaseModel`; `unique=True`, `db_index=True` |
+| `created_date` | `DateTimeField` | auto | `auto_now_add` | Inherited; `null=True`, `blank=True`, `db_index=True` |
+| `modified_date` | `DateTimeField` | auto | `auto_now` | Inherited; `null=True`, `blank=True`, `db_index=True` |
+| `deleted` | `BooleanField` | no | `False` | Inherited; soft-delete flag, `db_index=True`. The default manager (`BaseManager`) filters `deleted=False` |
+
+There are **no `JSONField`s** on this model, so there is no opaque JSON shape to document. The only constrained value is `flag`, whose allowed values come from the runtime registry rather than a static enum.
+
+### Flag type values
+
+`flag_type` is fixed to `FlagType.FACILITY`. The full `FlagType` enum (from [`feature_flag.py`](https://github.com/ohcnetwork/care/blob/develop/care/utils/registries/feature_flag.py)):
+
+| `FlagType` member | Value | Used by |
+| --- | --- | --- |
+| `USER` | `"USER"` | `UserFlag` ([User](../access/user.mdx)) |
+| `FACILITY` | `"FACILITY"` | `FacilityFlag` (this model) |
+
+`flag` values themselves are not enumerated in source — they are registered dynamically (`FlagRegistry.register(FlagType.FACILITY, "")`). A name not present in the `FACILITY` registry raises `FlagNotFoundError` on save.
+
+### Class-level configuration
+
+These class attributes wire `FacilityFlag` into the generic `BaseFlag` caching and validation logic. They are not database fields.
+
+| Attribute | Value | Notes |
+| --- | --- | --- |
+| `cache_key_template` | `"facility_flag_cache:{entity_id}:{flag_name}"` | Per-flag existence cache key |
+| `all_flags_cache_key_template` | `"facility_all_flags_cache:{entity_id}"` | Cache key for the full flag list of a facility |
+| `flag_type` | `FlagType.FACILITY` | Registry namespace used for flag-name validation |
+| `entity_field_name` | `"facility"` | Tells `BaseFlag` which FK is the owning entity; drives `entity` / `entity_id` properties |
+
+Module-level constants mirror these templates: `FACILITY_FLAG_CACHE_KEY` (`"facility_flag_cache:{facility_id}:{flag_name}"`), `FACILITY_ALL_FLAGS_CACHE_KEY` (`"facility_all_flags_cache:{facility_id}"`), and `FACILITY_FLAG_CACHE_TTL` (`60 * 60 * 24`, 1 day).
+
+### Constraints
+
+```text
+UniqueConstraint(
+ fields=["facility", "flag"],
+ condition=Q(deleted=False),
+ name="unique_facility_flag",
+)
+```
+
+A facility may hold a given flag at most once among non-deleted rows. Soft-deleted rows are excluded, so a flag can be removed and re-added. `Meta.verbose_name = "Facility Flag"`.
+
+## Methods & save behaviour
+
+### `save()` (inherited from `BaseFlag`)
+
+On every save:
+
+1. `validate_flag(self.flag)` runs `FlagRegistry.validate_flag_name(FlagType.FACILITY, flag)`. This first validates the flag type, then checks the name is registered. An unknown type or unregistered name raises `FlagNotFoundError` (a subclass of Django `ValidationError`) and the row is **not** written.
+2. The per-flag cache key (`facility_flag_cache:{facility_id}:{flag}`) is deleted.
+3. The all-flags cache key (`facility_all_flags_cache:{facility_id}`) is deleted.
+4. The row is persisted via `super().save()`.
+
+Deletes use the `BaseModel` soft-delete (`delete()` sets `deleted = True` and saves `update_fields=["deleted"]`); cache entries are **not** purged on delete, so they expire by TTL or are rebuilt on the next miss.
+
+### Lookups
+
+| Method | Returns | Notes |
+| --- | --- | --- |
+| `check_facility_has_flag(facility_id, flag_name)` | `bool` | Delegates to `BaseFlag.check_entity_has_flag`: validates the flag name, then `cache.get_or_set` on the per-flag key with an `.exists()` query; cached for `FLAGS_CACHE_TTL` (1 day) |
+| `get_all_flags(facility_id)` | `tuple[FlagName]` | Delegates to `BaseFlag.get_all_flags`: `cache.get_or_set` on the all-flags key, materializing `values_list("flag", flat=True)` for the facility; cached for 1 day |
+
+Both wrap the generic `BaseFlag` methods using `entity_field_name="facility"`. `Facility.get_facility_flags()` is a thin wrapper that calls `FacilityFlag.get_all_flags(self.id)`.
+
+### Properties (inherited)
+
+- `entity` → the related `Facility` instance (`getattr(self, "facility")`)
+- `entity_id` → `facility_id` (`getattr(self, "facility_id")`)
+
+## Resource specs (API schema)
+
+**`FacilityFlag` has no Pydantic resource spec and no REST endpoint of its own.** There is no `FacilityFlagCreateSpec` / `FacilityFlagUpdateSpec` / `FacilityFlagListSpec` / `FacilityFlagRetrieveSpec`, no serializer, and no viewset — confirmed by absence in `care/emr/resources/` and `care/facility/api/`. Flags are administered through Django admin or management/registration code, not the public API.
+
+The one place flags surface to API clients is the **Facility** resource, which exposes them as a derived read-only field:
+
+| Spec | Field | Role | Behaviour |
+| --- | --- | --- | --- |
+| `FacilityRetrieveSpec` ([`facility/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py)) | `flags: list[str]` | read · detail | Populated in `perform_extra_serialization` via `mapping["flags"] = obj.get_facility_flags()`. Server-maintained, read-only; clients cannot set or mutate flags through the Facility write specs |
+
+Notes on this binding:
+
+- The `flags` array is the materialized list of registered flag names attached to the facility (from `FacilityFlag.get_all_flags`, cache-backed, 1-day TTL).
+- The parallel pattern exists for users: the User resource exposes `flags` via `obj.get_all_flags()` backed by `UserFlag` (see [User](../access/user.mdx)).
+- There is no value set bound to `flag`; allowed values are the runtime `FACILITY` registry entries, not a coded concept.
+
+## Related models
+
+`FacilityFlag` defines no secondary models. Its behaviour is shared via two utilities in `care/utils`.
+
+### `BaseFlag` (abstract)
+
+```text
+flag → CharField(max_length=1024)
+(abstract base — no table of its own)
+class attrs: cache_key_template, all_flags_cache_key_template, flag_type, entity_field_name
+```
+
+Provides save-time validation, cache invalidation, and the `check_entity_has_flag` / `get_all_flags` lookups. Subclasses set `flag_type`, `entity_field_name`, and the two cache-key templates. See [`care/utils/models/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/utils/models/base.py).
+
+### `FlagRegistry`
+
+An in-process class-level registry (`_flags: dict[FlagType, dict[FlagName, bool]]`) of valid flag names per `FlagType`. Key methods:
+
+| Method | Notes |
+| --- | --- |
+| `register(flag_type, flag_name)` | Adds a name to a type's registry (creating the type bucket if needed) |
+| `register_wrapper(flag_type, flag_name)` | Class decorator form of `register` |
+| `unregister(flag_type, flag_name)` | Removes a name; logs a warning if absent |
+| `validate_flag_type(flag_type)` | Raises `FlagNotFoundError` ("Invalid Flag Type") if the type bucket is missing |
+| `validate_flag_name(flag_type, flag_name)` | Validates type, then raises `FlagNotFoundError` ("Flag not registered") if the name is absent |
+| `get_all_flags(flag_type)` | `list[FlagName]` of registered names |
+| `get_all_flags_as_choices(flag_type)` | `(value, value)` tuples for Django choices |
+
+Flags must be registered before a `FacilityFlag` referencing them can be saved. See [`care/utils/registries/feature_flag.py`](https://github.com/ohcnetwork/care/blob/develop/care/utils/registries/feature_flag.py).
+
+## API integration notes
+
+- `FacilityFlag` is a server-side gating primitive, not a FHIR resource and not a writable API resource. There is no CRUD endpoint; flags are managed via Django admin or registration/management code.
+- Clients observe a facility's flags only through the read-only `flags: list[str]` field on `FacilityRetrieveSpec` (see [Resource specs](#resource-specs-api-schema)).
+- Read access in application code should go through `check_facility_has_flag` / `get_all_flags` (or `Facility.get_facility_flags()`), which are cache-backed — do not query the table directly for hot-path checks.
+- Flag names are constrained to entries registered in the `FACILITY` `FlagRegistry`; clients cannot introduce arbitrary flag strings — an unregistered name fails validation on save with `FlagNotFoundError`.
+- The `{facility, flag}` uniqueness is enforced only over non-deleted rows, so soft-deleted history is preserved and a flag can be removed then re-added.
+
+## Related
+
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Facility Config](../facility/facility-config.mdx)
+- Reference: [User](../access/user.mdx) — the `USER` flag counterpart of the same `BaseFlag` machinery
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [facility_flag.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/facility/models/facility_flag.py)
diff --git a/versioned_docs/version-3.1/references/facility/facility.mdx b/versioned_docs/version-3.1/references/facility/facility.mdx
new file mode 100644
index 0000000..2649c7e
--- /dev/null
+++ b/versioned_docs/version-3.1/references/facility/facility.mdx
@@ -0,0 +1,283 @@
+---
+sidebar_position: 1
+---
+
+# Facility
+
+Technical reference for the `Facility` module in Care EMR.
+
+A `Facility` is a physical or virtual care site — hospital, clinic, lab, telemedicine endpoint — and the root of a deployment's administrative hierarchy. The Django **model** is the storage layer: several columns (`features`, `print_templates`) are opaque on the model, and the API contract (enums, validation, JSON-field shapes, read/write schemas) lives in the Pydantic **resource specs**.
+
+**Source:**
+- Model: [`care/facility/models/facility.py`](https://github.com/ohcnetwork/care/blob/develop/care/facility/models/facility.py)
+- Resource specs: [`care/emr/resources/facility/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py)
+- Base resource: [`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+- Common types: [`coding.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py), [`monetary_component.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Facility` | A care site and the root of a deployment's administrative org hierarchy |
+| `FacilityMonetoryConfig` | Per-facility billing config (one-to-one): discount codes, discount monetary components, discount configuration, invoice-number expression. Backs the discount/invoice fields of `FacilityRetrieveSpec`. See [Facility config](facility-config.mdx) |
+
+`Facility` extends [`BaseModel`](../foundation/base-model.mdx) (not `EMRBaseModel`), so it carries `external_id`, `created_date`/`modified_date`, and soft-delete via `deleted`, but **not** the `EMRBaseModel` audit/history/meta fields. It defines its own `created_by` FK directly and has no `updated_by`, `history`, or `meta` columns. `FacilityMonetoryConfig` extends `EMRBaseModel`.
+
+The model file also defines the choice sets and enums used by `Facility` — see [Choices & enums](#choices--enums).
+
+## `Facility` fields
+
+The **Type** column is the Django storage type; **Spec** notes the API representation, validation, and bound enum from the Pydantic specs where they differ.
+
+### Identity & classification
+
+| Field | Type | Spec / Notes |
+| --- | --- | --- |
+| `name` | `CharField(1000)` | Required (`blank=False, null=False`). Spec validates case-insensitive uniqueness across all facilities (`Lower(Trim(name))`); on update the current row is excluded. |
+| `description` | `TextField` | `blank=True, null=False`; defaults to empty string. Required (`str`) in `FacilityBaseSpec`. |
+| `facility_type` | `IntegerField` | Required; choices from `FACILITY_TYPES` (see below). **Wire format is the label string, not the integer:** create validates the label against `REVERSE_REVERSE_FACILITY_TYPES` then maps to the int; read maps the int back to the label via `REVERSE_FACILITY_TYPES`. |
+| `features` | `ArrayField[SmallIntegerField]` | Nullable; each element a `FacilityFeature` choice. Spec type `list[int]`. |
+| `is_active` | `BooleanField` | `default=True`. Not exposed by the specs (server-managed). |
+| `verified` | `BooleanField` | `default=False`. Not exposed by the specs (server-managed). |
+| `is_public` | `BooleanField` | `default=False`; controls public visibility. Exposed as `bool` on `FacilityBaseSpec`. |
+
+### Location & contact
+
+| Field | Type | Spec / Notes |
+| --- | --- | --- |
+| `address` | `TextField` | Free-text address. Required (`str`). |
+| `pincode` | `IntegerField` | `default=None, null=True` on model; required `int` in `FacilityBaseSpec`. |
+| `longitude` | `DecimalField(22, 16)` | Nullable. Spec type `Longitude \| None` (`pydantic_extra_types`, range −180…180). |
+| `latitude` | `DecimalField(22, 16)` | Nullable. Spec type `Latitude \| None` (`pydantic_extra_types`, range −90…90). |
+| `phone_number` | `CharField(14)` | `blank=True`; validated with `mobile_or_landline_number_validator`. Spec type `str`. |
+| `middleware_address` | `CharField(200)` | Nullable, `default=None`. Hostname of an external device/middleware integration. Spec type `str \| None`. |
+
+### Organization links & caches
+
+| Field | Type | Spec / Notes |
+| --- | --- | --- |
+| `geo_organization` | `FK → emr.Organization` | `on_delete=SET_NULL`, nullable. Geographic/administrative org placement. **Write** (`FacilityCreateSpec`): a required `UUID4` (the org's `external_id`); deserialization resolves it to an `Organization` with `org_type="govt"`. **Read**: a nested `OrganizationReadSpec` dict (empty `{}` when unset). |
+| `geo_organization_cache` | `ArrayField[int]` | `default=list`. Denormalized cache of `geo_organization`'s `parent_cache` chain plus its own id; rebuilt by `sync_cache()`. Not exposed by specs. |
+| `default_internal_organization` | `FK → emr.FacilityOrganization` | `on_delete=SET_NULL`, nullable, `related_name="default_facilities"`. The auto-created root `Administration` org. Not exposed by specs. |
+| `internal_organization_cache` | `ArrayField[int]` | `default=list`. Denormalized cache of all `FacilityOrganization` ids (with parent chains) under this facility; rebuilt by `sync_cache()`. Not exposed by specs. |
+
+Both cache fields are **platform-maintained denormalizations** rebuilt on every `save()` for fast org-scoped filtering without deep joins — do not write them directly. See [Methods & save behaviour](#methods--save-behaviour).
+
+### Media & presentation
+
+| Field | Type | Spec / Notes |
+| --- | --- | --- |
+| `cover_image_url` | `CharField(500)` | Nullable, `default=None`. Stores an **object key**, not a URL. Read specs expose both the raw `cover_image_url` and a resolved `read_cover_image_url` (full URL via `read_cover_image_url()`). |
+| `print_templates` | `JSONField` | `default=list`. Opaque on the model; the real shape is `list[PrintTemplate]` (see [PrintTemplate](#printtemplate-json-shape)). Accepted typed on create; returned as `list[dict]` on retrieve. |
+
+### Integration & audit
+
+| Field | Type | Spec / Notes |
+| --- | --- | --- |
+| `created_by` | `FK → users.User` | `on_delete=SET_NULL`, nullable. Set to the facility administrator when the root org is provisioned. Read specs expose it as a nested `UserSpec` dict (cache-backed), `None` when unset. |
+
+## Choices & enums
+
+The model module defines these choice sets consumed by `Facility` and related code.
+
+### `FacilityFeature` (`IntegerChoices`)
+
+| Value | Name | Label |
+| --- | --- | --- |
+| 1 | `CT_SCAN_FACILITY` | CT Scan Facility |
+| 2 | `MATERNITY_CARE` | Maternity Care |
+| 3 | `X_RAY_FACILITY` | X-Ray Facility |
+| 4 | `NEONATAL_CARE` | Neonatal Care |
+| 5 | `OPERATION_THEATER` | Operation Theater |
+| 6 | `BLOOD_BANK` | Blood Bank |
+
+### `HubRelationship` (`IntegerChoices`)
+
+| Value | Name | Label |
+| --- | --- | --- |
+| 1 | `REGULAR_HUB` | Regular Hub |
+| 2 | `TELE_ICU_HUB` | Tele ICU Hub |
+
+### `FACILITY_TYPES`
+
+List of `(int code, label)`. Codes are sparse and namespaced (8xx govt hospitals/health centres, 9xx labs/cooperatives, 10xx–16xx COVID-era centres, 3xxx/4xxx NGO/CBO). Commented-out legacy codes (`8`, `801`, `820`, `831`, `850`, `950`, `1000`) are retained in source as historical mapping notes and are **not** valid. The API binds on the **label**, not the code.
+
+| Code | Label | Code | Label |
+| --- | --- | --- | --- |
+| 1 | Educational Inst | 870 | Govt Medical College Hospitals |
+| 2 | Private Hospital | 900 | Co-operative hospitals |
+| 3 | Other | 910 | Autonomous healthcare facility |
+| 4 | Hostel | 1010 | COVID-19 Domiciliary Care Center |
+| 5 | Hotel | 1100 | First Line Treatment Centre |
+| 6 | Lodge | 1200 | Second Line Treatment Center |
+| 7 | TeleMedicine | 1300 | Shifting Centre |
+| 9 | Govt Labs | 1400 | Covid Management Center |
+| 10 | Private Labs | 1500 | Request Approving Center |
+| 800 | Primary Health Centres | 1510 | Request Fulfilment Center |
+| 802 | Family Health Centres | 1600 | District War Room |
+| 803 | Community Health Centres | 3000 | Clinical Non Governmental Organization |
+| 830 | Taluk Hospitals | 3001 | Non Clinical Non Governmental Organization |
+| 840 | Women and Child Health Centres | 4000 | Community Based Organization |
+| 860 | District Hospitals | | |
+
+`REVERSE_FACILITY_TYPES` (code → label) and `REVERSE_REVERSE_FACILITY_TYPES` (label → code) are derived from `FACILITY_TYPES` via `reverse_choices`. The create spec validates against the label set and stores the code; read specs map back to the label.
+
+### `DOCTOR_TYPES`
+
+List of `(int, label)` — 64 medical specialties (`1` General Medicine, `2` Pulmonology, `8` Cardiologist, `48` Nurse, `64` Critical Care Physician, etc.). Defined in this module but not referenced by the `Facility` model or facility specs.
+
+### `MonetaryComponentType` (str enum, from `monetary_component.py`)
+
+Used by the discount monetary components in `FacilityMonetaryCodeSpec` / `FacilityRetrieveSpec`.
+
+| Value |
+| --- |
+| `base` |
+| `surcharge` |
+| `discount` |
+| `tax` |
+| `informational` |
+
+### `DiscountApplicability` (str enum, from `monetary_component.py`)
+
+| Value |
+| --- |
+| `total_asc` |
+| `total_desc` |
+
+## Nested JSON shapes
+
+These Pydantic models define the real structure of the model's JSON/array fields and of the discount config.
+
+### `PrintTemplate` JSON shape
+
+Element type of the `print_templates` field.
+
+```text
+PrintTemplate {
+ slug: str (required)
+ page: PageConfig | null
+ print_setup: PrintSetupConfig | null
+ branding: BrandingConfig | null
+ watermark: WatermarkConfig | null
+}
+PageConfig { size: "A4"|"A5"|"Letter"|"Legal" | null,
+ orientation: "portrait"|"landscape" | null,
+ margin: PageMargin | null }
+PageMargin { top: float≥0, bottom: float≥0, left: float≥0, right: float≥0 } # all required
+PrintSetupConfig { auto_print: bool | null }
+BrandingConfig { logo: LogoConfig | null, header_image: HeaderImageConfig | null,
+ footer_image: FooterImageConfig | null }
+LogoConfig { url: str (required), width: float | null, height: float | null,
+ alignment: "left"|"center"|"right" (required) }
+HeaderImageConfig { url: str (required), height: float | null }
+FooterImageConfig { url: str | null, height: float | null }
+WatermarkConfig { enabled: bool | null, text: str | null,
+ opacity: float | null (0…1), rotation: float | null }
+```
+
+### Discount config shapes (`FacilityMonetaryCodeSpec` / `FacilityMonetoryConfig`)
+
+```text
+discount_codes: list[Coding] # see Coding below
+discount_monetary_components: list[MonetaryComponentDefinition]
+discount_configuration: DiscountConfiguration | null
+
+Coding { system: str|null, version: str|null, code: str (required), display: str|null } # extra="forbid"
+
+MonetaryComponentDefinition (extends MonetaryComponent) {
+ title: str (required)
+ monetary_component_type: MonetaryComponentType # base not allowed in a definition
+ code: Coding | null
+ factor: Decimal | null (max_digits=20, decimal_places=6)
+ amount: Decimal | null (max_digits=20, decimal_places=6)
+ tax_included_amount: Decimal | null (base component only)
+ global_component: bool = false
+ conditions: list[EvaluatorConditionSpec] = []
+}
+EvaluatorConditionSpec { metric: str, operation: str, value: dict|str } # metric validated against EvaluatorMetricsRegistry
+DiscountConfiguration { max_applicable: int≥0 (required), applicability_order: DiscountApplicability (required) }
+```
+
+`PeriodSpec` (`base.py`) — the shared period shape used across EMR resources (not used directly by `Facility`, included for reference): `{ start: datetime|null, end: datetime|null }`, both must be **timezone-aware**, and `start ≤ end`.
+
+## Resource specs (API schema)
+
+All specs subclass `EMRResource` (`base.py`), which provides `serialize` (DB object → Pydantic), `de_serialize` (Pydantic → DB object), and the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__exclude__` lists fields skipped during (de)serialization; `__store_metadata__` (default `False`) controls writing unknown fields into the model's `meta`.
+
+The class hierarchy is `FacilityBareMinimumSpec → FacilityBaseSpec → {FacilityCreateSpec, FacilityReadSpec → FacilityRetrieveSpec, FacilityMinimalReadSpec}`. There is **no separate `UpdateSpec`**: `FacilityCreateSpec` is reused for both create and update (its validators read `is_update`/`object` from the serializer context).
+
+| Spec class | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `FacilityBareMinimumSpec` | shared base (`@cacheable`, base manager) | `id: UUID4`, `name`. `__exclude__=["geo_organization"]`. Cacheable, fetchable via `model_from_cache`. |
+| `FacilityBaseSpec` | shared base | Adds `description`, `longitude`, `latitude`, `pincode`, `address`, `phone_number`, `middleware_address`, `facility_type`, `is_public`. |
+| `FacilityCreateSpec` | write · create & update | Adds `geo_organization: UUID4`, `features: list[int]`, `print_templates: list[PrintTemplate] = []`. Validates `facility_type` label and case-insensitive `name` uniqueness. `perform_extra_deserialization` resolves `geo_organization` (must be `org_type="govt"`) and maps the `facility_type` label → int. |
+| `FacilityReadSpec` | read · list | `FacilityBaseSpec` + `features`, `cover_image_url`, `read_cover_image_url`, `geo_organization: dict`, `created_by: dict\|null`. `perform_extra_serialization` sets `id=external_id`, resolves the cover image URL, maps `facility_type` int → label, serializes `geo_organization` via `OrganizationReadSpec`, and resolves `created_by` via cached `UserSpec`. |
+| `FacilityRetrieveSpec` | read · detail | Extends `FacilityReadSpec` + `FacilityPermissionsMixin`. Adds `flags` (from `get_facility_flags()`), facility + instance discount/tax/informational code lists, patient identifier configs, `invoice_number_expression`, and `print_templates: list[dict]`. Permissions mixin adds `permissions`, `root_org_permissions`, `child_org_permissions`. Discount/invoice fields come from `FacilityMonetoryConfig.get_monetory_config()`; instance-level lists come from Django `settings`. |
+| `FacilityMinimalReadSpec` | read · minimal | `FacilityBaseSpec` + `features`, `cover_image_url`, `read_cover_image_url`, `geo_organization: dict`. Same serialization as `FacilityReadSpec` minus `created_by`. |
+| `FacilityMonetaryCodeSpec` | write · billing config | Bound to `FacilityMonetoryConfig`. Validates ≤ 100 discount codes and ≤ 100 monetary components, forbids duplicate / system-redefining codes, and requires every monetary-component code to be a defined facility or system code. |
+| `FacilityInvoiceExpressionSpec` | write · invoice config | `invoice_number_expression: str`, validated by `evaluate_invoice_dummy_expression` (raises "Invalid Expression"). |
+
+### Validation & server-side behaviour
+
+- **`name`** — must be unique case-insensitively (`Lower(Trim(name))`); on update the current object (from context) is excluded from the check.
+- **`facility_type`** — write accepts the **label**; rejected with the sorted valid-label list if unknown. Stored as the integer code; read maps back to the label.
+- **`geo_organization`** — write is a `UUID4` resolved to an `Organization` filtered by `org_type="govt"`; resolves to `None` if no match. Read embeds the full `OrganizationReadSpec`.
+- **`features`** — `list[int]`; each int should correspond to a `FacilityFeature` value.
+- **Bound value sets / coded fields** — discount/tax/informational components use `Coding` (`extra="forbid"`); `MonetaryComponentDefinition` enforces no `base` type, single base rules inherited from `MonetaryComponent`, and amount/factor mutual exclusion.
+- **`cover_image_url`** — stores an object key; `read_cover_image_url` is the resolved URL only on read specs.
+- Permissions on `FacilityRetrieveSpec` are computed per requesting user from facility root and sub-org roles (`FacilityAccess`); `can_update_facility` is excluded from `child_org_permissions`.
+
+## Related models
+
+`Facility` is the anchor for the administrative hierarchy; its relationships are resolved through models in adjacent modules:
+
+```text
+geo_organization → FK emr.Organization (SET_NULL) # write: UUID4 of a govt org
+default_internal_organization → FK emr.FacilityOrganization (SET_NULL) # auto-provisioned root org
+created_by → FK users.User (SET_NULL)
+FacilityMonetoryConfig → OneToOne facility.Facility (CASCADE) # billing/discount config
+```
+
+On creation, `save()` provisions a root [`FacilityOrganization`](organization.mdx) named `Administration` (`org_type="root"`, `system_generated=True`) and a `FacilityOrganizationUser` linking `created_by` to it with the `FACILITY_ADMIN_ROLE`. Feature flags are stored in [`FacilityFlag`](facility-flag.mdx) and surfaced via `get_facility_flags()`.
+
+## Methods & save behaviour
+
+`Facility` overrides `save()` and adds several helpers.
+
+| Member | Behaviour |
+| --- | --- |
+| `save()` | On **create** only: persists the row, then creates the root `Administration` `FacilityOrganization`, sets it as `default_internal_organization` (extra write), and creates a `FacilityOrganizationUser` granting `created_by` the `FACILITY_ADMIN_ROLE`. On **every** save: calls `sync_cache()`. |
+| `sync_cache()` | Rebuilds `geo_organization_cache` (parent chain of `geo_organization` + its id) and `internal_organization_cache` (all `FacilityOrganization` parent chains + ids, de-duplicated), then persists only those two fields via `update_fields`. |
+| `read_cover_image_url()` | Resolves `cover_image_url` to a full URL via `settings.FACILITY_CDN`, else the S3 external endpoint/bucket; returns `None` when unset. |
+| `get_facility_flags()` | Delegates to `FacilityFlag.get_all_flags(self.id)` (cache-backed). |
+| `__str__()` | Returns `name`. |
+
+Integrators should expect **multiple write passes** when creating a facility through the ORM: the initial insert, the `default_internal_organization` update, and the `sync_cache()` write of the two cache columns.
+
+Deletes are soft (inherited `BaseModel.delete()` sets `deleted=True`); the row and its child organizations are retained.
+
+## API integration notes
+
+- `external_id` (UUID) is the public identifier exposed as `id` by every read spec — the integer `pk` is internal.
+- `facility_type` crosses the wire as the **label string** (e.g. `"Private Hospital"`), validated against `REVERSE_REVERSE_FACILITY_TYPES`; the integer code is storage-only.
+- `features` are integer `FacilityFeature` codes; map to labels client-side.
+- `geo_organization` is **written** as the org's UUID (must be a `govt` org) and **read** as an embedded `OrganizationReadSpec` object.
+- `print_templates` follows the typed [`PrintTemplate`](#printtemplate-json-shape) shape on write and is returned as `list[dict]` on retrieve.
+- `geo_organization_cache` / `internal_organization_cache` are platform-maintained — never set them from clients; they are rebuilt on every save.
+- Creating a facility automatically provisions its root `Administration` organization and an admin membership for `created_by`; do not create these manually.
+- `cover_image_url` stores an object key; clients should render `read_cover_image_url`.
+- Discount/tax/invoice billing config is managed through `FacilityMonetaryCodeSpec` / `FacilityInvoiceExpressionSpec` and surfaced on `FacilityRetrieveSpec`; it is backed by [`FacilityMonetoryConfig`](facility-config.mdx).
+- `FacilityRetrieveSpec` also returns the requesting user's permissions and feature flags; flags are read via `get_facility_flags()` rather than queried row-by-row.
+
+## Related
+
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Reference: [Facility config](facility-config.mdx)
+- Reference: [Facility flag](facility-flag.mdx)
+- Reference: [Organization](organization.mdx)
+- Reference: [Location](location.mdx)
+- Reference: [Healthcare service](healthcare-service.mdx)
+- Reference: [Device](device.mdx)
+- Source: [facility.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/facility/models/facility.py)
+- Source: [facility/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/facility/spec.py)
diff --git a/versioned_docs/version-3.1/references/facility/healthcare-service.mdx b/versioned_docs/version-3.1/references/facility/healthcare-service.mdx
new file mode 100644
index 0000000..565854a
--- /dev/null
+++ b/versioned_docs/version-3.1/references/facility/healthcare-service.mdx
@@ -0,0 +1,171 @@
+---
+sidebar_position: 6
+---
+
+# Healthcare Service
+
+Technical reference for the `HealthcareService` module in Care EMR.
+
+A `HealthcareService` describes a service offered at a facility (e.g. cardiology, lab, radiology, pharmacy), scoped to one or more locations and a managing facility organization. The Django model is the **storage** layer — `service_type` and `styling_metadata` are opaque `JSONField`s whose real structure lives in the Pydantic **resource specs**, which also define the bound value set, the enum for `internal_type`, and the read/write API schemas.
+
+**Source:**
+
+- Model: [`care/emr/models/healthcare_service.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/healthcare_service.py)
+- Spec: [`care/emr/resources/healthcare_service/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/healthcare_service/spec.py)
+- Value set: [`care/emr/resources/healthcare_service/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/healthcare_service/valueset.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `HealthcareService` | A bookable/offerable service provided at a facility (e.g. cardiology, lab, radiology), scoped to locations and a managing organization |
+
+`HealthcareService` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base giving `external_id`, audit fields, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `HealthcareService` fields
+
+### Identity & classification
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(1024)` | Display name of the service. Required (no default) |
+| `service_type` | `JSONField` → `Coding` | Defaults to `{}` (`dict`). Stores a single FHIR-style `Coding` object, not a string. In the API this field is a `ValueSetBoundCoding` bound to the **Healthcare Service Type Code** value set (see below). Optional (`None`) |
+| `internal_type` | `CharField(255)` | Nullable, default `None`. In the API constrained to the `HealthcareServiceInternalType` enum (`pharmacy` / `lab` / `scheduling` / `store`). Optional |
+| `extra_details` | `TextField` | Free-text additional details. Not nullable and has no DB default, so a value (empty string allowed) must be supplied. Required in write specs |
+
+### Scope & placement
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` | `PROTECT`; nullable (`default=None`, `blank=True`). The facility offering the service. Excluded from all specs (`__exclude__ = ["facility"]`) — set server-side from the URL context, never via the request body |
+| `locations` | `ArrayField[int]` | Defaults to `[]`; list of `FacilityLocation` **primary-key integers** (not FK rows) where the service is provided. Write specs accept location `external_id`s (UUIDs); retrieve specs return serialized location objects |
+| `managing_organization` | `FK → emr.FacilityOrganization` | `PROTECT`; nullable. The facility-scoped org (department/team) responsible for the service. Write specs accept its `external_id` (UUID); resolved server-side |
+
+### Presentation
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `styling_metadata` | `JSONField` → `dict` | Nullable in DB; defaults to `{}` in the specs. Open JSON bag of UI presentation hints (e.g. colors, icons). No backend semantics |
+
+## Coded field shapes (JSON fields)
+
+### `service_type` → `Coding`
+
+`service_type` stores a single `Coding` object. In the API it is a `ValueSetBoundCoding` whose `code` is validated against the **Healthcare Service Type Code** value set on de-serialization. Optional (`None`).
+
+```text
+Coding {
+ system: str | None # e.g. "http://terminology.hl7.org/CodeSystem/service-type"
+ version: str | None
+ code: str # required; validated against the bound value set
+ display: str | None
+}
+# extra="forbid" — unknown keys rejected
+```
+
+### Bound value set — Healthcare Service Type Code
+
+| Property | Value |
+| --- | --- |
+| Name | `Healthcare Service Type Code` |
+| Slug | `healthcare-service-type-code` |
+| Status | `active` |
+| Compose / include system | `http://terminology.hl7.org/CodeSystem/service-type` |
+| System version | `2.0.0` |
+
+Registered via `register_valueset(...)` and `register_as_system()`. The `service_type.code` submitted on create/update must resolve within this value set.
+
+## Enums
+
+### `HealthcareServiceInternalType` values
+
+Constrains `internal_type` in the specs (`str` enum; the model column is a plain `CharField` so legacy/other values may exist in storage).
+
+| Value |
+| --- |
+| `pharmacy` |
+| `lab` |
+| `scheduling` |
+| `store` |
+
+## Related models
+
+### `facility.Facility`
+
+```text
+facility → FK Facility (PROTECT, nullable)
+```
+
+The facility at which the service is offered. `PROTECT` prevents deleting a facility that still has healthcare services. Excluded from specs and set from request context. See [Facility](./facility.mdx).
+
+### `emr.FacilityOrganization`
+
+```text
+managing_organization → FK FacilityOrganization (PROTECT, nullable)
+```
+
+The facility-scoped organization (department/team) that owns the service. Write specs take its `external_id` (UUID); the retrieve spec embeds it serialized via `FacilityOrganizationReadSpec`. See [Organization](./organization.mdx).
+
+### Locations
+
+`locations` is an `ArrayField` of integer PKs rather than a relational join. Each entry references a `FacilityLocation` row by primary key; resolution and validation are handled in application code, not by a database foreign key. Write specs accept location `external_id`s (UUIDs); the retrieve spec embeds each location serialized via `FacilityLocationListSpec`. See [Location](./location.mdx).
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB object → spec) and `de_serialize` (spec → DB object) with the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__exclude__ = ["facility"]` keeps `facility` out of every payload.
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BaseHealthcareServiceSpec` | shared base | Fields: `id`, `service_type`, `internal_type`, `name`, `styling_metadata`, `extra_details`. `__model__ = HealthcareService`, `__exclude__ = ["facility"]` |
+| `HealthcareServiceWriteSpec` | write · create & update | Base fields **plus** `locations: list[UUID4]` (default `[]`) and `managing_organization: UUID4 \| None`. Resolves `managing_organization` from its `external_id` in `perform_extra_deserialization` |
+| `HealthcareServiceReadSpec` | read · list | Base fields. `perform_extra_serialization` sets `id` to `obj.external_id` |
+| `HealthcareServiceRetrieveSpec` | read · detail | Extends `ReadSpec`; overrides `locations: list[dict]` (each location serialized via `FacilityLocationListSpec`) and `managing_organization: dict \| None` (serialized via `FacilityOrganizationReadSpec`) |
+
+### Field exposure by spec
+
+| Field | Base | Write | Read (list) | Retrieve (detail) |
+| --- | --- | --- | --- | --- |
+| `id` (UUID4) | ✓ (read-only) | ✓ | ✓ (= `external_id`) | ✓ (= `external_id`) |
+| `name` (str, required) | ✓ | ✓ | ✓ | ✓ |
+| `service_type` (bound `Coding`, optional) | ✓ | ✓ | ✓ | ✓ |
+| `internal_type` (enum, optional) | ✓ | ✓ | ✓ | ✓ |
+| `styling_metadata` (dict, default `{}`) | ✓ | ✓ | ✓ | ✓ |
+| `extra_details` (str, required) | ✓ | ✓ | ✓ | ✓ |
+| `locations` | — | `list[UUID4]` (write) | — | `list[dict]` (embedded) |
+| `managing_organization` | — | `UUID4 \| None` (write) | — | `dict \| None` (embedded) |
+
+### Validation & server-side behaviour
+
+- **`service_type`** — coded as a `ValueSetBoundCoding`; on de-serialization the `code` is validated against the `healthcare-service-type-code` value set (`validate_valueset`). `extra="forbid"` on the underlying `Coding` rejects unknown keys.
+- **`managing_organization`** (write) — `perform_extra_deserialization` resolves the supplied UUID via `get_object_or_404(FacilityOrganization, external_id=...)` and assigns the FK; if falsy, sets `managing_organization = None`.
+- **`locations`** (write) — supplied as UUIDs; the retrieve spec resolves each PK back to a `FacilityLocation` and serializes it, silently skipping any that fail to load (`try/except … pass`).
+- **`id` / `external_id`** — never written from the body; `de_serialize` skips `id`/`external_id`, and `serialize`/`perform_extra_serialization` populate `id` from `obj.external_id`.
+- **`facility`** — excluded from all specs; assigned server-side, so it cannot be set or changed through the API body.
+- No `status` field exists on this resource, so there is no server-maintained `status_history`.
+
+## Methods & save behaviour
+
+- Inherits `save()`, soft-delete (`deleted`), audit fields (`created_by`/`updated_by`), `history`/`meta`, and `external_id` from [`EMRBaseModel`](../foundation/base-model.mdx).
+- `EMRResource.serialize` / `de_serialize` drive read/write conversion; the extra (de)serialization hooks above run the FK resolution and embedded serialization.
+- `facility` and `managing_organization` use `on_delete=PROTECT`; deleting either while a service references it is blocked at the DB level.
+
+## API integration notes
+
+- Exposed through Care's REST API. The bookable surface is built from the spec classes, not the raw model.
+- `service_type` must be a `Coding` object whose `code` is in the bound value set — supply a coding structure, not a bare string.
+- `internal_type` is one of `pharmacy`, `lab`, `scheduling`, `store` on the API boundary.
+- `locations` on write is a list of location `external_id`s (UUIDs); detail responses embed full location objects. There is no DB-level integrity check that referenced locations belong to the same `facility`.
+- `managing_organization` on write is the org `external_id`; detail responses embed the serialized organization.
+- `styling_metadata` is an open JSON bag for UI hints and carries no backend semantics.
+- `facility` is never accepted in the request body.
+
+## Related
+
+- Reference: [Facility](./facility.mdx)
+- Reference: [Organization](./organization.mdx)
+- Reference: [Location](./location.mdx)
+- Reference: [Value set](../forms/valueset.mdx)
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Source: [healthcare_service.py model on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/healthcare_service.py)
+- Source: [healthcare_service spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/healthcare_service/spec.py)
+- Source: [healthcare_service valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/healthcare_service/valueset.py)
diff --git a/versioned_docs/version-3.1/references/facility/location.mdx b/versioned_docs/version-3.1/references/facility/location.mdx
new file mode 100644
index 0000000..1159f46
--- /dev/null
+++ b/versioned_docs/version-3.1/references/facility/location.mdx
@@ -0,0 +1,251 @@
+---
+sidebar_position: 5
+---
+
+# Location
+
+Technical reference for the `FacilityLocation` module in Care EMR.
+
+**Source:**
+
+- Model: [`care/emr/models/location.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/location.py)
+- Resource spec: [`care/emr/resources/location/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/location/spec.py)
+- Base resource: [`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+- Coding type: [`care/emr/resources/common/coding.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py)
+
+Locations are abstract, hierarchical entities (buildings, wards, rooms, beds) that describe where resources sit and where care is delivered. Nesting is unbounded: every location may have a parent and children. Encounters and organizations attach to locations, and location-level access is resolved through an async cascade rather than synchronously. The model is derived from the [FHIR Location](https://build.fhir.org/location.html) resource.
+
+The Django model is the **storage** layer; several fields (`location_type`, `metadata`, `cached_parent_json`) are opaque `JSONField`s whose real shape comes from the Pydantic **resource specs** (`care/emr/resources/location/spec.py`). The specs also define every enum/choice and the read/write API schemas. See [Resource specs (API schema)](#resource-specs-api-schema).
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `FacilityLocation` | A node in a facility's location tree (building, ward, room, bed, …) |
+| `FacilityLocationOrganization` | Grants a `FacilityOrganization` access to a location |
+| `FacilityLocationEncounter` | Records how/when an encounter occupied a location (e.g. a bed) |
+
+All models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+## `FacilityLocation` fields
+
+### Descriptive
+
+| Field | Type | Spec values / shape | Notes |
+| --- | --- | --- | --- |
+| `name` | `CharField(255)` | `str`, required | Location name; unique within its level under a root (see [validation](#methods--save-behaviour)) |
+| `description` | `CharField(255)` | `str`, required | Free-text description |
+| `status` | `CharField(255)` | `StatusChoices` enum | Location status; one of [`StatusChoices`](#statuschoices-values) |
+| `operational_status` | `CharField(255)` | `FacilityLocationOperationalStatusChoices` enum | FHIR operational status code; one of [`FacilityLocationOperationalStatusChoices`](#facilitylocationoperationalstatuschoices-values) |
+| `system_availability_status` | `CharField(255)` | `str` (read-only) | Server-maintained; derived from encounter association. Conceptually one of [`LocationAvailabilityStatusChoices`](#locationavailabilitystatuschoices-values) (`available` / `reserved`) |
+| `mode` | `CharField(255)` | `FacilityLocationModeChoices` enum | `kind` (a type, e.g. a building/ward) or `instance` (a single location, e.g. Bed 1). See [`FacilityLocationModeChoices`](#facilitylocationmodechoices-values). Write-only on create; not updatable |
+| `location_type` | `JSONField` (`default=dict`, nullable) | `Coding \| None` (default `None`) | A single FHIR [`Coding`](#coding-shape) — **not** a `CodeableConcept`. No bound value set; free coding |
+| `form` | `CharField(255)` | `FacilityLocationFormChoices` enum | FHIR physical form of the location; one of [`FacilityLocationFormChoices`](#facilitylocationformchoices-values) |
+
+### Hierarchy & tree caches
+
+These fields denormalize the location tree so descendants can be queried without recursive joins. They are maintained by `save()` and the cascade task — clients should not set them directly.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `parent` | `FK → FacilityLocation` | `SET_NULL`, nullable. Immediate parent node. Sent on write as a UUID (`parent`), excluded from read serialization (replaced with serialized parent JSON) |
+| `root_location` | `FK → self` | `CASCADE`, `related_name="root"`, nullable. Top of this node's tree. Excluded from specs |
+| `has_children` | `BooleanField` | `default=False`; flipped to `True` on the parent when a child is created. Exposed read-only in list/retrieve specs |
+| `level_cache` | `IntegerField` | `default=0`; depth in the tree (`parent.level_cache + 1`) |
+| `parent_cache` | `ArrayField[int]` | Ordered ancestor IDs (`parent.parent_cache + [parent.id]`) |
+| `cached_parent_json` | `JSONField` (`default=dict`) | Serialized parent chain (`FacilityLocationListSpec`) with a `cache_expiry` ISO timestamp; rebuilt lazily by `get_parent_json()`, invalidated by the cascade task. `cache_expiry_days = 15` |
+| `sort_index` | `IntegerField` | `default=0`; in specs `int \| None`, default `0`, constrained `0 ≤ sort_index ≤ 10000` (`MIN_SORT_INDEX`/`MAX_SORT_INDEX`). Auto-assigned as `max(sibling sort_index) + 1` when unset |
+
+### Access & encounter
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` | `PROTECT`. Owning facility. Excluded from specs (resolved from route/context) |
+| `facility_organization_cache` | `ArrayField[int]` | `default=list`. Org IDs that can access this location; rebuilt by `sync_organization_cache()` |
+| `current_encounter` | `FK → Encounter` | `SET_NULL`, nullable, `default=None`. Populated from `FacilityLocationEncounter`. Excluded from base spec; surfaced as serialized JSON in list/retrieve specs |
+| `metadata` | `JSONField` | `default=dict`. Open extension bag for deployment-specific data |
+
+## Enum values
+
+All enums are `str` `Enum`s defined in `care/emr/resources/location/spec.py`. Values below are character-for-character from source.
+
+### `StatusChoices` values
+
+| Value | Meaning |
+| --- | --- |
+| `active` | Location is active |
+| `inactive` | Location is inactive |
+| `unknown` | Status unknown |
+
+### `FacilityLocationOperationalStatusChoices` values
+
+FHIR HL7 v2 bed/location operational status codes.
+
+| Code | Meaning |
+| --- | --- |
+| `C` | Closed |
+| `H` | Housekeeping |
+| `O` | Occupied |
+| `U` | Unoccupied |
+| `K` | Contaminated |
+| `I` | Isolated |
+
+### `FacilityLocationModeChoices` values
+
+| Value | Meaning |
+| --- | --- |
+| `instance` | A single, concrete location (e.g. Bed 1). Instances **cannot have children** |
+| `kind` | A class/type of location (e.g. a ward or building) |
+
+### `FacilityLocationFormChoices` values
+
+FHIR `location-physical-type` codes.
+
+| Code | Meaning |
+| --- | --- |
+| `si` | Site |
+| `bu` | Building |
+| `wi` | Wing |
+| `wa` | Ward |
+| `lvl` | Level |
+| `co` | Corridor |
+| `ro` | Room |
+| `bd` | Bed |
+| `ve` | Vehicle |
+| `ho` | House |
+| `ca` | Cabinet |
+| `rd` | Road |
+| `area` | Area |
+| `jdn` | Jurisdiction |
+| `vi` | Virtual |
+
+### `LocationEncounterAvailabilityStatusChoices` values
+
+Used by `FacilityLocationEncounter.status` (how an encounter occupies a location).
+
+| Value | Meaning |
+| --- | --- |
+| `planned` | Occupancy planned |
+| `active` | Currently occupied |
+| `reserved` | Reserved |
+| `completed` | Occupancy ended |
+
+### `LocationAvailabilityStatusChoices` values
+
+Conceptual values for `system_availability_status` (server-derived).
+
+| Value | Meaning |
+| --- | --- |
+| `available` | Not actively tied to an encounter |
+| `reserved` | Tied to an encounter |
+
+### `Coding` shape
+
+`location_type` is a single `Coding` object (`extra="forbid"`):
+
+```text
+Coding {
+ system: str | None = None
+ version: str | None = None
+ code: str # required
+ display: str | None = None
+}
+```
+
+## Related models
+
+### `FacilityLocationOrganization`
+
+Denotes which organization can access a given location. Users under that organization inherit access to the location (and its encounters) through the role they hold on the organization.
+
+```text
+location → FK FacilityLocation (CASCADE)
+organization → FK FacilityOrganization (CASCADE)
+```
+
+Saving a `FacilityLocationOrganization` calls `location.save()` (rebuilding `facility_organization_cache`) and then `location.cascade_changes()`, which propagates the change to descendants asynchronously. On write, the set of organizations is supplied through `FacilityLocationWriteSpec.organizations` (a list of UUIDs).
+
+### `FacilityLocationEncounter`
+
+Records how an encounter was associated to a location (e.g. a bed assignment over a time window).
+
+| Field | Type | Spec | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(25)` | `LocationEncounterAvailabilityStatusChoices` | One of `planned` / `active` / `reserved` / `completed` |
+| `location` | `FK → FacilityLocation` | excluded / nested | `CASCADE`. Surfaced as JSON in `...ListSpecWithLocation` |
+| `encounter` | `FK → Encounter` | UUID on write, JSON on read | `CASCADE`. Validated to exist on create |
+| `start_datetime` | `DateTimeField` | `datetime` (required) | Occupancy start |
+| `end_datetime` | `DateTimeField` (nullable) | `datetime \| None` | Occupancy end (open-ended when null) |
+
+`FacilityLocation.current_encounter` is populated from these rows.
+
+## Methods & save behaviour
+
+### `save()` side effects
+
+On insert (no `id` yet):
+
+1. If `parent` is set → `level_cache`, `root_location`, and `parent_cache` are derived from the parent; the parent's `has_children` is flipped to `True` if needed.
+2. If `sort_index` is unset → it is assigned `max(sibling sort_index) + 1`.
+
+On update, `cached_parent_json` is cleared so it rebuilds lazily. After every `super().save()`, `sync_organization_cache()` runs and persists `facility_organization_cache` via a second `save(update_fields=[...])`.
+
+### Caches & cascade
+
+- `sync_organization_cache()` — unions the parent's org cache, all linked `FacilityLocationOrganization` org chains, and the facility's `default_internal_organization_id`, then writes `facility_organization_cache`.
+- `get_parent_json()` — returns the cached parent chain, refreshing `cached_parent_json` (via `FacilityLocationListSpec.serialize(parent)`) when expired (`cache_expiry_days = 15`).
+- `cascade_changes()` / `handle_cascade(base_location)` — a Celery task that walks every descendant and re-saves it to invalidate its `cached_parent_json`. Triggered when a location's organizations change; expect propagation to be **eventually consistent**.
+
+### `validate_uniqueness()`
+
+Classmethod enforcing that `name` is unique among siblings at the same `level_cache` within the same `root_location` (or among root-level locations when there is no root). For a new instance with a `parent`, `level_cache` and `root_location` are computed from that parent before checking.
+
+## Resource specs (API schema)
+
+The Pydantic specs in `care/emr/resources/location/spec.py` define the API request/response shapes. All extend `EMRResource` (`serialize`/`de_serialize`, with `perform_extra_serialization`/`perform_extra_deserialization` hooks; see [base model](../foundation/base-model.mdx) and [`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)).
+
+### Location specs
+
+| Spec class | Role | Exposes / notes |
+| --- | --- | --- |
+| `FacilityLocationBaseSpec` | shared base | `__model__ = FacilityLocation`; `id: UUID4 \| None`. `__exclude__ = [parent, facility, organizations, root_location, current_encounter]` |
+| `FacilityLocationSpec` | shared fields | `status`, `operational_status`, `name`, `description`, `location_type` (`Coding \| None`), `form`, `sort_index` (`0..10000`, default `0`) |
+| `FacilityLocationWriteSpec` | write · create | Adds `parent: UUID4 \| None`, `organizations: list[UUID4]` (required), `mode: FacilityLocationModeChoices`. Validator: if `parent` set, parent must exist and must not be `mode = instance` ("Instances cannot have children"). `perform_extra_deserialization` resolves `parent` UUID → FK |
+| `FacilityLocationUpdateSpec` | write · update | Same fields as `FacilityLocationSpec` (no `parent`/`organizations`/`mode` — hierarchy and mode are fixed after creation) |
+| `FacilityLocationMinimalListSpec` | read · minimal | Adds `parent: dict`, `mode: str`, `has_children: bool`, `system_availability_status: str`. `perform_extra_serialization` sets `id = external_id` and `parent = obj.get_parent_json()` |
+| `FacilityLocationListSpec` | read · list | Extends minimal; adds `current_encounter: dict \| None`, serialized via `EncounterListSpec` when present |
+| `FacilityLocationRetrieveSpec` | read · detail | Extends list; adds `created_by`/`updated_by` (via `serialize_audit_users`) |
+
+### Encounter-association specs
+
+| Spec class | Role | Exposes / notes |
+| --- | --- | --- |
+| `FacilityLocationEncounterBaseSpec` | shared base | `__model__ = FacilityLocationEncounter`; `id: UUID4 \| None`. `__exclude__ = [encounter, location]` |
+| `FacilityLocationEncounterCreateSpec` | write · create | `status` (`LocationEncounterAvailabilityStatusChoices`), `encounter: UUID4`, `start_datetime`, `end_datetime: datetime \| None`. Validator: encounter must exist. `perform_extra_deserialization` resolves `encounter` UUID → FK |
+| `FacilityLocationEncounterUpdateSpec` | write · update | `status`, `start_datetime`, `end_datetime` (encounter not re-assignable) |
+| `FacilityLocationEncounterListSpec` | read · list | `encounter: UUID4`, `start_datetime`, `end_datetime`, `status: str`; `id = external_id` |
+| `FacilityLocationEncounterListSpecWithLocation` | read · list | Extends list; adds `location: dict` (serialized via `FacilityLocationListSpec`) |
+| `FacilityLocationEncounterReadSpec` | read · detail | `encounter: dict` (serialized via `EncounterRetrieveSpec`), `start_datetime`, `end_datetime`, `status`, `created_by`/`updated_by` |
+
+### Server-maintained behaviour
+
+- `system_availability_status`, `has_children`, `level_cache`, `parent_cache`, `cached_parent_json`, `facility_organization_cache`, `current_encounter`, `sort_index` (when unset) are platform-maintained — do not write them from clients.
+- On read, `parent` is replaced by the serialized parent chain (`get_parent_json()`), not the raw FK.
+- Granting access via `organizations` / `FacilityLocationOrganization` is **asynchronous**; the org cache and descendant caches settle after the cascade task runs.
+- `location_type` binds to a free `Coding` (no value set slug); `metadata` is an open extension bag — both add coded/extension data without schema migrations.
+
+## API integration notes
+
+- Locations are exposed through Care's REST API and modelled on the FHIR `Location` resource; payload field names may differ from these Django names.
+- `mode` distinguishes `kind` (a location type) from `instance` (a concrete location such as a bed) — beds are `FacilityLocation` instances, not a separate model. `instance` locations cannot have children.
+- `parent`, `organizations`, and `mode` are settable only at creation (`FacilityLocationWriteSpec`); updates use `FacilityLocationUpdateSpec`, which omits them.
+- `level_cache`, `parent_cache`, `cached_parent_json`, `has_children`, `sort_index`, `facility_organization_cache`, and `current_encounter` are platform-maintained — do not write them from clients.
+
+## Related
+
+- Reference: [Facility](./facility.mdx)
+- Reference: [Organization](./organization.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [location.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/location.py)
+- Source: [location/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/location/spec.py)
diff --git a/versioned_docs/version-3.1/references/facility/organization.mdx b/versioned_docs/version-3.1/references/facility/organization.mdx
new file mode 100644
index 0000000..d31576e
--- /dev/null
+++ b/versioned_docs/version-3.1/references/facility/organization.mdx
@@ -0,0 +1,215 @@
+---
+sidebar_position: 4
+---
+
+# Organization
+
+Technical reference for the `Organization` module in Care EMR.
+
+**Source:**
+[`care/emr/models/organization.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/organization.py) ·
+[`resources/organization/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/organization/spec.py) ·
+[`resources/organization/organization_user_spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/organization/organization_user_spec.py)
+
+Organizations are Care's FHIR-aligned grouping primitive: a nested tree used to group permissions and resources. An organization might represent all doctors, a `Cardiology` sub-team, or a governance unit. Permissions attached to a parent are implicitly available in its descendants.
+
+The Django model is the **storage** layer — several columns are opaque `JSONField`s (`metadata`, `cached_parent_json`) and arrays (`parent_cache`, `managing_organizations`) whose real structure lives in the **resource specs** (`care/emr/resources/organization/`). The specs define the `org_type` enum, validation, the nested JSON shape returned for `parent`, and the read/write API schemas. See [Resource specs (API schema)](#resource-specs-api-schema).
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `OrganizationCommonBase` | Abstract base holding the tree structure, caching, and uniqueness logic shared by both organization types |
+| `Organization` | Instance-wide organization (permission/governance tree, not tied to a facility) |
+| `FacilityOrganization` | Facility-scoped organization (departments/teams within one `Facility`) |
+| `OrganizationUser` | Membership row linking a `User` to an `Organization` with a `RoleModel` |
+| `FacilityOrganizationUser` | Membership row linking a `User` to a `FacilityOrganization` with a `RoleModel` |
+
+`Organization` and `FacilityOrganization` extend `OrganizationCommonBase`, which itself extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base giving `external_id`, audit fields, soft-delete, and `history`/`meta` JSON). `OrganizationUser` and `FacilityOrganizationUser` extend `EMRBaseModel` directly. `OrganizationCommonBase` is `abstract = True` and defines no table of its own.
+
+## `OrganizationCommonBase` fields
+
+These columns are contributed to both the `Organization` and `FacilityOrganization` tables.
+
+### Identity & classification
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `name` | `CharField(255)` | yes | — | Display name; participates in the sibling uniqueness check (`validate_uniqueness`) |
+| `org_type` | `CharField(255)` | yes | — | Category of the organization. Free text in the DB, but writes bind to the `OrganizationTypeChoices` enum. See [Organization type values](#organization-type-values) |
+| `description` | `TextField` | no | `null` | Nullable, blank-able. On the API the write/read specs default it to `""` |
+| `active` | `BooleanField` | no | `True` | |
+| `system_generated` | `BooleanField` | no | `False` | System-generated orgs cannot be edited or deleted. Read-only on the API (only surfaced by `OrganizationReadSpec`) |
+| `metadata` | `JSONField` | no | `{}` (`dict`) | Open key-value bag for deployment-specific data; no fixed schema, no migration needed. Exposed verbatim by the specs as `metadata: dict` |
+
+### Tree structure
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `parent` | `FK → self` | no | `null` | `CASCADE`, `related_name="children"`. Null parent means a root org. On write, supplied as the parent's `external_id` (UUID); on read, replaced by a nested JSON object — see [`parent` read shape](#parent-read-shape) |
+| `root_org` | `FK → self` | no | `null` | `CASCADE`, `related_name="root"`. Top of the tree, derived on save by `set_organization_cache()` |
+| `has_children` | `BooleanField` | no | `False` | Flipped to `True` on the parent when its first child is created. Read-only on the API |
+
+### Denormalized caches
+
+Platform-maintained — do **not** set these from clients. Rebuilt on insert to avoid recursive joins when reading the tree.
+
+| Field | Type | Default | Rebuilt by | Shape |
+| --- | --- | --- | --- | --- |
+| `level_cache` | `IntegerField` | `0` | `set_organization_cache()` | Depth in the tree (`parent.level_cache + 1`) |
+| `parent_cache` | `ArrayField[int]` | `[]` (`list`) | `set_organization_cache()` | Full ancestor **internal id** chain (`parent.parent_cache + [parent.id]`) |
+| `cached_parent_json` | `JSONField` | `{}` (`dict`) | `get_parent_json()` | Materialized parent record; see shape below. Rebuilt after `cache_expiry_days` (15) |
+
+`cached_parent_json` shape (built in `get_parent_json()`; `{}` for root orgs):
+
+```text
+{
+ id: str (parent.external_id, UUID as string)
+ name: str
+ description: str | null
+ org_type: str (OrganizationTypeChoices value)
+ metadata: dict
+ parent: dict (recursively, the parent's own cached_parent_json)
+ level_cache: int
+ cache_expiry: str (ISO datetime; cache is reused while now < cache_expiry)
+}
+```
+
+This same structure is what `OrganizationReadSpec.parent` returns on read (see [`parent` read shape](#parent-read-shape)).
+
+## Related models
+
+### `Organization`
+
+Instance-wide organization. Adds one field on top of `OrganizationCommonBase`:
+
+| Field | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `managing_organizations` | `ArrayField[int]` | `[]` (`list`) | Internal ids of orgs that manage this one. Expanded to a list of nested org JSON (`OrganizationReadSpec`) only by `OrganizationRetrieveSpec` |
+
+### `FacilityOrganization`
+
+Facility-scoped organization. Adds one field on top of `OrganizationCommonBase`:
+
+```text
+facility → FK facility.Facility (CASCADE)
+```
+
+See [Facility](../facility/facility.mdx).
+
+### `OrganizationUser`
+
+Grants a user access to resources owned by an organization through a role. Membership does not imply access to all resources of the org — only those assigned to it.
+
+```text
+organization → FK Organization (CASCADE)
+user → FK users.User (CASCADE)
+role → FK security.RoleModel (CASCADE)
+```
+
+### `FacilityOrganizationUser`
+
+The facility-scoped equivalent of `OrganizationUser`.
+
+```text
+organization → FK FacilityOrganization (CASCADE)
+user → FK users.User (CASCADE)
+role → FK security.RoleModel (CASCADE)
+```
+
+## Enum & coded values
+
+### Organization type values
+
+`org_type` is a `CharField` in the DB, but all writes bind to `OrganizationTypeChoices` (`resources/organization/spec.py`).
+
+| Value | Meaning |
+| --- | --- |
+| `team` | A team / department-style grouping |
+| `govt` | Governance / governmental unit (typically superadmin-managed) |
+| `role` | Role-grouping org. An `OrganizationUser` on a `role`-typed org invalidates the user's cached role-org list (see [`OrganizationUser.save()`](#organizationusersave)) |
+| `product_supplier` | Supplier organization (links to the supply chain) |
+
+`FacilityOrganization` in practice uses `team` and `root` types; the enum bound on the spec is the same `OrganizationTypeChoices` (instance scope). `govt`/`role` orgs and root orgs are restricted to superadmin management.
+
+## Resource specs (API schema)
+
+Resource specs (`EMRResource` subclasses) are the API/implementation layer. `serialize` builds a read object from a DB row (running `perform_extra_serialization` / `perform_extra_user_serialization` hooks); `de_serialize` builds a DB row from a write payload (running `perform_extra_deserialization`). `__exclude__` lists model fields the base serializer skips (handled manually in the hooks).
+
+### Organization specs (`resources/organization/spec.py`)
+
+| Spec | Role | Fields exposed | Notes |
+| --- | --- | --- | --- |
+| `OrganizationBaseSpec` | shared | `id`, `active`, `org_type`, `name`, `description`, `metadata` | `__model__ = Organization`, `__exclude__ = ["parent"]`. `org_type` bound to `OrganizationTypeChoices`; `description` defaults `""`; `metadata` defaults `{}` |
+| `OrganizationUpdateSpec` | write · update | (inherits `OrganizationBaseSpec`) | No extra fields — `parent` cannot be changed on update |
+| `OrganizationWriteSpec` | write · create | base + `parent: UUID4 \| null` | Validates `parent` exists (`validate_parent_organization`); on create, `perform_extra_deserialization` resolves `parent` from `external_id` (or sets `None`) |
+| `OrganizationReadSpec` | read · list | base + `level_cache`, `system_generated`, `has_children`, `parent: dict` | `perform_extra_serialization` sets `id = external_id` and `parent = obj.get_parent_json()` — see [`parent` read shape](#parent-read-shape) |
+| `OrganizationRetrieveSpec` | read · detail | `OrganizationReadSpec` + `permissions: list[str]`, `managing_organizations: list[dict]` | `perform_extra_user_serialization` fills `permissions` (via `AuthorizationController.get_permission_on_organization` for the requesting user), expands `managing_organizations` to nested `OrganizationReadSpec` JSON, and adds audit users (`serialize_audit_users`) |
+
+#### `parent` read shape
+
+`OrganizationReadSpec.parent` (and `OrganizationRetrieveSpec.parent`) is **not** a UUID — it is the nested object returned by `get_parent_json()`. Same shape as [`cached_parent_json`](#denormalized-caches): `{ id, name, description, org_type, metadata, parent (recursive), level_cache, cache_expiry }`, or `{}` for root orgs. On write, `parent` is instead a plain `UUID4` (the parent's `external_id`).
+
+### Membership specs (`resources/organization/organization_user_spec.py`)
+
+| Spec | Role | Fields exposed | Notes |
+| --- | --- | --- | --- |
+| `OrganizationUserBaseSpec` | shared | (none) | `__model__ = OrganizationUser`, `__exclude__ = ["user", "role"]` |
+| `OrganizationUserUpdateSpec` | write · update | `role: UUID4` | `validate_role` requires the `RoleModel` to exist; `perform_extra_deserialization` resolves `role` from `external_id`. Only the role can change on update |
+| `OrganizationUserWriteSpec` | write · create | `role: UUID4` + `user: UUID4` | Extends update spec; `validate_user` requires the `User` to exist. On create, resolves both `user` and `role` from `external_id` |
+| `OrganizationUserReadSpec` | read | `id: UUID4`, `user: dict`, `role: dict` | `user` = cached `UserSpec` JSON (`model_from_cache`); `role` = `RoleReadSpec` JSON (full permissions) |
+| `OrganizationUserExtendedReadSpec` | read | `id: UUID4`, `role: dict`, `organization: dict` | Used by `OrganizationUser.get_cached_role_orgs()`. `organization` = `OrganizationReadSpec` JSON; `role` = `RoleReadMinimalSpec` JSON (no permission list) |
+
+Nested membership JSON shapes (from `resources/role/spec.py`, `resources/user/spec.py`):
+
+```text
+role (RoleReadSpec): { id: UUID, name, description, is_system: bool,
+ is_archived: bool, contexts: [RoleContext],
+ permissions: [{ name, description, slug, context }] }
+role (RoleReadMinimalSpec): same as above without `permissions`
+user (UserSpec): cached user summary JSON
+```
+
+## Methods & save behaviour
+
+### `OrganizationCommonBase.save()`
+
+On **insert** (no `id` yet) the base saves the row, then calls `set_organization_cache()` to populate the tree caches. On **update** it saves normally without recomputing caches.
+
+`set_organization_cache()` side effects when a `parent` is set:
+
+1. Computes `parent_cache` = `parent.parent_cache + [parent.id]` and `level_cache` = `parent.level_cache + 1`.
+2. Derives `root_org` from the parent (`parent.root_org`, or the parent itself if the parent is a root).
+3. If the parent did not previously have children, flips `parent.has_children = True` and persists only that field.
+
+### `get_parent_json()`
+
+Returns the cached parent JSON if `cached_parent_json` is present and `cache_expiry` is still in the future; otherwise it recursively rebuilds the nested parent record, stamps a new expiry (`cache_expiry_days = 15`), and persists `cached_parent_json`. Returns `{}` for root orgs. This is what `OrganizationReadSpec.parent` exposes on every read.
+
+### `validate_uniqueness(queryset, pydantic_instance, model_instance)`
+
+Classmethod enforcing that `name` is unique among **siblings** — same `root_org` and same `level_cache`. For a new org it derives `level_cache`/`root_org` from the supplied `parent` (`external_id`); a null parent means `level_cache = 0` and a null `root_org`. Used by the resource layer before create/update.
+
+### `OrganizationUser.save()`
+
+After saving, if the linked organization's `org_type` is `role` (`OrganizationTypeChoices.role`), it clears `user.cached_role_orgs` (sets it to `None` and persists that field) so the user's role-org cache is rebuilt on next read.
+
+`OrganizationUser.get_cached_role_orgs(user_id)` is a classmethod that serializes all `role`-typed org memberships for a user via `OrganizationUserExtendedReadSpec`.
+
+## API integration notes
+
+- Organizations are exposed through Care's REST API and align with the FHIR `Organization` resource. Tree position (`level_cache`, `parent_cache`, `root_org`), `has_children`, and `cached_parent_json` are platform-maintained — do not set them directly from clients.
+- **Write path:** `OrganizationWriteSpec` (create) / `OrganizationUpdateSpec` (update). `org_type` is constrained to `OrganizationTypeChoices`; `parent` is supplied as the parent's `external_id` (UUID) and validated to exist; `validate_uniqueness` enforces sibling-name uniqueness. `parent` is immutable after create (the update spec omits it).
+- **Read path:** `OrganizationReadSpec` (list) returns `parent` as nested JSON (`get_parent_json()`) plus `level_cache`/`system_generated`/`has_children`; `OrganizationRetrieveSpec` (detail) additionally returns the caller's `permissions`, expanded `managing_organizations`, and audit users.
+- Membership is managed through `OrganizationUser` / `FacilityOrganizationUser` (specs above); `role` is required and bounded by the assigning user's own role in that org. Adding a member to a `role`-typed org invalidates that user's `cached_role_orgs`.
+- `metadata` is the supported place for deployment-specific key-value data without schema migrations; it is passed through the specs verbatim as `dict`.
+- Soft-delete applies (via `EMRBaseModel`); an organization cannot be deleted while it still has children, and system-generated orgs cannot be deleted.
+
+## Related
+
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Reference: [User](../access/user.mdx) · [Role](../access/role.mdx) · [Permission](../access/permission.mdx)
+- Source — models: [organization.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/organization.py)
+- Source — specs: [organization/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/organization/spec.py) · [organization/organization_user_spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/organization/organization_user_spec.py) · [resources/base.py (`EMRResource`)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+- Source — related: [role/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py) · [user/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/user/spec.py) · [security/models/role.py](https://github.com/ohcnetwork/care/blob/develop/care/security/models/role.py) · [users/models.py](https://github.com/ohcnetwork/care/blob/develop/care/users/models.py)
diff --git a/versioned_docs/version-3.1/references/forms/_category_.json b/versioned_docs/version-3.1/references/forms/_category_.json
new file mode 100644
index 0000000..979e57e
--- /dev/null
+++ b/versioned_docs/version-3.1/references/forms/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Forms & Terminology",
+ "position": 8,
+ "key": "forms-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Forms & Terminology",
+ "description": "Questionnaires that drive structured data capture, and the value sets that constrain coded answers."
+ }
+}
diff --git a/versioned_docs/version-3.1/references/forms/questionnaire-response-template.mdx b/versioned_docs/version-3.1/references/forms/questionnaire-response-template.mdx
new file mode 100644
index 0000000..a78b804
--- /dev/null
+++ b/versioned_docs/version-3.1/references/forms/questionnaire-response-template.mdx
@@ -0,0 +1,90 @@
+---
+sidebar_position: 3
+---
+
+# Questionnaire Response Template
+
+Technical reference for the `QuestionnaireResponseTemplate` module in Care EMR — a reusable, shareable template that pre-fills a [questionnaire response](./questionnaire-response.mdx) along with associated medication requests and activity definitions (a clinician's "favourite" / order set).
+
+The Django model is the **storage** layer; the Pydantic **resource specs** define the structured `template_data` payload, validation, and the read/write API schemas.
+
+**Source:**
+- Model: [`care/emr/models/questionnaire.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/questionnaire.py)
+- Spec: [`resources/questionnaire_response_template/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire_response_template/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `QuestionnaireResponseTemplate` | A named template bundling pre-filled questionnaire answers, medication requests, and activity definitions, scoped to a facility/users/organizations |
+
+`QuestionnaireResponseTemplate` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, soft-delete, and `history`/`meta` JSON).
+
+## `QuestionnaireResponseTemplate` fields
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(255)` | Template name |
+| `description` | `TextField` | `default=""` |
+| `template_data` | `JSONField` | `default=dict`. Structured payload — shape defined by `TemplateData` (see [shape](#templatedata-shape)) |
+| `facility` | `FK → Facility` | `CASCADE`, nullable. Owning facility (instance-wide when null) |
+| `questionnaire` | `FK → Questionnaire` | `CASCADE`, nullable, `default=None`. Optional questionnaire the template targets |
+| `facility_organizations` | `ArrayField[int]` | `default=list`. Facility-organization ids the template is shared with |
+| `users` | `ArrayField[int]` | `default=list`. User ids the template is shared with |
+| `available_keys` | `ArrayField[CharField(255)]` | `default=list`. Platform-maintained — the populated top-level keys of `template_data`, recomputed on every write |
+
+## `TemplateData` shape
+
+The real structure of the opaque `template_data` `JSONField` (`resources/questionnaire_response_template/spec.py`). Every section is optional; `available_keys` records which are populated.
+
+```text
+TemplateData {
+ medication_request: list[MedicationRequestTemplateSpec] | None
+ questionnaire: list[QuestionnaireAnswer] | None
+ activity_definition: list[ActivityDefinitionTemplateSpec] | None
+ meta: dict | None
+}
+
+QuestionnaireAnswer { question_id: str, answer: dict, meta: dict }
+
+MedicationRequestTemplateSpec # extends the medication request write spec
+ + requested_product: str | None # validated against ProductKnowledge.slug
+
+ActivityDefinitionTemplateSpec {
+ slug: str # validated against ActivityDefinition.slug
+ service_request: ServiceRequestUpdateSpec
+}
+```
+
+See [Medication Request](../medications/medication-request.mdx), [Activity Definition](../clinical/activity-definition.mdx), and [Service Request](../clinical/service-request.mdx) for the embedded specs.
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`resources/base.py`).
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `QuestionnaireResponseTemplateBaseSpec` | shared | `__model__ = QuestionnaireResponseTemplate`; `id` (`UUID4?`), `template_data` (`TemplateData`), `name`, `description` (`= ""`) |
+| `QuestionnaireResponseTemplateCreateSpec` | write · create | Adds `questionnaire` (slug `str?`), `facility` (`UUID4?`), `users` (`list[str]`), `facility_organizations` (`list[UUID4]`). Validates that a `facility` is present when `facility_organizations` are given; resolves the questionnaire by slug and facility by external id; recomputes `available_keys` |
+| `QuestionnaireResponseTemplateUpdateSpec` | write · update | `users`, `facility_organizations`; recomputes `available_keys`. Cannot change `facility`/`questionnaire` |
+| `QuestionnaireResponseTemplateReadSpec` | read · list | Adds `created_date`/`modified_date` |
+| `QuestionnaireResponseTemplateRetrieveSpec` | read · detail | Expands `users` (`UserSpec` via cache) and `facility_organizations` (`FacilityOrganizationReadSpec`), plus `created_by`/`updated_by` |
+
+### Validation & server-maintained behaviour
+
+- `validate_facility` (`model_validator(after)` on create): rejects facility organizations without a facility.
+- `available_keys` is derived on every create/update in `perform_extra_deserialization` — it lists the `template_data` keys whose value is non-empty. Clients should not set it directly.
+- Embedded `requested_product` and activity-definition `slug` are validated to exist (`ProductKnowledge`, `ActivityDefinition`) before save.
+
+## API integration notes
+
+- This is a Care-specific authoring/convenience resource (not a FHIR resource): one template can seed a questionnaire response, medication requests, and activity-definition-driven service requests at once.
+- Sharing is controlled by `facility`, `facility_organizations`, and `users`; instance-wide templates leave `facility` null.
+- `available_keys` lets clients cheaply discover which sections a template carries without parsing `template_data`.
+
+## Related
+
+- Reference: [Questionnaire Response](./questionnaire-response.mdx) — what a template helps pre-fill
+- Reference: [Questionnaire](./questionnaire.mdx)
+- Reference: [Medication Request](../medications/medication-request.mdx) · [Activity Definition](../clinical/activity-definition.mdx) · [Service Request](../clinical/service-request.mdx)
+- Source: [questionnaire.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/questionnaire.py)
diff --git a/versioned_docs/version-3.1/references/forms/questionnaire-response.mdx b/versioned_docs/version-3.1/references/forms/questionnaire-response.mdx
new file mode 100644
index 0000000..6ca9de7
--- /dev/null
+++ b/versioned_docs/version-3.1/references/forms/questionnaire-response.mdx
@@ -0,0 +1,139 @@
+---
+sidebar_position: 2
+---
+
+# Questionnaire Response
+
+Technical reference for the `QuestionnaireResponse` and `FormSubmission` modules in Care EMR — the **submitted answers** to a [questionnaire](./questionnaire.mdx) for a patient, and the submission envelope that carries a draft/finalised payload.
+
+The Django models are the **storage** layer; the Pydantic **resource specs** define the submit payload, the answer structure, status enums, and the read/write API schemas.
+
+**Source:**
+- Model: [`care/emr/models/questionnaire.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/questionnaire.py)
+- Specs: [`resources/questionnaire_response/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire_response/spec.py) · [`resources/form_submission/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/form_submission/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `QuestionnaireResponse` | A completed set of answers to a `Questionnaire` for a subject (patient/encounter) |
+| `FormSubmission` | A submission envelope (draft → submitted) holding a raw response dump for a questionnaire |
+
+Both extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, soft-delete via `deleted`, and `history`/`meta` JSON).
+
+## `QuestionnaireResponse` fields
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `questionnaire` | `FK → Questionnaire` | `CASCADE`, nullable. The questionnaire that was answered |
+| `subject_id` | `UUIDField` | The subject the response is about (e.g. patient external id) |
+| `responses` | `JSONField` | `default=list`. The raw answers — a list of `{ "question_id": UUID, ... }` entries |
+| `structured_responses` | `JSONField` | `default=dict`. Extracted/structured representation of the answers |
+| `structured_response_type` | `CharField` | Nullable. Discriminator for the structured payload |
+| `patient` | `FK → Patient` | `CASCADE`. The patient |
+| `encounter` | `FK → Encounter` | `CASCADE`, nullable. The encounter, when answered in a visit context |
+| `form_submission` | `FK → FormSubmission` | `CASCADE`, nullable. The submission this response came from |
+| `status` | `CharField(255)` | `default="completed"`. One of `QuestionnaireResponseStatusChoices` (see [enum](#questionnaire-response-status)) |
+
+### `render_responses()`
+
+Joins each entry in `responses` with the matching question definition from `questionnaire.get_questions_by_id()`, producing a list of `{ "answer": , "question": }` — a human-readable view of the answers against the current questionnaire. Returns `[]` when there are no responses or no linked questionnaire.
+
+## `FormSubmission` fields
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `questionnaire` | `FK → Questionnaire` | `CASCADE`. The questionnaire being submitted |
+| `patient` | `FK → Patient` | `CASCADE`. The patient |
+| `encounter` | `FK → Encounter` | `CASCADE`, nullable. Encounter context, if any |
+| `status` | `CharField(255)` | One of `FormSubmissionStatusChoices` (see [enum](#form-submission-status)) |
+| `response_dump` | `JSONField` | `default=dict`. Raw submitted payload |
+
+## Enums
+
+### Questionnaire response status
+
+`QuestionnaireResponseStatusChoices` (`resources/questionnaire_response/spec.py`), `str` values.
+
+| Value | Meaning |
+| --- | --- |
+| `completed` | The response is finalised (default) |
+| `entered_in_error` | The response was recorded in error |
+
+### Form submission status
+
+`FormSubmissionStatusChoices` (`resources/form_submission/spec.py`), `str` values.
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Saved but not finalised |
+| `submitted` | Finalised submission |
+| `entered_in_error` | Recorded in error |
+
+## Submit payload (nested shapes)
+
+Answers are submitted through the questionnaire submit endpoint, whose body is `QuestionnaireSubmitRequest`:
+
+```text
+QuestionnaireSubmitRequest {
+ resource_id: UUID4 # questionnaire being answered
+ patient: UUID4 (required)
+ encounter: UUID4 | None
+ form_submission: UUID4 | None
+ results: list[QuestionnaireSubmitResult]
+}
+
+QuestionnaireSubmitResult {
+ question_id: UUID4 | UUID5 (required)
+ body_site: Coding | None
+ method: Coding | None
+ taken_at: datetime | None
+ values: list[QuestionnaireSubmitResultValue]
+ note: str | None
+ sub_results: list[list[QuestionnaireSubmitResult]] # nested, for group questions
+}
+
+QuestionnaireSubmitResultValue {
+ value: str | None
+ unit: Coding | None # for Quantity answers
+ coding: Coding | None # for coded answers
+}
+```
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`resources/base.py`).
+
+### `QuestionnaireResponse` (`resources/questionnaire_response/spec.py`)
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `EMRQuestionnaireResponseBase` | shared | `__model__ = QuestionnaireResponse` |
+| `QuestionnaireResponseUpdate` | write · update | Only `status` (`QuestionnaireResponseStatusChoices`, default `completed`) — used to mark a response `entered_in_error` |
+| `QuestionnaireResponseReadSpec` | read | `id`, `status`, `questionnaire` (nested `QuestionnaireReadSpec`), `subject_id`, `responses`, `encounter` (external id or `null`), `structured_responses`, `structured_response_type`, `created_by`/`updated_by` (`UserSpec`), `created_date`/`modified_date` |
+
+New responses are created via the questionnaire submit flow (`QuestionnaireSubmitRequest`), not a generic create spec.
+
+### `FormSubmission` (`resources/form_submission/spec.py`)
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BaseFormSubmissionSpec` | shared | `__model__ = FormSubmission`; `id` (`UUID4?`) |
+| `FormSubmissionUpdateSpec` | write · update | `status` (`FormSubmissionStatusChoices`), `response_dump` (`dict`) |
+| `FormSubmissionWriteSpec` | write · create | Adds `questionnaire` (slug `str`), `patient` (`UUID4`), `encounter` (`UUID4?`). `perform_extra_deserialization` resolves the questionnaire by slug and the patient/encounter by external id; when an encounter is given, the patient is taken from it |
+| `FormSubmissionReadSpec` | read | `status`, `response_dump`, `created_date`/`modified_date`, `created_by`/`updated_by` (`UserSpec?`) |
+
+## API integration notes
+
+- Both align loosely with the FHIR `QuestionnaireResponse` resource; `responses` holds the raw answers, while `structured_responses` is the extracted, query-friendly form.
+- A `QuestionnaireResponse` is normally created by submitting a `QuestionnaireSubmitRequest`; the update spec exists mainly to flag responses `entered_in_error`.
+- `FormSubmission` supports a draft→submitted lifecycle; `response_dump` is an opaque JSON payload validated by the questionnaire definition, not by the model.
+- Audit users (`created_by`/`updated_by`) are platform-maintained.
+
+## Related
+
+- Reference: [Questionnaire](./questionnaire.mdx) — the form definition being answered
+- Reference: [Questionnaire Response Template](./questionnaire-response-template.mdx) — reusable pre-fill templates
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Concept: [Patient](../../concepts/clinical/patient.mdx)
+- Source: [questionnaire.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/questionnaire.py)
diff --git a/versioned_docs/version-3.1/references/forms/questionnaire.mdx b/versioned_docs/version-3.1/references/forms/questionnaire.mdx
new file mode 100644
index 0000000..1f7f3ef
--- /dev/null
+++ b/versioned_docs/version-3.1/references/forms/questionnaire.mdx
@@ -0,0 +1,390 @@
+---
+sidebar_position: 1
+---
+
+# Questionnaire
+
+Technical reference for the `Questionnaire` module in Care EMR.
+
+Questionnaires (a.k.a. **forms**) are generalized, FHIR-inspired data-collection structures: a versioned tree of questions answered about a subject (a patient or an encounter). They power both clinical data capture (answers become [Observations](../clinical/observation.mdx)) and non-clinical structured data. The Django model is the **storage** layer; the Pydantic **resource specs** in `care/emr/resources/questionnaire*` define the real API schema — enums, the nested shape of the JSON fields, validation, and the read/write contracts.
+
+**Source (model):** [`care/emr/models/questionnaire.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/questionnaire.py)
+
+**Source (specs):**
+[`resources/questionnaire/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire/spec.py) ·
+[`resources/questionnaire/utils.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire/utils.py) ·
+[`resources/questionnaire/questionnaire_organization.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire/questionnaire_organization.py) ·
+[`resources/questionnaire_response/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire_response/spec.py) ·
+[`resources/questionnaire_response_template/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire_response_template/spec.py) ·
+[`resources/form_submission/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/form_submission/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Questionnaire` | A versioned form definition: questions, styling, subject type, and status |
+| `FormSubmission` | A submission of a questionnaire against a patient (and optional encounter) |
+| `QuestionnaireResponse` | A stored set of answers for a subject, optionally tied to a submission |
+| `QuestionnaireOrganization` | Scopes a questionnaire to an instance-level `Organization` |
+| `QuestionnaireFacilityOrganization` | Scopes a questionnaire to a `FacilityOrganization` |
+| `QuestionnaireResponseTemplate` | Reusable prefill template for questionnaire responses |
+
+All models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+## `Questionnaire` fields
+
+### Definition
+
+| Field | Type | Req | Notes |
+| --- | --- | --- | --- |
+| `version` | `CharField(255)` | yes | Version label. Write spec frozen to `"1.0"` |
+| `slug` | `CharField(255)` | yes | `unique`; defaults to a generated `uuid4`. Write spec validates `SlugType` (5–50 chars, URL-safe, must not shadow an internal questionnaire type) |
+| `title` | `CharField(255)` | yes | Display title; write spec rejects blank/whitespace and strips it |
+| `description` | `TextField` | no | Defaults to `""` |
+| `subject_type` | `CharField(255)` | yes | `SubjectType` enum — see [values](#subjecttype-values) |
+| `status` | `CharField(255)` | yes | `QuestionnaireStatus` enum — see [values](#questionnairestatus-values) |
+| `styling_metadata` | `JSONField` | no | Defaults to `{}`; opaque UI/layout hints, **no validation** performed |
+| `questions` | `JSONField` | yes | Defaults to `{}` at the DB; in practice a `list[Question]` tree — see [Question shape](#question-nested-shape) |
+| `organization_cache` | `ArrayField[int]` | — | Denormalized cache, maintained server-side (see [cache sync](#organization-cache-sync)) |
+| `internal_organization_cache` | `ArrayField[int]` | — | Denormalized cache, maintained server-side (see [cache sync](#organization-cache-sync)) |
+
+The write spec also carries a `type: str` field (default `"custom"`) that is **not** persisted as a model column.
+
+#### Organization scope caches
+
+These are **denormalized caches** of organization IDs maintained by the through-models below, used for access filtering without deep joins.
+
+| Field | Maintained by |
+| --- | --- |
+| `organization_cache` | `QuestionnaireOrganization.sync_questionnaire_cache()` — instance `Organization` IDs plus each org's `parent_cache` |
+| `internal_organization_cache` | `QuestionnaireFacilityOrganization.sync_questionnaire_cache()` — `FacilityOrganization` IDs plus each org's `parent_cache` |
+
+## Enum values
+
+### `QuestionnaireStatus` values
+
+Status of the form definition. Inspired by FHIR publication-status; once a questionnaire is `active` it should not be edited/deleted, only `retired`.
+
+| Value |
+| --- |
+| `active` |
+| `retired` |
+| `draft` |
+
+### `SubjectType` values
+
+The kind of resource a form is about.
+
+| Value |
+| --- |
+| `patient` |
+| `encounter` |
+
+### `QuestionType` values
+
+The `type` of each question (`Question.type`). Commented-out members (`open_choice`, `attachment`, `reference`) are not implemented.
+
+| Value | Notes |
+| --- | --- |
+| `group` | Container; must have ≥1 sub-question |
+| `boolean` | Validated against `true/false/1/0` on submit |
+| `decimal` | |
+| `integer` | |
+| `string` | |
+| `text` | Length capped by `settings.MAX_QUESTIONNAIRE_TEXT_RESPONSE_SIZE` on submit |
+| `display` | Display-only, no answer |
+| `date` | ISO date |
+| `dateTime` | Must include timezone on submit |
+| `time` | `%H:%M:%S` |
+| `choice` | Requires `answer_option` or `answer_value_set` |
+| `url` | Must have scheme + netloc |
+| `quantity` | Requires `answer_option` or `answer_value_set`; answers need a `unit` |
+| `structured` | Skipped by response validation |
+
+### `EnableOperator` values
+
+Operator for an `enable_when` condition (`EnableWhen.operator`).
+
+| Value |
+| --- |
+| `exists` |
+| `equals` |
+| `not_equals` |
+| `greater` |
+| `less` |
+| `greater_or_equals` |
+| `less_or_equals` |
+
+### `EnableBehavior` values
+
+How multiple `enable_when` conditions combine (`Question.enable_behavior`).
+
+| Value | Meaning |
+| --- | --- |
+| `all` | Enable only if all conditions pass (default on submit) |
+| `any` | Enable if any condition passes |
+
+### `DisabledDisplay` values
+
+How a disabled question renders (`Question.disabled_display`).
+
+| Value |
+| --- |
+| `hidden` |
+| `protected` |
+
+### `AnswerConstraint` values
+
+Whether free input outside options is allowed (`Question.answer_constraint`).
+
+| Value |
+| --- |
+| `required` |
+| `optional` |
+
+### `QuestionnaireResponseStatusChoices` values
+
+`QuestionnaireResponse.status` (model default `"completed"`).
+
+| Value |
+| --- |
+| `completed` |
+| `entered_in_error` |
+
+### `FormSubmissionStatusChoices` values
+
+`FormSubmission.status`.
+
+| Value |
+| --- |
+| `draft` |
+| `submitted` |
+| `entered_in_error` |
+
+## `Question` nested shape
+
+`Questionnaire.questions` is an opaque `JSONField`, but the API contract is a recursive list of `Question` (`QuestionnaireBaseSpec`). Fields, with their real types and validation:
+
+| Field | Type | Req | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `link_id` | `str` | yes | — | Human-readable link ID; must be unique across the whole tree |
+| `id` | `UUID4 \| UUID5` | no | `uuid4()` | Machine ID; must be unique across the whole tree |
+| `code` | `Coding` bound to value set `system-observation` | no | `None` | LOINC-bound; required to emit observations |
+| `collect_time` | `bool` | no | `false` | Collect a per-answer timestamp |
+| `collect_performer` | `bool` | no | `false` | Collect a `Performer` reference |
+| `text` | `str` | yes | — | Question text |
+| `description` | `str \| None` | no | `None` | |
+| `type` | `QuestionType` | yes | — | See [values](#questiontype-values) |
+| `structured_type` | `str \| None` | no | `None` | |
+| `enable_when` | `list[EnableWhen] \| None` | no | `None` | Conditional display rules |
+| `enable_behavior` | `EnableBehavior \| None` | no | `None` | |
+| `disabled_display` | `DisabledDisplay \| None` | no | `None` | |
+| `collect_body_site` | `bool \| None` | no | `None` | |
+| `collect_method` | `bool \| None` | no | `None` | |
+| `required` | `bool \| None` | no | `None` | |
+| `repeats` | `bool \| None` | no | `None` | Multi-select / repeating group |
+| `read_only` | `bool \| None` | no | `None` | |
+| `max_length` | `int \| None` | no | `None` | |
+| `answer_constraint` | `AnswerConstraint \| None` | no | `None` | |
+| `answer_option` | `list[AnswerOption] \| None` | no | `None` | Inline choices |
+| `answer_value_set` | `str \| None` | no | `None` | Slug of a `ValueSet`; validated to exist |
+| `is_observation` | `bool \| None` | no | `None` | Store answer as an observation |
+| `unit` | `Coding` bound to value set `system-ucum-units` | no | `None` | UCUM-bound unit |
+| `questions` | `list[Question]` | no | `[]` | Recursive children |
+| `formula` | `str \| None` | no | `None` | Client-side calculated field |
+| `styling_metadata` | `dict` | no | `{}` | |
+| `templates` | `list[TemplateConfig]` | no | `[]` | |
+| `is_component` | `bool` | no | `false` | Emit child answers as observation components |
+
+**`Question` validation** (`model_validator`, mode `after`):
+- `choice` / `quantity` type → must have `answer_option` **or** `answer_value_set`.
+- `group` type → must have at least one sub-question.
+- `answer_value_set` → the slug must reference an existing `ValueSet`.
+
+### Sub-specs of `Question`
+
+| Spec | Shape |
+| --- | --- |
+| `EnableWhen` | `{ question: str (link_id), operator: EnableOperator, answer: Any }` |
+| `AnswerOption` | `{ value: Any (non-blank, stripped), initial_selected: bool = false }` |
+| `Performer` | `{ performer_type: str, performer_id: str \| None, text: str \| None }` |
+| `TemplateConfig` | `{ name: str, content: str, structured_content: dict \| None, meta: dict \| None }` |
+
+## Resource specs (API schema)
+
+All specs build on `EMRResource` (`serialize` / `de_serialize`; read specs run `perform_extra_serialization`, write specs run `perform_extra_deserialization`).
+
+### Questionnaire
+
+| Spec | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `QuestionnaireBaseSpec` | shared | `__model__ = Questionnaire` |
+| `QuestionnaireWriteSpec` | write (base) | `version` (frozen `"1.0"`), `slug` (`SlugType`), `title`, `description`, `type` (`"custom"`), `status`, `subject_type`, `styling_metadata`, `questions`. Validates: slug uniqueness + not shadowing internal types, non-empty title, **unique `link_id`s and `id`s across the whole tree** |
+| `QuestionnaireSpec` | write · create | Extends write spec with `organizations: list[UUID4]` (`min_length=1`). `perform_extra_deserialization` stashes them on `obj._organizations` (the view links them via `QuestionnaireOrganization`, which rebuilds `organization_cache`) |
+| `QuestionnaireUpdateSpec` | write · update | Same as `QuestionnaireWriteSpec` (no `organizations`) |
+| `QuestionnaireReadSpec` | read · list/detail | `id` (= `external_id`), `slug`, `version`, `title`, `description`, `status`, `subject_type`, `styling_metadata`, `questions` (raw list), `created_by` / `updated_by` (resolved via `serialize_audit_users`) |
+
+Bound value sets on questions: `code` → `system-observation` (LOINC), `unit` → `system-ucum-units` (UCUM); `answer_value_set` references any [`ValueSet`](./valueset.mdx) by slug.
+
+### Questionnaire response
+
+| Spec | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `EMRQuestionnaireResponseBase` | shared | `__model__ = QuestionnaireResponse` |
+| `QuestionnaireResponseUpdate` | write · update | `status: QuestionnaireResponseStatusChoices` (default `completed`) — used to mark a response `entered_in_error` |
+| `QuestionnaireResponseReadSpec` | read · detail | `id`, `status`, `questionnaire` (nested `QuestionnaireReadSpec`), `subject_id`, `responses` (raw list), `encounter` (external id or `None`), `structured_responses`, `structured_response_type`, `created_by` / `updated_by` (`UserSpec`), `created_date`, `modified_date` |
+
+#### Submit request schema (not a DB write spec)
+
+The submit endpoint (`/questionnaire//submit/`) takes a plain Pydantic request, validated and persisted by `handle_response()` in `utils.py`:
+
+| Spec | Shape |
+| --- | --- |
+| `QuestionnaireSubmitRequest` | `{ resource_id: UUID4, encounter: UUID4 \| None, patient: UUID4, results: list[QuestionnaireSubmitResult], form_submission: UUID4 \| None }` |
+| `QuestionnaireSubmitResult` | `{ question_id: UUID4\|UUID5, body_site: Coding \| None, method: Coding \| None, taken_at: datetime \| None, values: list[QuestionnaireSubmitResultValue], note: str \| None, sub_results: list[list[QuestionnaireSubmitResult]] }` |
+| `QuestionnaireSubmitResultValue` | `{ value: str \| None, unit: Coding \| None, coding: Coding \| None }` |
+
+### Form submission
+
+| Spec | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `BaseFormSubmissionSpec` | shared | `__model__ = FormSubmission`, `id: UUID4 \| None` |
+| `FormSubmissionUpdateSpec` | write · update | `status: FormSubmissionStatusChoices`, `response_dump: dict` |
+| `FormSubmissionWriteSpec` | write · create | Adds `questionnaire: str` (slug), `patient: UUID4`, `encounter: UUID4 \| None`. `perform_extra_deserialization` resolves the questionnaire by slug, the patient/encounter by `external_id`, and when an encounter is given **overrides `patient` with the encounter's patient** |
+| `FormSubmissionReadSpec` | read · detail | `id`, `status`, `response_dump`, `created_date`, `modified_date`, `created_by` / `updated_by` (`UserSpec`) |
+
+### Questionnaire response template
+
+| Spec | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `QuestionnaireResponseTemplateBaseSpec` | shared | `__model__ = QuestionnaireResponseTemplate`, `id: UUID4 \| None`, `template_data: TemplateData`, `name: str`, `description: str = ""` |
+| `QuestionnaireResponseTemplateCreateSpec` | write · create | Adds `questionnaire: str \| None` (slug), `facility: UUID4 \| None`, `users: list[str]`, `facility_organizations: list[UUID4]`. Validates `facility` is required when `facility_organizations` is set. Resolves questionnaire/facility and recomputes `available_keys` from the non-empty keys of `template_data` |
+| `QuestionnaireResponseTemplateUpdateSpec` | write · update | `users`, `facility_organizations`; recomputes `available_keys` |
+| `QuestionnaireResponseTemplateReadSpec` | read · list | `created_date`, `modified_date`; `id` = `external_id` |
+| `QuestionnaireResponseTemplateRetrieveSpec` | read · detail | Extends read with resolved `users: list[dict]`, `facility_organizations: list[dict]`, `created_by`, `updated_by` |
+
+#### `template_data` (`TemplateData`) shape
+
+`QuestionnaireResponseTemplate.template_data` is an opaque `JSONField`; the spec validates it as:
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `medication_request` | `list[MedicationRequestTemplateSpec] \| None` | Extends [MedicationRequest](../medications/medication-request.mdx) write spec with `requested_product: str \| None` (validated against `ProductKnowledge.slug`) |
+| `questionnaire` | `list[QuestionnaireAnswer] \| None` | `QuestionnaireAnswer = { question_id: str, answer: dict, meta: dict }` |
+| `activity_definition` | `list[ActivityDefinitionTemplateSpec] \| None` | `{ slug (validated against ActivityDefinition), service_request: ServiceRequestUpdateSpec }` |
+| `meta` | `dict \| None` | |
+
+`available_keys` (model `ArrayField`) is server-maintained: on create/update it is set to the list of `template_data` keys whose value is truthy.
+
+## Related models
+
+### `FormSubmission`
+
+A submission event linking a questionnaire to a patient and (optionally) an encounter.
+
+```text
+questionnaire → FK Questionnaire (CASCADE)
+patient → FK emr.Patient (CASCADE)
+encounter → FK emr.Encounter (CASCADE, nullable)
+status → CharField(255) # FormSubmissionStatusChoices
+response_dump → JSONField (default {})
+```
+
+### `QuestionnaireResponse`
+
+The answers for a subject. The `questionnaire` FK is nullable, so responses can outlive or detach from a definition.
+
+```text
+questionnaire → FK Questionnaire (CASCADE, nullable)
+subject_id → UUIDField
+responses → JSONField (default []) # raw submitted results
+structured_responses → JSONField (default {}) # extracted/structured data
+structured_response_type → CharField (nullable)
+patient → FK emr.Patient (CASCADE)
+encounter → FK emr.Encounter (CASCADE, nullable)
+form_submission → FK FormSubmission (CASCADE, nullable)
+status → CharField(255), default "completed"
+```
+
+### `QuestionnaireOrganization` / `QuestionnaireFacilityOrganization`
+
+Through-models that scope a questionnaire to organizations. The first links to an instance-level `Organization`, the second to a `FacilityOrganization`.
+
+```text
+questionnaire → FK Questionnaire (CASCADE)
+organization → FK Organization | FacilityOrganization (CASCADE)
+```
+
+Each overrides `save()` to recompute the matching cache on the parent `Questionnaire` (see [Methods & save behaviour](#organization-cache-sync)). Their specs (`questionnaire_organization.py`) exclude both FKs from serialization (`__exclude__ = ["questionnaire", "organization"]`); the write spec accepts `organization: UUID4`, the read spec returns `organization: dict`.
+
+### `QuestionnaireResponseTemplate`
+
+A reusable template that prefills questionnaire responses, optionally scoped to a facility and to specific facility organizations / users.
+
+```text
+facility → FK facility.Facility (CASCADE, nullable)
+name → CharField(255)
+description → TextField (default "")
+template_data → JSONField (default {}) # TemplateData shape
+questionnaire → FK Questionnaire (CASCADE, nullable, default None)
+facility_organizations → ArrayField[int] (default [])
+users → ArrayField[int] (default [])
+available_keys → ArrayField[CharField(255)] (default []) # server-maintained
+```
+
+## Methods & save behaviour
+
+### `Questionnaire.get_questions_by_id()`
+
+Walks the `questions` tree (recursing into nested `questions`) and returns a `{ str(question_id): question }` dict. The result is memoized on the instance via `_questions_by_id_cache`. If `questions` is not a list, an empty dict is returned.
+
+### `QuestionnaireResponse.render_responses()`
+
+Joins stored `responses` against the live questionnaire definition. For each answer it looks up the question via `questionnaire.get_questions_by_id()` and returns a list of `{ "answer": ..., "question": ... }`. Returns an empty list when there are no responses or no linked questionnaire; answers whose `question_id` is no longer in the definition are skipped.
+
+### Submission handling — `handle_response()`
+
+The submit flow (`resources/questionnaire/utils.py`) runs server-side on every submission:
+
+1. Rejects the submission if the questionnaire `status != "active"` (`questionnaire_inactive`).
+2. Resolves `encounter` (required when `subject_type == "encounter"`) and `patient` by `external_id`.
+3. Rejects empty submissions (`questionnaire_empty`).
+4. Prunes the question tree by `enable_when` rules; answers to disabled questions raise `enable_when_failed`.
+5. Validates each answer against its `type` (type checks per [QuestionType](#questiontype-values)), `required`, `answer_value_set` (coding must belong to the value set), and `quantity` units. Any failures abort with an `errors` payload.
+6. Builds `ObservationSpec` objects for coded/group questions and creates a `QuestionnaireResponse` (raw `responses` = the dumped results).
+7. When an encounter is present, bulk-creates the derived [Observations](../clinical/observation.mdx), linked back to the response.
+
+### Organization cache sync
+
+`QuestionnaireOrganization.save()` and `QuestionnaireFacilityOrganization.save()` call `super().save()` then `sync_questionnaire_cache()`, which:
+
+1. Loads all through-rows for the parent questionnaire.
+2. Collects each linked organization's `id` plus its `parent_cache` (ancestor chain).
+3. De-duplicates the IDs.
+4. Writes them back via `questionnaire.save(update_fields=[...])` — `organization_cache` for instance orgs, `internal_organization_cache` for facility orgs.
+
+Expect a **second write** to the `Questionnaire` row whenever an organization link is saved.
+
+## API integration notes
+
+- Questionnaires expose CRUD plus a submit endpoint (`/questionnaire//submit/`). Create uses `QuestionnaireSpec` (requires ≥1 `organizations`); update uses `QuestionnaireUpdateSpec`; reads use `QuestionnaireReadSpec`.
+- `questions` and `styling_metadata` are open `JSONField`s at the DB layer, but the API validates `questions` against the recursive [`Question`](#question-nested-shape) spec (unique `link_id`/`id`, choice/quantity/group constraints, value-set existence). `styling_metadata` is never validated.
+- `organization_cache` / `internal_organization_cache` and `QuestionnaireResponseTemplate.available_keys` are platform-maintained — do not set them from clients; write through the through-models / `template_data`.
+- Submitting answers validates type/required/value-set server-side and, for clinical questions, materializes [Observations](../clinical/observation.mdx); a response can be voided by setting its status to `entered_in_error` via `QuestionnaireResponseUpdate`.
+- `FormSubmissionWriteSpec` resolves the questionnaire by slug and, when an encounter is supplied, forces the submission's patient to the encounter's patient.
+
+## Related
+
+- Source: [questionnaire.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/questionnaire.py)
+- Spec: [questionnaire/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire/spec.py) · [response spec](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire_response/spec.py) · [submit utils](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire/utils.py) · [form_submission spec](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/form_submission/spec.py) · [response template spec](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/questionnaire_response_template/spec.py)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Reference: [Questionnaire Response](./questionnaire-response.mdx) — submitted answers (`QuestionnaireResponse` / `FormSubmission`)
+- Reference: [Questionnaire Response Template](./questionnaire-response-template.mdx) — reusable pre-fill templates
+- Reference: [ValueSet](./valueset.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Observation](../clinical/observation.mdx)
+- Reference: [Organization](../facility/organization.mdx)
+- Reference: [MedicationRequest](../medications/medication-request.mdx)
+- Reference: [ServiceRequest](../clinical/service-request.mdx)
+- Reference: [ActivityDefinition](../clinical/activity-definition.mdx)
diff --git a/versioned_docs/version-3.1/references/forms/valueset.mdx b/versioned_docs/version-3.1/references/forms/valueset.mdx
new file mode 100644
index 0000000..a055449
--- /dev/null
+++ b/versioned_docs/version-3.1/references/forms/valueset.mdx
@@ -0,0 +1,196 @@
+---
+sidebar_position: 4
+---
+
+# Value Set
+
+Technical reference for the `ValueSet` module in Care EMR. A value set is a FHIR-aligned composition of coded concepts drawn from one or more code systems; it constrains the valid options a [questionnaire](../forms/questionnaire.mdx) can offer for a coded answer.
+
+The Django model is the **storage layer** — `compose` is an opaque `JSONField`. The actual structure (include/exclude clauses, filters, status enum, validation, and the read/write API schemas) lives in the Pydantic **resource specs**. This page documents both.
+
+**Source:**
+- Model: [`care/emr/models/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/valueset.py)
+- Resource spec: [`care/emr/resources/valueset/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/valueset/spec.py)
+- Compose schema: [`care/emr/resources/common/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/valueset.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ValueSet` | A named, FHIR-compatible composition of codes from one or more code systems |
+| `UserValueSetPreference` | Stores a user's favorited codes within a value set |
+| `RecentViewsManager` | Redis-backed helper for tracking a user's recently used codes (not a database model) |
+
+`ValueSet` and `UserValueSetPreference` extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, `history`/`meta` JSON, and soft-delete semantics). `RecentViewsManager` is a plain Python class backed by Redis, not a Django model.
+
+## `ValueSet` fields
+
+A value set is **not** stored as a flat list of codes. It is stored as a `compose` object describing *rules* (include/exclude clauses against code systems); the actual member codes are resolved in real time against terminology servers when the value set is searched or a code is looked up.
+
+| Field | Model type | Spec type | Required | Default | Notes |
+| --- | --- | --- | --- | --- | --- |
+| `slug` | `SlugField(255)` | `SlugType` | yes | — | `unique`, indexed. Public identifier. Spec constrains to min 5 / max 50 chars, URL-safe (`^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$`); must start/end alphanumeric. Uniqueness re-checked in spec; `system-` prefix reserved (see validation) |
+| `name` | `CharField(255)` | `str` | yes | — | Display name. Spec rejects blank/whitespace-only and strips surrounding whitespace |
+| `description` | `TextField` | `str` | yes (in spec) | `""` (model) | Free text. Model defaults to empty string; spec requires the field present |
+| `compose` | `JSONField` | `ValueSetCompose` | yes | `dict` | Composition rules — structured `include`/`exclude` clauses (see [`compose` shape](#compose-shape)). On write, persisted via `compose.model_dump(exclude_defaults=True, exclude_none=True)` |
+| `status` | `CharField(255)` | `ValueSetStatusOptions` | yes | — | Publication status enum (see [status values](#valuesetstatusoptions-values)) |
+| `is_system_defined` | `BooleanField` | `bool` | no | `False` | Marks value sets shipped/maintained by the platform rather than authored by a deployment |
+
+### `ValueSetStatusOptions` values
+
+Enum (`str`) defined in `resources/valueset/spec.py`. The model column is a free `CharField`, but the spec restricts writes to these values.
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Authored, not yet in use |
+| `active` | Published and usable |
+| `retired` | No longer recommended for use |
+| `unknown` | Status not known |
+
+### `compose` shape
+
+`compose` holds a `ValueSetCompose` structure (`care.emr.resources.common.valueset`). All nested models set `extra="forbid"` — unknown keys are rejected. Each clause targets a `system` (code system URI) and may select concepts directly or via filters.
+
+#### `ValueSetCompose`
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `id` | `str \| None` | no | `None` | Optional identifier |
+| `include` | `list[ValueSetInclude]` | yes | — | Inclusion clauses (at least the field must be present) |
+| `exclude` | `list[ValueSetInclude] \| None` | no | `None` | Exclusion clauses; same shape as `include` |
+| `property` | `list[str] \| None` | no | `None` | Properties to return for member concepts |
+
+#### `ValueSetInclude` (used for both `include` and `exclude`)
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `id` | `str \| None` | no | `None` | Optional identifier |
+| `system` | `str \| None` | no | `None` | Code system URI this clause targets |
+| `version` | `str \| None` | no | `None` | Optional code system version |
+| `concept` | `list[ValueSetConcept] \| None` | no | `None` | Explicitly pinned concepts |
+| `filter` | `list[ValueSetFilter] \| None` | no | `None` | Rule-based selection |
+
+Validation: a single clause may set **`concept` or `filter`, not both** (`check_concept_or_filter`, mode=after).
+
+#### `ValueSetConcept`
+
+| Field | Type | Required | Default |
+| --- | --- | --- | --- |
+| `id` | `str \| None` | no | `None` |
+| `code` | `str \| None` | no | `None` |
+| `display` | `str \| None` | no | `None` |
+
+#### `ValueSetFilter`
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `id` | `str \| None` | no | `None` | |
+| `property` | `str \| None` | no | `None` | The concept property to filter on |
+| `op` | `str \| None` | no | `None` | Filter operator — validated against the allowed list below |
+| `value` | `str \| None` | no | `None` | Value to compare against |
+
+##### `op` allowed values
+
+`op` is validated by `validate_op`; any other value raises. Allowed: `=`, `is-a`, `descendent-of`, `is-not-a`, `regex`, `in`, `not-in`, `generalizes`, `child-of`, `descendent-leaf`, `exists`.
+
+```text
+compose:
+ include:
+ - system:
+ version:
+ concept: [ { code, display } ] # pin concepts (mutually exclusive with filter)
+ filter: [ { property, op, value } ] # rule-based selection
+ exclude:
+ - ... same ValueSetInclude shape ...
+ property: [ ]
+```
+
+`create_composition()` regroups these clauses by `system` into `{ : {include: [...], exclude: [...]} }` so each system can be queried independently, dumping each clause with `exclude_defaults=True`.
+
+> Note: a second, simpler `ValueSet` BaseModel (`name`, `status`, `compose`) and `ValueSetConcept`/`ValueSetFilter`/`ValueSetInclude`/`ValueSetCompose` all live in `resources/common/valueset.py`. The persisted/API resource model is `ValueSetBaseSpec` in `resources/valueset/spec.py`, which reuses the same `ValueSetCompose`.
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`resources/base.py`), which provides `serialize` (DB object → pydantic, via `model_construct`) and `de_serialize` (pydantic → DB object). `ValueSet` does not set `__store_metadata__`, so spec fields map directly to model columns (no `meta` bag).
+
+| Spec class | Role | Fields exposed |
+| --- | --- | --- |
+| `ValueSetBaseSpec` | shared base (`__model__ = ValueSet`) | `id` (UUID4), `slug`, `name`, `description`, `compose`, `status`, `is_system_defined` |
+| `ValueSetSpec` | write · create & update | inherits base + validation + `perform_extra_deserialization` |
+| `ValueSetReadSpec` | read · list & detail | inherits base + `created_by`, `updated_by` (dicts) |
+
+### Validation (`ValueSetSpec`)
+
+| Rule | Where | Behaviour |
+| --- | --- | --- |
+| Name not empty | `validate_name` (field) | Rejects blank/whitespace-only; strips surrounding whitespace |
+| Slug unique | `validate_slug` (field) | Queries existing `ValueSet` rows; on update, excludes the current object (via `context["object"].id` when `context["is_update"]`); raises `"Slug must be unique"` otherwise |
+| Reserved slug | `validate_slug_system` (model, after) | If **not** `is_system_defined` and slug contains `"system-"`, raises `"Cannot create valueset with system like slug"` |
+| Slug format | `SlugType` | min 5 / max 50 chars, URL-safe, alphanumeric start/end |
+| `op` allowlist | `ValueSetFilter.validate_op` | See [op allowed values](#op-allowed-values) |
+| `concept` xor `filter` | `ValueSetInclude.check_concept_or_filter` | Both present → error |
+
+### Server-side serialization hooks
+
+| Hook | Spec | Effect |
+| --- | --- | --- |
+| `perform_extra_deserialization(is_update, obj)` | `ValueSetSpec` | On write, sets `obj.compose = self.compose.model_dump(exclude_defaults=True, exclude_none=True)` — normalizes the JSON stored in the model |
+| `perform_extra_serialization(mapping, obj)` | `ValueSetReadSpec` | On read, sets `mapping["id"] = obj.external_id` and populates `created_by` / `updated_by` via `serialize_audit_users` |
+
+`ValueSetSpec.model_rebuild()` is called at module load so the forward reference to `ValueSetCompose` resolves.
+
+## Related models
+
+### `UserValueSetPreference`
+
+Stores per-user favorited codes for a value set.
+
+```text
+user → FK users.User (CASCADE)
+valueset → FK emr.ValueSet (CASCADE)
+favorite_codes → JSONField (default=list)
+```
+
+`unique_together = ("user", "valueset")` — one preference row per user per value set. The class constant `MAX_FAVORITES` (default `50`, overridable via `settings.MAX_FAVORITES_FOR_VALUESET`) caps how many codes a user may favorite.
+
+### `RecentViewsManager`
+
+Not a database model — a classmethod-only helper that tracks a user's recently viewed codes in a Redis list (one list per `cache_key`). Used to surface recently used codes alongside favorites in pickers.
+
+| Member | Behaviour |
+| --- | --- |
+| `get_client()` | Lazily opens the `"default"` Redis connection |
+| `get_recent_views(cache_key)` | Returns the decoded list of recent code objects |
+| `add_recent_view(cache_key, code_obj)` | De-dupes by `code` (no-op if `code` missing), `LPUSH`es the entry, then `LTRIM`s to `MAX_RECENT_VIEW` |
+| `remove_recent_view(cache_key, code_obj)` | Removes any list entry matching `code` (no-op if `code` missing) |
+| `clear_recent_views(cache_key)` | Deletes the whole list |
+| `_remove_by_code(cache_key, code)` | Internal: scans the list and `LREM`s matching entries; malformed JSON entries are skipped |
+
+`MAX_RECENT_VIEW` defaults to `20` (overridable via `settings.MAX_RECENT_VIEW_FOR_VALUESET`). Entries are JSON-encoded; malformed entries are skipped on read/remove.
+
+## Methods & save behaviour
+
+`ValueSet` does not override `save()`/`delete()` (soft-delete is inherited from the base). Its notable methods drive terminology resolution:
+
+- `create_composition()` — converts `compose` (dict or `ValueSetCompose`) into a per-system map of `include`/`exclude` clauses, dumping each clause with `exclude_defaults=True`.
+- `search(search="", count=10, display_language=None)` — for each system in the composition, runs `ValueSetResource().filter(...).search()` (optionally filtered by `display_language`) and concatenates the results. Codes are fetched from the terminology server at request time; nothing is materialized into the value set.
+- `lookup(code)` — checks whether `code` is a member of the value set by running `ValueSetResource().filter(...).lookup(code)` against each system; returns `True` if **any** system matches.
+
+Because membership is resolved live, editing `compose` immediately changes which codes the value set yields — there is no expansion/cache to rebuild.
+
+## API integration notes
+
+- Value sets are exposed through Care's REST API and align with the FHIR `ValueSet` resource; writes go through `ValueSetSpec`, reads through `ValueSetReadSpec`. `search` powers code pickers and `lookup` validates submitted codes.
+- The model column `compose` is opaque JSON; always write through the `ValueSetCompose` schema (clauses, filters, `op` allowlist, `concept` xor `filter`) — `perform_extra_deserialization` re-dumps it with `exclude_defaults`/`exclude_none` so stored JSON stays normalized.
+- Value sets store composition *rules*, not expanded code lists. Members are queried in real time from terminology servers, so results can vary as upstream systems change.
+- `is_system_defined` value sets are platform-maintained; deployments should treat them as read-only and author their own value sets for local terminology. The `system-` slug prefix is reserved for system value sets.
+- `favorite_codes` (via `UserValueSetPreference`) and recent views (via `RecentViewsManager`/Redis) are per-user personalization, not part of the value set definition.
+
+## Related
+
+- Reference: [Questionnaire](../forms/questionnaire.mdx) — consumes value sets to constrain coded answers
+- Reference: [Observation definition](../clinical/observation-definition.mdx) — binds coded fields to value sets
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Source (model): [valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/valueset.py)
+- Source (spec): [resources/valueset/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/valueset/spec.py)
+- Source (compose): [resources/common/valueset.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/valueset.py)
diff --git a/versioned_docs/version-3.1/references/foundation/_category_.json b/versioned_docs/version-3.1/references/foundation/_category_.json
new file mode 100644
index 0000000..38962e1
--- /dev/null
+++ b/versioned_docs/version-3.1/references/foundation/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Foundation",
+ "position": 1,
+ "key": "foundation-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Foundation",
+ "description": "Shared base models and conventions every Care EMR model builds on — identifiers, audit fields, soft-delete, history, metadata, and facility-scoped slugs."
+ }
+}
diff --git a/versioned_docs/version-3.1/references/foundation/base-model.mdx b/versioned_docs/version-3.1/references/foundation/base-model.mdx
new file mode 100644
index 0000000..5b0fd62
--- /dev/null
+++ b/versioned_docs/version-3.1/references/foundation/base-model.mdx
@@ -0,0 +1,400 @@
+---
+sidebar_position: 1
+---
+
+# Base models & conventions
+
+Technical reference for the shared base layer that every Care EMR resource is built on. There are **two layers**:
+
+- **Storage layer** — abstract Django models in `care/emr/models` and `care/utils/models`. They supply opaque IDs, audit fields, soft-delete, history, slugs, and feature flags. Several columns are opaque `JSONField`s whose real structure is *not* visible in the model.
+- **API / implementation layer** — Pydantic resource specs built on `EMRResource` (`care/emr/resources/base.py`). They define enums, field validation, the structured shape of those JSON fields (via nested specs), and the read/write schemas. The shared structured types live in `care/emr/resources/common/` and are reused across nearly every resource.
+
+This page documents both layers and the shared common spec types. Concrete resources (for example [Patient](../clinical/patient.mdx)) inherit a storage base and define their own resource specs on top of `EMRResource`.
+
+**Source:**
+[`care/emr/models/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/base.py),
+[`care/utils/models/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/utils/models/base.py),
+[`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py),
+[`care/emr/resources/common/`](https://github.com/ohcnetwork/care/tree/develop/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`: they define no database table of their own. Concrete resource models inherit one of them and contribute the columns described below to their own table.
+
+The storage inheritance chain is:
+
+```text
+models.Model
+ └─ BaseModel (care/utils/models/base.py)
+ ├─ BaseFlag (care/utils/models/base.py)
+ └─ EMRBaseModel (care/emr/models/base.py)
+ └─ SlugBaseModel
+```
+
+The API specs are a parallel hierarchy rooted at `pydantic.BaseModel`:
+
+```text
+pydantic.BaseModel
+ └─ EMRResource (care/emr/resources/base.py)
+ └─ SpecBase / CreateSpec / ...ReadSpec / ...RetrieveSpec
+```
+
+---
+
+## Storage layer
+
+### `BaseModel` fields
+
+The root abstract model. Every other base — and therefore every persisted Care resource — inherits these 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`. Updated on every save |
+| `deleted` | `BooleanField` | yes | `False` | `db_index`. Soft-delete marker |
+
+`objects` is overridden to a [`BaseManager`](#basemanager) instance, so the default queryset only returns live rows.
+
+#### Soft delete
+
+`BaseModel.delete()` does not issue a SQL `DELETE`. It flips the flag and persists only that column:
+
+```python
+def delete(self, *args):
+ self.deleted = True
+ self.save(update_fields=["deleted"])
+```
+
+Rows are therefore retained for audit and referential integrity. To read 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. Extends `BaseModel`, so it also carries `external_id`, the timestamps, and `deleted`, plus the following:
+
+| 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 (audit / 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` templating 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 (for example value sets and definitions). It adds no new columns — concrete subclasses provide 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--` |
+| `calculate_slug_from_instance(slug)` | Class helper → `i-` |
+| `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:
+
+```text
+facility-scoped: f--
+instance-scoped: i-
+```
+
+`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`
+
+Default manager applied as `objects` on `BaseModel`. It filters out soft-deleted rows at the queryset level:
+
+```python
+def get_queryset(self):
+ return super().get_queryset().filter(deleted=False)
+```
+
+Any query through `Model.objects` is implicitly scoped to `deleted=False`.
+
+### `BaseFlag`
+
+Abstract base for per-entity feature-flag tables (for example facility or organization flags). It extends `BaseModel` and stores a single validated flag name, with all lookups served from the 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 `_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 class (`care/emr/resources/base.py`) that every resource spec extends. It converts between Django model instances and the API schema in both directions and centralises the create/read serialization rules.
+
+| 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 |
+
+**Spec naming convention.** Concrete resources expose a small set of specs built on `EMRResource`. Read these as a table per resource:
+
+| Spec class | Kind | Role |
+| --- | --- | --- |
+| `SpecBase` | shared | Common fields shared by the write/read specs |
+| `CreateSpec` | write · create | Request body for POST; `de_serialize` builds a new model instance |
+| `UpdateSpec` | write · update | Request body for PUT/PATCH; `de_serialize` updates an existing instance (`is_update=True`) |
+| `ListSpec` | read · list | Lightweight response for list endpoints |
+| `ReadSpec` / `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). Resource specs declare the binding (e.g. via `json_schema_extra={"slug": ""}` on the field) and validate the submitted `Coding` against it in `perform_extra_deserialization`. Status fields commonly maintain a server-side `status_history` appended on create/update. See each resource page for the 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`](#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 |
+
+Cross-field rule: 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. Used wherever a spec field accepts a phone number.
+
+---
+
+## Shared common specs
+
+`care/emr/resources/common/` holds the structured types reused across resources. These are the real shapes behind many of the opaque `JSONField`s 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` — 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](../billing/charge-item-definition.mdx), [Charge Item](../billing/charge-item.mdx)).
+
+`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 |
+
+Validation rules (model validators):
+
+- `tax_included_amount` is only allowed when `monetary_component_type == base`.
+- A `base` component must have no `conditions` and must set `amount`.
+- `amount` and `factor` are mutually exclusive (not both).
+- Either `amount` or `factor` must be present — unless `global_component` is set together with a `code`.
+
+#### `MonetaryComponentType` values
+
+| Value |
+| --- |
+| `base` |
+| `surcharge` |
+| `discount` |
+| `tax` |
+| `informational` |
+
+`MonetaryComponentsWithoutBase` (a `RootModel` over `list[MonetaryComponent]`) adds: 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: at most **one** `base` component.
+
+`MonetaryComponentDefinition` extends `MonetaryComponent` for definition-time use: 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](../forms/valueset.mdx).
+
+- `ValueSet` — `name: str` (required), `status: str | None`, `compose: ValueSetCompose` (required).
+- `ValueSetCompose` — `id`, `include: list[ValueSetInclude]` (required), `exclude: list[ValueSetInclude] | None`, `property: list[str] | None`.
+- `ValueSetInclude` — `id`, `system`, `version`, `concept: list[ValueSetConcept] | None`, `filter: list[ValueSetFilter] | None`. **`concept` and `filter` are mutually exclusive.**
+- `ValueSetConcept` — `id`, `code`, `display` (all optional).
+- `ValueSetFilter` — `id`, `property`, `op`, `value`. `op` must be one of: `=`, `is-a`, `descendent-of`, `is-not-a`, `regex`, `in`, `not-in`, `generalizes`, `child-of`, `descendent-leaf`, `exists`.
+
+All ValueSet types use `extra="forbid"`.
+
+### `MailTypeChoices`
+
+Defined in `common/mail_type.py`.
+
+| Name | Value |
+| --- | --- |
+| `create` | `create_password` |
+| `reset` | `reset_password` |
+
+---
+
+## Methods & save behaviour
+
+- `BaseModel.delete()` performs a soft delete (sets `deleted=True`, saves only that field) instead of a hard `DELETE`.
+- `BaseFlag.save()` validates `flag` against `FlagRegistry`, then deletes the single-flag and all-flags cache entries for the entity before persisting, keeping the cached lookups consistent.
+- `BaseFlag.check_entity_has_flag` / `get_all_flags` populate those caches on read with a 1-day TTL (`FLAGS_CACHE_TTL = 60 * 60 * 24`).
+- `SlugBaseModel` exposes slug encode/parse helpers but does not override `save()`; subclasses decide when to call `calculate_slug()`.
+- `EMRResource.de_serialize` is where write-time side effects happen — subclasses override `perform_extra_deserialization` to append to status histories, resolve FKs from `external_id`, and validate coded fields against bound value sets.
+- `cacheable(...)` (in `base.py`) is a decorator that marks a spec cacheable and wires a `post_save` signal to invalidate the per-instance serializer cache; `model_from_cache` then serves serialized specs from cache by `pk` / `id` / `external_id`.
+
+## API integration notes
+
+- `external_id` (UUID) is the identifier used across Care's REST API and FHIR resources; the integer primary key is internal and never exposed. `EMRResource.serialize` surfaces it as `id`, and `de_serialize` refuses to write `id` / `external_id` from request bodies.
+- Deletes are soft — a resource removed through the API still exists in the database with `deleted=True` and is hidden by the default manager.
+- `history` is not returned by standard endpoints; every version (including migration-time changes) is stored flattened with its performer and served through a separate, on-request audit API.
+- `meta` is the supported place for system metadata without schema migrations; specs with `__store_metadata__ = True` round-trip extra fields through it. `created_by` / `updated_by` are platform-maintained audit fields and should not be set directly by clients.
+- Periods sent on write (`PeriodSpec`) must be timezone-aware and ordered (`start ≤ end`); the read-side `Period` type does not enforce this.
+- Feature-flag state (`BaseFlag` subclasses) is read through the cached classmethods rather than queried row-by-row.
+
+## Related
+
+- Reference: [Patient](../clinical/patient.mdx) — a representative `EMRBaseModel` + `EMRResource` subclass
+- Reference: [ValueSet](../forms/valueset.mdx) — uses `SlugBaseModel` and the ValueSet compose types
+- Reference: [Charge Item Definition](../billing/charge-item-definition.mdx) — uses `MonetaryComponent` pricing types
+- Source: [`base.py` (emr models)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/base.py)
+- Source: [`base.py` (utils models)](https://github.com/ohcnetwork/care/blob/develop/care/utils/models/base.py)
+- Source: [`base.py` (resources / `EMRResource`)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
+- Source: [`common/` shared spec types](https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/common)
diff --git a/versioned_docs/version-3.1/references/medications/_category_.json b/versioned_docs/version-3.1/references/medications/_category_.json
new file mode 100644
index 0000000..2218e89
--- /dev/null
+++ b/versioned_docs/version-3.1/references/medications/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Medications",
+ "position": 3,
+ "key": "medications-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Medications",
+ "description": "Prescribing, administration, dispensing, and statement of medications across an encounter."
+ }
+}
diff --git a/versioned_docs/version-3.1/references/medications/medication-administration.mdx b/versioned_docs/version-3.1/references/medications/medication-administration.mdx
new file mode 100644
index 0000000..e0b7118
--- /dev/null
+++ b/versioned_docs/version-3.1/references/medications/medication-administration.mdx
@@ -0,0 +1,213 @@
+---
+sidebar_position: 2
+---
+
+# Medication Administration
+
+Technical reference for the `MedicationAdministration` module in Care EMR.
+
+`MedicationAdministration` records that a medication was (or was attempted to be) given to a patient — the actual administration event that fulfils a [`MedicationRequest`](./medication-request.mdx). The Django model is the storage layer; several of its fields are opaque `JSONField`s whose real structure is defined by the Pydantic **resource specs** in `care/emr/resources/medication/administration/`. This page documents both layers.
+
+**Source:**
+- Model: [`care/emr/models/medication_administration.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_administration.py)
+- Spec: [`care/emr/resources/medication/administration/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/administration/spec.py)
+- Value sets: [`care/emr/resources/medication/valueset/`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/valueset)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `MedicationAdministration` | Records that a medication was (or was attempted to be) given to a patient — the actual administration event for a [`MedicationRequest`](./medication-request.mdx) |
+
+`MedicationAdministration` extends `EMRBaseModel` (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics — see [Base model](../foundation/base-model.mdx)). It maps to the FHIR [`MedicationAdministration`](https://hl7.org/fhir/medicationadministration.html) resource and closes the loop on the prescriber's intent captured in a `MedicationRequest`.
+
+## `MedicationAdministration` fields
+
+Types below reflect the **storage** column type and, where the column is a `JSONField`, the **structured shape** enforced by the resource spec.
+
+### Status & classification
+
+| Field | Storage | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(100)` | `MedicationAdministrationStatus` enum | Required. Administration event status — see [enum values](#medicationadministrationstatus-values) |
+| `status_reason` | `JSONField` (null) | `Coding` bound to `system-medication` value set | Optional. Coded reason for the status. NB: the spec binds this to the medication value set, not a "not given" reason set |
+| `category` | `CharField(100)` (null) | `MedicationAdministrationCategory` enum | Optional. Administration setting — see [enum values](#medicationadministrationcategory-values) |
+
+### Medication & product
+
+| Field | Storage | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `medication` | `JSONField` (default `{}`) | `Coding` bound to `system-medication` value set | Optional in spec. `{ system, version?, code, display? }`. Mutually exclusive with `administered_product` (model validator) |
+| `administered_product` | `FK → ProductKnowledge` (null, `CASCADE`) | `UUID4` (write) / nested `ProductKnowledgeReadSpec` (read) | Optional. The specific catalog product administered. Cannot be set together with `medication` |
+| `dosage` | `JSONField` (null) | `Dosage` nested spec | Optional. Dose, rate, route, site, and method actually administered — see [`Dosage`](#dosage-nested-spec) |
+
+### Subject & context
+
+| Field | Storage | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `patient` | `FK → Patient` (`CASCADE`) | not in spec (server-derived) | The patient who received the medication. Set server-side from the encounter's patient on create — not accepted from the client |
+| `encounter` | `FK → Encounter` (null, `CASCADE`) | `UUID4` (required, write) | The encounter during which the medication was administered. Validated to exist; read schema returns the encounter's `external_id` |
+| `request` | `FK → MedicationRequest` (null, `CASCADE`) | `UUID4` (required, write) | The order this administration fulfils. Validated to exist; read schema returns the request's `external_id` |
+
+### Timing & attribution
+
+| Field | Storage | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `authored_on` | `DateTimeField` (null) | `datetime` | Optional. When the administration record was authored |
+| `occurrence_period_start` | `DateTimeField` (default `datetime.now`) | `datetime` (required) | Start of the administration. Required by the create/read spec |
+| `occurrence_period_end` | `DateTimeField` (null) | `datetime` | Optional. End of the administration (for infusions / extended doses). Updatable via `MedicationAdministrationUpdateSpec` |
+| `recorded` | `DateTimeField` (null) | `datetime` | Optional. When the event was recorded into the system |
+| `performer` | `JSONField` (default `[]`) | `list[MedicationAdministrationPerformer]` | Optional. Actors who performed the administration and their function — see [`MedicationAdministrationPerformer`](#medicationadministrationperformer-nested-spec) |
+| `note` | `TextField` (null) | `str` | Optional. Free-text annotation. Updatable via `MedicationAdministrationUpdateSpec` |
+
+## Enum values
+
+### `MedicationAdministrationStatus` values
+
+`str` enum (`care/emr/resources/medication/administration/spec.py`). Note these use underscores (e.g. `not_done`), not the FHIR hyphenated forms.
+
+| Value |
+| --- |
+| `completed` |
+| `not_done` |
+| `entered_in_error` |
+| `stopped` |
+| `in_progress` |
+| `on_hold` |
+| `unknown` |
+| `cancelled` |
+
+### `MedicationAdministrationCategory` values
+
+| Value |
+| --- |
+| `inpatient` |
+| `outpatient` |
+| `community` |
+| `discharge` |
+
+### `MedicationAdministrationPerformerFunction` values
+
+| Value |
+| --- |
+| `performer` |
+| `verifier` |
+| `witness` |
+
+## Nested specs (JSON-field shapes)
+
+### `Dosage` (nested spec)
+
+Structured shape of the `dosage` `JSONField`. All fields optional.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `text` | `str \| None` | Free-text dosage instructions |
+| `site` | `Coding \| None` | Bound to `system-body-site` value set (SNOMED CT `<< 91723000`) |
+| `route` | `Coding \| None` | Bound to `system-route` value set (SNOMED CT `<< 284009009`) |
+| `method` | `Coding \| None` | Bound to `system-administration-method` value set (SNOMED CT `<< 736665006`) |
+| `dose` | `Quantity \| None` | The amount of medication administered |
+| `rate` | `Quantity \| None` | The speed of administration |
+
+`Quantity` shape (`care/emr/resources/common/quantity.py`, `extra="forbid"`): `{ value: Decimal? (max_digits 20, decimal_places 6), unit: Coding?, code: Coding?, meta: dict? }`.
+
+`Coding` shape (`care/emr/resources/common/coding.py`, `extra="forbid"`): `{ system: str?, version: str?, code: str (required), display: str? }`.
+
+### `MedicationAdministrationPerformer` (nested spec)
+
+Element shape of the `performer` `JSONField` list.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `actor` | `UUID4` (required) | The user who performed the administration. Validated server-side to reference an existing [`User`](../access/user.mdx) (`field_validator` raises "User not found") |
+| `function` | `MedicationAdministrationPerformerFunction \| None` | The performer's function — see [enum values](#medicationadministrationperformerfunction-values) |
+
+## Bound value sets
+
+Coded fields bind to Care system value sets (SNOMED CT). Validation rejects codes outside the bound set via `ValueSetBoundCoding`.
+
+| Field | Value set | Slug | SNOMED CT constraint |
+| --- | --- | --- | --- |
+| `medication`, `status_reason` | Medication | `system-medication` | `<< 763158003` (Medicinal product) |
+| `dosage.site` | Body Site | `system-body-site` | `is-a 91723000` |
+| `dosage.route` | Route | `system-route` | `is-a 284009009` |
+| `dosage.method` | Administration Method | `system-administration-method` | `is-a 736665006` |
+
+Other medication value sets defined alongside (not referenced by this resource's specs): Medication Not Given Reason (`system-medication-not-given`, `is-a 242990004` + `is-a 182895007`), Additional Instruction (`system-additional-instruction`, `is-a 419492006`), As Needed (`system-as-needed-reason`, `is-a 404684003`).
+
+## Resource specs (API schema)
+
+All specs build on `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB object → read schema) and `de_serialize` (write schema → DB object) with the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__exclude__` lists fields handled manually by those hooks rather than copied field-for-field.
+
+| Spec class | Role | Key fields / behaviour |
+| --- | --- | --- |
+| `BaseMedicationAdministrationSpec` | shared base | All common fields: `status`, `status_reason`, `category`, `medication`, `authored_on`, `occurrence_period_start`, `occurrence_period_end`, `recorded`, `encounter`, `request`, `performer`, `dosage`, `note`. `__exclude__ = ["patient", "encounter", "request", "administered_product"]` |
+| `MedicationAdministrationSpec` | write · create | Extends base; adds `administered_product: UUID4 \| None`. Validates `encounter` and `request` exist; rejects `medication` + `administered_product` set together |
+| `MedicationAdministrationUpdateSpec` | write · update | Narrow update schema: only `status`, `note`, `occurrence_period_end`. `__exclude__ = ["patient", "encounter", "request"]` — those are immutable after create |
+| `MedicationAdministrationReadSpec` | read · detail/list | Extends base; adds audit fields `created_by`, `updated_by`, `created_date`, `modified_date`, and nested `administered_product: dict` |
+| `Dosage` | nested | Shape of the `dosage` JSON field (see above) |
+| `MedicationAdministrationPerformer` | nested | Element of the `performer` JSON list (see above) |
+
+### Validation rules
+
+- **Mutual exclusion** (`MedicationAdministrationSpec.validate_administered_product`, `model_validator(mode="after")`): `medication` and `administered_product` cannot both be set.
+- **Existence checks** (field validators, create): `encounter` must reference an existing `Encounter`; `request` must reference an existing `MedicationRequest`; each `performer.actor` must reference an existing `User`.
+- **Bound codings**: `medication`, `status_reason`, and `dosage.{site,route,method}` are validated against their bound value sets; unknown codes raise.
+- **Required on create**: `status`, `occurrence_period_start`, `encounter`, `request`.
+
+### Server-maintained behaviour
+
+`MedicationAdministrationSpec.perform_extra_deserialization` (create only, `is_update == False`):
+
+- Resolves `encounter` from the supplied `external_id` and sets `obj.encounter`.
+- Derives `obj.patient` from `obj.encounter.patient` — `patient` is never taken from the client.
+- Resolves `request` from its `external_id` and sets `obj.request`.
+- If `administered_product` is supplied, resolves the `ProductKnowledge` and sets `obj.administered_product`.
+
+`MedicationAdministrationReadSpec.perform_extra_serialization`:
+
+- Sets `id` to `external_id`; flattens `encounter` and `request` to their `external_id`s.
+- Serializes `administered_product` (when present) via `ProductKnowledgeReadSpec`.
+- Adds `created_by` / `updated_by` via `serialize_audit_users`.
+
+## Related models
+
+`administered_product` references the supply-side catalog rather than carrying a nested record:
+
+```text
+administered_product → FK ProductKnowledge (nullable, CASCADE)
+patient → FK Patient (CASCADE)
+encounter → FK Encounter (nullable, CASCADE)
+request → FK MedicationRequest (nullable, CASCADE)
+```
+
+`medication`, `dosage`, `status_reason`, and `performer` are stored inline as `JSONField`s rather than as separate tables, mirroring the FHIR `MedicationAdministration` structure; their real shape comes from the resource specs above.
+
+## Methods & save behaviour
+
+- `EMRResource.serialize(obj)` builds a read schema from a DB row, copying mapped fields then calling `perform_extra_serialization` (sets `id`, flattens FK references, serializes nested `administered_product`, adds audit users).
+- `EMRResource.de_serialize(obj?)` builds/updates a DB row from a write schema using `model_dump(exclude_defaults=True)`, then calls `perform_extra_deserialization`. On create it resolves and assigns `encounter`, `patient`, `request`, and `administered_product`.
+- Audit and soft-delete fields (`external_id`, `created_by`, `created_date`, `modified_date`, `deleted`, etc.) are inherited from `EMRBaseModel` and maintained by the platform.
+
+## API integration notes
+
+- Write requests use `MedicationAdministrationSpec` (create) and `MedicationAdministrationUpdateSpec` (update); reads return `MedicationAdministrationReadSpec`. Field names mostly match the model; `occurrence_period_start` / `occurrence_period_end` map to the FHIR `occurence[x]` period.
+- Send coded fields (`medication`, `status_reason`, `dosage.site/route/method`) as `Coding` objects (`system` / `code` / `display`) drawn from the bound value sets — free strings and out-of-set codes are rejected.
+- `status` values use underscores (`in_progress`, `not_done`, `on_hold`, `entered_in_error`) — not the FHIR hyphenated spellings.
+- Provide `encounter` and `request` as `external_id` UUIDs; `patient` is derived server-side from the encounter and must not be sent.
+- Set either `medication` or `administered_product`, never both. `administered_product` is the bridge to inventory — set it when a specific catalogued product was used so administration can be reconciled against supply.
+- After create, only `status`, `note`, and `occurrence_period_end` are updatable; `encounter`, `request`, and `patient` are immutable.
+
+## Related
+
+- Reference: [Medication Request](./medication-request.mdx)
+- Reference: [Medication Dispense](./medication-dispense.mdx)
+- Reference: [Medication Statement](./medication-statement.mdx)
+- Reference: [Product Knowledge](../supply/product-knowledge.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [User](../access/user.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [medication_administration.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_administration.py)
+- Source: [administration/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/administration/spec.py)
+- Source: [medication value sets on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/valueset)
diff --git a/versioned_docs/version-3.1/references/medications/medication-dispense.mdx b/versioned_docs/version-3.1/references/medications/medication-dispense.mdx
new file mode 100644
index 0000000..0d54c28
--- /dev/null
+++ b/versioned_docs/version-3.1/references/medications/medication-dispense.mdx
@@ -0,0 +1,330 @@
+---
+sidebar_position: 3
+---
+
+# Medication Dispense
+
+Technical reference for the `MedicationDispense` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/medication_dispense.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_dispense.py)
+- Spec: [`care/emr/resources/medication/dispense/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/dispense/spec.py)
+- Spec: [`care/emr/resources/medication/dispense/dispense_order.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/dispense/dispense_order.py)
+- Viewset: [`care/emr/api/viewsets/medication_dispense.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/medication_dispense.py)
+
+`MedicationDispense` records products that have been dispensed to a patient. It both completes the workflow started by a [Medication Request](./medication-request.mdx) and drives inventory consumption within a location. It applies to inpatient settings (items dispensed and held in the room, refilled as needed) and outpatient settings (dispensed after an encounter is completed). A dispense may optionally carry a [Charge Item](../billing/charge-item.mdx) for billing.
+
+The Django model is the **storage** layer — several fields are opaque `JSONField`s. The Pydantic **resource specs** (`care/emr/resources/medication/dispense/`) define the API schema: enums, JSON-field shapes, validation, and the read/write contracts. Where the two disagree, the spec is the source of truth (e.g. `status` stores enum values with underscores such as `in_progress`, not the FHIR hyphenated forms).
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `MedicationDispense` | A single dispense event: a quantity of one inventory item handed to a patient |
+| `DispenseOrder` | Groups dispense events at a location into a named order with a shared status |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+## `MedicationDispense` fields
+
+### Status & classification
+
+| Field | Model type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(100)` | `MedicationDispenseStatus` | Required. See [status values](#medicationdispensestatus-values). Cancelling statuses block further updates |
+| `not_performed_reason` | `CharField(100)`, null | `MedicationDispenseNotPerformedReason \| None` | Coded reason a dispense was not performed. See [values](#medicationdispensenotperformedreason-values) |
+| `category` | `CharField(100)`, null | `MedicationDispenseCategory \| None` | Setting/location. See [values](#medicationdispensecategory-values) |
+
+### Timing & instructions
+
+| Field | Model type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `when_prepared` | `DateTimeField`, null | `datetime \| None` | When the product was prepared |
+| `when_handed_over` | `DateTimeField`, null | `datetime \| None` | When the product was handed to the patient |
+| `note` | `TextField`, null | `str \| None` | Free-text annotation |
+| `dosage_instruction` | `JSONField` (default `list`) | `list[DosageInstruction]` (default `[]`) | List of structured dosage directions. See [`DosageInstruction` shape](#dosageinstruction-shape) |
+| `substitution` | `JSONField` (default `dict`) | `MedicationDispenseSubstitution \| None` | Substitution record. See [shape](#medicationdispensesubstitution-shape) |
+
+### Quantities
+
+| Field | Model type | Spec type | Notes |
+| --- | --- | --- | --- |
+| `quantity` | `DecimalField(20, 6)` | `Decimal` (write: `max_digits=20, decimal_places=0`) | Required. Amount dispensed. Validated against inventory stock on create |
+| `days_supply` | `DecimalField(20, 6)`, null | `Decimal \| None` (`max_digits=20, decimal_places=0`) | Expected number of days the dispensed amount lasts |
+
+### Relationships
+
+| Field | Model type | Notes |
+| --- | --- | --- |
+| `encounter` | `FK → Encounter` | `CASCADE`. Resolved from `encounter` UUID on write; `patient` is derived from it (not sent directly) |
+| `patient` | `FK → Patient` | `CASCADE`. Server-set from `encounter.patient` |
+| `location` | `FK → FacilityLocation` | `CASCADE`. Must belong to the encounter's facility |
+| `authorizing_request` | `FK → MedicationRequest` | `SET_NULL`, null. The prescription this dispense fulfils; must be on the same encounter. Cleared server-side when the dispense is cancelled |
+| `item` | `FK → InventoryItem` | `CASCADE`. Stock item consumed; must be in a location of the encounter's facility |
+| `charge_item` | `FK → ChargeItem` | `CASCADE`, null. Server-created from the product's charge item definition when one exists |
+| `order` | `FK → DispenseOrder` | `CASCADE`, null. Parent dispense order, when grouped |
+
+`patient`, `encounter`, `authorizing_request`, `item`, and `location` are listed in `__exclude__` on the spec base — they are resolved manually in `perform_extra_deserialization` rather than copied straight off the Pydantic object.
+
+## Enum values
+
+### `MedicationDispenseStatus` values
+
+From `MedicationDispenseStatus` (`spec.py`).
+
+| Value |
+| --- |
+| `preparation` |
+| `in_progress` |
+| `cancelled` |
+| `on_hold` |
+| `completed` |
+| `entered_in_error` |
+| `stopped` |
+| `declined` |
+
+The set `MEDICATION_DISPENSE_CANCELLED_STATUSES = [cancelled, entered_in_error, stopped, declined]` is treated as terminal/cancelling: no updates are allowed once a dispense is in one of these, and transitioning into one triggers charge-item cancellation (see [Methods & save behaviour](#methods--save-behaviour)).
+
+### `MedicationDispenseNotPerformedReason` values
+
+From `MedicationDispenseNotPerformedReason` (`spec.py`). Coded reasons (FHIR `medicationdispense-status-reason`).
+
+| Value | Meaning |
+| --- | --- |
+| `outofstock` | Out of stock |
+| `washout` | Washout |
+| `surg` | Surgery |
+| `sintol` | Sensitivity / intolerance to drug |
+| `sddi` | Drug interaction |
+| `sdupther` | Duplicate therapy |
+| `saig` | Allergy to ingredient of medication |
+| `preg` | Patient pregnant |
+
+### `MedicationDispenseCategory` values
+
+From `MedicationDispenseCategory` (`spec.py`).
+
+| Value |
+| --- |
+| `inpatient` |
+| `outpatient` |
+| `community` |
+| `discharge` |
+
+### `SubstitutionType` values
+
+From `SubstitutionType` (`spec.py`) — used in `substitution.substitution_type`.
+
+| Value | Meaning |
+| --- | --- |
+| `E` | Equivalent |
+| `EC` | Equivalent composition |
+| `BC` | Brand composition |
+| `G` | Generic composition |
+| `TE` | Therapeutic alternative |
+| `TB` | Therapeutic brand |
+| `TG` | Therapeutic generic |
+| `F` | Formulary |
+| `N` | None |
+
+### `SubstitutionReason` values
+
+From `SubstitutionReason` (`spec.py`) — used in `substitution.reason`.
+
+| Value | Meaning |
+| --- | --- |
+| `CT` | Continuing therapy |
+| `FP` | Formulary policy |
+| `OS` | Out of stock |
+| `RR` | Regulatory requirement |
+
+## JSON field shapes
+
+### `MedicationDispenseSubstitution` shape
+
+`substitution` field (`MedicationDispenseSubstitution`). All three fields required when `substitution` is present:
+
+```text
+substitution = {
+ was_substituted: bool # required
+ substitution_type: SubstitutionType # required; see SubstitutionType values
+ reason: SubstitutionReason # required; see SubstitutionReason values
+}
+```
+
+### `DosageInstruction` shape
+
+`dosage_instruction` is a `list[DosageInstruction]`. `DosageInstruction` is reused from the [Medication Request](./medication-request.mdx) spec (`care/emr/resources/medication/request/spec.py`):
+
+```text
+DosageInstruction {
+ sequence: int | None
+ text: str | None
+ additional_instruction: list[Coding]@system-additional-instruction | None
+ patient_instruction: str | None
+ timing: Timing | None
+ as_needed_boolean: bool # required
+ as_needed_for: Coding@system-as-needed-reason | None
+ site: Coding@system-body-site | None
+ route: Coding@system-route | None
+ method: Coding@system-administration-method | None
+ dose_and_rate: DoseAndRate | None
+ max_dose_per_period: DoseRange | None
+}
+
+Timing { repeat: TimingRepeat, code: Coding | None }
+TimingRepeat{ frequency: int, period: Decimal(20,0), period_unit: TimingUnit, bounds_duration: TimingQuantity }
+TimingQuantity { value: Decimal(20,0), unit: TimingUnit }
+DoseAndRate { type: DoseType, dose_range: DoseRange | None, dose_quantity: DosageQuantity | None }
+DoseRange { low: DosageQuantity, high: DosageQuantity }
+DosageQuantity { value: Decimal(20,6), unit: Coding }
+```
+
+`Coding@` denotes a `Coding` bound to a Care value set (`ValueSetBoundCoding`); `Coding = { system?, version?, code (required), display? }`. `TimingUnit ∈ {s, min, h, d, wk, mo, a}`; `DoseType ∈ {ordered, calculated}`. The shared `PeriodSpec` (from `base.py`) — `{ start: datetime, end: datetime }`, both tz-aware, `start ≤ end` — is the standard period shape used elsewhere; `MedicationDispense` itself does not expose a period field.
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`base.py`) and use its `serialize` / `de_serialize` plumbing; the `serialize`-side data is assembled in `perform_extra_serialization`, and write-side resolution/side effects happen in `perform_extra_deserialization`.
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BaseMedicationDispenseSpec` | shared base | `__model__ = MedicationDispense`. Exposes `status`, `not_performed_reason`, `category`, `when_prepared`, `when_handed_over`, `note`, `dosage_instruction`, `substitution`. Excludes the FK fields |
+| `MedicationDispenseWriteSpec` | write · create | Adds `encounter`, `location`, `authorizing_request`, `item` (UUIDs), `quantity`, `days_supply`, `fully_dispensed`, `order`, `create_dispense_order`. Resolves FKs and runs create-time logic |
+| `MedicationDispenseUpdateSpec` | write · update | Base fields plus `fully_dispensed` and `order`. Cannot re-point `encounter`/`item`/`location` |
+| `MedicationDispenseReadSpec` | read · list | Serializes nested `item`, `charge_item`, `location`, `authorizing_request`, `order`; adds `created_date`, `modified_date` |
+| `MedicationDispenseRetrieveSpec` | read · detail | Extends `ReadSpec`, additionally serializes the full `encounter` |
+| `MedicationDispenseSubstitution` | nested | Shape of the `substitution` JSON field |
+| `CreateDispenseOrder` | nested (write) | Inline order creation payload — see below |
+
+### Write-spec fields
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `encounter` | `UUID4` | yes | Resolved to `Encounter`; `patient` derived from it |
+| `location` | `UUID4` | yes | Must be in `encounter.facility` |
+| `item` | `UUID4` | yes | `InventoryItem` in a location of `encounter.facility` |
+| `authorizing_request` | `UUID4 \| None` | no | `MedicationRequest` on the same encounter |
+| `quantity` | `Decimal` | yes | Write spec constrains to `decimal_places=0` (whole units) |
+| `days_supply` | `Decimal \| None` | no | `decimal_places=0` |
+| `fully_dispensed` | `bool \| None` | no | Drives the authorizing request's `dispense_status` (see below). Stashed on `instance._fully_dispensed`, not persisted on `MedicationDispense` |
+| `order` | `UUID4 \| None` | no | Existing `DispenseOrder` to attach to |
+| `create_dispense_order` | `CreateDispenseOrder \| None` | no | Inline order creation (get-or-create) |
+
+**Validation:** `validate_prescription` rejects sending both `order` and `create_dispense_order`.
+
+### `CreateDispenseOrder`
+
+Inline order payload on the write spec. When the matching order (by `alternate_identifier` + `patient` + `location`) does not exist it is created; if it exists but its status is not `draft`/`in_progress`, the request is rejected (`"Prescription is not active"`).
+
+```text
+CreateDispenseOrder {
+ name: str | None
+ note: str | None
+ alternate_identifier: str # required
+ status: CreateDispenseOrderStatusOptions # default "draft"
+}
+```
+
+`CreateDispenseOrderStatusOptions ∈ {draft, in_progress}`.
+
+### Bound value sets
+
+`dosage_instruction` codings bind to Care value sets (all SNOMED CT, registered as systems):
+
+| Field (within `DosageInstruction`) | Value set slug |
+| --- | --- |
+| `additional_instruction` | `system-additional-instruction` |
+| `as_needed_for` | `system-as-needed-reason` |
+| `site` | `system-body-site` |
+| `route` | `system-route` |
+| `method` | `system-administration-method` |
+
+The dispense-level `not_performed_reason` aligns with the `system-medication-not-given` value set (`medication_not_given_reason.py`) but is stored/validated as the `MedicationDispenseNotPerformedReason` enum, not a bound `Coding`.
+
+## Related models
+
+### `DispenseOrder`
+
+Groups one or more `MedicationDispense` events at a location into a named order with a single status.
+
+```text
+location → FK FacilityLocation (CASCADE)
+patient → FK Patient (CASCADE)
+facility → FK Facility (CASCADE)
+name → CharField(255), nullable
+status → CharField(255)
+note → TextField, nullable
+tags → ArrayField[int], default []
+alternate_identifier → CharField(100), nullable
+```
+
+`DispenseOrder` enforces the uniqueness constraint `unique_alternate_identifier_encounter_location` on `(alternate_identifier, patient, location)`, so an external identifier can appear at most once per patient and location.
+
+**Source / specs:**
+- Model: `DispenseOrder` in [`medication_dispense.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_dispense.py)
+- Specs: [`dispense_order.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/dispense/dispense_order.py)
+- Viewset: [`api/viewsets/inventory/dispense_order.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/inventory/dispense_order.py)
+
+#### `MedicationDispenseOrderStatusOptions` values
+
+| Value |
+| --- |
+| `draft` |
+| `in_progress` |
+| `completed` |
+| `abandoned` |
+| `entered_in_error` |
+
+`MEDICATION_DISPENSE_ORDER_COMPLETED_STATUSES = [abandoned, entered_in_error, completed]` are terminal-ish. `abandoned` and `entered_in_error` are the cancel transitions.
+
+#### `DispenseOrder` specs
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BaseMedicationDispenseOrderSpec` | shared base / update | Exposes `status`, `name`, `note`. Used directly as the update spec |
+| `MedicationDispenseOrderWriteSpec` | write · create | Adds `patient`, `location` (UUIDs), resolved in deserialization. `facility` is set server-side from the URL |
+| `MedicationDispenseOrderReadSpec` | read · list | Serializes nested `patient` (list spec), `location`; adds `created_date`, `modified_date` |
+| `MedicationDispenseOrderRetrieveSpec` | read · detail | Full `patient` (retrieve spec) plus `created_by` / `updated_by` audit users |
+
+## Methods & save behaviour
+
+The model has no custom `save()`; the dispense workflow lives in the viewset (`MedicationDispenseViewSet`) inside a `transaction.atomic()` block, with side effects layered on top of the spec's `perform_extra_deserialization`.
+
+**On create (`perform_create`):**
+- Acquires an `InventoryItemLock` on `item` and rejects if `item.net_content < quantity` (`"Inventory item does not have enough stock"`).
+- If `item.product.charge_item_definition` exists, applies it to create a `ChargeItem` (resource `medication_dispense`, linked to this dispense's `external_id`; `performer_actor` = the authorizing request's requester when present) and attaches it via `charge_item`.
+- Calls `sync_inventory_item(item.location, item.product)` to draw down stock.
+- If `fully_dispensed` is set and an `authorizing_request` exists, sets that request's `dispense_status` to `complete` (when `True`) or `partial` (when `False`).
+- Order resolution: `create_dispense_order` does a get-or-create by `(alternate_identifier, patient, location)`; an existing non-`draft`/`in_progress` order is rejected.
+
+**On update (`perform_update`):**
+- `validate_data` rejects any update when the current status is in `MEDICATION_DISPENSE_CANCELLED_STATUSES`.
+- A transition **into** a cancelling status while a `charge_item` is attached cancels the charge item (`handle_charge_item_cancel`, status → `aborted`), sets the authorizing request's `dispense_status` to `incomplete`, and detaches `authorizing_request` (`SET_NULL`).
+- Re-syncs inventory and re-applies the `fully_dispensed` → `dispense_status` (`complete`/`partial`) logic.
+
+**Dispense-order cancellation (`cancel_dispense_order` in the order viewset):** moving an order to `abandoned` or `entered_in_error` cascades to every member dispense — cancels their charge items, marks each dispense `cancelled` / `entered_in_error` respectively, sets authorizing requests' `dispense_status` to `incomplete`, and detaches them. An order already `abandoned`/`entered_in_error` cannot transition; a `completed` order can only be cancelled.
+
+## API integration notes
+
+- Exposed through `MedicationDispenseViewSet` (create, retrieve, update, list, upsert) and aligns with the FHIR `MedicationDispense` resource; API field names may differ from FHIR (e.g. `authorizing_request` ≈ FHIR `authorizingPrescription`). Enum **values** use underscores (`in_progress`, `entered_in_error`), not FHIR hyphens.
+- List/summary require either a `location` or `encounter` query param (else `400`); `include_children=true` widens a location query to descendant locations via `parent_cache`. Filters: `status`, `exclude_status`, `category`, `encounter`, `patient`, `item`, `authorizing_prescription`, `authorizing_request`, `location`, `order`. A `summary` action returns per-encounter dispense counts.
+- Permissions are location-centric: creates only require write access to the dispensing `location` (pharmacists often lack encounter access); reads require either location-list or encounter-view permission.
+- Send `item` as an `InventoryItem` UUID, not a bare product code — this is what draws down stock.
+- `quantity` / `days_supply` are constrained to whole units (`decimal_places=0`) on the write spec even though the column stores 6 decimal places.
+- `charge_item` is server-managed (created from the product's charge item definition, cancelled on dispense/order cancellation) — do not set it directly.
+- Use `fully_dispensed` to roll the linked [Medication Request](./medication-request.mdx) into `complete` / `partial` so pharmacists do not re-dispense an already-fulfilled request.
+- Use `create_dispense_order` to start an order inline, or `order` to attach to an existing one — never both.
+
+## Related
+
+- Reference: [Medication Request](./medication-request.mdx)
+- Reference: [Medication Administration](./medication-administration.mdx)
+- Reference: [Medication Statement](./medication-statement.mdx)
+- Reference: [Inventory Item](../supply/inventory-item.mdx)
+- Reference: [Charge Item](../billing/charge-item.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [medication_dispense.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_dispense.py)
+- Spec: [dispense/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/dispense/spec.py)
+- Spec: [dispense/dispense_order.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/dispense/dispense_order.py)
diff --git a/versioned_docs/version-3.1/references/medications/medication-request.mdx b/versioned_docs/version-3.1/references/medications/medication-request.mdx
new file mode 100644
index 0000000..af8d48f
--- /dev/null
+++ b/versioned_docs/version-3.1/references/medications/medication-request.mdx
@@ -0,0 +1,329 @@
+---
+sidebar_position: 1
+---
+
+# Medication Request
+
+Technical reference for the `MedicationRequest` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/medication_request.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_request.py)
+- Spec: [`resources/medication/request/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/request/spec.py)
+- Spec: [`resources/medication/request_prescription/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/request_prescription/spec.py)
+- Value sets: [`resources/medication/valueset/`](https://github.com/ohcnetwork/care/tree/develop/care/emr/resources/medication/valueset)
+
+A medication request captures the prescriber's intent to supply and/or administer a medication to a patient — the FHIR [`MedicationRequest`](https://hl7.org/fhir/medicationrequest.html) resource. Requests carry the medication, dosage instructions, timing, and route as structured JSON, and are grouped under a prescription per encounter.
+
+The Django model is the **storage** layer: several fields are opaque `JSONField`s whose real structure is not visible in the model. The Pydantic **resource specs** (`care/emr/resources/medication/request/`) are the API/implementation layer — they define the enums, the nested shape of those JSON fields, validation, the read/write schemas, and server-side side effects. The tables below combine both.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `MedicationRequestPrescription` | Groups medication requests authored together for an encounter (a prescription) |
+| `MedicationRequest` | A single medication order/request for a patient within an encounter |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) — the shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON.
+
+## `MedicationRequest` fields
+
+### Order status & intent
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(100)` → `MedicationRequestStatus` | Yes (spec) | Model nullable; spec requires it. See [status values](#medicationrequeststatus-values). Defaults to `active` per product spec |
+| `status_reason` | `CharField(100)` → `StatusReason \| None` | No | Discontinuation/change reason. See [status reason values](#statusreason-values) |
+| `intent` | `CharField(100)` → `MedicationRequestIntent` | Yes (spec) | Model nullable; spec requires it. See [intent values](#medicationrequestintent-values) |
+| `category` | `CharField(100)` → `MedicationRequestCategory` | Yes (spec) | Model nullable; spec requires it. See [category values](#medicationrequestcategory-values) |
+| `priority` | `CharField(100)` → `MedicationRequestPriority` | Yes (spec) | Model nullable; spec requires it. See [priority values](#medicationrequestpriority-values) |
+| `do_not_perform` | `BooleanField` → `bool` | Yes | `True` when the medication is explicitly **not** to be given |
+| `dispense_status` | `CharField(100)` → `MedicationRequestDispenseStatus \| None` | No | Model default `None`. Tracks dispense progress. See [dispense status values](#medicationrequestdispensestatus-values) |
+
+### Medication & dosage
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `medication` | `JSONField` → `ValueSetBoundCoding[system-medication] \| None` | Conditional | Model default `{}`. Single `Coding { system, code, display }` bound to the [Medication](#bound-value-sets) value set (SNOMED CT medicinal products). Exactly one of `medication` / `requested_product` must be set |
+| `method` | `JSONField` | n/a | Model default `{}`. Present in storage but **not exposed by any current spec** — administration method is carried per dosage line via `dosage_instruction[].method` |
+| `dosage_instruction` | `JSONField` → `list[DosageInstruction]` | Yes | Model default `[]`. List of structured dosage lines. See [DosageInstruction shape](#dosageinstruction-nested-spec) |
+| `note` | `TextField` → `str \| None` | No | Free-text note on the request |
+
+### Subject & provenance
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `patient` | `FK → Patient` | server-set | `CASCADE`. Not accepted from the client — set server-side from `encounter.patient` |
+| `encounter` | `FK → Encounter` | Yes (create) | `CASCADE`. Write spec accepts `encounter` as a UUID; validated to exist |
+| `authored_on` | `DateTimeField` → `datetime` | Yes (spec) | Model default `timezone.now`. When the request was authored (tz-aware) |
+| `requester` | `FK → users.User` | No | `SET_NULL`. Write spec accepts `requester` UUID; the prescriber |
+| `requested_product` | `FK → ProductKnowledge` | Conditional | `SET_NULL`, default `None`. Write spec accepts `requested_product` UUID. Exactly one of `medication` / `requested_product` must be set |
+| `prescription` | `FK → MedicationRequestPrescription` | No | `SET_NULL`, default `None`. Set via `prescription` UUID or `create_prescription` (see [validation](#resource-specs-api-schema)) |
+
+## Enum values
+
+The spec enums use **underscored** member values (e.g. `on_hold`, `entered_in_error`, `original_order`) — not the hyphenated FHIR spellings. These are the literal strings the API accepts and returns.
+
+### `MedicationRequestStatus` values
+
+| Value |
+| --- |
+| `active` |
+| `on_hold` |
+| `ended` |
+| `stopped` |
+| `completed` |
+| `cancelled` |
+| `entered_in_error` |
+| `draft` |
+| `unknown` |
+
+### `StatusReason` values
+
+| Value | Meaning (FHIR) |
+| --- | --- |
+| `altchoice` | Alternative choice |
+| `clarif` | Prescription requires clarification |
+| `drughigh` | Drug level too high |
+| `hospadm` | Admission to hospital |
+| `labint` | Lab interference issues |
+| `non_avail` | Patient not available |
+| `preg` | Patient is pregnant or breastfeeding |
+| `salg` | Allergy |
+| `sddi` | Drug interaction |
+| `sdupther` | Duplicate therapy |
+| `sintol` | Suspected intolerance |
+| `surg` | Patient scheduled for surgery |
+| `washout` | Washout |
+
+### `MedicationRequestIntent` values
+
+| Value |
+| --- |
+| `proposal` |
+| `plan` |
+| `order` |
+| `original_order` |
+| `reflex_order` |
+| `filler_order` |
+| `instance_order` |
+
+### `MedicationRequestPriority` values
+
+| Value |
+| --- |
+| `routine` |
+| `urgent` |
+| `asap` |
+| `stat` |
+
+### `MedicationRequestCategory` values
+
+| Value |
+| --- |
+| `inpatient` |
+| `outpatient` |
+| `community` |
+| `discharge` |
+
+### `MedicationRequestDispenseStatus` values
+
+| Value |
+| --- |
+| `complete` |
+| `partial` |
+| `incomplete` |
+| `declined` |
+
+### `TimingUnit` values
+
+Used by `Timing.repeat.period_unit` and `bounds_duration.unit` (UCUM time units).
+
+| Value | Unit |
+| --- | --- |
+| `s` | second |
+| `min` | minute |
+| `h` | hour |
+| `d` | day |
+| `wk` | week |
+| `mo` | month |
+| `a` | year |
+
+### `DoseType` values
+
+Used by `DoseAndRate.type`.
+
+| Value |
+| --- |
+| `ordered` |
+| `calculated` |
+
+## `DosageInstruction` nested spec
+
+Each entry of the `dosage_instruction` JSON list deserializes to a `DosageInstruction` (Pydantic, snake_case keys). This is the real structure behind the opaque `JSONField`.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `sequence` | `int \| None` | No | Order of the instruction |
+| `text` | `str \| None` | No | Free-text dosage instruction (SIG) |
+| `additional_instruction` | `list[Coding] \| None` | No | Bound to [Additional Instruction](#bound-value-sets) value set |
+| `patient_instruction` | `str \| None` | No | Patient-facing instructions |
+| `timing` | `Timing \| None` | No | See `Timing` below |
+| `as_needed_boolean` | `bool` | **Yes** | `True` for PRN orders |
+| `as_needed_for` | `Coding \| None` | No | Bound to [As Needed](#bound-value-sets) value set (reason for PRN) |
+| `site` | `Coding \| None` | No | Bound to [Body Site](#bound-value-sets) value set |
+| `route` | `Coding \| None` | No | Bound to [Route](#bound-value-sets) value set |
+| `method` | `Coding \| None` | No | Bound to [Administration Method](#bound-value-sets) value set |
+| `dose_and_rate` | `DoseAndRate \| None` | No | See `DoseAndRate` below |
+| `max_dose_per_period` | `DoseRange \| None` | No | Upper dose limit per period |
+
+### Sub-types under `DosageInstruction`
+
+```text
+Timing {
+ repeat: TimingRepeat # required
+ code: Coding | None # e.g. BID/TID/QID timing abbreviation
+}
+
+TimingRepeat {
+ frequency: int # required — repetitions per period
+ period: Decimal(20,0) # required — duration the frequency applies to
+ period_unit: TimingUnit # required — s|min|h|d|wk|mo|a
+ bounds_duration: TimingQuantity # required — total span
+}
+
+TimingQuantity { # integer-valued time amount
+ value: Decimal(20,0) # required
+ unit: TimingUnit # required
+}
+
+DoseAndRate {
+ type: DoseType # required — ordered | calculated
+ dose_range: DoseRange | None # for titrated doses
+ dose_quantity: DosageQuantity | None # for regular doses
+}
+
+DoseRange {
+ low: DosageQuantity # required
+ high: DosageQuantity # required
+}
+
+DosageQuantity { # measured dose amount
+ value: Decimal(20,6) # required
+ unit: Coding # required
+}
+```
+
+Note: in storage these JSON keys are snake_case as defined by the Pydantic specs (`as_needed_boolean`, `period_unit`, `dose_and_rate`, `max_dose_per_period`, `bounds_duration`), unlike the camelCase FHIR wire format.
+
+## Bound value sets
+
+Coded fields bind to Care value sets via `ValueSetBoundCoding[]`; the supplied `Coding.code` is validated against the value set on write. All draw from SNOMED CT (`http://snomed.info/sct`).
+
+| Field | Value set slug | SNOMED CT scope |
+| --- | --- | --- |
+| `medication` | `system-medication` | `<< 763158003` Medicinal product |
+| `dosage_instruction[].route` | `system-route` | `is-a 284009009` Route of administration |
+| `dosage_instruction[].site` | `system-body-site` | `is-a 91723000` Anatomical structure |
+| `dosage_instruction[].method` | `system-administration-method` | `is-a 736665006` |
+| `dosage_instruction[].as_needed_for` | `system-as-needed-reason` | `is-a 404684003` Clinical finding |
+| `dosage_instruction[].additional_instruction` | `system-additional-instruction` | `is-a 419492006` |
+
+(`system-medication-not-given` exists in this directory but binds on [Medication Administration](medication-administration.mdx), not on the request.)
+
+## Resource specs (API schema)
+
+All specs extend [`EMRResource`](../foundation/base-model.mdx) (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). `MedicationRequestResource` sets `__exclude__ = [patient, encounter, requester, requested_product, prescription]` so those relations are handled explicitly rather than auto-mapped.
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `MedicationRequestAbstractSpec` | shared (fields) | Plain `BaseModel` holding the clinical fields: `status`, `status_reason`, `intent`, `category`, `priority`, `do_not_perform`, `medication`, `dosage_instruction`, `authored_on`, `note`, `dispense_status` |
+| `MedicationRequestResource` | shared (binding) | Binds the spec to the `MedicationRequest` model + `__exclude__` |
+| `BaseMedicationRequestSpec` | shared | `MedicationRequestResource` + `MedicationRequestAbstractSpec` + `id: UUID4` |
+| `MedicationRequestSpec` | write · create | Adds `encounter` (UUID, required), `requester`, `requested_product`, `prescription` (UUIDs), and `create_prescription`. Carries the validators and `perform_extra_deserialization` |
+| `MedicationRequestUpdateSpec` | write · update | Narrow: only `status`, `note`, `dispense_status` are mutable post-create |
+| `MedicationRequestReadWithoutPrescriptionSpec` | read · embedded | Full read minus prescription; expands `requested_product` (`ProductKnowledgeReadSpec`), `requester`/`created_by`/`updated_by` (`UserSpec`), plus `created_date`/`modified_date` |
+| `MedicationRequestReadSpec` | read · detail/list | Adds expanded `prescription` (`MedicationRequestPrescriptionReadSpec`) and `encounter` external id |
+| `CreatePrescription` | write · nested | `{ name?, note?, alternate_identifier (required) }` — inline prescription creation |
+
+### Validation (create — `MedicationRequestSpec`)
+
+- **Medication XOR product:** cannot set both `medication` and `requested_product`; one is required.
+- **Prescription XOR create:** cannot set both `prescription` and `create_prescription`.
+- **Encounter existence:** `encounter` UUID must match an existing `Encounter` (field validator).
+- `authored_on` (and any nested `PeriodSpec`/timing datetimes) must be timezone-aware.
+
+### Server-side side effects (`perform_extra_deserialization`)
+
+- `encounter` is resolved from its UUID; `patient` is set from `encounter.patient` (never client-supplied).
+- `requester` / `requested_product` resolved by `external_id`. If `requested_product.facility` is set and differs from `encounter.facility`, raises `"Product not found in facility"`.
+- `prescription` resolved by `external_id` scoped to the same `encounter`.
+- `create_prescription`: looks up an existing prescription by `(alternate_identifier, encounter)`. If found but **not** `active`, raises `"Prescription is not active"`. If not found, creates a new `MedicationRequestPrescription` (`status=active`, copying `name`/`note`, `prescribed_by=requester`). The request is then linked to it.
+
+### Serialization (read)
+
+- `perform_extra_serialization` sets `id = external_id`, expands `requested_product`, `requester`, and audit users (`created_by`/`updated_by`) from cache; `MedicationRequestReadSpec` additionally expands `prescription` and emits `encounter` as its external id.
+
+## Related models
+
+### `MedicationRequestPrescription`
+
+Groups the medication requests authored together for an encounter into a single prescription. Each request points back via `MedicationRequest.prescription`.
+
+```text
+encounter → FK Encounter (CASCADE)
+patient → FK Patient (CASCADE)
+name → CharField(100), nullable
+note → TextField, nullable
+prescribed_by → FK users.User (CASCADE, nullable)
+status → CharField(100), nullable → MedicationRequestPrescriptionStatus
+approval_status → CharField(100), nullable
+alternate_identifier → CharField(100), nullable
+tags → ArrayField[int], default []
+```
+
+A `UniqueConstraint` (`unique_alternate_identifier_encounter`) enforces that `alternate_identifier` is unique per `encounter`.
+
+#### `MedicationRequestPrescriptionStatus` values
+
+`active`, `on_hold`, `ended`, `stopped`, `completed`, `cancelled`, `entered_in_error`, `draft`.
+
+Pharmacist-modifiable statuses (`MEDICATION_PRESCRIPTION_PHARMACIST_ALLOWED_STATUS`): `active`, `on_hold`, `completed`.
+
+#### Prescription specs
+
+| Spec class | Role | Notes |
+| --- | --- | --- |
+| `BaseMedicationRequestPrescriptionSpec` | shared | `id`, `status` (`MedicationRequestPrescriptionStatus`), `note`, `name` |
+| `MedicationRequestPrescriptionWriteSpec` | write · create | Adds `encounter` (UUID), `prescribed_by` (UUID); resolves `encounter`→`patient` and `prescribed_by` server-side |
+| `MedicationRequestPrescriptionUpdateSpec` | write · update | Same shared fields, no new FKs |
+| `MedicationRequestPrescriptionReadSpec` | read · list | Adds `created_date`, `modified_date`, expanded `prescribed_by` (`UserSpec`), `tags` (rendered via `SingleFacilityTagManager`) |
+| `MedicationRequestPrescriptionRetrieveSpec` | read · detail | Adds audit `created_by`/`updated_by` |
+| `MedicationRequestPrescriptionRetrieveMedicationsSpec` | read · detail+children | Adds `medications` (each via `MedicationRequestReadWithoutPrescriptionSpec`) and full `encounter` (`EncounterRetrieveSpec`) |
+| `MedicationRequestPrescriptionRetrieveDetailedSpec` | read · detail | Adds `encounter` via `EncounterListSpec` |
+
+## Methods & save behaviour
+
+- Inherited `EMRResource.serialize` / `de_serialize` drive read/write; `de_serialize` dumps with `exclude_defaults=True` and maps fields onto the model, then calls `perform_extra_deserialization` for the relation/prescription handling described above.
+- `patient` is always derived from `encounter`, never written directly by the client.
+- Inline prescription creation (`create_prescription`) is idempotent on `(alternate_identifier, encounter)` and only proceeds when an existing match is `active`.
+- `authored_on` defaults to `timezone.now` at the model layer when not supplied.
+
+## API integration notes
+
+- Coded fields use single `Coding` objects `{ system, code, display }` validated against bound SNOMED CT value sets — send the `code` from the correct value set or the write is rejected.
+- Enum members are underscored (`on_hold`, `entered_in_error`, `original_order`, etc.); send those literals, not the FHIR hyphenated forms.
+- `dosage_instruction` is the structured carrier for SIG text, timing/frequency, PRN flags, dose and rate, and max-dose limits — write the full nested shape (snake_case keys) rather than free text alone.
+- On create, supply either `medication` or `requested_product` (not both) and either `prescription` or `create_prescription` (not both, both optional).
+- Updates are restricted to `status`, `note`, and `dispense_status`.
+- `requester`, `requested_product`, and `prescription` use `SET_NULL`, so the request survives deletion of the referenced user, product, or prescription.
+
+## Related
+
+- Reference: [Medication Administration](medication-administration.mdx)
+- Reference: [Medication Dispense](medication-dispense.mdx)
+- Reference: [Medication Statement](medication-statement.mdx)
+- Reference: [Product Knowledge](../supply/product-knowledge.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
+- Source: [medication_request.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_request.py) · [request/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/request/spec.py) · [request_prescription/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/request_prescription/spec.py)
diff --git a/versioned_docs/version-3.1/references/medications/medication-statement.mdx b/versioned_docs/version-3.1/references/medications/medication-statement.mdx
new file mode 100644
index 0000000..195bfdd
--- /dev/null
+++ b/versioned_docs/version-3.1/references/medications/medication-statement.mdx
@@ -0,0 +1,162 @@
+---
+sidebar_position: 4
+---
+
+# Medication Statement
+
+Technical reference for the `MedicationStatement` module in Care EMR.
+
+A `MedicationStatement` records a medication the patient **is taking, has taken, or intends to take** — as *reported* (by the patient, a relative, or a clinician) rather than actively prescribed or dispensed. It aligns with the FHIR `MedicationStatement` resource.
+
+The Django model is the **storage** layer: `medication` and `effective_period` are opaque `JSONField`s whose real structure lives in the Pydantic **resource specs** (`care/emr/resources/medication/statement/`). The specs define the enums, the nested JSON shapes, validation, the value-set binding on `medication`, and the read/write API schemas. This page documents both.
+
+**Source:**
+
+- Model: [`care/emr/models/medication_statement.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_statement.py)
+- Spec: [`care/emr/resources/medication/statement/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/statement/spec.py)
+- Value set: [`care/emr/resources/medication/valueset/medication.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/valueset/medication.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `MedicationStatement` | A record of a medication the patient is taking, has taken, or intends to take — as reported rather than actively prescribed |
+
+`MedicationStatement` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_by`/`updated_by`, `created_date`/`modified_date`, `history`/`meta` JSON, and soft-delete semantics).
+
+## `MedicationStatement` fields
+
+The **Type** column gives the Django storage type; the **Spec shape** column gives the structured shape enforced by the Pydantic resource spec when reading/writing over the API.
+
+### Statement details
+
+| Field | Type | Spec shape | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(100)` | `MedicationStatementStatus` enum | Required. See [Status values](#medicationstatementstatus-values). |
+| `reason` | `CharField(100)` | `str \| None` | Nullable / optional. Reason the medication is being taken. |
+| `medication` | `JSONField` (`default=dict`) | `Coding` bound to the `system-medication` value set | Required on create. A single SNOMED CT `Coding` (not a full `CodeableConcept`). See [`Coding` shape](#coding-shape) and [Bound value set](#bound-value-set). |
+| `information_source` | `CharField(100)` | `MedicationStatementInformationSourceType` enum \| `None` | Optional. Who supplied the statement. See [Information-source values](#medicationstatementinformationsourcetype-values). |
+| `effective_period` | `JSONField` (`default=dict`) | `PeriodSpec` \| `None` | Optional. Period the medication is/was taken. See [`PeriodSpec` shape](#periodspec-shape). |
+| `dosage_text` | `TextField` | `str \| None` | Nullable / optional. Free-text dosage instructions. (The spec notes a structured `Dosage` may replace this in future.) |
+| `note` | `TextField` | `str \| None` | Nullable / optional. Additional free-text annotation. |
+
+### Relationships
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `patient` | `FK → Patient` | `on_delete=CASCADE`. The patient the statement is about. Set server-side from the encounter — not accepted from clients. |
+| `encounter` | `FK → Encounter` | `on_delete=CASCADE`. The encounter in which the statement was recorded. Supplied as a UUID on create. |
+
+```text
+patient → FK Patient (CASCADE)
+encounter → FK Encounter (CASCADE)
+```
+
+Both foreign keys cascade on delete, so removing a patient or encounter removes the associated medication statements.
+
+## Enum values
+
+### `MedicationStatementStatus` values
+
+From `MedicationStatementStatus(str, Enum)` in `spec.py`. Bound to `status`.
+
+| Value | Meaning |
+| --- | --- |
+| `active` | The medication is being taken |
+| `on_hold` | Taking has been paused |
+| `completed` | The course has finished |
+| `stopped` | The medication was stopped |
+| `unknown` | Status is not known |
+| `entered_in_error` | The record was entered in error |
+| `not_taken` | The patient is not taking the medication |
+| `intended` | The patient intends to take the medication |
+
+### `MedicationStatementInformationSourceType` values
+
+From `MedicationStatementInformationSourceType(str, Enum)` in `spec.py`. Bound to `information_source`.
+
+| Value | Meaning |
+| --- | --- |
+| `related_person` | A relative or related person reported it |
+| `practitioner` | A clinician reported it |
+| `patient` | The patient self-reported it |
+
+## Nested JSON shapes
+
+### `Coding` shape
+
+`medication` is stored as JSON but typed as a single `Coding` (from `care/emr/resources/common/coding.py`), not a `CodeableConcept`. `extra="forbid"`.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `system` | `str \| None` | Code system URI (e.g. `http://snomed.info/sct`) |
+| `version` | `str \| None` | Code-system version |
+| `code` | `str` | **Required.** The code, validated against the bound value set |
+| `display` | `str \| None` | Human-readable label |
+
+#### Bound value set
+
+`medication` is declared as `ValueSetBoundCoding[CARE_MEDICATION_VALUESET.slug]`. On write, the `code` is validated against the **`system-medication`** value set ([`medication.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/valueset/medication.py)), which includes SNOMED CT concepts under the constraint `<< 763158003 |Medicinal product|`. An out-of-set code is rejected.
+
+> The `care/emr/resources/medication/valueset/` directory holds additional value sets used by other medication resources (route, body site, administration method, additional instruction, as-needed reason, medication-not-given reason). `MedicationStatement` itself only binds `medication` → `system-medication`.
+
+### `PeriodSpec` shape
+
+`effective_period` is stored as JSON but typed as `PeriodSpec` (from `care/emr/resources/base.py`).
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `start` | `datetime \| None` | Must be **timezone-aware** if present (naive datetimes are rejected) |
+| `end` | `datetime \| None` | Must be **timezone-aware** if present |
+
+Validation (`validate_period`, mode `after`): a naive `start`/`end` raises an error, and `start` must not be greater than `end`.
+
+## Resource specs (API schema)
+
+All specs subclass `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB object → pydantic) and `de_serialize` (pydantic → DB object). `__model__ = MedicationStatement`; `__exclude__ = ["patient", "encounter"]` means those fields are never written/read by the generic field copy and are instead handled by the extra hooks.
+
+| Spec class | Role | Fields exposed |
+| --- | --- | --- |
+| `BaseMedicationStatementSpec` | shared base | `id`, `status`, `reason`, `medication`, `dosage_text`, `effective_period`, `encounter`, `information_source`, `note` |
+| `MedicationStatementSpec` | write · create | Inherits `BaseMedicationStatementSpec`; adds `encounter` existence validation + extra deserialization |
+| `MedicationStatementUpdateSpec` | write · update | `status`, `effective_period`, `note` only |
+| `MedicationStatementReadSpec` | read · list & detail | Inherits base; adds `created_by`, `updated_by` (`UserSpec`), `created_date`, `modified_date` |
+
+Key validation and server-maintained behaviour:
+
+- **`encounter` existence** — `MedicationStatementSpec.validate_encounter_exists` (a `field_validator` on `encounter`) raises `"Encounter not found"` unless an `Encounter` with that `external_id` exists.
+- **`patient` derived server-side** — `MedicationStatementSpec.perform_extra_deserialization` runs only on create (`is_update` false): it resolves the `Encounter` from the supplied UUID and sets `obj.patient = obj.encounter.patient`. Clients never send `patient`.
+- **Update is restricted** — `MedicationStatementUpdateSpec` only accepts `status`, `effective_period`, and `note`; `medication`, `reason`, `dosage_text`, `information_source`, and `encounter` are not updatable through it.
+- **Read serialization** — `MedicationStatementReadSpec.perform_extra_serialization` sets `id = obj.external_id`, `encounter = obj.encounter.external_id`, and serializes audit users via `serialize_audit_users` (`created_by`/`updated_by` as `UserSpec`).
+- **`medication` value-set check** — enforced at validation time on the `Coding.code` (see [Bound value set](#bound-value-set)).
+- **`effective_period` tz-awareness / ordering** — enforced by `PeriodSpec` (see [`PeriodSpec` shape](#periodspec-shape)).
+
+There is **no** `status_history` on this resource — status is a single current value, not an appended log.
+
+## Methods & save behaviour
+
+- Inherited from [`EMRBaseModel`](../foundation/base-model.mdx): `external_id` (UUID), audit fields, soft delete. Deletes are soft — a removed statement is retained with `deleted=True` and hidden by the default manager.
+- Create flow: client sends `MedicationStatementSpec` → `de_serialize` copies scalar/JSON fields → `perform_extra_deserialization` resolves `encounter` and back-fills `patient` → model saved.
+- Read flow: `MedicationStatementReadSpec.serialize(obj)` copies fields, then `perform_extra_serialization` injects `id`, `encounter` (as UUIDs) and audit users.
+
+## API integration notes
+
+- Exposed through Care's REST API, aligned with FHIR `MedicationStatement`. Use the spec field names above for payloads.
+- `medication` is a single SNOMED CT `Coding` object whose `code` must be in the `system-medication` value set — **not** a free CodeableConcept and not a scalar string.
+- `effective_period` datetimes must be timezone-aware and ordered (`start ≤ end`).
+- `encounter` is supplied as a UUID; `patient` is derived from it server-side and must not be sent.
+- Updates go through `MedicationStatementUpdateSpec` and can only change `status`, `effective_period`, and `note`.
+- `information_source` distinguishes a self-reported or third-party statement from an actively managed prescription ([`MedicationRequest`](medication-request.mdx)).
+- `external_id` (UUID), `created_by`/`updated_by`, `created_date`/`modified_date`, `history`, and `meta` are platform-maintained — do not set them directly from clients.
+
+## Related
+
+- Reference: [Medication Request](medication-request.mdx)
+- Reference: [Medication Administration](medication-administration.mdx)
+- Reference: [Medication Dispense](medication-dispense.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Source (model): [medication_statement.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/medication_statement.py)
+- Source (spec): [statement/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/statement/spec.py)
+- Source (value set): [valueset/medication.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/medication/valueset/medication.py)
diff --git a/versioned_docs/version-3.1/references/platform/_category_.json b/versioned_docs/version-3.1/references/platform/_category_.json
new file mode 100644
index 0000000..6aeafd6
--- /dev/null
+++ b/versioned_docs/version-3.1/references/platform/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Platform",
+ "position": 10,
+ "key": "platform-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Platform",
+ "description": "Cross-cutting platform models — file uploads, generated reports and templates, tagging, resource requests, and metadata artifacts."
+ }
+}
diff --git a/versioned_docs/version-3.1/references/platform/favorites.mdx b/versioned_docs/version-3.1/references/platform/favorites.mdx
new file mode 100644
index 0000000..9d1752c
--- /dev/null
+++ b/versioned_docs/version-3.1/references/platform/favorites.mdx
@@ -0,0 +1,143 @@
+---
+sidebar_position: 7
+---
+
+# Favorites
+
+Technical reference for the `UserResourceFavorites` module in Care EMR.
+
+Favorites are a Care-internal personalization feature, not a FHIR resource. A user keeps one or more **named lists** of favorited resource IDs, scoped by a `resource_type` and (optionally) a `facility`. Unlike most EMR resources, favorites have **no `EMRResource` serialize/de_serialize spec** — the implementation layer is an enum (`FavoriteResourceChoices`), a default-list constant, a DRF filter backend (`FavoritesFilter`), and a viewset mixin (`EMRFavoritesMixin`) that exposes the read/write endpoints.
+
+**Source:**
+- Model: [`care/emr/models/favorites.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/favorites.py)
+- Spec (enum + constant): [`care/emr/resources/favorites/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/favorites/spec.py)
+- Filter backend: [`care/emr/resources/favorites/filters.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/favorites/filters.py)
+- Viewset mixin: [`care/emr/api/viewsets/favorites.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/favorites.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `UserResourceFavorites` | Stores a per-user, named list of favorited resource integer PKs, optionally scoped to a facility and constrained to one resource type |
+
+`UserResourceFavorites` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_by` / `updated_by` / `created_date` / `modified_date`, and soft-delete semantics).
+
+## `UserResourceFavorites` fields
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `user` | `FK → User` | optional | — | `CASCADE`, nullable. Owner of the favorites list |
+| `favorites` | `ArrayField(IntegerField)` | yes | `[]` | Ordered list of favorited resource **primary-key integers** (not `external_id`s). Newest-first; capped at `settings.MAX_FAVORITES_PER_LIST` (default `50`) |
+| `favorite_list` | `CharField(255)` | yes | — | Name of the list. A user may keep multiple named lists per `(resource_type, facility)`. Endpoints default it to `"default"` (`DEFAULT_FAVORITE_LIST`) |
+| `resource_type` | `CharField(255)` | yes | — | The kind of resource favorited. In practice always one of the `FavoriteResourceChoices` values (set server-side from the viewset's `FAVORITE_RESOURCE`), never accepted from the client |
+| `facility` | `FK → Facility` | optional | — | `CASCADE`, nullable. Optional facility scope; `null` means an instance-wide (cross-facility) list |
+
+A row is uniquely addressed in practice by the tuple `(user, resource_type, facility, favorite_list)` — this combination is what the cache keys and the `get_or_create` in `add_favorite` are built from.
+
+### `resource_type` values — `FavoriteResourceChoices`
+
+`resource_type` is constrained at the API layer to the string values of `FavoriteResourceChoices` (`care/emr/resources/favorites/spec.py`). Each value is wired to a viewset via that viewset's `FAVORITE_RESOURCE` attribute.
+
+| Value | Bound resource | Wired viewset |
+| --- | --- | --- |
+| `activity_definition` | [Activity definition](../clinical/activity-definition.mdx) | `ActivityDefinitionViewSet` |
+| `charge_item_definition` | [Charge item definition](../billing/charge-item-definition.mdx) | `ChargeItemDefinitionViewSet` |
+| `product_knowledge` | [Product knowledge](../supply/product-knowledge.mdx) | `ProductKnowledgeViewSet` |
+| `observation_definition` | [Observation definition](../clinical/observation-definition.mdx) | _(enum value defined; no viewset wires it yet)_ |
+| `questionnaire` | [Questionnaire](../forms/questionnaire.mdx) | `QuestionnaireViewSet` |
+| `facility_organization` | [Organization](../facility/organization.mdx) (facility org) | `FacilityOrganizationViewSet` |
+
+## Resource specs (API schema)
+
+Favorites do **not** use the `EMRResource` Create/Update/List/Retrieve pattern. The "spec" surface is:
+
+| Symbol | Kind | Role |
+| --- | --- | --- |
+| `FavoriteResourceChoices` | `str, Enum` (`spec.py`) | The allowed `resource_type` values (table above). A viewset selects one via `FAVORITE_RESOURCE = FavoriteResourceChoices..value` |
+| `DEFAULT_FAVORITE_LIST` | constant (`spec.py`) | `"default"` — the list name used when the request omits `favorite_list` |
+| `FavoriteRequest` | `pydantic.BaseModel` (viewset) | Request body for write actions. Single field `favorite_list: str = DEFAULT_FAVORITE_LIST` |
+| `FavoritesFilter` | DRF `BaseFilterBackend` (`filters.py`) | Read path. Adds the `favorite_list` query param to the resource's list endpoint and restricts/orders results to that user's favorites |
+| `EMRFavoritesMixin` | viewset mixin (`api/viewsets/favorites.py`) | Adds the `favorite_lists`, `add_favorite`, `remove_favorite` actions |
+
+### `FavoriteRequest`
+
+| Field | Type | Required | Default |
+| --- | --- | --- | --- |
+| `favorite_list` | `str` | optional | `"default"` (`DEFAULT_FAVORITE_LIST`) |
+
+There is no read schema for the favorited objects themselves: favorited resources are returned through the **owning resource's own list endpoint** (e.g. the questionnaire list serializer), filtered and ordered by `FavoritesFilter`. The `favorite_lists` action returns only `{"lists": [, ...]}`.
+
+## Related models
+
+- [`User`](../access/user.mdx) — owner of every favorites row (`user` FK)
+- [`Facility`](../facility/facility.mdx) — optional scope (`facility` FK); `null` = instance-wide
+- Each `resource_type` binds to one favoritable resource — see the [`FavoriteResourceChoices` table](#resource_type-values--favoriteresourcechoices)
+
+## Methods & save behaviour
+
+This model maintains a Django cache alongside the database row.
+
+### Cache keys
+
+Two module-level helpers build the cache keys (a missing facility is encoded as `-`):
+
+```text
+favorite_lists_cache_key(user, resource_type, facility)
+ → "user_favorites_lists:{user.id}:{resource_type}:{facility.id|-}"
+
+favorite_list_object_cache_key(user, resource_type, facility, favorite_list)
+ → "user_favorites_list_object:{user.id}:{resource_type}:{facility.id|-}:{favorite_list}"
+```
+
+- The **lists** key caches the ordered, deduplicated set of `favorite_list` names a user has for a `(resource_type, facility)`.
+- The **list object** key caches the `favorites` integer array for a single named list.
+
+### `refresh_cache(refresh_list=False)`
+
+- Always writes the current `favorites` array under the list-object key.
+- When `refresh_list=True`, also recomputes the deduplicated, `modified_date`-descending set of `favorite_list` names for the `(user, resource_type, facility)` and writes it under the lists key.
+
+### `save()` side effects
+
+`save()` sets `refresh_list = not self.pk` (i.e. only when creating a new row), calls `super().save()`, then `refresh_cache(refresh_list=refresh_list)`. Effects:
+
+1. Creating a new list refreshes both the lists cache and the list-object cache.
+2. Updating an existing list refreshes only the list-object cache (the set of list names is unchanged).
+
+Integrators reading favorites through the cache should treat these keys as platform-maintained; write through the model / endpoints so the cache stays consistent.
+
+## API integration notes
+
+Favorites are exposed through the **owning resource's viewset** via `EMRFavoritesMixin`, not a standalone favorites endpoint. A viewset opts in by mixing in `EMRFavoritesMixin`, setting `FAVORITE_RESOURCE`, and adding `FavoritesFilter` to `filter_backends`:
+
+```python
+class QuestionnaireViewSet(EMRModelViewSet, EMRFavoritesMixin):
+ filter_backends = [filters.DjangoFilterBackend, FavoritesFilter]
+ FAVORITE_RESOURCE = FavoriteResourceChoices.questionnaire.value
+```
+
+### Endpoints (relative to the owning resource)
+
+| Action | Method · route | Body | Behaviour |
+| --- | --- | --- | --- |
+| `add_favorite` | `POST {resource}/{id}/add_favorite/` | `FavoriteRequest` | `get_or_create` the `(user, favorite_list, resource_type, facility)` row, `insert(0, obj.id)` (newest-first), dedupe, **trim to `MAX_FAVORITES_PER_LIST`**, save. Returns `{}` |
+| `remove_favorite` | `POST {resource}/{id}/remove_favorite/` | `FavoriteRequest` | Pops `obj.id` from the list. If the list becomes empty, **deletes the row and both cache keys**. 404/validation error if the list does not exist. Returns `{}` |
+| `favorite_lists` | `GET {resource}/favorite_lists/` | — | Returns `{"lists": [, ...]}` (cache-first, falls back to DB). Resolves `facility` from `facility_external_id` kwarg or `?facility=` query param |
+| list (filtered) | `GET {resource}/?favorite_list=` | — | `FavoritesFilter` restricts the resource list to `id__in=favorites` and orders by favorite position (`sort_index`). Empty/unknown list → empty queryset |
+
+Server-maintained / validation behaviour:
+
+- `resource_type` is never client-supplied; it is fixed per viewset by `FAVORITE_RESOURCE`. The client only ever sends `favorite_list`.
+- `facility` is derived server-side via `retrieve_facility_obj(obj)` (defaults to `obj.facility`), so favorites inherit the favorited object's facility scope.
+- `favorites` stores **integer primary keys**, not `external_id`s. Ordering is significant and newest-first; `add_favorite` re-inserts at position 0 and dedupes.
+- The list is capped at `settings.MAX_FAVORITES_PER_LIST` (env `MAX_FAVORITES_PER_LIST`, default `50`); the oldest entries beyond the cap are dropped on `add_favorite`.
+- Do not set the `user_favorites_lists:*` or `user_favorites_list_object:*` cache keys directly from clients — they are maintained by `save()` / `refresh_cache()` and torn down by `remove_favorite` when a list empties.
+- A `null` `facility` denotes an instance-wide list; a set `facility` denotes a facility-scoped list.
+
+## Related
+
+- Reference: [User](../access/user.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Favoritable resources: [Activity definition](../clinical/activity-definition.mdx), [Charge item definition](../billing/charge-item-definition.mdx), [Product knowledge](../supply/product-knowledge.mdx), [Observation definition](../clinical/observation-definition.mdx), [Questionnaire](../forms/questionnaire.mdx), [Organization](../facility/organization.mdx)
+- Source: [favorites.py (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/favorites.py), [spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/favorites/spec.py), [filters.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/favorites/filters.py), [viewset mixin](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/favorites.py)
diff --git a/versioned_docs/version-3.1/references/platform/file-upload.mdx b/versioned_docs/version-3.1/references/platform/file-upload.mdx
new file mode 100644
index 0000000..c6dd96e
--- /dev/null
+++ b/versioned_docs/version-3.1/references/platform/file-upload.mdx
@@ -0,0 +1,176 @@
+---
+sidebar_position: 1
+---
+
+# File Upload
+
+Technical reference for the `FileUpload` resource in Care EMR.
+
+`FileUpload` is metadata for a file held in object storage (S3-compatible). The Django model is the **storage layer**: it persists the record and an opaque `meta` JSON column. The Pydantic **resource specs** in `care/emr/resources/file_upload/spec.py` are the API layer — they define the `file_type` / `file_category` enums, validate the upload, decide what the `meta` JSON holds (`mime_type`), and shape every read/write payload (including the derived `extension`, `signed_url`, and `read_signed_url` fields that do not exist as columns).
+
+**Source:**
+- Model: [`care/emr/models/file_upload.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/file_upload.py)
+- Specs: [`care/emr/resources/file_upload/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/file_upload/spec.py)
+- Viewset: [`care/emr/api/viewsets/file_upload.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/file_upload.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `FileUpload` | Metadata record for a file stored in object storage (S3), associated with another Care resource by `file_type` + `associating_id` |
+
+`FileUpload` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, `meta` JSON, audit fields, and soft-delete semantics).
+
+## `FileUpload` fields
+
+### File metadata
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(2000)` | User-facing file name. Required on the API (`FileUploadBaseSpec.name`) |
+| `internal_name` | `CharField(2000)` | Storage object key. Set server-side from `original_name` on create, then a random key is generated on `save()`. Never set by clients |
+| `associating_id` | `CharField(100)` | ID (`external_id`) of the owning resource. Required (`blank=False, null=False`). No DB `ForeignKey` — integrity is enforced in the app layer |
+| `file_type` | `CharField(100)` | Domain of the owning resource. API-constrained to [`FileTypeChoices`](#filetypechoices-values) |
+| `file_category` | `CharField(100)` | File category. API-constrained to [`FileCategoryChoices`](#filecategorychoices-values) |
+| `upload_completed` | `BooleanField` | Default `False`. Flipped to `True` once the client finishes the direct-to-S3 upload (or immediately, for the inline `upload-file` path) |
+
+### `meta` JSON contents
+
+`meta` is the opaque `JSONField` from `EMRBaseModel`. The specs store one structured key in it:
+
+| Key | Type | Shape / Notes |
+| --- | --- | --- |
+| `mime_type` | `str` | MIME type of the file. Written in `FileUploadCreateSpec.perform_extra_deserialization` (`obj.meta["mime_type"] = self.mime_type`); read back in serialization via `obj.meta.get("mime_type")`. Validated against `settings.ALLOWED_MIME_TYPES` |
+
+### Archival
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `is_archived` | `BooleanField` | Default `False` |
+| `archive_reason` | `TextField` | Blank-able; set when archiving (required in the `archive` action body) |
+| `archived_datetime` | `DateTimeField` | Nullable; set to `timezone.now()` when archived |
+| `archived_by` | `FK → User` (PROTECT) | Nullable; `related_name="archived_files"` |
+
+### Storage manager
+
+`FileUpload` declares a class-level `files_manager = S3FilesManager(BucketType.PATIENT)`. This is not a database field — it is the object-storage helper used to generate signed write/read URLs and to `put_object`, all against the `PATIENT` bucket.
+
+## Enums
+
+### `FileTypeChoices` values
+
+Owning-resource domain (`spec.py`). String enum.
+
+| Value | Links a file to |
+| --- | --- |
+| `patient` | A [patient](../clinical/patient.mdx) |
+| `encounter` | An [encounter](../clinical/encounter.mdx) |
+| `consent` | A [consent](../clinical/consent.mdx) |
+| `diagnostic_report` | A [diagnostic report](../clinical/diagnostic-report.mdx) |
+| `service_request` | A [service request](../clinical/service-request.mdx) |
+
+### `FileCategoryChoices` values
+
+File category (`spec.py`). String enum.
+
+| Value |
+| --- |
+| `audio` |
+| `xray` |
+| `identity_proof` |
+| `unspecified` |
+| `discharge_summary` |
+| `consent_attachment` |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`) — `serialize` (DB → Pydantic) and `de_serialize` (Pydantic → DB), with `perform_extra_serialization` / `perform_extra_deserialization` hooks. `FileUpload` does **not** set `__store_metadata__`, so `mime_type` is moved in/out of `meta` explicitly by the hooks rather than the generic meta machinery.
+
+| Spec | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `FileUploadBaseSpec` | shared base | `id: UUID4 \| None`, `name: str` (required) |
+| `FileUploadCreateSpec` | write · create | Adds `original_name`, `file_type`, `file_category`, `associating_id`, `mime_type`. Validates `mime_type` and `original_name`; on deserialize sets `_just_created=True`, `internal_name = original_name`, and `meta["mime_type"]` |
+| `FileUploadUpdateSpec` | write · update | Same fields as base (`id`, `name`) — only the display name is editable |
+| `FileUploadListSpec` | read · list | Read view (see fields below); resolves `extension`, `mime_type`, audit users, `uploaded_by`, `archived_by` |
+| `FileUploadRetrieveSpec` | read · detail | Extends `FileUploadListSpec` with `signed_url`, `read_signed_url`, `internal_name`; emits a **write** URL for just-created rows, otherwise a **read** URL |
+| `ConsentFileUploadCreateSpec` | write · create (consent) | `original_name` + `associating_id: UUID4`; forces `file_type=consent`, `file_category=consent_attachment` server-side |
+
+No field in these specs binds to a value set or uses `CodeableConcept` / `Period` / other `common` types — `file_type` and `file_category` are plain string enums.
+
+### `FileUploadCreateSpec` validation
+
+| Field | Rule |
+| --- | --- |
+| `name` | Required (inherited from base) |
+| `original_name` | Non-empty; run through `file_name_validator` (`care/utils/models/validators.py`): ≤ 255 chars, must not start with `.`, must have an extension, extension must be in `ALLOWED_FILE_EXTENSIONS` and not in `BLOCKED_FILE_EXTENSIONS` |
+| `mime_type` | Must be in `settings.ALLOWED_MIME_TYPES`, else `"Invalid mime type"` |
+| `file_type` | Must be a `FileTypeChoices` member |
+| `file_category` | Must be a `FileCategoryChoices` member |
+| `associating_id` | Required `str` |
+
+### `FileUploadListSpec` read fields
+
+| Field | Type | Source |
+| --- | --- | --- |
+| `id` | `UUID4` | `obj.external_id` (set in `perform_extra_serialization`) |
+| `name` | `str` | column |
+| `file_type` | `FileTypeChoices` | column |
+| `file_category` | `FileCategoryChoices` | column |
+| `associating_id` | `str` | column |
+| `upload_completed` | `bool` | column |
+| `is_archived` | `bool \| None` | column |
+| `archive_reason` | `str \| None` | column |
+| `archived_datetime` | `datetime \| None` | column |
+| `created_date` | `datetime` | column |
+| `extension` | `str` | derived via `obj.get_extension()` |
+| `mime_type` | `str` | `obj.meta.get("mime_type")` |
+| `uploaded_by` | `dict \| None` | `UserSpec` from cache (`obj.created_by_id`) |
+| `archived_by` | `UserSpec \| None` | `UserSpec` from cache (`obj.archived_by_id`) |
+| `created_by` / `updated_by` | `dict \| None` | `serialize_audit_users` |
+
+### `FileUploadRetrieveSpec` extra fields
+
+| Field | Type | When populated |
+| --- | --- | --- |
+| `signed_url` | `str \| None` | When `obj._just_created` is truthy — a **write** (PUT) URL from `files_manager.signed_url(obj)`. This is the upload target returned at create time |
+| `read_signed_url` | `str \| None` | Otherwise — a **read** (GET) URL from `files_manager.read_signed_url(obj)` |
+| `internal_name` | `str` | The storage object key (marked in source as possibly not needing to be returned) |
+
+## Methods & save behaviour
+
+### `get_extension()`
+
+Returns the file extension derived from `internal_name` via `parse_file_extension`, formatted as `.ext` (or `.tar.gz`-style multi-part), or `""` when none is found. Surfaced to clients as the `extension` read field.
+
+### `save()` side effects
+
+On save, when `internal_name` is empty or the row is new (`not self.id`) and the caller has **not** passed `skip_internal_name=True`:
+
+1. A random `internal_name` is generated as `uuid4() + int(time.time())`.
+2. If an extension can be derived, it is appended to that random name.
+3. `internal_name` is set before the row is persisted.
+
+This keeps the storage key free of the user-supplied `name`, limiting PII leakage if the bucket is compromised. The inline `upload-file` path calls `save(skip_internal_name=True)` after uploading so the generated key is preserved.
+
+## API integration notes
+
+### Two upload flows
+
+1. **Signed-URL (direct-to-S3), default:** `POST` a `FileUploadCreateSpec`. The server validates, deserializes (`_just_created=True`), and returns `FileUploadRetrieveSpec` including a write `signed_url`. The client `PUT`s the bytes directly to S3, then calls `POST .../{external_id}/mark_upload_completed/` to flip `upload_completed=True`.
+2. **Inline base64 (`POST .../upload-file/`):** the client sends `original_name` + base64 `file_data`. The server decodes it, enforces `settings.MAX_FILE_UPLOAD_SIZE` (MB), sniffs the real MIME type with `python-magic`, re-checks `ALLOWED_MIME_TYPES`, builds a `FileUploadCreateSpec`, sets `_just_created=False`, uploads via `files_manager.put_object`, sets `upload_completed=True`, and returns `FileUploadRetrieveSpec` (so the response carries `read_signed_url`, not a write URL).
+
+### Server-maintained behaviour
+
+- `internal_name` is platform-maintained — never set it from clients; the model generates a random PII-free key.
+- `mime_type` lives in `meta`, not a column; it is written by `FileUploadCreateSpec` and read back during serialization. On the inline path it is **re-derived from file content** (`magic.from_buffer`) rather than trusting the client.
+- `associating_id` + `file_type` together link a file to its owning resource; `list` requires both as query params and only returns `upload_completed=True` rows.
+- Every action runs `file_authorizer(user, file_type, associating_id, "read"|"write")`.
+- Archival (`POST .../{external_id}/archive/`, body `{ archive_reason }`) sets `is_archived`, `archive_reason`, `archived_datetime`, `archived_by` server-side and returns `FileUploadListSpec`. This soft state is distinct from the `EMRBaseModel` soft-delete (`deleted`).
+- `mark_upload_completed` and `archive` both return `FileUploadListSpec` (no signed URL).
+
+## Related
+
+- Base model: [EMRBaseModel](../foundation/base-model.mdx)
+- Owning resources: [patient](../clinical/patient.mdx), [encounter](../clinical/encounter.mdx), [consent](../clinical/consent.mdx), [diagnostic-report](../clinical/diagnostic-report.mdx), [service-request](../clinical/service-request.mdx)
+- User (audit / `uploaded_by` / `archived_by`): [user](../access/user.mdx)
+- Source: [file_upload.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/file_upload.py), [spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/file_upload/spec.py)
diff --git a/versioned_docs/version-3.1/references/platform/meta-artifact.mdx b/versioned_docs/version-3.1/references/platform/meta-artifact.mdx
new file mode 100644
index 0000000..1b3f091
--- /dev/null
+++ b/versioned_docs/version-3.1/references/platform/meta-artifact.mdx
@@ -0,0 +1,125 @@
+---
+sidebar_position: 2
+---
+
+# Meta Artifact
+
+Technical reference for the `MetaArtifact` module in Care EMR.
+
+`MetaArtifact` is a generic, polymorphic metadata record attached to any Care object by type + external ID. The Django model is the storage layer: `object_value` is an opaque `JSONField`, and `associating_type` / `object_type` are bare `CharField`s. The constrained values and the API request/response schemas live in the Pydantic resource specs.
+
+**Source:**
+
+- Model: [`care/emr/models/meta_artifact.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/meta_artifact.py)
+- Resource specs: [`care/emr/resources/meta_artifact/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/meta_artifact/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `MetaArtifact` | Generic, polymorphic metadata record attached to any Care object by type + external ID |
+
+`MetaArtifact` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_by` / `updated_by`, `created_date` / `modified_date`, and soft-delete `deleted`).
+
+## `MetaArtifact` fields
+
+### Association
+
+A `MetaArtifact` is not tied to a parent through a Django `ForeignKey`. Instead it points at any object generically by recording the object's type and its `external_id`.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `associating_type` | `CharField(255)` | Non-null. Identifies the kind of object this artifact is attached to. API-constrained to [`MetaArtifactAssociatingTypeChoices`](#metaartifactassociatingtypechoices-values) |
+| `associating_external_id` | `UUIDField` | Non-null. The `external_id` of the associated object. Set server-side from the spec's `associating_id` (see [Resource specs](#resource-specs-api-schema)) |
+
+These two fields are covered by a composite database index (see [Methods & save behaviour](#methods--save-behaviour)) so lookups by associated object are efficient.
+
+### Content
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(255)` | Display name for the artifact. Spec enforces non-blank on create |
+| `object_type` | `CharField(255)` | Describes the kind of payload held in `object_value`. API-constrained to [`MetaArtifactObjectTypeChoices`](#metaartifactobjecttypechoices-values) |
+| `object_value` | `JSONField` | Structured payload for the artifact. Spec types it as `dict \| list` (required, no default) |
+| `note` | `TextField` | Nullable free-text note. Spec: `str \| None`, default `None` |
+
+## Enum values
+
+These choice classes constrain `associating_type` and `object_type` at the API layer. They are defined in `spec.py` (there is no separate `constants.py` / `valueset.py` for this resource). Both are `str` enums.
+
+### `MetaArtifactAssociatingTypeChoices` values
+
+Binds `associating_type`.
+
+| Value |
+| --- |
+| `patient` |
+| `encounter` |
+
+### `MetaArtifactObjectTypeChoices` values
+
+Binds `object_type`.
+
+| Value |
+| --- |
+| `drawing` |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` ([`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)) — `serialize` (model → pydantic) / `de_serialize` (pydantic → model), with `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__model__ = MetaArtifact`.
+
+| Spec | Role | Fields exposed |
+| --- | --- | --- |
+| `MetaArtifactBaseSpec` | shared | `id` (`UUID4 \| None`), `object_value` (`dict \| list`), `note` (`str \| None`) |
+| `MetaArtifactCreateSpec` | write · create | base + `associating_type`, `associating_id` (`UUID4`), `object_type`, `name` |
+| `MetaArtifactUpdateSpec` | write · update | identical to `MetaArtifactBaseSpec` (only `object_value` / `note` are mutable; association and type are fixed after create) |
+| `MetaArtifactReadSpec` | read · detail/list | base + `associating_type`, `associating_id`, `object_type`, `name`, `created_date`, `modified_date`, `created_by` (`dict \| None`), `updated_by` (`dict \| None`) |
+
+Field-level details and validation:
+
+| Field | Spec type | Required | Default | Validation / behaviour |
+| --- | --- | --- | --- | --- |
+| `id` | `UUID4 \| None` | optional | `None` | Read: populated from `obj.external_id` in `perform_extra_serialization` |
+| `object_value` | `dict \| list` | yes | — | Arbitrary JSON object or array |
+| `note` | `str \| None` | no | `None` | Free text |
+| `associating_type` | `MetaArtifactAssociatingTypeChoices` | yes (create) | — | Bound enum; `patient` or `encounter` |
+| `associating_id` | `UUID4` | yes (create) | — | `external_id` of the parent. On create, copied to model field `associating_external_id` in `perform_extra_deserialization` |
+| `object_type` | `MetaArtifactObjectTypeChoices` | yes (create) | — | Bound enum; `drawing` |
+| `name` | `str` | yes (create) | — | `validate_name`: rejects blank/whitespace-only with `"Name cannot be empty"` |
+| `created_date` | `datetime` | read | — | From `EMRBaseModel` |
+| `modified_date` | `datetime` | read | — | From `EMRBaseModel` |
+| `created_by` / `updated_by` | `dict \| None` | read | `None` | Serialized via `serialize_audit_users` (cached `UserSpec`) |
+
+Server-maintained behaviour:
+
+- **Create** (`MetaArtifactCreateSpec.perform_extra_deserialization`): sets `obj.associating_external_id = self.associating_id`. The spec's `associating_id` (the parent's `external_id`) is the public handle; the model stores it as `associating_external_id`.
+- **Read** (`MetaArtifactReadSpec.perform_extra_serialization`): sets `mapping["id"] = obj.external_id` and calls `serialize_audit_users` to attach `created_by` / `updated_by`.
+- **Update**: `MetaArtifactUpdateSpec` exposes only `object_value` and `note` (plus the inherited optional `id`); `associating_type`, `associating_id`, `object_type`, and `name` are not re-accepted, so association and classification are immutable after creation.
+
+No coded field on this resource binds to a value set (no `valueset.py`); the only constrained vocabularies are the two `str` enums above.
+
+## Methods & save behaviour
+
+`MetaArtifact` does not override `save()` or `delete()` and defines no custom methods; it inherits all audit and soft-delete behaviour from [`EMRBaseModel`](../foundation/base-model.mdx).
+
+The model's `Meta` declares one composite index to support querying artifacts for a given parent object:
+
+```text
+Index(fields=["associating_type", "associating_external_id"])
+```
+
+## API integration notes
+
+- The generic `associating_type` + `associating_external_id` pair lets a single table store metadata for many different Care object types without per-type foreign keys; clients reference the target by its `external_id` (UUID), never its internal primary key. Clients send this as `associating_id` on create.
+- `associating_type` and `object_type` are open `CharField`s in storage but are constrained at the API by `str` enums (`patient` / `encounter` and `drawing` respectively). Adding new association or artifact kinds requires extending those enums in `spec.py`, not a schema migration.
+- `object_value` is an open `JSONField` typed as `dict | list` by the spec, so artifact payloads can carry deployment- or feature-specific structure without schema migrations; `object_type` records what shape to expect (e.g. `drawing`).
+- `name` is validated non-blank on create; whitespace-only names are rejected.
+- After creation the association (`associating_type` / `associating_id`), `object_type`, and `name` are fixed — only `object_value` and `note` are updatable via `MetaArtifactUpdateSpec`.
+- Audit fields (`created_by`, `updated_by`, `created_date`, `modified_date`), the public `external_id` (exposed as `id`), and soft-delete (`deleted`) are platform-maintained via `EMRBaseModel` — do not set them directly from clients.
+
+## Related
+
+- Base model: [EMRBaseModel](../foundation/base-model.mdx)
+- Common patient associations: [Patient](../clinical/patient.mdx), [Encounter](../clinical/encounter.mdx)
+- Model source: [meta_artifact.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/meta_artifact.py)
+- Spec source: [meta_artifact/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/meta_artifact/spec.py)
diff --git a/versioned_docs/version-3.1/references/platform/report.mdx b/versioned_docs/version-3.1/references/platform/report.mdx
new file mode 100644
index 0000000..b815ec2
--- /dev/null
+++ b/versioned_docs/version-3.1/references/platform/report.mdx
@@ -0,0 +1,199 @@
+---
+sidebar_position: 3
+---
+
+# Report & Templates
+
+Technical reference for the `ReportUpload` and `Template` modules in Care EMR.
+
+This page documents two layers:
+
+- **Storage layer** — the Django models (`care/emr/models/report/`). Several fields are opaque (`TextField`/`JSONField`) and their real shape is not visible here.
+- **API layer** — the Pydantic resource specs (`care/emr/resources/report/`), built on [`EMRResource`](../foundation/base-model.mdx). These define the enums, validation, the structure of the JSON fields, and the read/write schemas exposed by the API.
+
+**Source:**
+
+- Models: [`report_upload.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/report/report_upload.py), [`template.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/report/template.py)
+- Resource specs: [`report/report_upload/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/report/report_upload/spec.py), [`report/template/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/report/template/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ReportUpload` | An uploaded, generated report file (PDF/HTML/etc.) produced from a `Template` and linked to a subject resource via `associating_id` |
+| `Template` | A facility-scoped (or instance-wide) reusable report definition (markup + render options) used to generate reports |
+
+`ReportUpload` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, `meta`/`history` JSON, audit FKs, and soft-delete semantics).
+
+`Template` extends [`SlugBaseModel`](../foundation/base-model.mdx), the facility-scoped slug variant (`FACILITY_SCOPED = True`, adds `slug` handling on top of the EMR base). Slugs are stored prefixed: `f--` when facility-scoped, `i-` when instance-wide (see [`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/base.py)).
+
+## `ReportUpload` fields
+
+### Core
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `template` | `FK → Template (PROTECT)` | Template the report was generated from. Cannot be deleted while reports reference it |
+| `name` | `CharField(2000)` | Display name of the report. Required in every spec |
+| `internal_name` | `CharField(2000)` | Randomized storage key; auto-generated on save to avoid leaking PII in the file name (see [save behaviour](#methods--save-behaviour)). Exposed only in `RetrieveSpec` |
+| `associating_id` | `CharField(100)` | Non-null. Free-form identifier linking the report to its subject resource (e.g. an encounter) |
+| `upload_completed` | `BooleanField` | Default `False`. Set once the file upload to storage finishes |
+| `report_type` | `CharField(50)` | File/content type of the report; also exposed via the `file_type` property for `S3FilesManager` |
+
+### Archival
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `is_archived` | `BooleanField` | Default `False` |
+| `archive_reason` | `TextField` | Blank allowed |
+| `archived_datetime` | `DateTimeField` | Nullable; set when archived |
+| `archived_by` | `FK → User (PROTECT)` | Nullable. `related_name="archived_reports"` |
+
+### Inherited / metadata
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `meta` | `JSONField` (default `{}`) | Inherited from `EMRBaseModel`. Holds `mime_type` (surfaced by specs as `mime_type`) and other free-form metadata |
+| `history` | `JSONField` (default `{}`) | Inherited audit history |
+| `created_by` / `updated_by` | `FK → User (SET_NULL)` | Inherited audit FKs; serialized as `created_by`/`updated_by` `UserSpec` |
+
+### Storage
+
+`ReportUpload` declares a class-level `files_manager = S3FilesManager(BucketType.REPORT)` — the file body lives in object storage (REPORT bucket), not in the database. The model row holds metadata and the `internal_name` storage key.
+
+## `Template` fields
+
+The Django model stores most config as plain strings plus an opaque `options` `JSONField`. The spec layer constrains `status`, `default_format`, the slug, and the structure/compatibility of `template_type`, `context`, and `options`.
+
+| Field | Type (model) | Spec constraint | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → Facility (PROTECT, nullable)` | `UUID4 \| None` (write) | Null → instance-wide template; set → facility-scoped. Excluded from base serialization (`__exclude__`); resolved server-side on write |
+| `slug` | `CharField(255)` | written via `slug_value: SlugType`; read as `slug` + parsed `slug_config` | Stored prefixed (`f-…`/`i-…`). `slug_value` validated: 5–50 chars, URL-safe, must start/end alphanumeric |
+| `name` | `CharField(255)` | `str`, required | |
+| `status` | `CharField(255)` | `TemplateStatusOptions` enum | See [status values](#templatestatusoptions-values) |
+| `template_data` | `TextField` | `str`, required (write) | The report markup/body. Read only in `RetrieveSpec`, not in list/read |
+| `template_type` | `CharField(255)` | `str`, validated against `ReportTypeRegistry` | Must be a registered report type; must be compatible with `context` (matching `associating_model`) |
+| `default_format` | `CharField(255)` | `TemplateFormatOptions` enum | See [format values](#templateformatoptions-values). Drives which generator validates `options` |
+| `context` | `CharField(100)` (default `"encounter_base"`) | `str`, validated against `DataPointRegistry` | Default `encounter_base` → reports are encounter-oriented by default. Must be a registered context compatible with `template_type` |
+| `description` | `TextField` (blank, default `""`) | `str = ""` | |
+| `options` | `JSONField` (default `{}`) | `dict = {}`, validated against the format generator's `options_model` | Render/config options; the accepted keys depend on `default_format` (PDF vs HTML generator) |
+
+### `TemplateStatusOptions` values
+
+`status` is bound to this enum (`care/emr/resources/report/template/spec.py`):
+
+| Value | Meaning |
+| --- | --- |
+| `draft` | Template being authored, not yet usable |
+| `active` | Published / usable for generating reports |
+| `retired` | Withdrawn from use |
+
+### `TemplateFormatOptions` values
+
+`default_format` is bound to this enum:
+
+| Value | Meaning |
+| --- | --- |
+| `pdf` | Render to PDF (uses the PDF generator's `options_model`) |
+| `html` | Render to HTML (uses the HTML generator's `options_model`) |
+
+### `slug_config` shape (read)
+
+On read, `TemplateReadSpec` adds a parsed `slug_config` dict derived from the stored prefixed slug:
+
+```text
+slug_config (facility-scoped) → { facility: , slug_value: }
+slug_config (instance-wide) → { slug_value: } # parsed from "i-"
+```
+
+## Resource specs (API schema)
+
+All specs subclass [`EMRResource`](../foundation/base-model.mdx). Read flow: `serialize()` → DB-field copy → `perform_extra_serialization()` hook. Write flow: `de_serialize()` → DB-field copy → `perform_extra_deserialization()` hook.
+
+### `ReportUpload` specs (`report/report_upload/spec.py`)
+
+| Spec class | Role | Fields / behaviour |
+| --- | --- | --- |
+| `ReportUploadBaseSpec` | shared | `id: UUID4 \| None`, `name: str` |
+| `ReportUploadListSpec` | read · list | Adds `template` (nested `TemplateReadSpec`), `report_type`, `associating_id`, `archived_by` (`UserSpec`), `archived_datetime`, `upload_completed`, `is_archived`, `archive_reason`, `created_date`, `extension`, `uploaded_by`, `mime_type` |
+| `ReportUploadRetrieveSpec` | read · detail | Extends list spec; adds `signed_url`, `read_signed_url`, `internal_name` |
+
+Server-maintained read behaviour (`perform_extra_serialization`):
+
+- `id` ← `obj.external_id`.
+- `extension` ← `obj.get_extension()` (dotted extension parsed from `internal_name`).
+- `mime_type` ← `obj.meta["mime_type"]`.
+- `template` ← serialized via `TemplateReadSpec` (full nested template object).
+- `created_by` / `updated_by` populated from the user cache via `serialize_audit_users`.
+- **Signed URLs (retrieve only):** if the object was just created (`_just_created`), `signed_url` is set to a write/upload URL via `files_manager.signed_url(obj)`; otherwise `read_signed_url` is set via `files_manager.read_signed_url(obj)`. Exactly one is populated per response.
+
+> `ReportUpload` has no `CreateSpec`/`UpdateSpec` in this module — uploads are created through the file-upload flow ([File Upload](./file-upload.mdx)) and the report metadata/`internal_name` are platform-maintained. See [save behaviour](#methods--save-behaviour).
+
+### `Template` specs (`report/template/spec.py`)
+
+| Spec class | Role | Fields / behaviour |
+| --- | --- | --- |
+| `TemplateBaseSpec` | shared | `id`, `name`, `status` (`TemplateStatusOptions`), `default_format` (`TemplateFormatOptions`), `description = ""`, `options = {}`. `__exclude__ = ["facility"]` |
+| `TemplateCreateSpec` | write · create | Adds `facility: UUID4 \| None`, `slug_value: SlugType`, `template_data: str`, `template_type: str`, `context: str` |
+| `TemplateUpdateSpec` | write · update | Identical to `TemplateCreateSpec` (`pass` subclass) |
+| `TemplateReadSpec` | read · list | Base fields + `slug_config: dict`, `slug: str`, `template_type: str`, `context: str` (no `template_data`) |
+| `TemplateRetrieveSpec` | read · detail | Extends read spec; adds nested `facility` (`FacilityBareMinimumSpec`) and `template_data: str` |
+
+Write-side validation & side effects (`TemplateCreateSpec`):
+
+- `slug_value` — `SlugType`: 5–50 chars, pattern `^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$`.
+- `template_type` (`field_validator`) — must be non-empty and resolvable via `ReportTypeRegistry`; else `Invalid report type`.
+- `context` (`field_validator`) — must be non-empty and resolvable via `DataPointRegistry`; else `Invalid Context type`.
+- `validate_report_type_and_context` (`model_validator`, after):
+ - both `template_type` and `context` must resolve, else `Invalid report type or context`;
+ - their `associating_model` must match (`template_type.associating_model == context.__associating_model__`), else `Report Type and Context are not compatible`;
+ - `options` is validated against `GeneratorRegistry.get(default_format).options_model` — so the allowed `options` keys depend on whether `default_format` is `pdf` or `html`.
+- `perform_extra_deserialization` (side effects): resolves `facility` external ID to the `Facility` row (`get_object_or_404`) when provided, and sets `obj.slug = self.slug_value` (the raw value; the model prefixes it on `calculate_slug`).
+
+Read-side behaviour:
+
+- `TemplateReadSpec.perform_extra_serialization`: `id ← external_id`; `slug_config ← obj.parse_slug(obj.slug)` (see [`slug_config` shape](#slug_config-shape-read)).
+- `TemplateRetrieveSpec`: additionally serializes `facility` via `FacilityBareMinimumSpec` when set.
+
+## Methods & save behaviour
+
+### `ReportUpload.save()`
+
+On create (no `id`) or when `internal_name` is empty, `save()` generates a random `internal_name` from `uuid4()` plus the current epoch. This intermediate name decouples the stored object from the human-readable `name`, so a storage/data leak does not expose PII in file names.
+
+- Pass `skip_internal_name=True` to `save()` to bypass generation and keep an explicitly set `internal_name`.
+- If `internal_name` already carried a value, the file extension is parsed (via `parse_file_extension`) and appended to the new randomized name.
+
+### `ReportUpload.get_extension()`
+
+Returns the dotted extension(s) parsed from `internal_name` (e.g. `.pdf`), or `""` if none. Surfaced as the `extension` field in read specs.
+
+### `ReportUpload.file_type` (property)
+
+Alias for `report_type`, provided for compatibility with `S3FilesManager`.
+
+### `Template` slug methods (inherited from `SlugBaseModel`)
+
+- `calculate_slug()` — returns the prefixed slug: `f--` when facility-scoped, else `i-`.
+- `parse_slug(slug)` — inverse; returns `slug_config` (used by `TemplateReadSpec`).
+
+## API integration notes
+
+- Report files are stored in object storage (S3-compatible REPORT bucket) and accessed via `S3FilesManager`; the database row is metadata only.
+- `internal_name` is platform-maintained — do not set it from clients; it is generated on save to keep storage keys free of PII. Use `skip_internal_name` only for controlled migrations. It is exposed only via `ReportUploadRetrieveSpec`.
+- On a freshly created report, the retrieve response carries an upload `signed_url`; on subsequent reads it carries a `read_signed_url` instead.
+- `upload_completed` reflects upload state; clients should treat a report as available only once it is `True`.
+- `associating_id` is the link to the report's subject resource; populate it when generating a report so the file can be retrieved in that resource's context.
+- `Template.options` is validated server-side against the chosen format's generator schema — it is not an arbitrary bag; valid keys depend on `default_format` (`pdf`/`html`).
+- `template_type` and `context` must be a compatible pair (same `associating_model`); the default `context` is `encounter_base`, so templates target encounters unless changed.
+- `Template.facility` being nullable distinguishes instance-wide templates (`i-` slug) from facility-scoped ones (`f-` slug).
+
+## Related
+
+- Reference: [File Upload](./file-upload.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [User](../access/user.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [report_upload.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/report/report_upload.py), [template.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/report/template.py)
+- Source: [report_upload/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/report/report_upload/spec.py), [template/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/report/template/spec.py)
diff --git a/versioned_docs/version-3.1/references/platform/resource-category.mdx b/versioned_docs/version-3.1/references/platform/resource-category.mdx
new file mode 100644
index 0000000..0e29f54
--- /dev/null
+++ b/versioned_docs/version-3.1/references/platform/resource-category.mdx
@@ -0,0 +1,208 @@
+---
+sidebar_position: 5
+---
+
+# Resource Category
+
+Technical reference for the `ResourceCategory` module in Care EMR.
+
+`ResourceCategory` is the **storage** layer (Django model). The **API/implementation** layer is defined by the Pydantic resource specs in `care/emr/resources/resource_category/`, which add the `resource_type` enum, the structured shape of the `*_monetary_components` JSON fields (via `MonetaryComponent`), slug validation, and the read/write schemas. Several model fields are opaque `JSONField`s whose real structure only exists in the specs — see [Resource specs (API schema)](#resource-specs-api-schema).
+
+**Source:**
+- Model: [`care/emr/models/resource_category.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/resource_category.py)
+- Spec: [`care/emr/resources/resource_category/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/resource_category/spec.py)
+- Shared: [`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py) · [`common/monetary_component.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py) · [`common/coding.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/coding.py)
+
+## 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`](../foundation/base-model.mdx) (`FACILITY_SCOPED = True`), the facility-scoped slug variant of the base (adds `slug` handling on top of the EMR base fields: `external_id`, audit fields, soft-delete, and `history`/`meta` JSON).
+
+## `ResourceCategory` fields
+
+### Core
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → facility.Facility (PROTECT)` | yes | Owning facility; cannot be deleted while categories reference it. Excluded from spec write/read payloads (set server-side) |
+| `resource_type` | `CharField(255)` | yes | Top-level classification. Spec constrains to the `ResourceCategoryResourceTypeOptions` enum — see [values](#resourcecategoryresourcetypeoptions-values) |
+| `resource_sub_type` | `CharField(255)` | yes | Secondary classification; free string in the spec (`resource_sub_type: str`) |
+| `title` | `CharField(255)` | yes | Display name (`title: str`) |
+| `slug` | `CharField(255)` | yes | Facility-scoped encoded slug `f--`. Clients send the unencoded `slug_value` (validated `SlugType`); the model encodes via `calculate_slug()` |
+| `description` | `TextField` | no | Nullable, blank-able (`description: str \| None = None`) |
+
+The `Meta` declares a composite index on `(slug, facility)` for fast 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`; flipped to `True` on the parent when a child is created (see [save behaviour](#methods--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
+
+These denormalized fields are populated by `set_organization_cache()` and `get_parent_json()` so the ancestor chain can be read without recursive joins. They are platform-maintained — never written by clients.
+
+| Field | Type | Rebuilt by |
+| --- | --- | --- |
+| `parent_cache` | `ArrayField[int]` | `set_organization_cache()` — 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 (see shape below) with a `cache_expiry` timestamp; refreshed after `cache_expiry_days` (15) |
+
+`cached_parent_json` (and the `parent` field of `ResourceCategoryReadSpec`) shape, from `get_parent_json()`:
+
+```text
+{
+ "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](#monetarycomponent-nested-shape)). Only serialized 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](#methods--save-behaviour)). 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](../supply/product-knowledge.mdx) entries |
+| `activity_definition` | Category classifies [Activity Definitions](../clinical/activity-definition.mdx) |
+| `charge_item_definition` | Category classifies [Charge Item Definitions](../billing/charge-item-definition.mdx); enables 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`). `__model__ = ResourceCategory` and `__exclude__ = ["parent"]` (the `parent` FK is never auto-mapped; it is resolved 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`. `perform_extra_deserialization` resolves `obj.parent = ResourceCategory.objects.get(slug=self.parent)` when `parent` is set, and sets `obj.slug = self.slug_value` |
+| `ResourceCategoryUpdateSpec` | write · update | Base + `slug_value: SlugType`. `perform_extra_deserialization` sets `obj.slug = self.slug_value` (no `parent` reassignment on update) |
+| `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 |
+
+> The code labels these "ChargeItemDefinition Category" in docstrings; the actual model is `ResourceCategory`. There is no separate `*ListSpec`/`*RetrieveSpec` — `ResourceCategoryReadSpec` serves both.
+
+### `ResourceCategoryReadSpec` serialization (`perform_extra_serialization`)
+
+- `id = obj.external_id`
+- `parent = obj.get_parent_json()` — nested ancestor snapshot (shape above)
+- `slug_config = obj.parse_slug(obj.slug)` — for a facility-scoped slug returns `{"facility": , "slug_value": }`; for an instance slug `{"slug_value": }`
+- `calculated_monetary_components` / `configured_monetary_components` — set **only** when `resource_type == "charge_item_definition"`; otherwise the (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, `-`, `_`), must start and end alphanumeric
+
+The stored `slug` is the encoded form `f--` (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](#monetarycomponenttype-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:
+
+- `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's monetary fields are stored/validated 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 itself.
+
+## Methods & save behaviour
+
+`ResourceCategory` overrides `save()` and maintains tree/charge caches through several methods plus a Celery task.
+
+### `save()`
+
+- On **create** (`self.id` not yet set): calls `super().save()`, then `set_organization_cache()`, then enqueues monetary-component summarisation via `summarise_monetary_components(self)`.
+- On **update**: a plain `super().save(*args, **kwargs)` with no cache rebuild.
+
+### `set_organization_cache()`
+
+Run 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 (and lazily rebuilds) `cached_parent_json`. If a cached snapshot exists and is not past its `cache_expiry`, it is returned as-is; otherwise the parent chain is walked, a fresh nested snapshot is built with a new expiry (`now + cache_expiry_days`), persisted via `save(update_fields=["cached_parent_json"])`, and returned. 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, 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"])`.
+- 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, while components without a `code` are appended unmerged.
+
+## API integration notes
+
+- Categories are facility-scoped; the `(slug, facility)` index and `SlugBaseModel` slug encoding (`f--`) make slugs unique per facility. Clients send `slug_value` (5–50, URL-safe); the server encodes and `slug_config` decodes it.
+- `parent` is written by **slug** (`ResourceCategoryWriteSpec.parent` → `ResourceCategory.objects.get(slug=...)`), not id; on read it is the nested `get_parent_json()` snapshot.
+- `parent_cache`, `level_cache`, `root_org`, `has_children`, `cached_parent_json`, and `calculated_monetary_components` are platform-maintained — do not set them directly; write `parent` and `configured_monetary_components` and let `save()`/the Celery task derive the rest.
+- Cache rebuilds (`set_organization_cache`, monetary-component summarisation) run only on **create**; updating an existing node's `parent` or configured components does not auto-rebuild the caches.
+- Monetary-component propagation is asynchronous (Celery), so `calculated_monetary_components` on descendants is eventually consistent after a write.
+- Monetary-component fields are serialized only for `resource_type == "charge_item_definition"`; component identity in merges relies on the nested `code.system` + `code.code` pair.
+
+## Related
+
+- Reference: [Charge Item Definition](../billing/charge-item-definition.mdx)
+- Reference: [Charge Item](../billing/charge-item.mdx)
+- Reference: [Activity Definition](../clinical/activity-definition.mdx)
+- Reference: [Product Knowledge](../supply/product-knowledge.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Base Model](../foundation/base-model.mdx)
+- Source: [resource_category.py (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/resource_category.py) · [spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/resource_category/spec.py) · [monetary_component.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py)
diff --git a/versioned_docs/version-3.1/references/platform/resource-request.mdx b/versioned_docs/version-3.1/references/platform/resource-request.mdx
new file mode 100644
index 0000000..6f3b1a8
--- /dev/null
+++ b/versioned_docs/version-3.1/references/platform/resource-request.mdx
@@ -0,0 +1,164 @@
+---
+sidebar_position: 4
+---
+
+# Resource Request
+
+Technical reference for the `ResourceRequest` module in Care EMR.
+
+`ResourceRequest` models an inter-facility request for a resource (transfer, supply, or assistance), tracked from origin through approval and assignment. The Django model is the **storage layer**: `status`, `category`, and `priority` are open `CharField`/`IntegerField` columns with no model-level choices. The **API/implementation layer** lives in the Pydantic resource specs, which constrain those fields to enums, add validation, resolve foreign keys, and define the read/write schemas.
+
+**Sources:**
+
+- Model: [`care/emr/models/resource_request.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/resource_request.py)
+- Spec: [`care/emr/resources/resource_request/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/resource_request/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ResourceRequest` | An inter-facility request for a resource (transfer, supply, or assistance), tracked from origin through approval and assignment |
+| `ResourceRequestComment` | A free-text comment thread entry attached to a `ResourceRequest` |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields — `created_by`, `updated_by`, `created_date`, `modified_date` — and soft-delete semantics).
+
+## `ResourceRequest` fields
+
+### Facilities
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `origin_facility` | `FK → Facility` (PROTECT) | Required. Facility raising the request; `related_name="resource_requesting_facilities"`. Cannot be changed after create (set only when `is_update` is false). |
+| `approving_facility` | `FK → Facility` (SET_NULL) | Nullable; facility responsible for approving; `related_name="resource_approving_facilities"` |
+| `assigned_facility` | `FK → Facility` (SET_NULL) | Nullable; facility the request is assigned to/fulfilled by; `related_name="resource_assigned_facilities"`. Required when `assigned_to` is set. |
+
+### Request details
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `emergency` | `bool` (`BooleanField`) | Yes (spec) | `False` (model) | Flags urgent requests |
+| `title` | `str` (`CharField(255)`) | Yes | — | Not null, not blank |
+| `reason` | `str` (`TextField`) | Yes (spec) | `""` (model) | Free text |
+| `status` | `StatusChoices` enum (`CharField(100)`) | Yes | — | Constrained to [Status values](#statuschoices-values) by the spec; no model-level choices |
+| `category` | `CategoryChoices` enum (`CharField(100)`) | Yes | — | Constrained to [Category values](#categorychoices-values) by the spec; no model-level choices |
+| `priority` | `int` (`IntegerField`) | Yes (spec) | `None` (model, nullable) | Spec requires an integer; the model column is nullable |
+
+### Referring contact
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `referring_facility_contact_name` | `str` (`TextField`) | Yes (spec) | `""` (model) | Blank allowed at model level |
+| `referring_facility_contact_number` | `str` (`CharField(14)`) | Yes (spec) | `""` (model) | Validated with `mobile_or_landline_number_validator` at the model layer |
+
+### Assignment & patient
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `is_assigned_to_user` | `bool` (`BooleanField`) | Defaults to `False`; indicates a user-level assignment exists. Not exposed by the spec classes. |
+| `assigned_to` | `FK → User` (SET_NULL) | Nullable; user the request is assigned to; `related_name="resource_request_assigned"`. Requires `assigned_facility`, and the user must be a member of that facility (see [validation](#validation)). |
+| `related_patient` | `FK → Patient` (CASCADE) | Nullable; defaults to `None`; links the request to a patient when applicable. Set only on create. |
+
+## Enum values
+
+The spec layer binds `status` and `category` to string enums defined in `spec.py`. The Django model stores them as plain `CharField`s, so these values are enforced only through the API.
+
+### `StatusChoices` values
+
+| Value |
+| --- |
+| `pending` |
+| `approved` |
+| `rejected` |
+| `cancelled` |
+| `transportation_to_be_arranged` |
+| `transfer_in_progress` |
+| `completed` |
+
+### `CategoryChoices` values
+
+| Value |
+| --- |
+| `patient_care` |
+| `comfort_devices` |
+| `medicines` |
+| `financial` |
+| `supplies` |
+| `other` |
+
+## Resource specs (API schema)
+
+All specs build on `EMRResource` ([`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)), which provides `serialize` (DB object → pydantic, read path) and `de_serialize` (pydantic → DB object, write path), with the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__exclude__` lists fields the base (de)serializer skips so they can be handled explicitly (e.g. FK resolution).
+
+### `ResourceRequest` specs
+
+| Spec | Role | Fields beyond base |
+| --- | --- | --- |
+| `ResourceRequestBaseSpec` | shared | `id`, `emergency`, `title`, `reason`, `referring_facility_contact_name`, `referring_facility_contact_number`, `status`, `category`, `priority`. `__exclude__`s the five FK fields (`origin_facility`, `approving_facility`, `assigned_facility`, `related_patient`, `assigned_to`). |
+| `ResourceRequestCreateSpec` | write · create & update | Adds the FKs as `UUID4` external ids: `origin_facility` (required), `approving_facility`, `assigned_facility`, `related_patient`, `assigned_to` (all optional). Resolves each external id to a DB object in `perform_extra_deserialization`. |
+| `ResourceRequestListSpec` | read · list | `origin_facility: dict`, `assigned_facility: dict \| None`, `created_date`, `modified_date`. Inlines `origin_facility`/`assigned_facility` via `FacilityReadSpec`. |
+| `ResourceRequestRetrieveSpec` | read · detail | All of List plus `approving_facility`, `related_patient` (via `PatientListSpec`), `assigned_to` (via `UserSpec`, cache-backed), `created_by`, `updated_by`. |
+
+There is no separate `UpdateSpec`: `ResourceRequestCreateSpec` handles both create and update, branching on the `is_update` flag.
+
+#### Validation
+
+- `validate_assigned_to_user` (model validator, `mode="after"`): if `assigned_to` is set, `assigned_facility` is **required**, and the assigned user must be a member of the assigned facility (checked via `FacilityOrganizationUser`). Otherwise raises `ValueError`.
+
+#### Server-side behaviour (`perform_extra_deserialization`)
+
+- `origin_facility` external id → `Facility` object, **only on create** (`is_update` false); it is not reassigned on update.
+- `approving_facility`, `assigned_facility`, `assigned_to` external ids → their DB objects whenever present (create or update).
+- `related_patient` external id → `Patient` object, **only on create**.
+- Each lookup uses `get_object_or_404` on `external_id`, so an unknown id returns 404.
+
+#### Read serialization (`perform_extra_serialization`)
+
+- `id` is emitted as the string `external_id`.
+- `origin_facility` (and `assigned_facility`, `approving_facility` where present) are serialized inline via `FacilityReadSpec`.
+- `related_patient` via `PatientListSpec`; `assigned_to` via `UserSpec` resolved from cache (`model_from_cache` keyed on `assigned_to_id`).
+- `created_by` / `updated_by` populated by `serialize_audit_users`.
+
+### `ResourceRequestComment` specs
+
+| Spec | Role | Fields beyond base |
+| --- | --- | --- |
+| `ResourceRequestCommentBaseSpec` | shared | `comment: str`. `__exclude__`s `request`. |
+| `ResourceRequestCommentCreateSpec` | write · create | No additional fields (the parent `ResourceRequest` is bound from the URL, not the body). |
+| `ResourceRequestCommentListSpec` | read · list | Adds `created_by`, `created_date`; populates `created_by` via `serialize_audit_users`. |
+| `ResourceRequestCommentRetrieveSpec` | read · detail | Subclasses `ResourceRequestCommentListSpec` with no changes. |
+
+## Related models
+
+### `ResourceRequestComment`
+
+A comment entry on a resource request. Deleting a comment does not cascade to the request — the FK uses `PROTECT`.
+
+```text
+request → FK ResourceRequest (PROTECT, required)
+comment → TextField (default "")
+```
+
+## Methods & save behaviour
+
+- Read path: `.serialize(obj)` copies model fields, runs `perform_extra_serialization` (FK inlining, audit users, `id` as string), and constructs the pydantic instance without re-validation (`model_construct`).
+- Write path: `.de_serialize()` writes scalar fields onto the model (`model_dump(exclude_defaults=True)`), then `perform_extra_deserialization` resolves FK external ids and enforces the create-only constraints on `origin_facility`/`related_patient`.
+- Coded fields (`status`, `category`) carry no `status_history` in this resource; transitions are stored directly on the column.
+
+## API integration notes
+
+- `ResourceRequest` and `ResourceRequestComment` are exposed through Care's REST API; write bodies use the `CreateSpec` schema, reads use `ListSpec`/`RetrieveSpec`.
+- `status` and `category` are open `CharField`s in the database but are constrained to `StatusChoices` / `CategoryChoices` by the spec — only those values pass API validation.
+- `priority` is a required integer at the API layer even though the model column is nullable.
+- Foreign keys are sent/returned as `external_id` UUIDs in write payloads and as nested objects (`FacilityReadSpec`, `PatientListSpec`, `UserSpec`) in read payloads.
+- Assigning a request to a user requires `assigned_facility`, and the user must belong to that facility.
+- `referring_facility_contact_number` is validated against the shared `mobile_or_landline_number_validator`; submit numbers in a format that validator accepts.
+- `related_patient` is optional and cascades on patient deletion; `origin_facility` is `PROTECT`-ed and cannot be deleted while requests reference it, and cannot be changed after the request is created.
+
+## Related
+
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [User](../access/user.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [resource_request.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/resource_request.py)
+- Source: [resource_request/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/resource_request/spec.py)
diff --git a/versioned_docs/version-3.1/references/platform/tag-config.mdx b/versioned_docs/version-3.1/references/platform/tag-config.mdx
new file mode 100644
index 0000000..3dc2991
--- /dev/null
+++ b/versioned_docs/version-3.1/references/platform/tag-config.mdx
@@ -0,0 +1,186 @@
+---
+sidebar_position: 6
+---
+
+# Tagging
+
+Technical reference for the `TagConfig` module in Care EMR. Tags are hierarchical labels that can be applied to resources (patients, encounters, and more) for classification and filtering; `TagConfig` defines the available tags and their tree structure.
+
+The Django model (`care/emr/models/tag_config.py`) is the **storage layer**: it holds opaque `JSONField`s (`metadata`, `cached_parent_json`) whose real structure is not visible in the model. The Pydantic **resource specs** (`care/emr/resources/tag/config_spec.py`) are the API/implementation layer: they define the enums, the structured shape of those JSON fields, field validation, and the read/write schemas.
+
+**Source:**
+- Model: [`care/emr/models/tag_config.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/tag_config.py)
+- Resource spec: [`care/emr/resources/tag/config_spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/tag/config_spec.py)
+- Cache invalidation: [`care/emr/resources/tag/cache_invalidation.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/tag/cache_invalidation.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `TagConfig` | Definition of a single tag — its display, category, scope, status, metadata, and position in the tag tree |
+
+`TagConfig` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_by`/`updated_by`, `created_date`/`modified_date`, soft-delete via `deleted`, and `history`/`meta` JSON). Applied tag IDs are stored 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.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → Facility` | `PROTECT`, nullable, `default=None`. Facility that owns the tag |
+| `facility_organization` | `FK → FacilityOrganization` | `CASCADE`, nullable. Owning facility organization. Not allowed on instance-level (no-facility) tags |
+| `organization` | `FK → Organization` | `CASCADE`, nullable. Owning instance-wide organization |
+| `resource` | `CharField(255)` | The resource type the tag applies to. Constrained by `TagResource` in the write spec (see [enum](#tagresource-values)) |
+
+### Display & classification
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `status` | `CharField(255)` | Lifecycle status. Constrained by `TagStatus` in the spec — `active` / `archived` (see [enum](#tagstatus-values)) |
+| `display` | `CharField(255)` | Human-readable label. Required in the spec |
+| `description` | `TextField` | Nullable, blank-able. Required key in the base spec but accepts `null` |
+| `category` | `CharField(255)` | Grouping category. Constrained by `TagCategoryChoices` in the spec (see [enum](#tagcategorychoices-values)) |
+| `priority` | `IntegerField` | `default=100`. Ordering weight |
+| `metadata` | `JSONField` | `default=None`, nullable. Shape defined by `TagConfigMetadata`: `{ color: str?, icon: str? }` (see [nested spec](#tagconfigmetadata-shape-of-metadata)) |
+
+### Tree structure & caches
+
+`TagConfig` is self-referential — tags form a tree, with denormalized caches rebuilt on insert to avoid recursive joins (the same pattern as [`Organization`](../facility/organization.mdx)). These fields are platform-maintained and are **not** writable through the create/update specs.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `parent` | `FK → self` | `CASCADE`, nullable. `related_name="children"`. Null parent means a root tag |
+| `root_tag_config` | `FK → self` | `CASCADE`, nullable. `related_name="root"`. Top of the tree, derived on save |
+| `has_children` | `BooleanField` | `default=False`. Flipped to `True` on the parent when a child is created |
+| `level_cache` | `IntegerField` | `default=0`. Depth in the tree (`parent.level_cache + 1`) |
+| `parent_cache` | `ArrayField[int]` | `default=list`. Full ancestor id chain (`parent.parent_cache + [parent.id]`) |
+| `cached_parent_json` | `JSONField` | `default=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).
+
+| Value | Meaning |
+| --- | --- |
+| `active` | Tag is available for use |
+| `archived` | Tag retired; retained for historical references |
+
+## Nested specs (shape of JSON fields)
+
+### `TagConfigMetadata` (shape of `metadata`)
+
+Defines the real structure of the opaque `metadata` `JSONField`. Plain Pydantic `BaseModel` (not an `EMRResource`).
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `color` | `str \| None` | no | `None` | Display color hint |
+| `icon` | `str \| None` | no | `None` | Display icon hint |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` (DB object → spec) / `de_serialize` (spec → DB object) and 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 class | Role | Notes |
+| --- | --- | --- |
+| `TagConfigBaseSpec` | shared | `__model__ = TagConfig`; `__exclude__ = [facility, facility_organization, organization, parent]`. Fields: `id` (`UUID4?`), `display`, `category` (`TagCategoryChoices`), `description` (`str \| None`), `priority` (`int = 100`), `status` (`TagStatus`), `metadata` (`TagConfigMetadata?`) |
+| `TagConfigWriteSpec` | write · create | Base + `facility?`, `facility_organization?`, `organization?`, `parent?` (all `UUID4`), `resource` (`TagResource`, required). Runs two `model_validator(after)` checks and resolves FKs in `perform_extra_deserialization` |
+| `TagConfigUpdateSpec` | write · update | Base + `facility_organization?`, `organization?` (`UUID4`). Cannot change `facility`, `parent`, or `resource`. Resolves `organization`/`facility_organization` in `perform_extra_deserialization` (facility organization scoped to `obj.facility`) |
+| `TagConfigReadSpec` | read · 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` |
+| `TagConfigRetrieveSpec` | read · detail | Extends 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_id`s 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 unless it is provided elsewhere it is not populated from storage. 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`. On every save it:
+
+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`), derives `root_tag_config` from the parent (the parent itself if the parent is a root, otherwise the parent's `root_tag_config`), and — if the parent had no children — flips `parent.has_children = True`, persisting only that field. Finishes with `super().save()`.
+
+### `get_parent_json()`
+
+Returns `cached_parent_json` when present and its `cache_expiry` is still in the future; otherwise 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 — a **second write pass**. On **update**, `save()` persists normally without recomputing caches. Either path triggers the `post_save` cache-invalidation signal above.
+
+## API integration notes
+
+- Tag definitions are exposed through Care's REST API; 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.
+- Create uses `TagConfigWriteSpec` (sets `resource`, scope FKs, and `parent`); update uses `TagConfigUpdateSpec` (only org/facility-org reassignment plus the shared display/category/status/metadata fields). List responses use `TagConfigReadSpec`; detail responses use `TagConfigRetrieveSpec`.
+- Tree position (`level_cache`, `parent_cache`, `root_tag_config`) and `has_children` are platform-maintained — do not set them directly from clients.
+- A tag is owned by at most one of `facility`, `facility_organization`, or `organization`; `resource` declares which resource type it targets and must match a value from [`TagResource`](#tagresource-values).
+- `metadata` is the supported place for display hints (`color`, `icon`) without schema migrations; its shape is enforced by `TagConfigMetadata`.
+
+## Related
+
+- Reference: [Organization](../facility/organization.mdx) — tags reuse the same tree-cache pattern and can be org-scoped
+- Reference: [Patient](../clinical/patient.mdx) — carries `instance_tags` / `facility_tags`
+- Reference: [Encounter](../clinical/encounter.mdx) — carries `tags`
+- Reference: [Facility](../facility/facility.mdx) — facility-scoped tags; read spec inlines a `FacilityBareMinimumSpec`
+- Reference: [Base models & conventions](../foundation/base-model.mdx)
+- Source: [tag_config.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/tag_config.py)
+- Source: [config_spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/tag/config_spec.py)
diff --git a/versioned_docs/version-3.1/references/scheduling/_category_.json b/versioned_docs/version-3.1/references/scheduling/_category_.json
new file mode 100644
index 0000000..944c54a
--- /dev/null
+++ b/versioned_docs/version-3.1/references/scheduling/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Scheduling",
+ "position": 5,
+ "key": "scheduling-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Scheduling",
+ "description": "Practitioner schedules and availability, patient bookings (appointments), and queue tokens."
+ }
+}
diff --git a/versioned_docs/version-3.1/references/scheduling/booking.mdx b/versioned_docs/version-3.1/references/scheduling/booking.mdx
new file mode 100644
index 0000000..c1ae339
--- /dev/null
+++ b/versioned_docs/version-3.1/references/scheduling/booking.mdx
@@ -0,0 +1,139 @@
+---
+sidebar_position: 2
+---
+
+# Booking
+
+Technical reference for the `TokenBooking` and `TokenSlot` modules in Care EMR.
+
+**Source:**
+
+- Model: [`care/emr/models/scheduling/booking.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/booking.py)
+- Resource spec: [`care/emr/resources/scheduling/slot/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/slot/spec.py)
+- Viewset (side effects): [`care/emr/api/viewsets/scheduling/booking.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/booking.py)
+
+A booking is the appointment of a patient into a concrete time slot generated from a [Schedule](../scheduling/schedule.mdx). `TokenSlot` materializes a bookable unit of availability; `TokenBooking` records a patient's appointment against that slot and tracks its status through the appointment lifecycle.
+
+The Django model is the **storage** layer — `status` is an unconstrained `CharField` and several relations are opaque at the DB level. The **Pydantic resource specs** (`care/emr/resources/scheduling/slot/`) are the API layer: they constrain `status` to a fixed enum, validate writes, and define the read schemas (which embed slot, resource, facility, token, charge item, encounter, and patient sub-objects).
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `TokenSlot` | A concrete, bookable time window generated from a schedulable resource's availability |
+| `TokenBooking` | A patient's appointment against a `TokenSlot`, with status, notes, and optional links to an encounter, token, and charge item |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON).
+
+## `TokenBooking` fields
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `token_slot` | `FK → TokenSlot` (PROTECT) | The slot this appointment occupies; required. PROTECT prevents deleting a slot that has bookings |
+| `patient` | `FK → emr.Patient` (CASCADE) | The booked patient; required |
+| `booked_on` | `DateTimeField` | `auto_now_add` — set once at creation; platform-maintained |
+| `booked_by` | `FK → User` (CASCADE) | User who created the booking; nullable (e.g. self-service or system-created) |
+| `status` | `CharField` (storage) → `BookingStatusChoices` (API) | Appointment lifecycle status. Unbounded on the model; constrained to the [`BookingStatusChoices`](#bookingstatuschoices-values) enum by the write spec |
+| `note` | `TextField` | Free-text note; nullable in storage, required (`str`) on write |
+| `tags` | `ArrayField[int]` | Tag IDs; defaults to empty list. Managed via the `SingleFacilityTagManager` (read returns rendered tag objects, not raw IDs) |
+| `associated_encounter` | `FK → emr.Encounter` (PROTECT) | Encounter linked to the appointment; nullable, defaults to `None`. Only surfaced by `TokenBookingRetrieveSpec` |
+| `token` | `FK → emr.Token` (PROTECT) | Queue token issued for this booking; nullable, defaults to `None`, `related_name="token_booking"`. Created by the `generate_token` action |
+| `charge_item` | `FK → emr.ChargeItem` (CASCADE) | Billing charge item for the appointment; nullable. Auto-created on booking when the schedule has a `charge_item_definition` |
+
+### `BookingStatusChoices` values
+
+`BookingStatusChoices` (`str, Enum`) — the only values the write spec accepts for `status`:
+
+| Value | Notes |
+| --- | --- |
+| `proposed` | |
+| `pending` | |
+| `booked` | Default applied server-side when a booking is created via `lock_create_appointment` |
+| `arrived` | |
+| `fulfilled` | Terminal; in `COMPLETED_STATUS_CHOICES` |
+| `cancelled` | Set only via the `cancel` endpoint; in `CANCELLED_STATUS_CHOICES` + `COMPLETED_STATUS_CHOICES` |
+| `noshow` | Terminal; in `COMPLETED_STATUS_CHOICES` |
+| `entered_in_error` | Set only via the `cancel` endpoint; in `CANCELLED_STATUS_CHOICES` + `COMPLETED_STATUS_CHOICES` |
+| `checked_in` | |
+| `waitlist` | |
+| `in_consultation` | Cannot be cancelled (the cancel handler rejects it) |
+| `rescheduled` | Set only via the `reschedule` endpoint; in `CANCELLED_STATUS_CHOICES` + `COMPLETED_STATUS_CHOICES` |
+
+Derived sets (from `slot/spec.py`):
+
+| Set | Values |
+| --- | --- |
+| `CANCELLED_STATUS_CHOICES` | `entered_in_error`, `cancelled`, `rescheduled` |
+| `COMPLETED_STATUS_CHOICES` | `fulfilled`, `noshow`, `entered_in_error`, `cancelled`, `rescheduled` |
+
+`TokenBookingWriteSpec.perform_extra_deserialization` rejects any write whose `status` is in `CANCELLED_STATUS_CHOICES` with `"Cannot cancel a booking. Use the cancel endpoint"` — cancellation/rescheduling must go through the dedicated endpoints (see below).
+
+## Related models
+
+### `TokenSlot`
+
+A bookable window derived from a resource's availability. Bookings reference it via PROTECT, so slots with active bookings cannot be deleted.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `resource` | `FK → SchedulableResource` (CASCADE) | The schedulable resource (practitioner, location, or healthcare service) the slot belongs to; required |
+| `availability` | `FK → Availability` (CASCADE) | The availability rule that generated this slot; nullable |
+| `start_datetime` | `DateTimeField` | Slot start; required (tz-aware) |
+| `end_datetime` | `DateTimeField` | Slot end; required (tz-aware) |
+| `allocated` | `IntegerField` | Count of bookings currently allocated to the slot; used to enforce capacity. Defaults to `0`. Platform-maintained — never written by clients |
+
+`SchedulableResource` and `Availability` are defined in the [Schedule](../scheduling/schedule.mdx) module. `SchedulableResource.resource_type` is one of `SchedulableResourceTypeOptions`: `practitioner`, `location`, `healthcare_service`.
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` ([`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)) — `serialize` (DB → pydantic, read), `de_serialize` (pydantic → DB, write), with `perform_extra_serialization` / `perform_extra_deserialization` hooks.
+
+| Spec class | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `TokenBookingBaseSpec` | shared | `__model__ = TokenBooking`, `__exclude__ = ["token_slot", "patient"]` |
+| `TokenBookingWriteSpec` | write · create + update | Fields: `status: BookingStatusChoices`, `note: str`. Rejects `status` in `CANCELLED_STATUS_CHOICES` (use cancel/reschedule endpoints). Used as both `pydantic_model` and `pydantic_update_model` |
+| `TokenBookingMinimumReadSpec` | read · embed | Lightweight read used when a booking is embedded in another resource (e.g. token). Fields: `id`, `token_slot` (embedded `TokenSlotBaseSpec`), `booked_on`, `status: str`, `note`, `created_date`, `modified_date` |
+| `TokenBookingBaseReadSpec` | read · shared | Adds `booked_by: UserSpec`, `resource_type`, `resource` (serialized resource), `facility`, `created_by`/`updated_by`, `token: TokenReadSpec`, `tags: list[dict]` (rendered), `charge_item: dict` |
+| `TokenBookingReadSpec` | read · list/default | `TokenBookingBaseReadSpec` + `patient: PatientRetrieveSpec`. The viewset's `pydantic_read_model` (list responses) |
+| `TokenBookingOTPReadSpec` | read · OTP flow | `TokenBookingBaseReadSpec` + `patient: PatientOTPReadSpec` (public/OTP booking flow) |
+| `TokenBookingRetrieveSpec` | read · detail | `TokenBookingReadSpec` + `associated_encounter: dict` (serialized `EncounterListSpec`). The viewset's `pydantic_retrieve_model` |
+| `TokenSlotBaseSpec` | read · embed | `__model__ = TokenSlot`, `__exclude__ = ["resource", "availability"]`. Fields: `id`, `availability` (embedded as `{name, tokens_per_slot, id, schedule:{name,id}}`), `start_datetime`, `end_datetime`, `allocated` |
+
+### Read serialization details (`perform_extra_serialization`)
+
+- `id` is always set from `obj.external_id` (UUID), not the integer PK.
+- `token_slot` is embedded via `TokenSlotBaseSpec.serialize(...)`.
+- `resource_type` comes from `token_slot.resource.resource_type`; `resource` is built by `serialize_resource()` — a `UserSpec` for `practitioner`, `HealthcareServiceReadSpec` for `healthcare_service`, `FacilityLocationListSpec` for `location`.
+- `facility` is a cached `FacilityBareMinimumSpec`; `booked_by` / `created_by` / `updated_by` are cached `UserSpec`s.
+- `tags` are rendered through `SingleFacilityTagManager().render_tags(obj)` (objects, not raw IDs).
+- `token` (`TokenReadSpec`), `charge_item` (`ChargeItemReadSpec`), and `associated_encounter` (`EncounterListSpec`) are included only when present.
+
+## Methods & save behaviour
+
+`status` has no model-level `choices`; the lifecycle is enforced entirely in the spec/viewset layer (`TokenBookingViewSet`). Key server-maintained behaviour:
+
+- **Creation (`lock_create_appointment`)** — under a per-resource lock and a transaction: rejects past slots (`end_datetime < now`) and full slots (`allocated >= availability.tokens_per_slot`), rejects a duplicate active booking for the same patient/slot (any status not in `COMPLETED_STATUS_CHOICES`), then increments `token_slot.allocated`, creates the booking with `status="booked"`, and — if the schedule has a `charge_item_definition` — auto-creates the linked `charge_item`.
+- **Cancel (`cancel` action)** — accepts `CancelBookingSpec { reason: cancelled | entered_in_error | rescheduled, note?: str }`. Rejects cancelling an `in_consultation` booking. Decrements `token_slot.allocated` (unless already cancelled), sets `status = reason`, optionally overwrites `note`, sets `updated_by`, and aborts any linked `charge_item` (`handle_charge_item_cancel` + status `aborted`).
+- **Reschedule (`reschedule` action)** — accepts `RescheduleBookingSpec { new_slot: UUID, new_booking_note: str, previous_booking_note?: str, tags: list[UUID] }`. Requires `can_reschedule_booking`. Cancels the existing booking with reason `rescheduled`, then `lock_create_appointment` creates a fresh booking on `new_slot` for the same patient; rejects rescheduling to the same slot.
+- **Generate token (`generate_token` action)** — accepts `TokenGenerationSpec { category: UUID, note?: str, queue?: UUID }`. Rejects if a token already exists. Resolves/creates a `TokenQueue` for the slot's date and resource, allocates the next `number` under a queue lock, creates a `Token` (`status="CREATED"`), and links it to `booking.token`.
+- **`booked_on`** is `auto_now_add` — never set by clients.
+
+## API integration notes
+
+- `TokenSlot` and `TokenBooking` are exposed through Care's scheduling REST API and align with the FHIR `Slot` and `Appointment` resources; payload field names may differ from Django model names.
+- Writes use `TokenBookingWriteSpec` (`status` constrained to `BookingStatusChoices`, `note` required). `cancelled` / `entered_in_error` / `rescheduled` cannot be set through a plain update — use the `cancel` / `reschedule` actions.
+- List responses use `TokenBookingReadSpec`; detail responses use `TokenBookingRetrieveSpec` (adds `associated_encounter`).
+- `allocated` on `TokenSlot` is a capacity counter maintained by `lock_create_appointment` / cancel; do not write it directly.
+- `tags` is an array of integer tag IDs in storage but returned as rendered tag objects on read.
+- List requires a `resource_type` query param and authorization scoped to organizations or resource IDs.
+
+## Related
+
+- Reference: [Schedule](../scheduling/schedule.mdx)
+- Reference: [Token](../scheduling/token.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Charge item](../billing/charge-item.mdx)
+- Source: [booking.py (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/booking.py)
+- Source: [slot/spec.py (resource spec)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/slot/spec.py)
+- Source: [booking.py (viewset)](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/booking.py)
diff --git a/versioned_docs/version-3.1/references/scheduling/schedule.mdx b/versioned_docs/version-3.1/references/scheduling/schedule.mdx
new file mode 100644
index 0000000..b06a364
--- /dev/null
+++ b/versioned_docs/version-3.1/references/scheduling/schedule.mdx
@@ -0,0 +1,194 @@
+---
+sidebar_position: 1
+---
+
+# Schedule & Availability
+
+Technical reference for the `Schedule` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/scheduling/schedule.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/schedule.py)
+- Resource specs: [`schedule/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/schedule/spec.py), [`availability_exception/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/availability_exception/spec.py), [`resource/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/resource/spec.py)
+
+A schedule is a repeatable block of time during which a resource (a practitioner, location, or healthcare service) is available for booking. A schedule carries one or more **availabilities** that describe how that time is broken into slots, and **exceptions** that block out specific dates.
+
+The **Django model** (`care/emr/models/scheduling/schedule.py`) is the storage layer — including the opaque `Availability.availability` `JSONField`. The **Pydantic resource specs** (`care/emr/resources/scheduling/...`) are the API/implementation layer: they define the enums, the real shape of that JSON field, validation rules, and the read/write schemas. Everything below the `## Models` section reflects the specs.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `SchedulableResource` | A bookable resource (a user, location, or healthcare service) within a facility |
+| `Schedule` | A named, date-bounded block of availability attached to a resource |
+| `Availability` | Slot configuration for a schedule (slot size, tokens, weekly recurrence) |
+| `AvailabilityException` | A date/time range that blocks a resource (leave, holidays) |
+
+All models extend [`EMRBaseModel`](../foundation/base-model.mdx) — the shared Care EMR base providing `external_id`, `created_date`/`modified_date`, soft-delete via `deleted`, `created_by`/`updated_by`, and `history`/`meta` JSON.
+
+## `Schedule` fields
+
+| Field | Type | Req. | Notes |
+| --- | --- | --- | --- |
+| `resource` | `FK → SchedulableResource` (CASCADE) | server | Set server-side from `resource_type` + `resource_id` (get-or-create). Not accepted directly in the body |
+| `name` | `CharField(255)` | yes | Display name (e.g. "Morning OPD") |
+| `valid_from` | `DateTimeField` | yes | Start of the effective window. Must be ≥ now; must be ≤ `valid_to` |
+| `valid_to` | `DateTimeField` | yes | End of the effective window. Must be ≥ now |
+| `revisit_allowed_days` | `IntegerField` | no | Nullable. Window within which a follow-up uses the revisit charge. Set only via `set_charge_item_definition` action |
+| `is_public` | `BooleanField` | yes | Whether the schedule is exposed for public booking. Model default `False` |
+
+### Billing links
+
+Both charge links are managed only through the `set_charge_item_definition` action (see [Resource specs](#resource-specs-api-schema)), never via the create/update body.
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `charge_item_definition` | `FK → ChargeItemDefinition` (PROTECT) | Nullable; charge applied to bookings against this schedule. `related_name="schedule_charge_item_definition"` |
+| `revisit_charge_item_definition` | `FK → ChargeItemDefinition` (PROTECT) | Nullable; charge applied when a booking falls within `revisit_allowed_days`. `related_name="schedule_revisit_charge_item_definition"` |
+
+## Enums
+
+### `SchedulableResourceTypeOptions`
+
+The discriminator for what kind of entity a schedule/exception is attached to. Sent as `resource_type` on write; the matching `resource_id` is the `external_id` of that entity.
+
+| Value | Resolves to |
+| --- | --- |
+| `practitioner` | A [`User`](../access/user.mdx) who is a member of the facility (serialized via `UserSpec`) |
+| `location` | A `FacilityLocation` in the facility (serialized via `FacilityLocationListSpec`) |
+| `healthcare_service` | A [`HealthcareService`](../facility/healthcare-service.mdx) in the facility (serialized via `HealthcareServiceReadSpec`) |
+
+Note: the `SchedulableResource.resource_type` model column defaults to the string `"practitioner"`.
+
+### `SlotTypeOptions`
+
+Type of an individual `Availability` block (`Availability.slot_type` on the model, `slot_type` in the spec).
+
+| Value | Meaning |
+| --- | --- |
+| `open` | Open block; `slot_size_in_minutes` / `tokens_per_slot` are cleared to `null` on save |
+| `appointment` | Time-precise appointment block; requires both `slot_size_in_minutes` and `tokens_per_slot` |
+| `closed` | Closed block; `slot_size_in_minutes` / `tokens_per_slot` are cleared to `null` on save |
+
+## Related models
+
+### `SchedulableResource`
+
+A bookable entity within a facility. Exactly one of `user`, `location`, or `healthcare_service` identifies the underlying resource; `resource_type` discriminates which. It is **never created directly via the API** — `Schedule` and `AvailabilityException` writes call `get_or_create_resource(resource_type, resource_id, facility)` server-side, validating that the target belongs to the facility.
+
+```text
+facility → FK Facility (CASCADE)
+resource_type → CharField(255), default "practitioner"
+user → FK User (CASCADE, nullable)
+location → FK FacilityLocation (CASCADE, nullable)
+healthcare_service → FK HealthcareService (CASCADE, nullable)
+```
+
+Uniqueness is enforced per facility + `resource_type` against each target via three `UniqueConstraint`s:
+
+| Constraint | Fields |
+| --- | --- |
+| `unique_facility_resource_user` | `facility`, `resource_type`, `user` |
+| `unique_facility_resource_location` | `facility`, `resource_type`, `location` |
+| `unique_facility_resource_healthcare_service` | `facility`, `resource_type`, `healthcare_service` |
+
+On read, a `SchedulableResource` is serialized by `serialize_resource(obj)` (`resource/spec.py`), which dispatches on `resource_type` to `UserSpec` / `HealthcareServiceReadSpec` / `FacilityLocationListSpec`.
+
+### `Availability`
+
+Defines how a schedule's time is divided into bookable slots. A schedule can hold several availabilities (e.g. a morning block and an afternoon block).
+
+| Field | Type | Req. | Spec detail |
+| --- | --- | --- | --- |
+| `schedule` | `FK Schedule` (CASCADE) | server | Excluded from the availability spec; set from the URL / parent schedule |
+| `name` | `CharField(255)` | yes | Block name |
+| `slot_type` | `CharField` | yes | One of [`SlotTypeOptions`](#slottypeoptions) |
+| `slot_size_in_minutes` | `IntegerField` (nullable) | conditional | `int \| None`, `ge=1`. Required when `slot_type == appointment`; forced to `null` otherwise |
+| `tokens_per_slot` | `IntegerField` (nullable) | conditional | `int \| None`, `ge=1`. Capacity per slot. Required when `slot_type == appointment`; forced to `null` otherwise |
+| `create_tokens` | `BooleanField` | no | Default `False`. When `True`, a token is issued for each booking (queue/token workflows) |
+| `reason` | `TextField` (nullable) | no | Spec default `""` |
+| `availability` | `JSONField`, default `dict` | yes | **Not a dict** — the spec models it as a `list[AvailabilityDateTimeSpec]` (weekly recurrence). See below |
+
+#### `availability` JSON shape — `AvailabilityDateTimeSpec`
+
+Each entry is one weekly recurrence window. The `JSONField` stores a **list** of these objects.
+
+```text
+AvailabilityDateTimeSpec {
+ day_of_week: int # 0–6, validated le=6 (Monday=0 … Sunday=6, ISO-style)
+ start_time: time # HH:MM:SS
+ end_time: time # HH:MM:SS
+}
+```
+
+Validation on the list (`AvailabilityForScheduleSpec.validate_availability` + `validate_for_slot_type`):
+- For every entry, `start_time < end_time` (strict).
+- No two entries on the **same** `day_of_week` may overlap (`has_overlapping_availability`: ranges overlap when `a.start ≤ b.end and b.start ≤ a.end`). Overlap is checked across all availabilities of the same schedule on create.
+- When `slot_type == appointment`: `slot_size_in_minutes` and `tokens_per_slot` are mandatory; each window's duration must be an exact multiple of `slot_size_in_minutes`; and the resulting slot count must not exceed `settings.MAX_SLOTS_PER_AVAILABILITY` (default **30**).
+
+### `AvailabilityException`
+
+Blocks a resource for a date/time range regardless of its schedules — used for leave, holidays, or one-off closures.
+
+| Field | Type | Req. | Spec detail |
+| --- | --- | --- | --- |
+| `resource` | `FK SchedulableResource` (CASCADE) | server | Set from `resource_type` + `resource_id` (get-or-create); excluded from the spec body |
+| `name` | `CharField(255)` | yes | Exception name |
+| `reason` | `TextField` (nullable) | no | `str \| None` |
+| `valid_from` | `DateField` | yes | `date`. Must be ≥ today; must be ≤ `valid_to` |
+| `valid_to` | `DateField` | yes | `date`. Must be ≥ today |
+| `start_time` | `TimeField` | yes | `time` |
+| `end_time` | `TimeField` | yes | `time` |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` ([`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)), which provides `serialize()` (DB → pydantic) and `de_serialize()` (pydantic → DB) plus the `perform_extra_serialization` / `perform_extra_deserialization` hooks. `__exclude__` lists fields skipped during (de)serialization.
+
+| Spec | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `ScheduleBaseSpec` | shared | `id`, `is_public`. `__exclude__ = ["resource", "facility"]` |
+| `ScheduleCreateSpec` | write · create | `facility`, `name`, `valid_from`, `valid_to`, `availabilities: list[AvailabilityForScheduleSpec]`, `resource_type`, `resource_id`, `is_public`. Validates dates ≥ now, `valid_from ≤ valid_to`, and cross-availability non-overlap. `perform_extra_deserialization` stashes `facility`, `_resource_id`, `_resource_type`, and `availabilities` on the instance for the viewset |
+| `ScheduleUpdateSpec` | write · update | `name`, `valid_from`, `valid_to`, `is_public`. `perform_extra_deserialization` blocks narrowing validity that would drop allocated slots — compares `Sum(TokenSlot.allocated)` in the old vs new range and raises if they differ |
+| `ScheduleReadSpec` | read · list + detail | All of the above plus `availabilities` (re-serialized from `Availability` rows), `resource_type`, `charge_item_definition` / `revisit_charge_item_definition` (full `ChargeItemDefinitionReadSpec` or `null`), `revisit_allowed_days`, `created_by`, `updated_by` |
+| `AvailabilityBaseSpec` | shared | `id`. `__exclude__ = ["schedule"]` |
+| `AvailabilityForScheduleSpec` | write/read (nested in schedule) | `name`, `slot_type`, `slot_size_in_minutes`, `tokens_per_slot`, `create_tokens`, `reason`, `availability: list[AvailabilityDateTimeSpec]`. Carries the availability + slot-type validators |
+| `AvailabilityCreateSpec` | write · create (standalone) | Adds `schedule: UUID4`; on create, re-checks overlap against all existing availabilities of that schedule |
+| `AvailabilityDateTimeSpec` | nested | `day_of_week` (le=6), `start_time`, `end_time` — the shape of the `availability` JSON field |
+| `AvailabilityExceptionBaseSpec` | shared | `id`, `reason`, `valid_from`, `valid_to`, `start_time`, `end_time`. `__exclude__ = ["resource", "facility"]` |
+| `AvailabilityExceptionWriteSpec` | write · create/upsert | Adds `facility`, `resource_type`, `resource_id`. Validates dates ≥ today and `valid_from ≤ valid_to`; stashes `_resource_type`/`_resource_id` |
+| `AvailabilityExceptionReadSpec` | read · list + detail | Base fields; `perform_extra_serialization` maps `id` → `external_id` |
+| `ChargeItemDefinitionSetSpec` | write (action body) | `charge_item_definition: str \| None`, `re_visit_allowed_days: int`, `re_visit_charge_item_definition: str \| None` — body for `POST .../set_charge_item_definition` (charge fields resolved by `slug` within the facility) |
+
+### Server-maintained behaviour (viewset hooks)
+
+- **Resource resolution.** On schedule/exception create, `get_or_create_resource(resource_type, resource_id, facility)` validates the target is in the facility (practitioner must be a `FacilityOrganizationUser`; location/healthcare-service must belong to the facility) and gets-or-creates the `SchedulableResource`. `facility` is injected from the URL via `clean_create_data`.
+- **Nested availability create.** `ScheduleViewSet.perform_create` (atomic) sets `resource`, saves the schedule, then de-serializes and saves each `availability` linked to it.
+- **Charge items.** `revisit_allowed_days`, `charge_item_definition`, and `revisit_charge_item_definition` are set **only** through the `set_charge_item_definition` detail action — never the create/update body.
+- **Locking + slot guards.** Update/destroy take a `Lock("booking:resource:")`. Deleting a schedule or availability is rejected if future allocated `TokenSlot`s exist; otherwise availabilities and slots are soft-deleted (`deleted=True`).
+- **Exception slot clearing.** Creating an `AvailabilityException` soft-deletes overlapping `TokenSlot`s, but rejects the request if any of them are already allocated ("There are bookings during this exception").
+- **List requires resource.** `Schedule` and `AvailabilityException` list endpoints require `resource_type` and `resource_id` query params and scope results to that `SchedulableResource`.
+
+## Methods & save behaviour
+
+- `Availability.slot_type` drives whether `slot_size_in_minutes` / `tokens_per_slot` survive: only `appointment` keeps them; `open` and `closed` null them out (`validate_for_slot_type`).
+- Schedule validity windows are guarded both ways: they cannot start in the past, and they cannot be edited to a range that excludes already-allocated slots.
+- Slot generation is derived from `Availability` at read time, not stored as rows on the schedule.
+
+## API integration notes
+
+- Schedules, availabilities, and exceptions are exposed through Care's REST API; slot generation is derived from `Availability` rather than stored as rows.
+- A `SchedulableResource` is the join point for booking — clients send `resource_type` + `resource_id`; resolve the underlying `user`, `location`, or `healthcare_service` via `resource_type` rather than assuming one is set.
+- `ChargeItemDefinition` links are `PROTECT` — a definition referenced by a schedule cannot be deleted while the schedule exists.
+- `availability` on `Availability` is a JSON **list** of weekly recurrence windows (`AvailabilityDateTimeSpec`); treat it as the canonical source for which days/times the block covers.
+- `create_tokens` and `tokens_per_slot` drive token/queue behaviour for high-volume, precision-optional workflows; `appointment` slot types are for time-precise booking and must divide each window evenly (≤ `MAX_SLOTS_PER_AVAILABILITY`, default 30).
+
+## Related
+
+- Reference: [Booking](../scheduling/booking.mdx)
+- Reference: [Token](../scheduling/token.mdx)
+- Reference: [Charge item definition](../billing/charge-item-definition.mdx)
+- Reference: [Healthcare service](../facility/healthcare-service.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [User](../access/user.mdx)
+- Source (model): [schedule.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/schedule.py)
+- Source (specs): [schedule/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/schedule/spec.py), [availability_exception/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/availability_exception/spec.py), [resource/spec.py](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/resource/spec.py)
diff --git a/versioned_docs/version-3.1/references/scheduling/token.mdx b/versioned_docs/version-3.1/references/scheduling/token.mdx
new file mode 100644
index 0000000..c688a1b
--- /dev/null
+++ b/versioned_docs/version-3.1/references/scheduling/token.mdx
@@ -0,0 +1,213 @@
+---
+sidebar_position: 3
+---
+
+# Token
+
+Technical reference for the `Token` module in Care EMR — the token-queue subsystem of scheduling. It models walk-in / queue-based flow (a patient gets a numbered token in a queue, optionally routed to a sub-queue / room) on top of a [schedulable resource](./schedule.mdx) (practitioner, location, or healthcare service).
+
+The Django models are the **storage layer**; several constraints (status enums, the resource binding, queue progression) live only in the Pydantic **resource specs** and the API viewsets. This page documents both.
+
+**Source:**
+- Model: [`care/emr/models/scheduling/token.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/token.py)
+- Specs: [`token/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token/spec.py) · [`token_category/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token_category/spec.py) · [`token_queue/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token_queue/spec.py) · [`token_sub_queue/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token_sub_queue/spec.py)
+- Viewsets: [`api/viewsets/scheduling/token.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/token.py) · [`token_queue.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/token_queue.py) · [`token_sub_queue.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/token_sub_queue.py) · [`token_category.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/token_category.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `TokenQueue` | A queue of tokens for one schedulable resource on one date |
+| `TokenSubQueue` | A sub-queue splitting a resource's tokens (e.g. multiple rooms) |
+| `TokenCategory` | Reusable token categories per facility / resource type |
+| `Token` | A numbered token issued to a patient within a queue |
+
+All models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_by`/`updated_by`/`created_date`/`modified_date`, and soft-delete via `deleted`).
+
+The `resource` FK on `TokenQueue` / `TokenSubQueue` points at `emr.SchedulableResource` — a polymorphic binding to a practitioner (`user`), `location`, or `healthcare_service`, distinguished by `resource_type`. See [Schedule](./schedule.mdx).
+
+## `Token` fields
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `facility` | `FK → facility.Facility` (CASCADE) | yes (server) | Set server-side from the queue's facility |
+| `patient` | `FK → emr.Patient` (CASCADE) | optional | Nullable; the patient the token belongs to. See [Patient](../../concepts/clinical/patient.mdx) |
+| `queue` | `FK → TokenQueue` (CASCADE) | yes (server) | Set server-side from the URL queue |
+| `category` | `FK → TokenCategory` (CASCADE) | yes | Resolved from the `category` UUID on create. Must be in the same facility as the queue |
+| `sub_queue` | `FK → TokenSubQueue` (CASCADE) | optional | Nullable. Must match the queue's facility **and** resource |
+| `number` | `IntegerField` | yes (server) | Auto-assigned: `count(tokens in queue with same category) + 1` |
+| `status` | `CharField(255)` | yes (server) | `TokenStatusOptions` enum (see below). Defaults to `CREATED` on create |
+| `is_next` | `BooleanField` (default `False`) | server | Queue-progression flag; not set by clients |
+| `note` | `TextField` | optional | Nullable free-text note |
+| `booking` | `FK → emr.TokenBooking` (CASCADE) | optional | Nullable; links the token to a [booking](./booking.mdx). `related_name="booking_token"` |
+
+A patient may hold multiple tokens in a given queue. `number` is **per category within a queue**, not globally unique across the queue.
+
+### `TokenStatusOptions` values
+
+Defined in `token/spec.py`. Bound on `Token.status` via the read/update specs.
+
+| Value | Meaning |
+| --- | --- |
+| `UNFULFILLED` | Token issued but not served |
+| `CREATED` | Default state on creation |
+| `IN_PROGRESS` | Currently being served (set when made the sub-queue's current token) |
+| `FULFILLED` | Service completed |
+| `CANCELLED` | Cancelled |
+| `ENTERED_IN_ERROR` | Set automatically on delete (soft-delete) |
+
+## Related models
+
+### `TokenQueue`
+
+A queue of tokens for one schedulable resource on one date.
+
+```text
+facility → FK Facility (CASCADE)
+resource → FK SchedulableResource (CASCADE)
+name → CharField(255)
+is_primary → BooleanField (default True)
+date → DateField
+system_generated → BooleanField (default False)
+```
+
+- `is_primary` is resolved server-side on create: the first queue for a `(resource, date)` pair becomes primary, later ones do not. Re-pointed via the `set_primary` action.
+- `system_generated` queues are created implicitly by `generate_token` (name `"System Generated"`) when no primary queue exists for the date.
+
+### `TokenSubQueue`
+
+Splits a resource's tokens so the same queue can route tokens to several physical points (e.g. multiple vaccination rooms, each drawing from its own sub-queue).
+
+```text
+facility → FK Facility (CASCADE)
+resource → FK SchedulableResource (CASCADE)
+name → CharField(255)
+status → CharField(255) # TokenSubQueueStatusOptions
+current_token → FK Token (CASCADE, nullable)
+```
+
+`status` is enum-bound in the spec to `TokenSubQueueStatusOptions`:
+
+| Value |
+| --- |
+| `active` |
+| `inactive` |
+
+`current_token` is the token currently being served at that point; maintained server-side by the queue-progression actions (`set_next`, `set_next_token_to_subqueue`).
+
+### `TokenCategory`
+
+Reusable token categories scoped to a facility and resource type (e.g. "General", "Priority").
+
+```text
+facility → FK Facility (CASCADE)
+resource_type → CharField(255) # SchedulableResourceTypeOptions
+name → CharField(255)
+shorthand → CharField(255) # spec limits to max_length 5
+metadata → JSONField (default dict)
+default → BooleanField (default False)
+```
+
+- `resource_type` is enum-bound in the spec to `SchedulableResourceTypeOptions` (`practitioner`, `location`, `healthcare_service`).
+- `shorthand` is constrained to `max_length=5` by the spec (the model column allows 255).
+- `metadata` is an open JSON bag for deployment-specific category config; exposed as `dict | None`.
+- `default` is set exclusively via the `set_default` action (which clears `default` on all other categories of the same facility + resource type); it is not a writable field on the create spec.
+
+### `SchedulableResourceTypeOptions` values
+
+Defined in `scheduling/schedule/spec.py`; used by `TokenCategory.resource_type` and all `*WithQueue` / create specs that bind to a resource.
+
+| Value |
+| --- |
+| `practitioner` |
+| `location` |
+| `healthcare_service` |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`): `serialize` builds the read payload from the model (plus `perform_extra_serialization`), `de_serialize` writes a model from the payload (plus `perform_extra_deserialization`). `id` is always the model's `external_id` (UUID).
+
+### Token
+
+| Spec | Role | Exposed fields |
+| --- | --- | --- |
+| `TokenBaseSpec` | shared | `id` |
+| `TokenGenerateSpec` | write · create (nested under a queue) | `patient?` (UUID), `category` (UUID, required), `note?`, `sub_queue?` (UUID) |
+| `TokenGenerateWithQueueSpec` | write · create (queue resolved/created) | adds `resource_type` (`SchedulableResourceTypeOptions`), `resource_id` (UUID), `date` |
+| `TokenUpdateSpec` | write · update | `status?` (`TokenStatusOptions`), `note?`, `sub_queue` (UUID, nullable) |
+| `TokenMinimalSpec` | read · embedded | `note`, `number`, `status`, `category` (serialized via `TokenCategoryReadSpec`) |
+| `TokenReadSpec` | read · list | `category`, `sub_queue`, `note`, `patient`, `number`, `status`, `queue` (`TokenQueueReadSpec`) |
+| `TokenRetrieveSpec` | read · detail | extends `TokenReadSpec` + `created_by`/`updated_by` (`UserSpec`), `booking`, `resource_type`, `resource`, `encounter?` |
+
+Validation & server behaviour (from `perform_extra_deserialization` and the viewset):
+
+- `TokenGenerateSpec.perform_extra_deserialization`: resolves `patient`, `category`, and `sub_queue` UUIDs to model instances (404 if missing).
+- `TokenUpdateSpec.perform_extra_deserialization`: resolves `sub_queue` UUID, or sets `sub_queue = None` when omitted (so update can clear it).
+- On **create** (`perform_create`): `queue` and `facility` are set from the URL queue; category-vs-queue and sub-queue-vs-queue facility/resource matching is enforced; `number` is computed under a per-queue lock; `status` is forced to `CREATED`.
+- On **update**: changing the sub-queue clears the old sub-queue's `current_token` if it pointed at this token; a sub-queue that already has a current token cannot be reassigned.
+- On **delete** (`perform_destroy`): soft-delete — sets `status = ENTERED_IN_ERROR`, `deleted = True`.
+- `TokenRetrieveSpec` embeds the booking (via `TokenBookingMinimumReadSpec`), the booking's `associated_encounter` ([Encounter](../clinical/encounter.mdx)) when present, and the resolved `resource` (practitioner `UserSpec` / `HealthcareServiceReadSpec` / `FacilityLocationListSpec`) keyed by `resource_type`.
+
+### TokenQueue
+
+| Spec | Role | Exposed fields |
+| --- | --- | --- |
+| `TokenQueueBaseSpec` | shared | `id`, `name` |
+| `TokenQueueCreateSpec` | write · create | adds `resource_type` (`SchedulableResourceTypeOptions`), `resource_id` (UUID), `date` |
+| `TokenQueueUpdateSpec` | write · update | `id`, `name` only |
+| `TokenQueueReadSpec` | read · list | adds `date`, `is_primary`, `system_generated` |
+| `TokenQueueRetrieveSpec` | read · detail | adds `created_by`, `updated_by` |
+
+`TokenQueueCreateSpec.perform_extra_deserialization` stashes `resource_type`/`resource_id` onto the instance; the viewset's `perform_create` resolves/creates the `SchedulableResource`, sets `facility`, and computes `is_primary`.
+
+### TokenSubQueue
+
+| Spec | Role | Exposed fields |
+| --- | --- | --- |
+| `TokenSubQueueBaseSpec` | shared / write · update | `id`, `name`, `status` (`TokenSubQueueStatusOptions`) |
+| `TokenSubQueueCreateSpec` | write · create | adds `resource_type` (`SchedulableResourceTypeOptions`), `resource_id` (UUID) |
+| `TokenSubQueueReadSpec` | read | adds `current_token` (`TokenMinimalSpec`, nullable) |
+
+`TokenSubQueueCreateSpec.perform_extra_deserialization` stashes `resource_type`/`resource_id`; the viewset resolves/creates the resource on create and validates it. `current_token` is serialized only when set.
+
+### TokenCategory
+
+| Spec | Role | Exposed fields |
+| --- | --- | --- |
+| `TokenCategoryBaseSpec` | shared | `id`, `name`, `resource_type` (`SchedulableResourceTypeOptions`), `shorthand` (max 5), `metadata?` (dict) |
+| `TokenCategoryCreateSpec` | write · create | same as base |
+| `TokenCategoryReadSpec` | read · list | adds `default` |
+| `TokenCategoryRetrieveSpec` | read · detail | adds `created_by`, `updated_by` |
+
+`facility` is set from the URL on create; `default` is read-only here and toggled via the `set_default` action.
+
+## Methods & save behaviour
+
+- **Number assignment** — `Token.number` is `count(tokens with same queue + category) + 1`, computed inside a `Lock("booking:token:{queue.id}")` + atomic transaction to avoid duplicates.
+- **Status lifecycle** — create forces `CREATED`; `set_next` / `set_next_token_to_subqueue` set `IN_PROGRESS`; delete sets `ENTERED_IN_ERROR`. There is no separate status-history table; status is a single field.
+- **Queue progression** — `current_token` (on `TokenSubQueue`) and `is_next` are maintained by the platform's custom actions, not by direct client writes:
+ - `Token` action `set_next` (`POST .../{id}/set_next`, body `{ sub_queue }`): points the sub-queue's `current_token` at this token and marks it `IN_PROGRESS`.
+ - `TokenQueue` action `set_next_token_to_subqueue` (`POST .../{queue}/set_next_token_to_subqueue`, body `{ sub_queue, category? }`): picks the oldest `CREATED` token (optionally filtered by category), assigns it to the sub-queue as `current_token`, sets `IN_PROGRESS`.
+ - `TokenQueue` action `set_primary`: makes one queue primary for its `(resource, date)`, clearing the flag on siblings.
+ - `TokenCategory` action `set_default`: makes one category default per `(facility, resource_type)`.
+- **Token generation shortcut** — `TokenQueue` action `generate_token` (`POST .../generate_token`, body = `TokenGenerateWithQueueSpec`) finds or creates the primary (`system_generated`) queue for `(resource, date)` and issues a token in one call.
+- **Queue summary** — `TokenQueue` action `summary` returns per-category, per-status token counts for a queue.
+
+## API integration notes
+
+- Tokens are created **under a queue** (`TokenGenerateSpec` — `queue`/`facility`/`number`/`status` are server-set), or via the queue's `generate_token` action (`TokenGenerateWithQueueSpec`, which also resolves/creates the queue).
+- Send `category` / `patient` / `sub_queue` as **UUIDs** (`external_id`); the server resolves them to FKs. Cross-facility and cross-resource mismatches are rejected with `400`.
+- `status` only accepts `TokenStatusOptions` values via the update spec; `TokenSubQueue.status` only accepts `TokenSubQueueStatusOptions`. The underlying columns are plain `CharField`s, so the constraint is enforced by the spec layer, not the database.
+- Do **not** set `number`, `is_next`, `current_token`, or `is_primary` directly — they are queue-progression state maintained by the viewset actions above.
+- `TokenCategory.metadata` is an open JSON bag for deployment-specific config; `shorthand` is capped at 5 characters by the spec.
+- `booking` links a token back to a [`TokenBooking`](./booking.mdx); on retrieve it expands to the booking and its associated [encounter](../clinical/encounter.mdx).
+
+## Related
+
+- Reference: [Schedule](./schedule.mdx)
+- Reference: [Booking](./booking.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Concept: [Patient](../../concepts/clinical/patient.mdx)
+- Reference: [Encounter](../clinical/encounter.mdx)
+- Source: [token.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/token.py)
+- Specs: [token](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token/spec.py) · [token_category](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token_category/spec.py) · [token_queue](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token_queue/spec.py) · [token_sub_queue](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/token_sub_queue/spec.py)
diff --git a/versioned_docs/version-3.1/references/supply/_category_.json b/versioned_docs/version-3.1/references/supply/_category_.json
new file mode 100644
index 0000000..b86520b
--- /dev/null
+++ b/versioned_docs/version-3.1/references/supply/_category_.json
@@ -0,0 +1,10 @@
+{
+ "label": "Supply & Inventory",
+ "position": 4,
+ "key": "supply-references",
+ "link": {
+ "type": "generated-index",
+ "title": "Supply & Inventory",
+ "description": "Products, product knowledge, stock items, and the supply requests and deliveries that move them between locations."
+ }
+}
diff --git a/versioned_docs/version-3.1/references/supply/inventory-item.mdx b/versioned_docs/version-3.1/references/supply/inventory-item.mdx
new file mode 100644
index 0000000..f8d0b7c
--- /dev/null
+++ b/versioned_docs/version-3.1/references/supply/inventory-item.mdx
@@ -0,0 +1,143 @@
+---
+sidebar_position: 3
+---
+
+# Inventory Item
+
+Technical reference for the `InventoryItem` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/inventory_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/inventory_item.py)
+- Spec: [`care/emr/resources/inventory/inventory_item/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/spec.py)
+- Helpers: [`create_inventory_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/create_inventory_item.py), [`sync_inventory_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/sync_inventory_item.py)
+
+`InventoryItem` represents the availability of a single [`Product`](../supply/product.mdx) at a single [facility location](../facility/location.mdx). It is the on-hand counterpart to the supply workflow: deliveries move product into a location and dispenses move it out, and the resulting balance is recomputed into `net_content`. Inventory rows are **created and maintained automatically by the server** — they are not directly created through a write API; the only client-facing mutation is changing `status` (e.g. marking a line `inactive` when an item is damaged or no longer dispensed).
+
+The Django model is the **storage** layer. The Pydantic **resource specs** (`care/emr/resources/inventory/inventory_item/`) define the `status` enum and the read schema, including the nested `product` / `location` objects that the model exposes only as foreign keys.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `InventoryItem` | Stock of one product at one location, with its current net quantity |
+
+`InventoryItem` extends `EMRBaseModel` (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics). See [Base model](../foundation/base-model.mdx).
+
+## `InventoryItem` fields
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `location` | `FK → FacilityLocation` | yes | — | `on_delete=PROTECT`. The facility location holding the stock (e.g. central store, ward pharmacy). |
+| `product` | `FK → Product` | yes | — | `on_delete=PROTECT`. The product being tracked. |
+| `status` | `CharField(255)` | yes | — | Lifecycle/availability status. Spec constrains values to `InventoryItemStatusOptions` (see below). |
+| `net_content` | `DecimalField` | no | `Decimal(0)` | `max_digits=20`, `decimal_places=6`. Current on-hand quantity. Server-computed (see [`sync()`](#sync_inventory_item)). May go negative if dispenses/outgoing deliveries exceed incoming. |
+
+The pair `(location, product)` is treated as unique — there is at most one inventory item per product per location (enforced in `save()`, see [Methods & save behaviour](#methods--save-behaviour)).
+
+### `InventoryItemStatusOptions` values
+
+Defined in [`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/spec.py). The spec restricts `status` to these values (the model field itself is an unconstrained `CharField`):
+
+| Value | Meaning |
+| --- | --- |
+| `active` | Stock line is live and dispensable. Default applied when the server auto-creates a row. |
+| `inactive` | No longer actively dispensed (e.g. damaged stock, or other concern). |
+| `entered_in_error` | Record created in error. |
+
+## Related models
+
+`InventoryItem` references two models from other domains, plus the transactional records that drive its quantity:
+
+```text
+location → FK FacilityLocation (PROTECT)
+product → FK Product (PROTECT)
+
+drives net_content (read in sync_inventory_item):
+ SupplyDelivery (incoming completed, outgoing in-progress/completed)
+ MedicationDispense (dispensed-out, excluding cancelled statuses)
+```
+
+Both foreign keys use `on_delete=PROTECT`, so a location or product cannot be hard-deleted while inventory rows reference it. See [Supply Delivery](../supply/supply-delivery.mdx) and [Medication Dispense](../medications/medication-dispense.mdx) for the records that adjust `net_content`.
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` (`care/emr/resources/base.py`), which provides `serialize` / `de_serialize` and the `perform_extra_serialization` hook used to inline the nested `product` and `location` objects.
+
+| Spec class | Role | Fields exposed | Notes |
+| --- | --- | --- | --- |
+| `BaseInventoryItemSpec` | shared | `id`, `status` | `__model__ = InventoryItem`, `__exclude__ = []`. `status` typed as `InventoryItemStatusOptions`. |
+| `InventoryItemWriteSpec` | write | `id`, `status` | Inherits `BaseInventoryItemSpec` unchanged — only `status` is client-writable; `product`, `location`, and `net_content` are server-maintained. |
+| `InventoryItemReadSpec` | read · list | `id`, `status`, `net_content`, `product`, `location` | See serialization below. |
+| `InventoryItemRetrieveSpec` | read · detail | same as `InventoryItemReadSpec` | `pass` subclass — identical shape to the list spec. |
+
+### Read serialization (`InventoryItemReadSpec.perform_extra_serialization`)
+
+| Field | Serialized shape | Source |
+| --- | --- | --- |
+| `id` | `UUID4` | `obj.external_id` (the public id, not the internal pk). |
+| `net_content` | `Decimal` (`max_digits=20`, `decimal_places=0` on the read field) | model `net_content`. |
+| `product` | nested `dict` | `ProductReadSpec.serialize(obj.product).to_json()` — includes nested `product_knowledge` and optional `charge_item_definition`. See [Product](../supply/product.mdx). |
+| `location` | nested `dict` | `FacilityLocationListSpec.serialize(obj.location).to_json()` — includes `parent`, `mode`, `has_children`, `system_availability_status`, and optional `current_encounter`. See [Location](../facility/location.mdx). |
+
+`status` (typed `InventoryItemStatusOptions`) is carried through from `BaseInventoryItemSpec`. There is no separate `CreateSpec` — inventory rows are not created via a client write path.
+
+## Methods & save behaviour
+
+### `save()`
+
+On creation only (when `self.id` is unset), `save()` enforces uniqueness of the `(location, product)` pair:
+
+```text
+if creating and InventoryItem with same (location, product) exists:
+ raise ValueError("Inventory item already exists")
+```
+
+- The check runs **only for new rows**; updates to an existing item skip it.
+- Violations raise a plain `ValueError`, not a database `IntegrityError` — callers creating items through the ORM should handle it.
+
+### `create_inventory_item(product, location)`
+
+Helper in [`create_inventory_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/create_inventory_item.py). Idempotent get-or-create:
+
+- Returns the existing `(product, location)` row if one exists.
+- Otherwise creates one with `status=active`, `net_content=0`, and saves it.
+
+### `sync_inventory_item()`
+
+Helper in [`sync_inventory_item.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/sync_inventory_item.py) — the source of truth for `net_content`. It runs under `InventoryLock(product, location)` and recomputes the balance from transactional records, then writes it back:
+
+```text
+net_content =
+ Σ incoming completed deliveries (SupplyDelivery.status == completed,
+ order.destination == location,
+ supplied_inventory_item.product == product)
+ - Σ outgoing in-progress + completed deliveries
+ (SupplyDelivery.order.origin is not null,
+ supplied_inventory_item == this item,
+ status in {in_progress, completed})
+ - Σ dispenses (MedicationDispense.item == this item,
+ excluding cancelled statuses)
+```
+
+- If no row exists for `(product, location)`, it is auto-created (`status=active`, `net_content=0`) before computing.
+- `net_content` is the **aggregate sum** of `supplied_item_quantity` / dispense `quantity`; it can be negative.
+- Called whenever a delivery is completed or a dispense changes, keeping availability current.
+
+## API integration notes
+
+- Inventory items are **server-maintained**: created via `create_inventory_item` / `sync_inventory_item`, never through a direct client create. The only meaningful client write is changing `status` (e.g. to `inactive`).
+- One inventory row exists per `(location, product)` pair — `net_content` is adjusted on the existing row rather than by inserting duplicates.
+- `net_content` is recomputed by `sync_inventory_item` under an `InventoryLock`; do not patch it directly — it will be overwritten on the next sync.
+- Read responses inline the full `product` (with `product_knowledge`) and `location` objects, not just their ids.
+- `location` and `product` are `PROTECT` foreign keys — referenced products and locations cannot be hard-deleted while stock rows exist.
+
+## Related
+
+- Reference: [Product](../supply/product.mdx)
+- Reference: [Supply Request](../supply/supply-request.mdx)
+- Reference: [Supply Delivery](../supply/supply-delivery.mdx)
+- Reference: [Medication Dispense](../medications/medication-dispense.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [inventory_item.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/inventory_item.py)
+- Source: [inventory_item spec on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/spec.py)
diff --git a/versioned_docs/version-3.1/references/supply/product-knowledge.mdx b/versioned_docs/version-3.1/references/supply/product-knowledge.mdx
new file mode 100644
index 0000000..d659490
--- /dev/null
+++ b/versioned_docs/version-3.1/references/supply/product-knowledge.mdx
@@ -0,0 +1,250 @@
+---
+sidebar_position: 2
+---
+
+# Product Knowledge
+
+Technical reference for the `ProductKnowledge` module in Care EMR.
+
+`ProductKnowledge` stores the foundational, reusable information about a product that need not be duplicated across individual stock items — for example its coding, base presentation unit, ingredient/nutrient definition, drug characteristics, and storage guidelines. Every concrete `Product` in Care references a `ProductKnowledge` entry, which may be defined 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 `JSONField`s whose real structure is defined by the Pydantic **resource specs** (the API/implementation layer). The enum values, nested JSON shapes, validation, and read/write schemas below all come from those specs.
+
+**Source:**
+- Model: [`care/emr/models/product_knowledge.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/product_knowledge.py)
+- Spec: [`care/emr/resources/inventory/product_knowledge/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product_knowledge/spec.py)
+- Value sets: [`care/emr/resources/inventory/product_knowledge/valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product_knowledge/valueset.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `ProductKnowledge` | Foundational, reusable definition of a product (medication, nutritional product, or consumable) |
+
+`ProductKnowledge` extends [`SlugBaseModel`](../foundation/base-model.mdx), which itself extends `EMRBaseModel` (shared Care EMR base with `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 slug helpers (`calculate_slug`, `parse_slug`), so a record can be addressed by an instance slug (`i-`) or a facility slug (`f--`).
+
+## `ProductKnowledge` fields
+
+### Identity & scope
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` | `PROTECT`, nullable. Null = instance-level definition; set = facility-scoped. Excluded from the spec base (`__exclude__ = ["facility"]`); set on write via `facility` UUID, surfaced on read as `is_instance_level` |
+| `slug` | `CharField(255)` | Slug value; combined with `facility` to form the addressable slug. Set server-side from `slug_value` on write |
+| `alternate_identifier` | `CharField(255)` | Nullable; secondary external identifier |
+| `status` | `CharField(255)` | Lifecycle status. Spec-constrained to `ProductKnowledgeStatusOptions` (required) |
+| `product_type` | `CharField(255)` | Product category. Spec-constrained to `ProductTypeOptions` (required) |
+| `category` | `FK → emr.ResourceCategory` | `CASCADE`, nullable; classifies the product into a [resource category](../platform/resource-category.mdx). On write supplied as a category **slug** string and resolved via `ResourceCategory.objects.get(slug=...)`; on read serialized as a nested object |
+
+### Naming
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(255)` | Primary display name (required in spec) |
+| `names` | `JSONField` (list) | Nullable; list of `ProductName { name_type: ProductNameTypes, name: str }`. `name_type` is bound to the `ProductNameTypes` enum (not a FHIR value set) |
+| `names_cache` | `CharField(2048)` | Nullable; denormalized space-joined string of `name` + all `names` entries, rebuilt on every `save()` for search. Platform-maintained — not in any spec, do not set from clients |
+
+### Coding & definition (JSON fields)
+
+The model declares these as bare `JSONField`s. Their real shape is enforced by the spec:
+
+| Field | Model type | Spec type / shape | Notes |
+| --- | --- | --- | --- |
+| `code` | `JSONField` (dict) | `Coding \| None` | Nullable; product code as a [`Coding`](#coding) (not value-set bound at the spec layer) |
+| `base_unit` | `JSONField` (dict) | `ValueSetBoundCoding[CARE_UCUM_UNITS]` | **Required** in spec. A `Coding` bound to the UCUM units value set (`system-ucum-units`) |
+| `names` | `JSONField` (list) | `list[ProductName] \| None` | See [ProductName](#productname) |
+| `storage_guidelines` | `JSONField` (list) | `list[StorageGuideline] \| None` | See [StorageGuideline](#storageguideline) |
+| `definitional` | `JSONField` (dict) | `ProductDefinitionSpec \| None` | Type-specific definition payload. See [ProductDefinitionSpec](#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 the structured shapes of the JSON fields, defined as plain Pydantic `BaseModel`s in `spec.py`.
+
+### `ProductName`
+
+`names[]` — alternate names for the product.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `name_type` | `ProductNameTypes` | yes | Enum, see above |
+| `name` | `str` | yes | |
+
+### `StorageGuideline`
+
+`storage_guidelines[]` — storage notes with a stability duration.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `note` | `str` | yes | |
+| `stability_duration` | `DurationSpec { value: int, unit: Coding }` | yes | `value` is an integer count; `unit` is a free `Coding` |
+
+### `ProductDefinitionSpec`
+
+`definitional` — type-specific definition payload.
+
+| Field | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `dosage_form` | `ValueSetBoundCoding[MEDICATION_FORM_CODES] \| None` | required (nullable) | `Coding` bound to medication form codes (`system-medication-form-codes`, SNOMED is-a `736542009`) |
+| `intended_routes` | `list[Coding]` | `[]` | Free `Coding`s (not value-set bound) |
+| `ingredients` | `list[ProductIngredient]` | `[]` | See [ProductIngredient](#productingredient) |
+| `nutrients` | `list[ProductNutrient]` | `[]` | See [ProductNutrient](#productnutrient) |
+| `drug_characteristic` | `list[DrugCharacteristic]` | `[]` | See [DrugCharacteristic](#drugcharacteristic) |
+
+### `ProductIngredient`
+
+`definitional.ingredients[]`.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `is_active` | `bool` | yes | Active vs. inactive ingredient |
+| `substance` | `ValueSetBoundCoding[CARE_SUBSTANCE_VALUSET]` | yes | `Coding` bound to the substance value set (`system-substance`, SNOMED is-a `105590001`) |
+| `strength` | `ProductStrength` | yes | See [ProductStrength](#productstrength) |
+
+### `ProductNutrient`
+
+`definitional.nutrients[]`.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `item` | `ValueSetBoundCoding[CARE_NUTRIENTS_VALUESET]` | yes | `Coding` bound to the nutrients value set (`system-nutrients`, SNOMED is-a `226355009`) |
+| `amount` | `ProductStrength` | yes | See [ProductStrength](#productstrength) |
+
+### `ProductStrength`
+
+Shared by `ProductIngredient.strength` and `ProductNutrient.amount`.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `ratio` | `Ratio { numerator: Quantity, denominator: Quantity }` | yes | |
+| `quantity` | `Quantity` | yes | See [Quantity](#quantity) |
+
+### `DrugCharacteristic`
+
+`definitional.drug_characteristic[]`.
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `code` | `DrugCharacteristicCode` | yes | Enum, see above |
+| `value` | `str` | yes | |
+
+## Shared common types
+
+### `Coding`
+
+From `care/emr/resources/common/coding.py` (`extra="forbid"`).
+
+| Field | Type | Required |
+| --- | --- | --- |
+| `system` | `str \| None` | no |
+| `version` | `str \| None` | no |
+| `code` | `str` | yes |
+| `display` | `str \| None` | no |
+
+A `ValueSetBoundCoding[]` 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"`).
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `value` | `Decimal \| None` | max 20 digits, 6 decimal places |
+| `unit` | `Coding \| None` | human-readable unit |
+| `code` | `Coding \| None` | machine-processable unit |
+| `meta` | `dict \| None` | |
+
+`Ratio { numerator: Quantity, denominator: Quantity }` (both required).
+
+## Resource specs (API schema)
+
+All specs build 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` only copies non-FK database fields that are also declared spec fields and not in `__exclude__`.
+
+| Spec class | Role | Adds / behaviour |
+| --- | --- | --- |
+| `BaseProductKnowledgeSpec` | shared base | `__exclude__ = ["facility"]`. Fields: `id`, `alternate_identifier`, `status`, `product_type`, `code`, `base_unit` (required, UCUM-bound), `name`, `names`, `storage_guidelines`, `definitional` |
+| `ProductKnowledgeUpdateSpec` | write · update | Adds `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` |
+| `ProductKnowledgeWriteSpec` | write · create | Extends 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 |
+| `ProductKnowledgeReadSpec` | read · detail/list | Adds `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`; sets `slug_config = parse_slug(slug)` |
+
+### Validation & bound value sets
+
+- `status` ∈ `ProductKnowledgeStatusOptions`; `product_type` ∈ `ProductTypeOptions` — both required.
+- `base_unit` is **required** and bound to `CARE_UCUM_UNITS` (`system-ucum-units`, system `http://unitsofmeasure.org`).
+- `definitional.dosage_form` bound to `MEDICATION_FORM_CODES`; `ingredients[].substance` bound to `CARE_SUBSTANCE_VALUSET`; `nutrients[].item` bound to `CARE_NUTRIENTS_VALUESET`. `code` and `intended_routes` are free `Coding`s.
+- `slug_value` uses `SlugType`: 5–50 chars, URL-safe (`^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$`), must start/end alphanumeric.
+- `category` on write is a slug string; an unknown slug raises (`.get(...)` `DoesNotExist`).
+
+### Server-maintained behaviour
+
+- `facility` is never copied through the generic field loop (it is in `__exclude__`); it is set explicitly from the `facility` UUID only by `ProductKnowledgeWriteSpec` (create).
+- `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` — they are not stored columns.
+
+## Methods & save behaviour
+
+### `save()` side effects
+
+`save()` is overridden to rebuild `names_cache` before persisting:
+
+1. `names_cache` is reset to `" "`.
+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 slug logic inherited from `SlugBaseModel`).
+
+`names_cache` is therefore platform-maintained — clients should not set it directly; write to `name`/`names` instead.
+
+## API integration notes
+
+- Exposed through Care's REST API; aligns with FHIR `MedicationKnowledge` / `NutritionProduct` concepts.
+- `product_type` selects the meaning of `definitional` (`medication`, `nutritional_product`, or `consumable`).
+- `facility` controls scope: omit (instance-wide) vs. set (facility-local). Address records by slug (`i-` or `f--`); the parsed parts are returned as `slug_config`.
+- `code`, `names`, `storage_guidelines`, `definitional`, and `base_unit` are JSON fields whose structure and value-set binding live in the spec, not the model. `base_unit` is required.
+- `names_cache` is maintained by the platform on save — do not set it from clients.
+
+## Related
+
+- Reference: [Product](../supply/product.mdx)
+- Reference: [Inventory Item](../supply/inventory-item.mdx)
+- Reference: [Resource Category](../platform/resource-category.mdx)
+- Base: [Base model](../foundation/base-model.mdx)
+- Source: [model](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/product_knowledge.py) · [spec](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product_knowledge/spec.py) · [value sets](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product_knowledge/valueset.py)
diff --git a/versioned_docs/version-3.1/references/supply/product.mdx b/versioned_docs/version-3.1/references/supply/product.mdx
new file mode 100644
index 0000000..4e2e864
--- /dev/null
+++ b/versioned_docs/version-3.1/references/supply/product.mdx
@@ -0,0 +1,128 @@
+---
+sidebar_position: 1
+---
+
+# Product
+
+Technical reference for the `Product` module in Care EMR.
+
+`Product` is the STORAGE layer (Django model). The API/implementation layer lives in the Pydantic **resource specs**, which define the enums, the structured shape of the model's opaque `JSONField`s, validation rules, and the read/write schemas. Several model fields are opaque `JSONField`s whose real structure is only visible in the specs — see [Resource specs (API schema)](#resource-specs-api-schema).
+
+**Source:**
+- Model: [`care/emr/models/product.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/product.py)
+- Spec: [`care/emr/resources/inventory/product/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product/spec.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `Product` | A concrete, batch-level instantiation of a `ProductKnowledge` definition at a facility (medication, nutritional product, or consumable) |
+
+`Product` extends [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields, and soft-delete semantics).
+
+A `Product` carries only the data that is unique to a particular batch — lot number, expiry, purchase price. All catalogue-level detail (name, codes, dosage form) lives on the linked [`ProductKnowledge`](../supply/product-knowledge.mdx); a `Product` is its instantiation at a facility.
+
+## `Product` fields
+
+### Relationships
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `facility` | `FK → facility.Facility` (`PROTECT`) | Facility the product belongs to. Required at storage; not a spec field — set by the viewset from the URL context, not from the request body. |
+| `product_knowledge` | `FK → emr.ProductKnowledge` (`PROTECT`) | Catalogue definition this product instantiates. On write the spec accepts a `slug` string and resolves it; on read it is serialized to the nested [`ProductKnowledge`](../supply/product-knowledge.mdx) read object. |
+| `charge_item_definition` | `FK → emr.ChargeItemDefinition` (`PROTECT`, nullable) | Used to create charge items when the product is billed. Write accepts a `slug` string; read serializes to the nested [`ChargeItemDefinition`](../billing/charge-item-definition.mdx) read object. |
+
+### Classification & status
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `status` | `CharField(255)` | yes (spec) | Constrained by the spec to [`ProductStatusOptions`](#productstatusoptions-values): `active` / `inactive` / `entered_in_error`. |
+| `product_type` | `CharField(255)` | — | Kind of product. Present at storage but **not exposed by the `Product` resource spec** — `product_type` is carried on the linked [`ProductKnowledge`](../supply/product-knowledge.mdx) (`ProductTypeOptions`: `medication` / `nutritional_product` / `consumable`). |
+
+### Batch & pricing
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `batch` | `JSONField` (nullable) | no | `dict` / `None` | Spec shape: [`ProductBatch`](#productbatch-shape) `{ lot_number: str \| None }`. The model stores an opaque dict; the spec narrows it to a single optional `lot_number`. |
+| `expiration_date` | `DateTimeField` (nullable) | no | `None` | Batch expiry. Spec type `datetime \| None`. |
+| `standard_pack_size` | `IntegerField` (nullable) | no | `None` | Units per standard pack. Spec type `int \| None`. |
+| `purchase_price` | `DecimalField(max_digits=20, decimal_places=6)` (nullable) | no | `None` | Acquisition cost for this batch. Spec type `Decimal \| None`, constrained `max_digits=20, decimal_places=6`. |
+| `extensions` | `JSONField` | yes (spec) | `dict` | Open extension bag. On write it is validated against the registered schemas for `ExtensionResource.product` via `ExtensionValidator` (unknown keys are dropped; invalid data raises). |
+
+## Enums
+
+### `ProductStatusOptions` values
+
+Defined in [`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product/spec.py); bound to the model `status` field.
+
+| Value | Meaning |
+| --- | --- |
+| `active` | Product is in use |
+| `inactive` | Product is no longer in use but retained |
+| `entered_in_error` | Record created in error |
+
+### `ProductTypeOptions` values
+
+Not a field on the `Product` spec — defined in [`product_knowledge/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product_knowledge/spec.py) and carried on the linked [`ProductKnowledge`](../supply/product-knowledge.mdx). Listed here because the model has a `product_type` storage column.
+
+| Value |
+| --- |
+| `medication` |
+| `nutritional_product` |
+| `consumable` |
+
+## Nested JSON shapes
+
+### `ProductBatch` shape
+
+Structure of the `batch` `JSONField` (`care/emr/resources/inventory/product/spec.py`).
+
+| Field | Type | Required | Default | Notes |
+| --- | --- | --- | --- | --- |
+| `lot_number` | `str \| None` | no | `None` | Batch/lot identifier |
+
+## Resource specs (API schema)
+
+All specs derive from `EMRResource` ([`care/emr/resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)), which provides `serialize` (DB → pydantic, with the `perform_extra_serialization` hook) and `de_serialize` (pydantic → DB, with the `perform_extra_deserialization` hook).
+
+| Spec class | Role | Fields beyond the base | Behaviour |
+| --- | --- | --- | --- |
+| `BaseProductSpec` | shared | `id`, `status`, `batch`, `expiration_date`, `extensions`, `standard_pack_size`, `purchase_price` | `__model__ = Product`. `__exclude__ = ["product_knowledge", "charge_item_definition"]` (these FKs are handled by the hooks, not by automatic field mapping). `___extension_resource_type__ = ExtensionResource.product`. |
+| `ProductWriteSpec` | write · create | adds `product_knowledge: str` (slug), `charge_item_definition: str \| None` (slug) | Mixes in `ExtensionValidator`. `perform_extra_deserialization` resolves `product_knowledge` via `get_object_or_404(ProductKnowledge, slug=...)` and, if present, `charge_item_definition` via `get_object_or_404(ChargeItemDefinition, slug=...)`. |
+| `ProductUpdateSpec` | write · update | adds `charge_item_definition: str \| None` (slug) | Mixes in `ExtensionValidator`. `perform_extra_deserialization` resolves `charge_item_definition` via `ChargeItemDefinition.objects.get(slug=...)` when supplied. Does **not** re-bind `product_knowledge` (immutable after create). |
+| `ProductReadSpec` | read · list & detail | adds `product_knowledge: dict`, `charge_item_definition: dict \| None` | `perform_extra_serialization` sets `id = external_id`, serializes `product_knowledge` via `ProductKnowledgeReadSpec` and (when set) `charge_item_definition` via `ChargeItemDefinitionReadSpec`, both inlined as JSON. A single read spec serves both list and detail. |
+
+Notes:
+- **`product_type` and `facility` are not request/response fields** of the product spec. `facility` is set server-side from the route; `product_type` lives on `ProductKnowledge`.
+- **Slug-based references**: `product_knowledge` and `charge_item_definition` are written as slug strings and read back as fully serialized nested objects.
+- **Extension validation**: `extensions` on write passes through `ExtensionValidator.validate_extensions` against the JSON schemas registered for `ExtensionResource.product`; keys with no registered handler are silently dropped (current behaviour, marked TODO to become an error).
+- **No status history**: unlike some EMR resources, `Product` does not maintain a server-side `status_history`.
+- The base `de_serialize` dumps with `exclude_defaults=True`, so unset optional fields are not written to the model.
+
+## Methods & save behaviour
+
+- `Product` adds no model methods of its own beyond `EMRBaseModel`; persistence, `external_id`, audit fields, and soft delete are inherited.
+- Write flow: request body → `ProductWriteSpec` / `ProductUpdateSpec` → `de_serialize` → `perform_extra_deserialization` (FK slug resolution) → `obj.save()`.
+- Read flow: `Product` row → `ProductReadSpec.serialize` → `perform_extra_serialization` (inline `product_knowledge` and `charge_item_definition`) → JSON.
+- All foreign keys use `PROTECT`, so a referenced `Facility`, `ProductKnowledge`, or `ChargeItemDefinition` cannot be deleted while products reference it.
+
+## API integration notes
+
+- A `Product` is a batch-level instantiation of [`ProductKnowledge`](../supply/product-knowledge.mdx); catalogue detail (name, codes, dosage form, `product_type`) is read from the linked knowledge record, not duplicated here.
+- On write, send `product_knowledge` and `charge_item_definition` as **slugs**; on read they are returned as full nested objects.
+- `charge_item_definition` links the product to billing, so charge items can be created automatically when the product is billed.
+- `batch` is a structured object (`{ lot_number }`), not a free-form dict, despite the model storing it as a `JSONField`.
+- `extensions` is the supported place for deployment-specific key-value data without schema migrations; values are validated against schemas registered for the `product` extension resource.
+
+## Related
+
+- Reference: [Product knowledge](../supply/product-knowledge.mdx)
+- Reference: [Inventory item](../supply/inventory-item.mdx)
+- Reference: [Supply request](../supply/supply-request.mdx)
+- Reference: [Supply delivery](../supply/supply-delivery.mdx)
+- Reference: [Charge item definition](../billing/charge-item-definition.mdx)
+- Reference: [Facility](../facility/facility.mdx)
+- Reference: [Base model](../foundation/base-model.mdx)
+- Source: [model `product.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/product.py)
+- Source: [spec `inventory/product/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/product/spec.py)
+- Source: [base `resources/base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
diff --git a/versioned_docs/version-3.1/references/supply/supply-delivery.mdx b/versioned_docs/version-3.1/references/supply/supply-delivery.mdx
new file mode 100644
index 0000000..cef8727
--- /dev/null
+++ b/versioned_docs/version-3.1/references/supply/supply-delivery.mdx
@@ -0,0 +1,190 @@
+---
+sidebar_position: 5
+---
+
+# Supply Delivery
+
+Technical reference for the `SupplyDelivery` module in Care EMR.
+
+**Source:**
+- Model: [`care/emr/models/supply_delivery.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/supply_delivery.py)
+- Resource specs: [`spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_delivery/spec.py) · [`delivery_order.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_delivery/delivery_order.py)
+
+A supply delivery records the movement of a product from one location (or an external supplier) to a destination location. Together with supply dispensing, deliveries are a source of truth for facility inventory: `supply delivered − supply dispensed = current stock`. Deliveries are grouped under a `DeliveryOrder`, which is typically created from a [supply request](./supply-request.mdx).
+
+The Django model is the **storage layer**: `status`, `delivery_type`, and `supplied_item_condition` are plain `CharField`s and `extensions` is an opaque `JSONField`. The **real shape** of those fields — enum values, validation, the read/write schemas — lives in the Pydantic resource specs, documented below.
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `SupplyDelivery` | A single line of delivered product (quantity, condition, source product/inventory) |
+| `DeliveryOrder` | Groups deliveries for a shipment between an origin and destination |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_by`/`updated_by`, `created_date`/`modified_date`, soft-delete via `deleted`, and `history`/`meta` JSON).
+
+## `SupplyDelivery` fields
+
+### Delivered item & quantity
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `supplied_item` | `FK → Product (CASCADE)` | Nullable. The product being delivered. Required at the API layer **only when the order has no `origin`** (external delivery). On write, scoped to `order.destination.facility` |
+| `supplied_inventory_item` | `FK → InventoryItem (CASCADE)` | Nullable. Specific inventory item (batch/lot) drawn from. Required at the API layer **when the order has an `origin`** (intra-facility move); scoped to `order.origin.facility` |
+| `supplied_item_quantity` | `DecimalField(max_digits=20, decimal_places=6)` | Nullable in DB. On write a `Decimal` with `decimal_places=0` (whole units); on read serialized as `int`. Auto-derived as `pack_quantity × pack_size` when both are given |
+| `supplied_item_pack_quantity` | `IntegerField` | Nullable; default `None`. Number of packs delivered |
+| `supplied_item_pack_size` | `IntegerField` | Nullable; default `None`. Units per pack |
+| `supplied_item_condition` | `CharField(255)` | Optional. Condition of the delivered item — enum `SupplyDeliveryConditionOptions` (see below) |
+
+### Status & classification
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `status` | `CharField(255)` | Required. Lifecycle state — enum `SupplyDeliveryStatusOptions` (see below) |
+| `delivery_type` | `CharField(255)` | Type of item delivered — enum `SupplyDeliveryTypeOptions` (`product` / `device`). Stored on the model; not exposed on the current Create/Update specs |
+
+### Links & pricing
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `supply_request` | `FK → SupplyRequest (CASCADE)` | Nullable. Request this delivery fulfils. Set by `external_id` on write |
+| `order` | `FK → DeliveryOrder (CASCADE)` | Nullable in DB, but **required on create** (referenced by `external_id`). Optional on update |
+| `total_purchase_price` | `DecimalField(max_digits=20, decimal_places=6)` | Nullable. Total purchase cost. Spec: `Decimal` `max_digits=20, decimal_places=6` |
+| `extensions` | `JSONField` | Default `{}`. Validated per registered extension handler for `ExtensionResource.supply_delivery` (see [Resource specs](#resource-specs-api-schema)) |
+
+### `SupplyDelivery` enum values
+
+These enums are `str` enums in `spec.py` — the stored value equals the member value (snake_case), not a FHIR URI.
+
+#### `SupplyDeliveryStatusOptions` (`status`)
+
+| Value |
+| --- |
+| `in_progress` |
+| `completed` |
+| `abandoned` |
+| `entered_in_error` |
+
+#### `SupplyDeliveryConditionOptions` (`supplied_item_condition`)
+
+| Value |
+| --- |
+| `normal` |
+| `damaged` |
+
+#### `SupplyDeliveryTypeOptions` (`delivery_type`)
+
+| Value |
+| --- |
+| `product` |
+| `device` |
+
+## Related models
+
+### `DeliveryOrder`
+
+Groups one or more `SupplyDelivery` rows into a single logical shipment moving between two facility locations (or from an external supplier into a facility).
+
+```text
+supplier → FK Organization (CASCADE, nullable) -- external/source supplier
+origin → FK FacilityLocation (CASCADE, nullable) -- related_name="origin_delivery_orders"
+destination → FK FacilityLocation (CASCADE) -- related_name="destination_delivery_orders"
+patient → FK Patient (PROTECT, nullable) -- patient the order is dispensed to
+patient_invoice → FK Invoice (PROTECT, nullable) -- linked billing invoice
+```
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(255)` | Required. Human-readable order name |
+| `status` | `CharField(255)` | Required. Order lifecycle — enum `SupplyDeliveryOrderStatusOptions` (see below) |
+| `note` | `TextField` | Nullable. Free-text note |
+| `tags` | `ArrayField[int]` | Default `[]`. Tag IDs; rendered via `SingleFacilityTagManager` on read |
+| `supplier` | `FK → Organization (CASCADE)` | Nullable. On write must be an Organization with `org_type = product_supplier` |
+| `origin` | `FK → FacilityLocation (CASCADE)` | Nullable. Source location; absence means stock is entering from an external `supplier` |
+| `destination` | `FK → FacilityLocation (CASCADE)` | **Required.** Receiving location |
+| `patient` | `FK → Patient (PROTECT)` | Nullable. Patient the order is dispensed to. Cannot be set together with `origin` |
+| `patient_invoice` | `FK → Invoice (PROTECT)` | Nullable. Linked billing invoice; serialized as `patient_invoice_id` on read |
+| `extensions` | `JSONField` | Default `{}`. Validated for `ExtensionResource.supply_delivery_order` |
+
+`patient`/`patient_invoice` use `PROTECT` so referenced patients and invoices cannot be deleted while a delivery order points to them.
+
+#### `SupplyDeliveryOrderStatusOptions` (`status`)
+
+| Value | Notes |
+| --- | --- |
+| `draft` | Allowed on create |
+| `pending` | Allowed on create |
+| `in_progress` | |
+| `completed` | Terminal (in `SUPPLY_DELIVERY_ORDER_COMPLETED_STATUSES`) |
+| `abandoned` | Terminal (in `SUPPLY_DELIVERY_ORDER_COMPLETED_STATUSES`) |
+| `entered_in_error` | Terminal (in `SUPPLY_DELIVERY_ORDER_COMPLETED_STATUSES`) |
+
+## Resource specs (API schema)
+
+All specs extend `EMRResource` ([`base.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)): `serialize` builds the read payload from the DB object (`perform_extra_serialization` hook), `de_serialize` builds the DB object from the request (`perform_extra_deserialization` hook). `to_json()` dumps the model excluding `meta`.
+
+### `SupplyDelivery` specs
+
+| Spec | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `BaseSupplyDeliverySpec` | shared | `id`, `status` (required), `supplied_item_condition?`, `total_purchase_price?`. `__exclude__ = ["supplied_item", "supply_request", "supplied_inventory_item"]` (these are resolved by hooks, not direct field copy) |
+| `SupplyDeliveryWriteSpec` | write · create | Adds `supplied_item_pack_quantity?`, `supplied_item_pack_size?`, `supplied_item_quantity` (Decimal, `decimal_places=0`), `supplied_item?`, `supplied_inventory_item?`, `supply_request?`, `order` (**required**), `extensions`. Mixes in `ExtensionValidator` |
+| `SupplyDeliveryUpdateSpec` | write · update | `order?` only (plus shared base fields + `extensions`). Mixes in `ExtensionValidator` |
+| `SupplyDeliveryReadSpec` | read · list | `supplied_item_quantity: int`, `created_date`, `modified_date`, `supplied_item_pack_quantity?`, `supplied_item_pack_size?`, `extensions`, and nested objects `supplied_item`, `supplied_inventory_item`, `supply_request`, `order` (serialized as `dict`). Mixes in `ExtensionListRenderer` |
+| `SupplyDeliveryRetrieveSpec` | read · detail | Extends `SupplyDeliveryReadSpec`; adds `created_by`/`updated_by` (`UserSpec`). Mixes in `ExtensionRetrieveRenderer` |
+
+**Write validation (`SupplyDeliveryWriteSpec`):**
+- `validate_quantity`: when both `supplied_item_pack_quantity` and `supplied_item_pack_size` are set, `supplied_item_quantity` is overwritten with their product.
+- `validate_supplied_item` (resolves `order` from `external_id`):
+ - if `order.origin` is set → `supplied_inventory_item` is **required** (intra-facility stock move);
+ - if `order.origin` is **not** set → `supplied_item` is **required** (external delivery);
+ - `supplied_item` and `supplied_inventory_item` **cannot both** be provided.
+- `perform_extra_deserialization`: resolves `order` by `external_id`; resolves `supplied_item` scoped to `order.destination.facility`; if `order.origin` exists, resolves `supplied_inventory_item` scoped to `order.origin.facility`; resolves `supply_request` by `external_id`.
+
+**Update (`SupplyDeliveryUpdateSpec`):** `perform_extra_deserialization` re-resolves `order` from `external_id` only when `order` is provided.
+
+**Read serialization:** `perform_extra_serialization` sets `id = external_id` and inlines related objects via their read specs — `supplied_item` → `ProductReadSpec`, `supplied_inventory_item` → `InventoryItemReadSpec`, `supply_request` → `SupplyRequestReadSpec`, `order` → `SupplyDeliveryOrderReadSpec`.
+
+### `DeliveryOrder` specs
+
+| Spec | Role | Exposes / behaviour |
+| --- | --- | --- |
+| `BaseSupplyDeliveryOrderSpec` | shared | `id`, `status` (required), `name` (required), `note?`. Mixes in `ExtensionValidator` |
+| `SupplyDeliveryOrderWriteSpec` | write · create/update | Adds `supplier?`, `origin?`, `destination` (**required**), `patient?` (all by `external_id`) |
+| `SupplyDeliveryOrderReadSpec` | read · list | Nested `origin?`, `destination`, `supplier?`, `patient?` (`dict`), `tags`, `patient_invoice_id?`, `created_date`, `modified_date`, `created_by?`, `updated_by?`. Mixes in `ExtensionListRenderer` |
+| `SupplyDeliveryOrderRetrieveSpec` | read · detail | Extends `SupplyDeliveryOrderReadSpec` (no extra fields). Mixes in `ExtensionRetrieveRenderer` |
+
+**Write validation (`SupplyDeliveryOrderWriteSpec.perform_extra_deserialization`):**
+- resolves `destination` (required), `origin?`, `patient?` by `external_id`;
+- resolves `supplier?` constrained to `org_type = product_supplier`;
+- `patient` and `origin` **cannot** be provided together;
+- on create the `status` **must be `draft` or `pending`**.
+
+**Read serialization:** `id = external_id`; inlines `origin`/`destination` via `FacilityLocationListSpec`, `supplier` via `OrganizationReadSpec`, `patient` via `PatientListSpec`; renders `tags` via `SingleFacilityTagManager`; exposes the linked invoice as `patient_invoice_id` (string); attaches audit users.
+
+### Extensions
+
+`extensions` is not free-form JSON. On write, `ExtensionValidator` runs each registered handler for the resource type (`supply_delivery` / `supply_delivery_order`) — unknown keys are dropped, known keys are validated and re-serialized. On read, `ExtensionListRenderer` / `ExtensionRetrieveRenderer` render every registered extension for the resource (list vs retrieve variants).
+
+## API integration notes
+
+- Coded fields carry the **snake_case enum values** above (e.g. `status = "in_progress"`), not FHIR URIs. Validate against the enum, not free text.
+- A delivery is created against a `DeliveryOrder` (`order` is required on create). The order's `origin` decides whether you send `supplied_inventory_item` (intra-facility) or `supplied_item` (external) — never both.
+- Send `supplied_item_pack_quantity` + `supplied_item_pack_size` to have `supplied_item_quantity` computed server-side; otherwise send `supplied_item_quantity` directly (whole units).
+- A `DeliveryOrder` must start in `draft` or `pending`; `completed`/`abandoned`/`entered_in_error` are terminal states.
+- `patient` and `origin` are mutually exclusive on an order (patient dispense vs location-to-location move).
+- `extensions` is the supported place for deployment-specific data — but only **registered** extension keys are persisted.
+
+## Related
+
+- Reference: [Supply Request](./supply-request.mdx)
+- Reference: [Inventory Item](./inventory-item.mdx)
+- Reference: [Product](./product.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Organization](../facility/organization.mdx)
+- Reference: [Patient](../clinical/patient.mdx)
+- Reference: [Invoice](../billing/invoice.mdx)
+- Source: [supply_delivery.py (model)](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/supply_delivery.py)
+- Source: [spec.py (SupplyDelivery specs)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_delivery/spec.py)
+- Source: [delivery_order.py (DeliveryOrder specs)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_delivery/delivery_order.py)
+- Source: [base.py (EMRResource)](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py)
diff --git a/versioned_docs/version-3.1/references/supply/supply-request.mdx b/versioned_docs/version-3.1/references/supply/supply-request.mdx
new file mode 100644
index 0000000..54d6291
--- /dev/null
+++ b/versioned_docs/version-3.1/references/supply/supply-request.mdx
@@ -0,0 +1,187 @@
+---
+sidebar_position: 4
+---
+
+# Supply Request
+
+Technical reference for the `SupplyRequest` module in Care EMR.
+
+A `SupplyRequest` is a line item asking for a quantity of a catalogue item (`ProductKnowledge`). Each request belongs to a `RequestOrder`, which groups requests describing the movement of items into a destination facility location (optionally from a supplier organization and/or an origin location).
+
+The Django model is the **storage** layer — `status`, `priority`, `intent`, `reason`, `category`, and `supplied_item_condition` are stored as plain `CharField`s with no DB-level constraint. The **real allowed values, validation, and API request/response shapes** live in the Pydantic resource specs (`care/emr/resources/inventory/supply_request/`), documented below.
+
+**Source:**
+
+- Model: [`care/emr/models/supply_request.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/supply_request.py)
+- Spec: [`care/emr/resources/inventory/supply_request/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_request/spec.py)
+- Spec: [`care/emr/resources/inventory/supply_request/request_order.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_request/request_order.py)
+
+## Models
+
+| Model | Purpose |
+| --- | --- |
+| `SupplyRequest` | A request for a quantity of a catalogue item (product knowledge), grouped under an order |
+| `RequestOrder` | An order that groups supply requests for movement of items into a destination facility location |
+
+Both models extend [`EMRBaseModel`](../foundation/base-model.mdx) (shared Care EMR base with `external_id`, audit fields `created_date`/`modified_date`/`created_by`/`updated_by`, soft-delete via `deleted`, and `history`/`meta` JSON).
+
+## `SupplyRequest` fields
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `status` | `CharField(255)` | Lifecycle status. Spec-bound to `SupplyRequestStatusOptions` (see below). Required on write. |
+| `quantity` | `DecimalField` | Nullable in DB; `max_digits=20`, `decimal_places=6`. Quantity requested. The write spec accepts a `Decimal` (`max_digits=20`, `decimal_places=0`); the read spec returns it as an `int`. |
+| `supplied_item_condition` | `CharField(255)` | Condition of the supplied item. Not exposed by any current spec (model-only field). |
+| `item` | `FK → ProductKnowledge` | `CASCADE`; the catalogue item being requested. Required on create; resolved server-side from an `external_id`. |
+| `order` | `FK → RequestOrder` | `CASCADE`, nullable in DB. The parent order. Required on both create and update in the spec; resolved server-side from an `external_id`. |
+
+### `SupplyRequestStatusOptions` values
+
+Enum class `SupplyRequestStatusOptions` (`spec.py`). Bound to `SupplyRequest.status`.
+
+| Value |
+| --- |
+| `draft` |
+| `active` |
+| `suspended` |
+| `cancelled` |
+| `processed` |
+| `completed` |
+| `entered_in_error` |
+
+## Related models
+
+### `RequestOrder`
+
+Groups one or more `SupplyRequest` rows into a single order and describes the movement of items into a `destination` facility location, optionally from a `supplier` organization and/or an `origin` location.
+
+```text
+supplier → FK Organization (nullable, CASCADE)
+origin → FK FacilityLocation (nullable, CASCADE, related_name="origin_request_orders")
+destination → FK FacilityLocation (CASCADE, related_name="destination_request_orders")
+```
+
+| Field | Type | Notes |
+| --- | --- | --- |
+| `name` | `CharField(255)` | Order name/label. Required. |
+| `status` | `CharField(255)` | Order lifecycle status. Spec-bound to `SupplyRequestOrderStatusOptions`. |
+| `note` | `TextField` | Nullable; free-text note. Optional (`str \| None`). |
+| `tags` | `ArrayField[int]` | Default `list`; tag IDs attached to the order. Rendered on read via `SingleFacilityTagManager`. |
+| `priority` | `CharField(255)` | Order priority. Spec-bound to `SupplyRequestPriorityOptions`. |
+| `intent` | `CharField(255)` | Intent of the order. Spec-bound to `SupplyRequestIntentOptions`. |
+| `reason` | `CharField(255)` | Reason for the order. Spec-bound to `SupplyRequestReason`. |
+| `category` | `CharField(255)` | Order category. Spec-bound to `SupplyRequestCategoryOptions`. |
+| `supplier` | `FK → Organization` | Nullable `CASCADE`. On write, must be an organization of `org_type == "product_supplier"` (validated server-side). |
+| `origin` | `FK → FacilityLocation` | Nullable `CASCADE`, `related_name="origin_request_orders"`. Sending location. |
+| `destination` | `FK → FacilityLocation` | `CASCADE`, `related_name="destination_request_orders"`. Receiving location. Required. |
+
+`SupplyRequest.order` points back to a `RequestOrder`, so a single order fans out to many supply requests. `origin` and `destination` both target `FacilityLocation` but use distinct `related_name`s, letting a location surface both the orders it sends (`origin_request_orders`) and the orders it receives (`destination_request_orders`).
+
+### `RequestOrder` enum values
+
+Enums from `request_order.py`. Each binds to the matching `RequestOrder` `CharField`.
+
+#### `SupplyRequestOrderStatusOptions` (→ `status`)
+
+| Value |
+| --- |
+| `draft` |
+| `pending` |
+| `in_progress` |
+| `completed` |
+| `abandoned` |
+| `entered_in_error` |
+
+`SUPPLY_REQUEST_ORDER_COMPLETED_STATUSES` = `[abandoned, entered_in_error, completed]` — the terminal/closed statuses.
+
+#### `SupplyRequestIntentOptions` (→ `intent`)
+
+| Value |
+| --- |
+| `proposal` |
+| `plan` |
+| `directive` |
+| `order` |
+| `original_order` |
+| `reflex_order` |
+| `filler_order` |
+| `instance_order` |
+
+#### `SupplyRequestCategoryOptions` (→ `category`)
+
+| Value |
+| --- |
+| `central` |
+| `nonstock` |
+
+#### `SupplyRequestPriorityOptions` (→ `priority`)
+
+| Value |
+| --- |
+| `routine` |
+| `urgent` |
+| `asap` |
+| `stat` |
+
+#### `SupplyRequestReason` (→ `reason`)
+
+| Value |
+| --- |
+| `patient_care` |
+| `ward_stock` |
+
+## Resource specs (API schema)
+
+All specs extend [`EMRResource`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py) (`serialize` / `de_serialize`, with `perform_extra_serialization` / `perform_extra_deserialization` hooks). Write specs resolve foreign keys from `external_id` values in `perform_extra_deserialization`; read specs expand related objects in `perform_extra_serialization`.
+
+### Supply request specs (`spec.py`)
+
+| Spec class | Role | Fields | Notes |
+| --- | --- | --- | --- |
+| `BaseSupplyRequestSpec` | shared | `id`, `status`, `quantity` | `__model__ = SupplyRequest`, `__exclude__ = ["item"]`. `quantity: Decimal(max_digits=20, decimal_places=0)`. `status: SupplyRequestStatusOptions`. |
+| `SupplyRequestWriteSpec` | write · create | base + `item: UUID4`, `order: UUID4` | Resolves `item` → `ProductKnowledge` and `order` → `RequestOrder` by `external_id` (404 if missing). |
+| `SupplyRequestUpdateSpec` | write · update | base + `order: UUID4` | Resolves `order` → `RequestOrder` by `external_id`. Does **not** accept `item` (item is fixed after create). |
+| `SupplyRequestReadSpec` | read · detail/list | base + `quantity: int`, `item: dict`, `order: dict` | `id` = `external_id`. `item` expanded via `ProductKnowledgeReadSpec`; `order` expanded via `SupplyRequestOrderReadSpec`. |
+
+### Request order specs (`request_order.py`)
+
+| Spec class | Role | Fields | Notes |
+| --- | --- | --- | --- |
+| `BaseSupplyRequestOrderSpec` | shared | `id`, `status`, `name`, `note`, `intent`, `category`, `priority`, `reason` | `__model__ = RequestOrder`. Coded fields bound to their respective enums; `note` optional. |
+| `SupplyRequestOrderWriteSpec` | write · create/update | base + `supplier: UUID4 \| None`, `origin: UUID4 \| None`, `destination: UUID4` | Resolves `supplier`/`origin`/`destination` by `external_id`. Validates `supplier.org_type == "product_supplier"`, else raises `ValidationError`. |
+| `SupplyRequestOrderReadSpec` | read · detail/list | base + `supplier`, `origin: dict \| None`, `destination: dict`, `tags: list[dict]`, `created_date`, `modified_date`, `created_by`, `updated_by` | `id` = `external_id`. `supplier` expanded via `OrganizationReadSpec`; `origin`/`destination` via `FacilityLocationListSpec`; `tags` rendered via `SingleFacilityTagManager`; audit users via `serialize_audit_users`. |
+
+### Validation & server-maintained behaviour
+
+- **FK resolution:** all foreign keys on write are supplied as `external_id` UUIDs and resolved server-side with `get_object_or_404` (`item`, `order`, `supplier`, `origin`, `destination`).
+- **Supplier type guard:** `SupplyRequestOrderWriteSpec` rejects any `supplier` whose `org_type` is not `product_supplier`.
+- **`id` handling:** read specs set `id` to `external_id`; `de_serialize` never writes `id`/`external_id` back to the row.
+- **`quantity`:** write accepts a `Decimal` constrained to `decimal_places=0` (whole numbers) even though the DB column allows `decimal_places=6`; read returns an `int`.
+- **`item` immutability:** create accepts `item`; update (`SupplyRequestUpdateSpec`) omits it, so the requested catalogue item cannot be changed after creation.
+
+## Methods & save behaviour
+
+- Neither model overrides `save()`; standard `EMRBaseModel` audit and soft-delete behaviour applies.
+- Status is free-text at the DB layer; lifecycle is enforced only by the spec enums at the API boundary. There is no automatic `status_history` tracking on these models.
+- Deleting a `RequestOrder` cascades to its `SupplyRequest` rows (`order` FK is `CASCADE`); deleting a referenced `ProductKnowledge` cascades to the request.
+
+## API integration notes
+
+- Field names in API payloads follow the spec field names above; foreign keys are exchanged as `external_id` UUIDs on write and as expanded objects on read.
+- `status`, `priority`, `intent`, `reason`, and `category` are validated against the spec enums — send the exact string values listed above.
+- `supplied_item_condition` exists on the model but is not surfaced by any current spec.
+- `quantity` is sent/returned as a whole number through the specs; the DB column itself retains six decimal places.
+- `tags` on `RequestOrder` is an integer array of tag IDs (not a relational join); on read it is rendered to tag objects via the facility tag manager.
+- `external_id` (from `EMRBaseModel`) is the stable public identifier used in API URLs — not the internal primary key.
+
+## Related
+
+- Reference: [Product Knowledge](../supply/product-knowledge.mdx)
+- Reference: [Supply Delivery](../supply/supply-delivery.mdx)
+- Reference: [Inventory Item](../supply/inventory-item.mdx)
+- Reference: [Location](../facility/location.mdx)
+- Reference: [Organization](../facility/organization.mdx)
+- Reference: [EMR base model](../foundation/base-model.mdx)
+- Source: [supply_request.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/supply_request.py)
+- Spec: [supply_request/spec.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_request/spec.py)
+- Spec: [supply_request/request_order.py on GitHub](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/supply_request/request_order.py)
diff --git a/versioned_sidebars/version-3.0-sidebars.json b/versioned_sidebars/version-3.0-sidebars.json
index b45504f..2aaff30 100644
--- a/versioned_sidebars/version-3.0-sidebars.json
+++ b/versioned_sidebars/version-3.0-sidebars.json
@@ -33,10 +33,8 @@
"collapsed": false,
"items": [
{
- "type": "category",
- "label": "Clinical",
- "key": "clinical-references",
- "items": ["references/clinical/patient"]
+ "type": "autogenerated",
+ "dirName": "references"
}
]
}
diff --git a/versioned_sidebars/version-3.1-sidebars.json b/versioned_sidebars/version-3.1-sidebars.json
index b45504f..2aaff30 100644
--- a/versioned_sidebars/version-3.1-sidebars.json
+++ b/versioned_sidebars/version-3.1-sidebars.json
@@ -33,10 +33,8 @@
"collapsed": false,
"items": [
{
- "type": "category",
- "label": "Clinical",
- "key": "clinical-references",
- "items": ["references/clinical/patient"]
+ "type": "autogenerated",
+ "dirName": "references"
}
]
}