Skip to main content

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:

AggregateID prefixFHIR canonicalPurpose
Problemprb_ConditionLongitudinal problem list entry
Allergyalg_AllergyIntoleranceAllergy/intolerance record with reactions
VitalsSetvit_grouping for Observation panelA captured panel of vital-sign observations
Observationobs_Observation (category vital-signs)Individual measurement within a VitalsSet
ClinicalNotenote_Composition + optional DocumentReferenceTemplated clinical note with lifecycle
ChartAccessacc_Provenance (optional)Break-glass + sensitive-segment access record

Entities scoped to aggregates:

EntityParent aggregateRole
ProblemHistoryEntryProblemAppend-only status change log
AllergyReactionAllergyManifestation + severity per reaction
NoteSectionClinicalNoteSection of the Composition (Subjective, Objective, Plan, etc.)
NoteSignatureClinicalNoteAuthor + cosigner signatures
NoteAddendumClinicalNotePost-sign amendment
NoteAIProvenanceClinicalNoteAI-assist feature/model/version + accepted-at timestamp

2. Ubiquitous language

TermMeaning
ChartThe patient's composite longitudinal record scoped to tenant.
BannerThe chart header: demographics proxy + alerts + encounter context.
Summary widgetA bounded composition unit (problems, allergies, vitals, meds, labs, etc.).
Timeline eventA datetime-keyed chart item (note, problem change, vitals, order, result…).
ProblemA condition or diagnosis with clinical status and verification status.
AllergyCanonical term for AllergyIntolerance; includes food, medication, environment, biologic.
NKA / NKDANo Known Allergies / No Known Drug Allergies — exclusive status assertions.
VitalsSetA group of measurements taken at one time by one performer at one location.
Abnormal flagComputed flag (L, H, LL, HH, A) applied when a measurement is out of configured range.
NoteClinical narrative; structured Composition with template-derived sections.
CosignSecondary attestation by an authorized role (e.g., attending for resident).
AddendumPost-sign append; original content immutable.
Break-glassAuthorized emergency override of access restrictions with captured reason.
Sensitive segmentPolicy-flagged clinical category (e.g., mental-health, sexual-health) with extra access rules.
SnapshotHandoff 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 via verificationStatus=entered-in-error.
  • verificationStatus ∈ {unconfirmed, provisional, differential, confirmed, refuted, entered-in-error}.
  • code must include at least one coding with system ∈ {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-error transition requires reason + author + timestamp; emits problem.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 optional severity ∈ {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 VitalsSet belongs to one tenant + one patient.
  • recordedBy MUST be derived from the authenticated principal, not client-supplied.
  • Optional encounterId; when absent, collectionLocationId MAY be supplied (FR-VIT-007 legacy).
  • BMI is computed when both height and weight present; stored as a derived Observation with derivedFrom links.
  • Range validation is advisory when policy is warn; hard-stop when policy is reject.
  • 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)LabelUnits (UCUM)Typical range
8310-5Body temperatureCel35.0–40.0
8867-4Heart rate/min40–180
9279-1Respiratory rate/min8–40
8480-6Systolic BPmm[Hg]70–200
8462-4Diastolic BPmm[Hg]40–120
2708-6SpO₂%70–100
29463-7Body weightkg0.5–300
8302-2Body heightcm30–250
39156-5BMIkg/m2derived
72514-3Pain 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.
  • signedAt and signedBy set atomically with status=signed; subsequent cosign requests do not change signedAt.
  • 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=signed requires the cosigner attestation; until then status=pending-cosign (non-FHIR sub-state persisted as platform extension).
  • Every AI-accepted chunk inside a section MUST write a NoteAIProvenance row (feature, model version, actor, accepted-at).
  • Attachments are DocumentReference ids 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 objectShape
TenantIdBranded string (ten_*)
PatientIdBranded string (pat_*) referencing registration-service
EncounterIdBranded string (enc_*)
PractitionerIdBranded string (prc_*)
LocationIdBranded 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 }
NoteSectionKeyBranded string matching configured template section ids

9. Domain events (produced)

EventAggregateTrigger
patient_chart.problem.added.v1ProblemAdded to list
patient_chart.problem.updated.v1ProblemAny non-status attribute change
patient_chart.problem.resolved.v1ProblemStatus → resolved
patient_chart.problem.inactivated.v1ProblemStatus → inactive
patient_chart.problem.entered_in_error.v1ProblemverificationStatus=entered-in-error
patient_chart.allergy.added.v1AllergyAdded
patient_chart.allergy.updated.v1AllergyAttribute change
patient_chart.allergy.inactivated.v1AllergyStatus → inactive
patient_chart.allergy.entered_in_error.v1AllergyEntered-in-error
patient_chart.vitals.recorded.v1VitalsSetSet created
patient_chart.vitals.updated.v1VitalsSetCorrection version
patient_chart.vitals.abnormal_flagged.v1ObservationValue out of range + severity
patient_chart.note.created.v1ClinicalNoteDraft created
patient_chart.note.updated_draft.v1ClinicalNoteDraft edit
patient_chart.note.signed.v1ClinicalNoteSigned
patient_chart.note.cosign_requested.v1ClinicalNoteCosign routing
patient_chart.note.cosigned.v1ClinicalNoteCosigner attested
patient_chart.note.addendum_created.v1ClinicalNoteAddendum appended
patient_chart.note.entered_in_error.v1ClinicalNoteEntered-in-error
patient_chart.note.ai_accepted.v1ClinicalNoteAI-assist chunk accepted
patient_chart.chart.snapshot_exported.v1ChartSnapshot export
patient_chart.breakglass.invoked.v1ChartAccessBreak-glass
patient_chart.sensitive_access.recorded.v1ChartAccessSensitive-segment access
patient_chart.chart.item_opened.v1ChartDeep-link open (audited)

Payload schemas and retention classes in EVENT_SCHEMAS.md.

10. Domain errors

Error codeMeaning
CHART_CROSS_TENANT_REFERENCEAny aggregate id crosses tenant boundary
CHART_NKA_CONFLICTAttempt to add substance allergy while active NKA exists
CHART_NKDA_CONFLICTAttempt to add medication allergy while active NKDA exists
CHART_DUPLICATE_ALLERGYSame coded substance (or normalized free-text) already active
CHART_DUPLICATE_PROBLEMSame coded condition already active
CHART_PROBLEM_IMMUTABLEAttempt to mutate entered-in-error problem
CHART_NOTE_SIGNED_IMMUTABLEAttempt to edit section body on signed/amended note
CHART_NOTE_COSIGN_REQUIREDSign attempted without required cosign routing
CHART_VITALS_RANGE_REJECTEDHard-stop validation fired
CHART_INVALID_VERSIONOptimistic-concurrency version mismatch
CHART_BREAKGLASS_REASON_MISSINGBreak-glass call without reason
CHART_SENSITIVE_NOT_AUTHORIZEDSensitive segment denied by policy
CHART_AI_PROVENANCE_MISSINGAI accept without provenance payload

11. FHIR mapping summary

AggregateFHIR resourceRead surfaceWrite surface
ProblemConditionGET /fhir/R4/Condition?patient=...POST /v1/problems (product REST)
AllergyAllergyIntoleranceGET /fhir/R4/AllergyIntolerance?patient=...POST /v1/allergies
VitalsSet + ObservationObservation (category vital-signs)GET /fhir/R4/Observation?patient=...&category=vital-signsPOST /v1/vitals
ClinicalNoteComposition + DocumentReferenceGET /fhir/R4/Composition?subject=..., /fhir/R4/DocumentReference?subject=...POST /v1/clinical-notes, lifecycle endpoints
ChartAccessProvenance (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 List resource for the chart summary (informative), or keep it product-only?