Value Set
A ValueSet is a FHIR-aligned composition of coded concepts drawn from one or more code systems. Reach for one when a questionnaire needs to constrain a coded answer to a fixed set of options.
The Django model is only storage — compose is an opaque JSONField. The structure that matters (include/exclude clauses, filters, the status enum, validation, and the read/write API schemas) lives in the Pydantic resource specs, covered alongside the model here.
Source:
- Model:
care/emr/models/valueset.py - Resource spec:
care/emr/resources/valueset/spec.py - Compose schema:
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, the shared Care EMR base that supplies external_id, audit fields, history/meta JSON, and soft-delete semantics.
ValueSet fields
A value set stores rules, not a flat list of codes: a compose object of include/exclude clauses against code systems. Members resolve 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 and 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. The model defaults to an empty string; the spec requires the field present |
compose | JSONField | ValueSetCompose | yes | dict | Composition rules — structured include/exclude clauses (see 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) |
is_system_defined | BooleanField | bool | no | False | Marks value sets shipped and maintained by the platform rather than authored by a deployment |
ValueSetStatusOptions values
A str enum in resources/valueset/spec.py. The model column is a free CharField; the spec restricts writes to these four 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). Each clause targets a system (a code system URI) and selects concepts directly or through filters. Every nested model sets extra="forbid", so unknown keys are rejected.
ValueSetCompose
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
id | str | None | no | None | Optional identifier |
include | list[ValueSetInclude] | yes | — | Inclusion clauses; 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 |
A clause may set concept or filter, not both — enforced by 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
validate_op rejects anything outside this set: =, is-a, descendent-of, is-not-a, regex, in, not-in, generalizes, child-of, descendent-leaf, exists.
compose:
include:
- system: <code system uri>
version: <optional system version>
concept: [ { code, display } ] # pin concepts (mutually exclusive with filter)
filter: [ { property, op, value } ] # rule-based selection
exclude:
- ... same ValueSetInclude shape ...
property: [ <property names to return> ]
create_composition() regroups these clauses by system into { <system>: {include: [...], exclude: [...]} } so each system can be queried independently, dumping each clause with exclude_defaults=True.
Note: a second, simpler
ValueSetBaseModel (name,status,compose) plusValueSetConcept/ValueSetFilter/ValueSetInclude/ValueSetComposeall live inresources/common/valueset.py. The persisted/API resource model isValueSetBaseSpecinresources/valueset/spec.py, which reuses the sameValueSetCompose.
Resource specs (API schema)
Every spec extends EMRResource (resources/base.py), which provides serialize (DB object → pydantic, via model_construct) and de_serialize (pydantic → DB object). ValueSet leaves __store_metadata__ unset, so spec fields map directly to model columns — there is 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"]); otherwise raises "Slug must be unique" |
| Reserved slug | validate_slug_system (model, after) | If the value set is not is_system_defined and the 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 |
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), normalizing 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() runs at module load so the forward reference to ValueSetCompose resolves.
Related models
UserValueSetPreference
Holds the codes a single user has favorited within one value set.
user → FK users.User (CASCADE)
valueset → FK emr.ValueSet (CASCADE)
favorite_codes → JSONField (default=list)
unique_together = ("user", "valueset") enforces 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
A classmethod-only helper, not a database model. It tracks a user's recently viewed codes in a Redis list (one per cache_key) so pickers can surface recent codes alongside favorites.
| 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), LPUSHes the entry, then LTRIMs 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 LREMs 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 ones are skipped on read and remove.
Methods & save behaviour
ValueSet does not override save()/delete() — soft-delete is inherited from the base. Its methods drive terminology resolution:
create_composition()— convertscompose(a dict orValueSetCompose) into a per-system map ofinclude/excludeclauses, dumping each clause withexclude_defaults=True.search(search="", count=10, display_language=None)— runsValueSetResource().filter(...).search()for each system in the composition (optionally filtered bydisplay_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 whethercodeis a member by runningValueSetResource().filter(...).lookup(code)against each system, returningTrueif any system matches.
Membership resolves live, so editing compose immediately changes which codes the value set yields. There is no expansion or cache to rebuild.
API integration notes
- The REST API aligns with the FHIR
ValueSetresource. Writes go throughValueSetSpec, reads throughValueSetReadSpec;searchpowers code pickers andlookupvalidates submitted codes. - The model column
composeis opaque JSON, so always write through theValueSetComposeschema (clauses, filters, theopallowlist,conceptxorfilter).perform_extra_deserializationre-dumps it withexclude_defaults/exclude_noneto keep stored JSON normalized. - Composition rules are stored, not expanded code lists. Members are queried in real time from terminology servers, so results shift as upstream systems change.
- Treat
is_system_definedvalue sets as read-only — they are platform-maintained. Author your own for local terminology; thesystem-slug prefix is reserved for system value sets. favorite_codes(viaUserValueSetPreference) and recent views (viaRecentViewsManager/Redis) are per-user personalization, not part of the value set definition.
Related
- Reference: Questionnaire — consumes value sets to constrain coded answers
- Reference: Observation definition — binds coded fields to value sets
- Reference: Base models & conventions
- Source (model): valueset.py on GitHub
- Source (spec): resources/valueset/spec.py on GitHub
- Source (compose): resources/common/valueset.py on GitHub