Skip to main content

Organizing Communications Using Threads

Introduction

In a healthcare context, messages are sent all the time and can include many scenarios (patient to physician, physician to physician, and more), so ensuring they are well-organized is vital. This guide covers how to model and organize threads using Medplum.

  • Representing individual messages
  • Building and structuring threads
  • How to "tag" or group threads
  • Searching for and sorting communications and threads

Representing Individual Messages

The FHIR Communication resource is a representation of any message sent in a healthcare setting. This can include emails, SMS messages, phone calls and more.

ElementDescriptionRelevant ValuesetExample
payloadText, attachments, or resources that are being communicated to the recipient.You have an appointment scheduled for 2pm.
senderThe person or team that sent the message.Practitioner/doctor-alice-smith
recipientThe person or team that received the message.Practitioner/doctor-gregory-house
topicA description of the main focus of the message. Similar to the subject line of an email.Custom Internal CodeIn person physical with Homer Simpson on April 10th, 2023
categoryThe type of message being conveyed. Like a tag that can be applied to the message.SNOMED CodesSee below
reasonCodeThe specific reason as to why the message was sent. It is recommended to define two reasons a message was sent: a medical reason or a workflow reason. For a medical reason, it is recommended to use the clinical findings subset of SNOMED codes. For workflow reasons, it is recommended to use a custom internal coding.SNOMED Clinical Findings Codes, Custom Internal Code301180005 - Cardiovascular system normal (finding)
partOfA reference to another resource of which the Communication is a component.See below
inResponseToA reference to another Communication resource which the current one was created to respond to.Communication/previous-communication
mediumThe technology used for this Communication (e.g. email, fax, phone).Participation Mode Codesemail
subjectA reference to the patient or group that this Communication is about.Patient/homer-simpson
encounterA reference to a medical encounter to which this Communication is tightly associated.Encounter/example-appointment
sent/receivedThe time that the message was either sent or received.2023-04-10T10:00:00Z
statusThe status of transmission.Event Status Codesin-progress
The Communication lifecycle

Most messaging based workflows track messages through three stages: sent, received, and read.

While FHIR standard doesn't offer specific guidance on representing this lifecycle, Medplum recommends the following model:

StageRepresentation
sentCommunication.sent is populated
receivedCommunication.received is populated
readCommunication.status is "completed"
category vs. reasonCode

The category and reasonCode elements are similar, but offer different use cases. The category field is used to broadly classify messages, while the reasonCode is used to provide more granular detail about why a message was sent. For example, a category may be a notification while the reasonCode could be an appointment reminder.

Building and Structuring Threads

Beyond producing individual messages, most healthcare communication tools group messages into "threads". When building a thread in FHIR, it is important to model these groupings so that they are easily identifiable and searchable.

When creating threads, a two-level hierarchy should be used. This will include one parent Communication resource, or thread header, that represents that thread itself, and child Communication resources that represent each individual message.

Threads should be grouped using a top-level "thread header" Communication resource that represents the thread itself, rather than any individual message. The child resources should be linked to the thread header using the partOf field. This allows you to create a thread where each message is linked to the thread header as a common reference point.

In these threads, the thread header needs to be distinguished from the children. Since the header resource will not have any content or refer to a header of its own, this can be done by omitting a message in the payload field and a resource reference in the partOf field.

Additionally, to help organize threads, it is useful to use add a topic element to give the thread a subject. Similar to the subject line of an email, the topic should be given a high level of specificity to help distinguish the thread. The topic should be assigned to both the thread header and child Communication resources in any thread.

Example of a thread grouped using a Communication resource
{
resourceType: 'Communication',
id: 'example-thread-header',
// There is no `partOf` of `payload` field on this communication
// ...
topic: {
text: 'Homer Simpson April 10th lab tests',
},
},

// The initial message
{
resourceType: 'Communication',
id: 'example-message-1',
payload: [
{
id: 'example-message-1-payload',
contentString: 'The specimen for you patient, Homer Simpson, has been received.',
},
],
topic: {
text: 'Homer Simpson April 10th lab tests',
},
// ...
partOf: [
{
resource: {
resourceType: 'Communication',
id: 'example-thread-header',
status: 'completed',
},
},
],
},

// A response directly to `example-message-1` but still referencing the parent communication
{
resourceType: 'Communication',
id: 'example-message-2',
payload: [
{
id: 'example-message-2-payload',
contentString: 'Will the results be ready by the end of the week?',
},
],
topic: {
text: 'Homer Simpson April 10th lab tests',
},
// ...
partOf: [
{
resource: {
resourceType: 'Communication',
id: 'example-thread-header',
status: 'completed',
},
},
],
inResponseTo: [
{
resource: {
resourceType: 'Communication',
id: 'example-message-1',
status: 'completed',
},
},
],
},

// A second response
{
resourceType: 'Communication',
id: 'example-message-3',
payload: [
{
id: 'example-message-2-payload',
contentString: 'Yes, we will have them to you by Thursday.',
},
],
topic: {
text: 'Homer Simpson April 10th lab tests',
},
// ...
partOf: [
{
resource: {
resourceType: 'Communication',
id: 'example-thread-header',
status: 'completed',
},
},
],
inResponseTo: [
{
resource: {
resourceType: 'Communication',
id: 'example-message-2',
status: 'completed',
},
},
],
},

How to Tag or Group Threads

It can be useful to "tag", or group, threads so that a user can easily reference or interpret a certain type of message at a high level. For example, if there is a thread about a task that needs to be performed by a nurse, it can be tagged as such.

Tagging can be effectively done using the Communication.category element, which represents the type of message being conveyed. It allows messages to be classified into different types or groups based on specifications like purpose, nature, or intended audience.

When assigning a category to a thread, it should be included on both the thread header and child Communication resources. It is also important to note that the category field is an array, so each Communication can have multiple tags.

Here are some common types of tags that can be used for grouping:

Type of TagCodesystem
Level of credentialSNOMED Care Team Member Function valueset
Clinical specialtySNOMED Care Team Member Function valueset
Product offeringSNOMED, LOINC, Custom Internal Coding
Example of Multiple Categories
{
resourceType: 'Communication',
id: 'example-communication',
status: 'completed',
category: [
{
text: 'Doctor',
coding: [
{
code: '158965000',
system: SNOMED,
},
],
},
{
text: 'Endocrinology',
coding: [
{
code: '394583002',
system: SNOMED,
},
],
},
{
text: 'Diabetes self-management plan',
coding: [
{
code: '735985000',
system: SNOMED,
},
],
},
],
};
Designing category schemes

There are different ways that you can categorize threads, each one with its own pros and cons. For example, you can have threads with multiple category fields, one for specialty and one for level of credentials, etc., where you would search for multiple categories at once. The pros to this are that the data model is more self-explanatory, since each category is explicitly represented, and better maintainability, since it is easier to update and add individual categories. However, this can also lead to more complex queries.

Alternatively, you can have threads that have just one category that combines specialty, level of credentials, etc., and search for that specific category. This allows for simpler searching, needing only one category search parameter, and a simpler, more compact data model. The downside is that it may require more parsing and logic on the front-end to handle the combined categories and that as more combinations arise, maintaining the coding system may become difficult.

Searching for and Sorting Communication Resources

Searching for All Threads in a System

To search for all threads in the system, we need to find the thread header Communication resource. One of the factors that differentiates a thread header resource from a "message-level", or child, resource is that thread header resources do not have a value in the partOf field.

// Search for a Communication-grouped thread
await medplum.searchResources('Communication', {
'part-of:missing': true,
});

In this example, we use the :missing search modifier to search for any Communication resources that do not reference another resource in their partOf field. This gives us all of the thread header Communication resources in the system.

Searching For All Messages in a Thread

Once you have found the thread you want, you may want to retrieve the messages from only that specific thread, in the correct order.

await medplum.searchResources('Communication', {
'part-of': 'Communication/example-communication',
_sort: 'sent',
});

In the above example, we search for Communication resources that reference our thread header in the partOf field. This will retrieve all the messages, but there is no guarantee they will be in the correct order, so we use the sort search parameter to sort by the sent field. For more details on using the search functionality, see the Search docs.

Putting It All Together

To put this all together, we can also search for all threads and return their messages with them.

await medplum.searchResources('Communication', {
'part-of:missing': true,
_revinclude: 'Communication:part-of',
});

Here we are using the same initial search to return all of the thread headers in the system. However, we use the _revinclude parameter, allowing us to also search for all Communication resources that reference one of our search results in the partOf field. This allows us to return all of the child messages as well.

You can also filter down your searches further by including additional parameters.

await medplum.searchResources('Communication', {
'part-of:missing': true,
_revinclude: 'Communication:part-of',
subject: 'Patient/example-patient',
});

Here we build upon our search by adding the subject parameter to search for all threads that are related to a given patient. For other items to filter your search on, see the Communication Search Parameters.