Skip to main content

Chart Data Model

A charting UI reads patient-level data that persists across visits and encounter-level data created during each visit. This page is a reference: the first half covers longitudinal patient context (summary, demographics, devices), the second covers encounter-time resources (Observations, Conditions, Allergies, external documents). Each major resource has its own section. For visit orchestration and the SOAP-aligned template pattern, see Visit Templates and the SOAP Approach.

Patient Summary and Queries

Demographics come from the Patient resource. You can load related records with Patient $everything, but that response can be large for a dashboard view. Prefer targeted searches for active CarePlan, MedicationRequest, Condition, and other slices your UI needs. See Search to compose queries.

React components that help assemble timelines and summaries include PatientTimeline, Timeline, Search control, ResourceAvatar, FhirPathDisplay, and tab navigation (for example Mantine Tabs, used throughout Medplum apps).

Key Resources for Patient Context

ResourceDescription
ObservationPoint-in-time clinical measurements and findings.
ConditionDiagnoses and longitudinal problems.
RiskAssessmentModeled risk scores and similar assessments.
AllergyIntoleranceAdverse reactions to drugs or substances.
ImmunizationVaccination history.
MedicationDrug definitions; ordering uses MedicationRequest and summaries may use MedicationStatement.
MedicationAdministrationDocuments when the patient received or took a medication (administration event).

Key Code Systems

Code SystemDescription
LOINCUsed in Observation and RiskAssessment for clinical coding, compliance, billing, and reporting.
ICD-10Used in Condition for interoperability and billing; also common on encounter-related billing metadata.
RXNORMDrug coding for allergies and medication orders.
SNOMEDSubstances and clinical concepts.
CVXImmunization vaccine types.

Encounter-Centric Resources

During a visit, resources typically link to an Encounter. Notes often use ClinicalImpression; measurements use Observation; orders use ServiceRequest and MedicationRequest.

Note capture varies by product: some apps use a simple free-text area and persist narrative on Encounter and/or ClinicalImpression. Others drive charting from a library of Questionnaires and use Bots or $extract to create the right resources. Medplum is headless, so either pattern (or a mix) is valid – see Visit Templates and the SOAP Approach for template-driven visits.

ResourceRole
EncounterVisit container in-person or virtual.
ClinicalImpressionAssessment narrative and findings for the encounter.
ConditionEncounter diagnoses when categorized accordingly.
ObservationVitals and other measurements tied to the encounter.
RiskAssessmentStructured risk scores for the encounter when used.

Encounter billing often references CPT and ICD-10 on billing-related structures; see Provider visits and Billing.

Patient Demographics

Capturing patient demographics is a core EHR capability. ONC references this under demographics (a)(5). Data may arrive via UI, integration, or API.

The USCDI V2 standard defines technical expectations; see the US Core Patient profile.

React Components

Medplum provides React components for Patient demographics aligned with common collection requirements.

Capturing Aliases

Use Patient.name with period and use to represent legal history – for example maiden names:

{
"name": [
{
"given": ["Marge", "Jacqueline"],
"family": "Simpson",
"period": { "start": "1980-01-01T00:00:00Z" },
"use": "official"
},
{
"given": ["Marge", "Jacqueline"],
"family": "née Bouvier",
"period": { "end": "1980-01-01T00:00:00Z" },
"use": "old"
}
]
}

Demographic Value Sets

Use standard value sets rather than ad hoc codes. Correct coding supports reporting and interoperability.

CategoryName
GenderAdministrative Gender Value Set
RaceUS Core Race Extension
EthnicityUS Core Ethnicity Extension
Birth SexBirth Sex Extension

Lifecycle

Managing duplicates belongs in every implementation – see Patient deduplication.

When a patient dies, record time of death on Patient.deceased. Represent cause of death as an Observation with ICD-10 or another appropriate ontology.

Common Identifiers

Capture identifiers such as driver license or national identifiers as namespaced identifiers on Patient. See Naming data identifiers. Integration covers exchange-oriented lookups.

Pre-release

ONC (a)(5) certification is under development.

Implantable Devices

Implantable device lists support safety, recalls, and adverse-event reporting. ONC describes expectations under implantable device list (a)(14). Data may be captured via UI, integration, or API.

See the US Core implantable device profile and USCDI.

Device Identifiers and Safety

Recording implantable devices supports:

  • Considering implants during treatment and testing
  • Finding patients affected by recalls
  • Supporting adverse-event reporting

At minimum, record presence of the device and a coded device type.

Also support collecting when applicable:

  • Device Identifier (UDI-DI)
  • Full UDI (human-readable or AIDC form)
  • Parsed production identifiers (manufacture date, expiration, lot, serial, distinct identifier)
  • MRI safety and latex presence where relevant

React Components

Medplum provides implantable device React components. List devices with a search control over Device; see search by reference.

A sample FHIR Bundle illustrates implantable device data.

Querying the FDA API

Medplum can query the FDA Device Lookup API to populate identifiers, manufacturer, and safety fields when a device identifier is supplied.

Observations and Vital Signs

Vital signs measure core physiological functions – blood pressure, pulse, respiration, temperature, and related values. Store them as Observation resources with coded Observation.code (typically LOINC per US Core expectations).

Observation Elements

ElementDescriptionCode SystemExample
statusPreliminary, final, amended, cancelled, etc.Observation statusregistered
codeWhat was observed.LOINC8867-4 Heart Rate
subjectWho or what the observation is about.Patient/homer-simpson
encounterVisit when observation was taken.Encounter/example
basedOnPlan or order that prompted the observation.CarePlan/example
performerResponsible party – patient for home readings, clinician or device in clinic.Practitioner/example
value[x]Result – see Observation datatypes.
dataAbsentReasonWhy a value is missing.Data absent reasonasked-but-unknown
interpretationHigh / low / normal categories.InterpretationN
deviceDevice used to produce the measurement.Device/apple-watch
specimenSpecimen when lab-derived.Specimen/example
componentSub-results that belong together (for example BP systolic/diastolic). See Multi-component observations.
Example: body temperature
{
"resourceType": "Observation",
"id": "example-observation-1",
"code": {
"system": "http://loinc.org",
"code": "8310-5",
"display": "Body temperature",
},
"valueQuantity": {
"value": 98.2,
"unit": "degrees Fahrenheit",
"system": "http://unitsofmeasure.org/",
"code": "[degF]",
},
"status": "final",
}
Example: observation from survey input
{
"resourceType": "Observation",
"id": "example-observation-2",
"code": {
"system": "http://loinc.org",
"code": "29463-7",
"display": "Body weight",
},
"valueQuantity": {
"value": 165,
"unit": "pounds",
"system": "http://unitsofmeasure.org/",
"code": "[lb_av]",
},
"status": "preliminary",
"method": {
"system": "http://example-practice.org/",
"code": "entry-survey",
"display": "entry survey",
},
}
Example: device-measured observation
{
"resourceType": "Observation",
"id": "example-observation-3",
"code": {
"system": "http://loinc.org",
"code": "8480-6",
"display": "Systolic blood pressure",
},
"valueQuantity": {
"value": 100,
"unit": "mmHg",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]",
},
"status": "preliminary",
"device": {
"resource": {
"resourceType": "Device",
"id": "example-device",
},
},
}
Example: patient-performed observation
{
"resourceType": "Observation",
"id": "example-observation-4",
"code": {
"system": "http://loinc.org",
"code": "8867-4",
"display": "Heart rate",
},
"valueQuantity": {
"value": 70,
"unit": "beats per minute",
"system": "http://unitsofmeasure.org",
"code": "{Beats}/min",
},
"status": "preliminary",
"subject": {
"resource": {
"resourceType": "Patient",
"id": "example-patient",
},
},
"performer": {
"resource": {
"resourceType": "Patient",
"id": "example-patient"
},
},
}
Units

valueQuantity uses FHIR Quantity: numeric value plus coded units when possible via UCUM.

Observation Datatypes

value[x]DescriptionDatatypeApplicationExample
valueQuantityNumeric value with unitQuantityHeight177 cm
valueCodeableConceptCoded answerCodeableConceptLab interpretationSNOMED coded
valueStringFree textstringPain descriptionmild pain
valueBooleanYes/nobooleanScreening flagstrue
valueIntegerWhole numbernumberCounts28
valueRangeIntervalRangeTemperature band98.0 – 98.7
valueRatioRatioRatioRatiosexample ratio
valueSampledDataTime seriesSampledDataWearables summariessee below
valueTimeTime onlystringTime-of-day phenomena15:30:00
valueDateTimeDateTimestringInstant readings2023-07-24Z
valuePeriodInterval over timePeriodEpisodesstart–end

Using valueSampledData for Time Series

valueSampledData holds dense series (wearables, CGM summaries) in one Observation.

Use it when:

  • Samples repeat on a fixed cadence
  • A wearable exports many points
  • Many discrete Observation rows would be inefficient
Wearables scale

Medplum is not optimized for massive wearable ingestion at second-level granularity across huge populations. Prefer summaries and aggregated Observations in Medplum; stream raw high-frequency feeds to analytics stores when needed.

Example: heart rate sampled data
{
"resourceType": "Observation",
"status": "final",
"code": {
"system": "http://loinc.org",
"code": "8867-4",
"display": "Heart rate"
},
"subject": {
"reference": "Patient/example-patient"
},
"device": {
"reference": "Device/apple-watch-123"
},
"effectivePeriod": {
"start": "2024-01-15T08:00:00Z",
"end": "2024-01-15T09:00:00Z"
},
"valueSampledData": {
"origin": {
"value": 60,
"unit": "beats/min",
"system": "http://unitsofmeasure.org",
"code": "{Beats}/min"
},
"period": 60000,
"dimensions": 1,
"factor": 1,
"data": "5 3 2 4 1 2 3 2 1 0 2 1 3 2 4 3 2 1 0 1 2 3 4 5 4 3 2 1 0 1 2 3 4 5 6 5 4 3 2 1 0 1 2 3 4 5 4 3 2 1 0 1 2 3 4 5 4 3 2 1 0"
}
}

Helper Functions

@medplum/core exposes three helpers for working with valueSampledData. Use whichever fits the call site–they are independent.

expandSampledData turns the packed string into an array of numeric values:

import { expandSampledData } from '@medplum/core';

const values = expandSampledData({
origin: { value: 60, unit: 'beats/min' },
period: 60000,
dimensions: 1,
factor: 1,
data: '5 3 2 4 1',
});

expandSampledObservation lifts the same expansion to the Observation level, returning one Observation per sample:

import { expandSampledObservation } from '@medplum/core';

const expandedObservations = expandSampledObservation({
resourceType: 'Observation',
code: { /* ... */ },
valueSampledData: { /* ... */ },
effectiveDateTime: '2024-01-15T08:00:00Z',
});

summarizeObservations and DataSampler aggregate many observations into a single summary–useful for dashboards built off wearable data:

import { summarizeObservations, DataSampler } from '@medplum/core';

const summary = summarizeObservations(
observations,
{ text: 'Average Heart Rate' },
(data) => data.reduce((a, b) => a + b, 0) / data.length,
);

const sampler = new DataSampler({
code: { text: 'Heart Rate' },
unit: { unit: 'beats/min', code: '{Beats}/min' },
});

Multi-component Observations

Use Observation.component when multiple values share method, performer, device, and time – classic case is blood pressure.

Use Sparingly

component adds complexity. Prefer it only when necessary.

Example: blood pressure panel
{
"resourceType": "Observation",
"id": "example-component-observation",
"code": {
"system": "http://loinc.org",
"code": "85354-9",
"display": "Blood pressure panel with all children optional",
},
"component": [
{
"code": {
"system": "http://loinc.org",
"code": "8480-6",
"display": "Systolic blood pressure",
},
"valueQuantity": {
"value": 100,
"unit": "mmHg",
"system": "http://unitsofmeasure.org/",
"code": "mm[Hg]",
},
},
{
"code": {
"system": "http://loinc.org",
"code": "8462-4",
"display": "Diastolic blood pressure",
},
"valueQuantity": {
"value": 80,
"unit": "mmHg",
"system": "http://unitsofmeasure.org/",
"code": "mm[Hg]",
},
},
],
}

Reference Ranges

Use Observation.referenceRange for patient-specific normals. Definitions often live in ObservationDefinition resources – see Observation reference ranges.

Diagnoses and Problem List

Diagnoses generally fall into encounter diagnoses versus longitudinal problem list items. Condition covers both, plus SDOH and other coded problems.

Applications include:

  • Social determinants of health
  • Chronic conditions
  • Substance use
  • Cognitive or functional impairment

Condition Elements

ElementDescriptionCode SystemExample
codeCondition identityICD-10, SNOMEDExample code
categoryencounter-diagnosis vs problem-list-itemCondition categoryproblem-list-item
clinicalStatusActive, recurrence, etc.Clinical statusactive
verificationStatusProvisional, confirmed, etc.Verificationprovisional
severitySubjective severitySeveritysevere
subjectPatientPatient/example
onset[x]Onset timing
abatement[x]Resolution timing
recordedDateWhen documented
stageStaging when applicable
evidenceSupporting resources such as Observation
noteAdditional narrative

Each Condition is an instance of a problem – recurrence is a new resource; link clinically via narrative or extensions as your workflow requires.

Example Condition
{
resourceType: 'Condition',
subject: {
reference: 'Patient/homer-simpson',
},
code: {
coding: [
{
system: 'http://hl7.org/fhir/sid/icd-10',
code: 'C46.50',
display: "Kaposi's sarcoma of unspecified lung",
},
],
},
clinicalStatus: {
coding: [
{
system: 'http://hl7.org/fhir/ValueSet/condition-clinical',
code: 'active',
display: 'Active',
},
],
},
verificationStatus: {
coding: [
{
system: 'http://hl7.org/fhir/ValueSet/condition-ver-status',
code: 'confirmed',
display: 'Confirmed',
},
],
},
category: [
{
coding: [
{
system: 'http://hl7.org/fhir/ValueSet/condition-category',
code: 'encounter-diagnosis',
display: 'Encounter Diagnosis',
},
],
},
],
severity: {
coding: [
{
system: 'http://snomed.info/sct',
code: '24484000',
display: 'Severe',
},
],
},
onsetString: '2023-11-11',
stage: [
{
summary: {
coding: [
{
system: 'https://example-org.com/cancer-stages',
code: 'stage-3',
display: 'Stage 3',
},
],
},
},
],
evidence: [
{
detail: [
{
reference: 'Observation/lung-tumor',
},
],
},
],
note: [
{
text: 'Patient has a family history of cancer.',
},
],
};
Example ValueSet of Condition codes
{
resourceType: 'ValueSet',
status: 'active',
url: 'http://hl7.org/fhir/sid/icd-10',
name: 'example-conditions',
title: 'Example Conditions',
compose: {
include: [
{
system: 'http://hl7.org/fhir/sid/icd-10',
concept: [
{
code: 'D63.1',
display: 'Anemia in chronic kidney disease',
},
{
code: 'D64.9',
display: 'Anemia, unspecified',
},
{
code: 'E04.2',
display: 'Nontoxic multinodular goiter',
},
{
code: 'E05.90',
display: 'Thyrotoxicosis, unspecified without thyrotoxic crisis or storm',
},
{
code: 'E11.9',
display: 'Type 2 diabetes mellitus without complications',
},
{
code: 'E11.42',
display: 'Type 2 diabetes mellitus with diabetic polyneuropathy',
},
{
code: 'E55.9',
display: 'Vitamin D deficiency, unspecified',
},
{
code: 'E78.2',
display: 'Mixed hyperlipidemia',
},
{
code: 'E88.89',
display: 'Other specified metabolic disorder',
},
{
code: 'F06.8',
display: 'Other specified mental disorders due to known physiological condition',
},
{
code: 'I10',
display: 'Essential (primary) hypertension',
},
{
code: 'K70.30',
display: 'Alcoholic cirrhosis of the liver without ascites',
},
{
code: 'K76.0',
display: 'Fatty (change of) liver, not elsewhere classified',
},
{
code: 'M10.9',
display: 'Gout, unspecified',
},
{
code: 'N13.5',
display: 'Crossing vessel and stricture of ureter without hyrdronephrosis',
},
{
code: 'N18.3',
display: 'Chronic kidney disease, stage 3 (moderate)',
},
{
code: 'R53.83',
display: 'Other fatigue',
},
{
code: 'Z00.00',
display: 'Encounter for general adult medical examination without abnormal findings',
},
{
code: 'Z34.90',
display: 'Encounter for supervision of normal pregnancy, unspecified, unspecified trimester',
},
],
},
],
},
};

Do not use Condition for allergies – use AllergyIntolerance; see Allergies and intolerances. Social factors may still be recorded as Condition when that matches your IG.

Encounter Diagnosis

Set category to encounter-diagnosis and link encounter to the visit. Follow US Core encounter diagnosis when applicable.

Problem List Item

Use category problem-list-item for longitudinal active problems. Best practice: keep separate Condition instances for encounter diagnoses versus promoted problem-list rows so history stays auditable. Usually require an explicit action to promote an encounter diagnosis to the problem list.

Symptoms vs Conditions

Observation is point-in-time; Condition is ongoing. Use Observation for transient symptoms; Condition when tracking over time. Example: single fever spike as Observation; prolonged fever course as Condition. More detail appears under Observations and vital signs.

Allergies and Intolerances

Allergies use AllergyIntolerance.

ElementDescription
clinicalStatusActive vs inactive over time
verificationStatusConfirmed vs unconfirmed
categoryFood, medication, environment, biologic
codeRxNorm for drugs, SNOMED often for foods/environment
patientWho experiences the allergy
encounterEncounter where reviewed
recorderWho documented
asserterWho asserted (patient, clinician, related person, etc.)

Recording Allergy Status

Cover:

  1. Known allergies – document specifics.
  2. No known allergies – document explicit negatives when clinically asserted (for example NKDA concepts).
  3. Unknown status – document when workup is required.

Only record NKDA or unknown when clinically relevant. Documenting NKDA “by default” when allergies were never assessed can mislead future care.

Example: Unconfirmed Egg Protein

{
resourceType: 'AllergyIntolerance',
subject: {
reference: 'Patient/homer-simpson',
},
code: {
coding: [
{
system: 'http://hl7.org/fhir/sid/snomed',
code: '213020009',
display: "Allergy to egg protein",
},
],
},
verificationStatus: {
coding: [
{
system: 'http://hl7.org/fhir/ValueSet/condition-ver-status',
code: 'unconfirmed',
display: 'Unconfirmed',
},
],
},
};

Example: Patient-Confirmed No Drug Allergies

Use SNOMED no known allergy concepts when appropriate; adjust asserter if the source is not the patient.

{
resourceType: 'AllergyIntolerance',
subject: {
reference: 'Patient/homer-simpson',
},
code: {
coding: [
{
system: 'http://hl7.org/fhir/sid/snomed',
code: '409137002',
display: "No known drug allergy",
},
],
},
clinicalStatus: {
coding: [
{
system: 'http://hl7.org/fhir/ValueSet/condition-clinical',
code: 'active',
display: 'Active',
},
],
},
verificationStatus: {
coding: [
{
system: 'http://hl7.org/fhir/ValueSet/condition-ver-status',
code: 'confirmed',
display: 'Confirmed',
},
],
},
asserter : {
reference : 'Patient/homer-simpson',
},
};

When allergy documentation is not clinically relevant (for example isolated orthopedic brace care without meds), omit AllergyIntolerance rather than forcing NKDA.

External Documents in the Chart

External PDFs and images are indexed with DocumentReference pointing at Binary or URLs – see External files and Document references.

See Also