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
| Resource | Description |
|---|---|
Observation | Point-in-time clinical measurements and findings. |
Condition | Diagnoses and longitudinal problems. |
RiskAssessment | Modeled risk scores and similar assessments. |
AllergyIntolerance | Adverse reactions to drugs or substances. |
Immunization | Vaccination history. |
Medication | Drug definitions; ordering uses MedicationRequest and summaries may use MedicationStatement. |
MedicationAdministration | Documents when the patient received or took a medication (administration event). |
Key Code Systems
| Code System | Description |
|---|---|
| LOINC | Used in Observation and RiskAssessment for clinical coding, compliance, billing, and reporting. |
| ICD-10 | Used in Condition for interoperability and billing; also common on encounter-related billing metadata. |
| RXNORM | Drug coding for allergies and medication orders. |
| SNOMED | Substances and clinical concepts. |
| CVX | Immunization 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.
| Resource | Role |
|---|---|
Encounter | Visit container in-person or virtual. |
ClinicalImpression | Assessment narrative and findings for the encounter. |
Condition | Encounter diagnoses when categorized accordingly. |
Observation | Vitals and other measurements tied to the encounter. |
RiskAssessment | Structured 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.
| Category | Name |
|---|---|
| Gender | Administrative Gender Value Set |
| Race | US Core Race Extension |
| Ethnicity | US Core Ethnicity Extension |
| Birth Sex | Birth 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.
Related Reading
- ONC (a)(5)
- Sample data
- US Core Patient Profile
- US Core Patient Storybook
- ONC Certification for Medplum
- (a)(5) User Testing Video
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.
Related Reading
- ONC (a)(14)
- Sample device bundle
- US Core Implantable Device
- GUDID
- Implantable Device Storybook
- User Safety Testing Script
- (a)(14) video
- Device bot reference
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
| Element | Description | Code System | Example |
|---|---|---|---|
status | Preliminary, final, amended, cancelled, etc. | Observation status | registered |
code | What was observed. | LOINC | 8867-4 Heart Rate |
subject | Who or what the observation is about. | Patient/homer-simpson | |
encounter | Visit when observation was taken. | Encounter/example | |
basedOn | Plan or order that prompted the observation. | CarePlan/example | |
performer | Responsible party – patient for home readings, clinician or device in clinic. | Practitioner/example | |
value[x] | Result – see Observation datatypes. | ||
dataAbsentReason | Why a value is missing. | Data absent reason | asked-but-unknown |
interpretation | High / low / normal categories. | Interpretation | N |
device | Device used to produce the measurement. | Device/apple-watch | |
specimen | Specimen when lab-derived. | Specimen/example | |
component | Sub-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"
},
},
}
valueQuantity uses FHIR Quantity: numeric value plus coded units when possible via UCUM.
Observation Datatypes
| value[x] | Description | Datatype | Application | Example |
|---|---|---|---|---|
valueQuantity | Numeric value with unit | Quantity | Height | 177 cm |
valueCodeableConcept | Coded answer | CodeableConcept | Lab interpretation | SNOMED coded |
valueString | Free text | string | Pain description | mild pain |
valueBoolean | Yes/no | boolean | Screening flags | true |
valueInteger | Whole number | number | Counts | 28 |
valueRange | Interval | Range | Temperature band | 98.0 – 98.7 |
valueRatio | Ratio | Ratio | Ratios | example ratio |
valueSampledData | Time series | SampledData | Wearables summaries | see below |
valueTime | Time only | string | Time-of-day phenomena | 15:30:00 |
valueDateTime | DateTime | string | Instant readings | 2023-07-24Z |
valuePeriod | Interval over time | Period | Episodes | start–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
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.
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
| Element | Description | Code System | Example |
|---|---|---|---|
code | Condition identity | ICD-10, SNOMED | Example code |
category | encounter-diagnosis vs problem-list-item | Condition category | problem-list-item |
clinicalStatus | Active, recurrence, etc. | Clinical status | active |
verificationStatus | Provisional, confirmed, etc. | Verification | provisional |
severity | Subjective severity | Severity | severe |
subject | Patient | Patient/example | |
onset[x] | Onset timing | ||
abatement[x] | Resolution timing | ||
recordedDate | When documented | ||
stage | Staging when applicable | ||
evidence | Supporting resources such as Observation | ||
note | Additional 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.
| Element | Description |
|---|---|
clinicalStatus | Active vs inactive over time |
verificationStatus | Confirmed vs unconfirmed |
category | Food, medication, environment, biologic |
code | RxNorm for drugs, SNOMED often for foods/environment |
patient | Who experiences the allergy |
encounter | Encounter where reviewed |
recorder | Who documented |
asserter | Who asserted (patient, clinician, related person, etc.) |
Recording Allergy Status
Cover:
- Known allergies – document specifics.
- No known allergies – document explicit negatives when clinically asserted (for example NKDA concepts).
- 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
- Visit Templates and the SOAP Approach
- Intake
- Patient FHIR resource API
- Observation FHIR resource API
- Condition FHIR resource API
- AllergyIntolerance FHIR resource API
- Parsing Questionnaire Responses
- Ordering Labs And Imaging
- Representing Prescriptions