Skip to main content

Representing Asynchronous Encounters

Intro

In healthcare, an "encounter" refers to any diagnostic or treatment interaction between a patient and provider. In traditional healthcare settings, this typically refers to an in-person visit, and is represented by the Encounter resource.

But in digital health, this can take on a variety of asynchronous forms, including SMS chains, in-app chat threads, or even an email exchange.

In this guide, we'll show you how to represent these kinds of asynchronous encounters in FHIR.

Defining Sessions

To get started, you'll first need to determine what determines a "session" in your care setting.

A session could be a single SMS chain, or a single email thread. Many digital health apps ask the patient to explicitly initiate a messaging session with a provider.

Alternatively, if your care setting has more of a rolling interaction model (e.g. a continuous text thread), you may choose to group all communications from the same day into a session.

Other common choices include treating one messaging thread as one session, or starting a session only when the patient explicitly opens a new care interaction.

Representing Sessions in FHIR

Each session should be represented by an Encounter resource. All of the messages that are part of this session should be represented as a thread of Communication resources. The thread should be linked to the session using the Communication.encounter element of only the thread header. For more details on modeling threads, see Building and Structuring Threads.

Encounter on the thread header only

Set Communication.encounter on the thread header only. Child messages point at the header with Communication.partOf; they do not need their own encounter element for this pattern.

You should record the participating physicians using the Encounter.participant element. You can also record any family members who are part of the session here (see our Family Relationships guide).

Asynchronous Encounter Ontologies

The Encounter.class element is required in FHIR and should be taken from the HL7 Act Encounter Code Valueset. Asynchronous care contexts will almost always use the code VR ("virtual").

Also check our USCDI guide for information on how to make your Encounter compatible with the US Core standards.

If your session only involves providing care for a single patient, then you can set the Encounter.subject element to refer to the patient and you're all set! If, however, multiple patients are involved in the session, continue reading.

If you need encounters for billing, quality programs, or compliance, create a session Encounter, then link your existing thread header with a JSON Patch on Communication.encounter. This assumes you already have a thread header Communication. For how thread headers and messages are structured, see Building and Structuring Threads.

Create the Session Encounter

The snippet uses virtual class VR and example Patient and Practitioner references. Replace them with ids that exist in your project.

// Single-patient session: set Encounter.subject to that patient. Replace references with real ids from your project.
const asyncEncountersSessionEncounter = await medplum.createResource({
resourceType: 'Encounter',
status: 'in-progress',
class: {
system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode',
code: 'VR',
display: 'virtual',
},
subject: { reference: 'Patient/homer-simpson' },
participant: [
{
individual: { reference: 'Practitioner/doctor-alice-smith' },
},
],
});
console.log(asyncEncountersSessionEncounter);

Patch only the thread header. The snippet uses the session Encounter id from the previous step and an existing thread header Communication (no payload, no partOf).

// Link only the thread header: child messages inherit encounter context via Communication.partOf → header.
// Uses the session Encounter created above and an existing thread header Communication.
const asyncEncountersLinkedHeader = await medplum.patchResource('Communication', threadHeader.id, [
{ op: 'add', path: '/encounter', value: { reference: `Encounter/${asyncEncountersSessionEncounter.id}` } },
]);
console.log(asyncEncountersLinkedHeader);

Handling Multiple-Patient Sessions

In some care settings, a session may discuss the health of multiple patients. For example, a mother may ask about the health of both of her children in the same email exchange.

In these situations, we'll have to represent distinct "medical encounters" for each patient.

We can use the hierarchical nature of the Encounter resource to split out medical encounters for each session. The Encounter.partOf element creates a parent-child relationship between Encounters, which is perfect for encounters that overlap in time.

To properly represent your asynchronous encounter, you should:

  1. Create an Encounter for the session
  2. Create a new Encounter for each patient to represent a medical encounter
  3. Set the Encounter.subject of each medical encounter to the corresponding patient
  4. Populate each medical encounter with the clinical details (diagnoses, reasons for visit) of the corresponding patient.
  5. Set the Encounter.partOf element for each medical encounter to refer to the session's Encounter

The thread of Communication resources should still be linked to the "session" encounter, but the clinical details for each patient will live on the medical encounters. You can add a value to the Encounter.type element to tag encounters as either "sessions" or "medical encounters."

While creating an Encounter hierarchy like this is a bit more work up front, it promotes good data hygiene. A well documented encounter, with the correct practitioner, diagnosis codes, Encounter.serviceType, and Encounter.reasonCode per patient is critical to billing, and it is important for patient analytics use cases, such as computing quality of care metrics.

Walkthrough: Child Encounter for One Patient

Create one child Encounter per patient, referencing the session Encounter from the walkthrough above in Encounter.partOf. Use Encounter.type (here a custom coding) to distinguish medical encounters from the parent session. Replace the patient reference with your data.

// Multi-patient session: one child Encounter per patient; thread header stays linked to the session Encounter.
const asyncEncountersChildEncounter = await medplum.createResource({
resourceType: 'Encounter',
status: 'in-progress',
class: {
system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode',
code: 'VR',
display: 'virtual',
},
subject: { reference: 'Patient/bart-simpson' },
partOf: { reference: `Encounter/${asyncEncountersSessionEncounter.id}` },
reasonCode: [
{
coding: [{ system: SNOMED, code: '38341003', display: 'Hypertensive disorder, systemic arterial' }],
},
],
type: [
{
coding: [
{
system: 'https://medplum.com/fhir/CodeSystem/encounter-type',
code: 'medical-encounter',
display: 'Medical Encounter',
},
],
},
],
});
console.log(asyncEncountersChildEncounter);
Family participants

Record family members involved in the session on Encounter.participant. See Family Relationships for modeling related persons.

Use these searches to confirm virtual encounters exist and that the thread header is linked to the session Encounter. The TypeScript tab chains the session Encounter id from the walkthrough above. For CLI and cURL, replace {sessionEncounterId} with your session Encounter id.

// List virtual (VR) encounters; find thread headers linked to a specific session Encounter.
const asyncEncountersVirtualList = await medplum.searchResources('Encounter', {
class: 'http://terminology.hl7.org/CodeSystem/v3-ActCode|VR',
});
console.log(asyncEncountersVirtualList);

const asyncEncountersThreadsForSession = await medplum.searchResources('Communication', {
encounter: `Encounter/${asyncEncountersSessionEncounter.id}`,
'part-of:missing': true,
});
console.log(asyncEncountersThreadsForSession);

See Also