Patient Chart Service — Domain Model
Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · 02 DDD · FHIR-First
1. Aggregates
The patient-chart-service owns five aggregate roots, bound by tenancy and patient:
| Aggregate | ID prefix | FHIR canonical | Purpose |
|---|---|---|---|
Problem | prb_ | Condition | Longitudinal problem list entry |
Allergy | alg_ | AllergyIntolerance | Allergy/intolerance record with reactions |
VitalsSet | vit_ | grouping for Observation panel | A captured panel of vital-sign observations |
Observation | obs_ | Observation (category vital-signs) | Individual measurement within a VitalsSet |
ClinicalNote | note_ | Composition + optional DocumentReference | Templated clinical note with lifecycle |
ChartAccess | acc_ | Provenance (optional) | Break-glass + sensitive-segment access record |
Entities scoped to aggregates:
| Entity | Parent aggregate | Role |
|---|---|---|
ProblemHistoryEntry | Problem | Append-only status change log |
AllergyReaction | Allergy | Manifestation + severity per reaction |
NoteSection | ClinicalNote | Section of the Composition (Subjective, Objective, Plan, etc.) |
NoteSignature | ClinicalNote | Author + cosigner signatures |
NoteAddendum | ClinicalNote | Post-sign amendment |
NoteAIProvenance | ClinicalNote | AI-assist feature/model/version + accepted-at timestamp |
2. Ubiquitous language
| Term | Meaning |
|---|---|
| Chart | The patient's composite longitudinal record scoped to tenant. |
| Banner | The chart header: demographics proxy + alerts + encounter context. |
| Summary widget | A bounded composition unit (problems, allergies, vitals, meds, labs, etc.). |
| Timeline event | A datetime-keyed chart item (note, problem change, vitals, order, result…). |
| Problem | A condition or diagnosis with clinical status and verification status. |
| Allergy | Canonical term for AllergyIntolerance; includes food, medication, environment, biologic. |
| NKA / NKDA | No Known Allergies / No Known Drug Allergies — exclusive status assertions. |
| VitalsSet | A group of measurements taken at one time by one performer at one location. |
| Abnormal flag | Computed flag (L, H, LL, HH, A) applied when a measurement is out of configured range. |
| Note | Clinical narrative; structured Composition with template-derived sections. |
| Cosign | Secondary attestation by an authorized role (e.g., attending for resident). |
| Addendum | Post-sign append; original content immutable. |
| Break-glass | Authorized emergency override of access restrictions with captured reason. |
| Sensitive segment | Policy-flagged clinical category (e.g., mental-health, sexual-health) with extra access rules. |
| Snapshot | Handoff composition: active problems + meds + allergies + recent vitals + critical results + active goals. |
3. Problem aggregate
3.1 Invariants
clinicalStatus ∈ {active, inactive, resolved}; entered-in-error is tracked viaverificationStatus=entered-in-error.verificationStatus ∈ {unconfirmed, provisional, differential, confirmed, refuted, entered-in-error}.codemust include at least one coding withsystem ∈ {ICD-10, ICD-11, SNOMED CT}OR free-text with a coding-task flag when a terminology policy mandates later coding.- Abatement date MUST be null unless
clinicalStatus ∈ {inactive, resolved}. entered-in-errortransition requires reason + author + timestamp; emitsproblem.entered_in_error.v1.- Links to encounters, notes, care-plan goals, orders are optional associations, not invariants.
3.2 State machine
4. Allergy aggregate
4.1 Invariants
clinicalStatus ∈ {active, inactive, resolved}.verificationStatus ∈ {unconfirmed, confirmed, refuted, entered-in-error}.category ∈ {medication, food, environment, biologic, other}(multi-valued allowed per FHIR).- NKA rule: if an active NKA record exists, no other active substance allergies may be added until NKA inactivated.
- NKDA rule: if NKDA active, no new active medication allergies; NKDA is inactivated automatically when NKA is set.
- Reactions array may be empty. Each reaction has
manifestation(CodeableConcept) and optionalseverity ∈ {mild, moderate, severe}. - Duplicate active allergy for the same coded substance OR the same normalized free-text substance must be rejected or queued for reconciliation (BR-ALL-003).
4.2 State machine
5. VitalsSet + Observation aggregate
5.1 Invariants
- Every
VitalsSetbelongs to one tenant + one patient. recordedByMUST be derived from the authenticated principal, not client-supplied.- Optional
encounterId; when absent,collectionLocationIdMAY be supplied (FR-VIT-007 legacy). - BMI is computed when both height and weight present; stored as a derived Observation with
derivedFromlinks. - Range validation is advisory when policy is
warn; hard-stop when policy isreject. - Corrections are new versions; original is immutable (legacy BR-VIT-004).
- A measurement code MUST map to a LOINC code where LOINC is available; non-LOINC measurements store
system=local.
5.2 Measurement types (baseline)
| Code (LOINC) | Label | Units (UCUM) | Typical range |
|---|---|---|---|
8310-5 | Body temperature | Cel | 35.0–40.0 |
8867-4 | Heart rate | /min | 40–180 |
9279-1 | Respiratory rate | /min | 8–40 |
8480-6 | Systolic BP | mm[Hg] | 70–200 |
8462-4 | Diastolic BP | mm[Hg] | 40–120 |
2708-6 | SpO₂ | % | 70–100 |
29463-7 | Body weight | kg | 0.5–300 |
8302-2 | Body height | cm | 30–250 |
39156-5 | BMI | kg/m2 | derived |
72514-3 | Pain score | {score} | 0–10 |
6. ClinicalNote aggregate
6.1 Invariants
status ∈ {draft, signed, amended, entered-in-error}.- A signed note's section content is immutable; amendments append a
NoteAddendum. signedAtandsignedByset atomically withstatus=signed; subsequent cosign requests do not changesignedAt.- A note bound to an encounter may not be signed if encounter status is
planned. - Cosign workflow: when policy mandates cosign for the author's role,
status=signedrequires the cosigner attestation; until thenstatus=pending-cosign(non-FHIR sub-state persisted as platform extension). - Every AI-accepted chunk inside a section MUST write a
NoteAIProvenancerow (feature, model version, actor, accepted-at). - Attachments are
DocumentReferenceids from document-service; byte storage is not this service's responsibility.
6.2 State machine
7. ChartAccess aggregate
Captures break-glass invocations and sensitive-segment access events. Each record is append-only; emits patient_chart.breakglass.invoked.v1 or patient_chart.sensitive_access.recorded.v1.
8. Value objects
| Value object | Shape |
|---|---|
TenantId | Branded string (ten_*) |
PatientId | Branded string (pat_*) referencing registration-service |
EncounterId | Branded string (enc_*) |
PractitionerId | Branded string (prc_*) |
LocationId | Branded string (loc_*) |
CodeableConcept | { codings: Coding[]; text?: string } |
Coding | { system: string; code: string; display?: string } |
Range | { low?: Quantity; high?: Quantity } |
Quantity | { value: number; unit: string; system: 'http://unitsofmeasure.org'; code: string } |
Period | { start?: string; end?: string } |
Reference | { reference: string; display?: string } |
Signature | { type: Coding; when: string; whoReference: Reference; sigFormat: string } |
NoteSectionKey | Branded string matching configured template section ids |
9. Domain events (produced)
| Event | Aggregate | Trigger |
|---|---|---|
patient_chart.problem.added.v1 | Problem | Added to list |
patient_chart.problem.updated.v1 | Problem | Any non-status attribute change |
patient_chart.problem.resolved.v1 | Problem | Status → resolved |
patient_chart.problem.inactivated.v1 | Problem | Status → inactive |
patient_chart.problem.entered_in_error.v1 | Problem | verificationStatus=entered-in-error |
patient_chart.allergy.added.v1 | Allergy | Added |
patient_chart.allergy.updated.v1 | Allergy | Attribute change |
patient_chart.allergy.inactivated.v1 | Allergy | Status → inactive |
patient_chart.allergy.entered_in_error.v1 | Allergy | Entered-in-error |
patient_chart.vitals.recorded.v1 | VitalsSet | Set created |
patient_chart.vitals.updated.v1 | VitalsSet | Correction version |
patient_chart.vitals.abnormal_flagged.v1 | Observation | Value out of range + severity |
patient_chart.note.created.v1 | ClinicalNote | Draft created |
patient_chart.note.updated_draft.v1 | ClinicalNote | Draft edit |
patient_chart.note.signed.v1 | ClinicalNote | Signed |
patient_chart.note.cosign_requested.v1 | ClinicalNote | Cosign routing |
patient_chart.note.cosigned.v1 | ClinicalNote | Cosigner attested |
patient_chart.note.addendum_created.v1 | ClinicalNote | Addendum appended |
patient_chart.note.entered_in_error.v1 | ClinicalNote | Entered-in-error |
patient_chart.note.ai_accepted.v1 | ClinicalNote | AI-assist chunk accepted |
patient_chart.chart.snapshot_exported.v1 | Chart | Snapshot export |
patient_chart.breakglass.invoked.v1 | ChartAccess | Break-glass |
patient_chart.sensitive_access.recorded.v1 | ChartAccess | Sensitive-segment access |
patient_chart.chart.item_opened.v1 | Chart | Deep-link open (audited) |
Payload schemas and retention classes in EVENT_SCHEMAS.md.
10. Domain errors
| Error code | Meaning |
|---|---|
CHART_CROSS_TENANT_REFERENCE | Any aggregate id crosses tenant boundary |
CHART_NKA_CONFLICT | Attempt to add substance allergy while active NKA exists |
CHART_NKDA_CONFLICT | Attempt to add medication allergy while active NKDA exists |
CHART_DUPLICATE_ALLERGY | Same coded substance (or normalized free-text) already active |
CHART_DUPLICATE_PROBLEM | Same coded condition already active |
CHART_PROBLEM_IMMUTABLE | Attempt to mutate entered-in-error problem |
CHART_NOTE_SIGNED_IMMUTABLE | Attempt to edit section body on signed/amended note |
CHART_NOTE_COSIGN_REQUIRED | Sign attempted without required cosign routing |
CHART_VITALS_RANGE_REJECTED | Hard-stop validation fired |
CHART_INVALID_VERSION | Optimistic-concurrency version mismatch |
CHART_BREAKGLASS_REASON_MISSING | Break-glass call without reason |
CHART_SENSITIVE_NOT_AUTHORIZED | Sensitive segment denied by policy |
CHART_AI_PROVENANCE_MISSING | AI accept without provenance payload |
11. FHIR mapping summary
| Aggregate | FHIR resource | Read surface | Write surface |
|---|---|---|---|
| Problem | Condition | GET /fhir/R4/Condition?patient=... | POST /v1/problems (product REST) |
| Allergy | AllergyIntolerance | GET /fhir/R4/AllergyIntolerance?patient=... | POST /v1/allergies |
| VitalsSet + Observation | Observation (category vital-signs) | GET /fhir/R4/Observation?patient=...&category=vital-signs | POST /v1/vitals |
| ClinicalNote | Composition + DocumentReference | GET /fhir/R4/Composition?subject=..., /fhir/R4/DocumentReference?subject=... | POST /v1/clinical-notes, lifecycle endpoints |
| ChartAccess | Provenance (optional) | GET /fhir/R4/Provenance?target=Patient/{id} | Implicit — write-only via domain events |
Writes via FHIR HTTP are out of scope until a platform release declares them.
12. Open Questions
- Is the sensitive-segment policy engine a per-tenant config in config-service, or platform-level with tenant overrides?
- Should the platform expose a FHIR
Listresource for the chart summary (informative), or keep it product-only?