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.
Element | Description | Relevant Valueset | Example |
---|---|---|---|
payload | Text, attachments, or resources that are being communicated to the recipient. | You have an appointment scheduled for 2pm. | |
sender | The person or team that sent the message. | Practitioner/doctor-alice-smith | |
recipient | The person or team that received the message. | Practitioner/doctor-gregory-house | |
topic | A description of the main focus of the message. Similar to the subject line of an email. | Custom Internal Code | In person physical with Homer Simpson on April 10th, 2023 |
category | The type of message being conveyed. Like a tag that can be applied to the message. | SNOMED Codes | See below |
reasonCode | The 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 Code | 301180005 - Cardiovascular system normal (finding) |
partOf | A reference to another resource which the Communication is a component. | See below | |
inResponseTo | A reference to another Communication resource which the current one was created to respond to. | Communication/previous-communication | |
medium | The technology used for this Communication (e.g. email, fax, phone). | Participation Mode Codes | |
subject | A reference to the patient or group that this Communication is about. | Patient/homer-simpson | |
encounter | A reference to a medical encounter to which this Communication is tightly associated. | Encounter/example-appointment | |
sent /received | The time that the message was either sent or received. | 2023-04-10T10:00:00Z | |
status | The status of transmission | Event Status Codes | in-progress |
Communication
lifecycleMost 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:
Stage | Representation |
---|---|
sent | Communication.sent is populated |
received | Communication.received is populated |
read | Communication.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 consider what type of resource should be used to group the thread together. Depending on the circumstances it makes sense to use different resources.
Threads Involving Patients
If the messages are between a patient and a provider, you should use an Encounter
resource to group the thread. An Encounter
can be any interaction between a patient and provider, including various types of messages, so it is important that these are classified correctly. Using an Encounter
resource to represent patient visits also makes it easier to submit claims to insurance to be reimbursed. For more details, please see the Representing Asynchronous Encounters docs.
Threads Between Providers
However, when a patient is not involved, threads should be grouped using the Communication
resource. A thread will have a two-level hierarchy – one parent Communication
resource that represents the thread itself, and child Communication
resources that represent each individual message. The child resources should be linked to the parent using the partOf
field, which represents a parent resource of which the current Communication
is a component. This allows you to refer to the parent resource to create a thread where each message is linked to the parent as a common reference point.
In these threads, the parent resource needs to be distinguished from the children. Since the parent resource will not have any content or refer to a parent 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 parent and children Communication
resources in any thread.
Because of how specific the topic
field should be, it is best to use a custom coding rather than LOINC
or SNOMED
codes to classify the element.
Example of a thread grouped using a Communication resource
{
resourceType: 'Communication',
id: 'example-parent-communication',
// 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-parent-communication',
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-parent-communication',
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-parent-communication',
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 parent 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 Tag | Codesystem |
---|---|
Level of credential | SNOMED Care Team Member Function valueset |
Clinical specialty | SNOMED Care Team Member Function valueset |
Product offering | SNOMED, 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,
},
],
},
],
};
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
When searching for threads, we need to differentiate between threads that are grouped by the Communication
resource and those that are grouped with the Encounter
resource. We'll begin with threads grouped by Communication
.
To search for all threads in the system, we need to find each parent Communication
resource. One of the factors that differentiates a "thread-level", or parent, resource from a "message-level", or child, resource is that thread-level resources do not have a value in the partOf
field.
- Typescript
- CLI
- cURL
// Search for a Communication-grouped thread
await medplum.searchResources('Communication', {
'part-of:missing': true,
});
medplum get 'Communication?part-of:missing=true'
curl 'https://api.medplum.com/fhir/R4/Communication?part-of:missing=true' \
-H 'authorization: Bearer $ACCESS_TOKEN' \
-H 'content-type: application/fhir+json' \
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 the parent Communication
of all threads 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 order. In the above example, though we retrieved the messages with each thread, there is no guarantee that they will be in the correct order. You can also filter down results so that you only get the messages specific to the thread you want.
Again, we will separate how to search for Communication
and Encounter
grouped threads, beginning with Communication
.
- Typescript
- CLI
- cURL
await medplum.searchResources('Communication', {
'part-of': 'Communication/example-communication',
_sort: 'sent',
});
medplum get 'Communication?part-of=Communication/example-communication&_sort=sent'
curl 'https://api.medplum.com/fhir/R4/Communication?part-of=Communication/example-communication&_sort=sent' \
-H 'authorization: Bearer $ACCESS_TOKEN' \
-H 'content-type: application/fhir+json' \
In the above example, we search for Communication
resources that reference our parent in the partOf
field and sort by the sent
field. For more details on using the search functionality, see the Search docs.
To search for specific threads that are grouped by Encounter
:
- Typescript
- CLI
- cURL
await medplum.searchResources('Communication', {
encounter: 'Encounter/example-encounter',
_include: 'Communication:encounter',
_sort: 'sent',
});
medplum get 'Communication?encounter=Encounter/example-encounter&_include=Communication:encounter&_sort=sent'
curl 'https://api.medplum.com/fhir/R4/Communication?encounter=Encounter/example-encounter&_include=Communication:encounter&_sort=sent' \
-H 'authorization: Bearer $ACCESS_TOKEN' \
-H 'content-type: application/fhir+json' \
In this example, we search for any Communication
resource that references our Encounter
in the encounter
field. We also _include
that Encounter
, though you can leave this out if you only want to return the messages themselves. We then use _sort
to get them in the order they were sent.
Putting It All Together
To put this all together, we can also search for all threads and return their messages with them.
- Typescript
- CLI
- cURL
await medplum.searchResources('Communication', {
'part-of:missing': true,
_revinclude: 'Communication:part-of',
});
medplum get 'Communication?part-of:missing=true&_revinclude:Communication:part-of'
curl 'https://api.medplum.com/fhir/R4/Communication?part-of:missing=true&_revinclude=Communication:part-of' \
-H 'authorization: Bearer $ACCESS_TOKEN' \
-H 'content-type: application/fhir+json' \
Here we are using the same initial search to return all of the parent threads in the system. However, we include 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.
Searching for threads grouped by Encounter
is a little different. Since there is no link from the parent Encounter
to the child messages, we still search for Communication
resources at the top level.
- Typescript
- CLI
- cURL
await medplum.searchResources('Communication', {
'encounter:missing': false,
_include: 'Communication:encounter',
});
medplum get 'Communication?encounter:missing=false&_include=Communication:encounter'
curl 'https://api.medplum.com/fhir/R4/Communication?encounter:missing=false&_include=Communication:encounter' \
-H 'authorization: Bearer $ACCESS_TOKEN' \
-H 'content-type: application/fhir+json' \
In this example, we set the encounter:missing
parameter to false, to include only Communication
resources that reference an encounter. We then use _include
to include those Encounter
resources in our search results. Note that this search will include all of the messages as well as the parent resources.
You can also filter down your searches further by including additional parameters.
- Typescript
- CLI
- cURL
await medplum.searchResources('Communication', {
'part-of:missing': true,
_revinclude: 'Communication:part-of',
subject: 'Patient/example-patient',
});
medplum get 'Communication?part-of:missing=true&_revinclude=Communication:part-of&subject=Patient/example-patient'
curl 'https://api.medplum.com/fhir/R4/Communication?part-of:missing=true&_revinclude=Communication:part-of&subject=Patient/example-patient' \
-H 'authorization: Bearer $ACCESS_TOKEN' \
-H 'content-type: application/fhir+json' \
- Typescript
- CLI
- cURL
await medplum.searchResources('Communication', {
'encounter:missing': false,
_include: 'Communication:encounter',
subject: 'Patient/example-patient',
});
medplum get 'Communication?encounter:missing=false&_include=Communication:encounter&subject=Patient/example-patient'
curl 'https://api.medplum.com/fhir/R4/Communication?encounter:missing=false&_include=Communication:encounter&subject=Patient/example-patient' \
-H 'authorization: Bearer $ACCESS_TOKEN' \
-H 'content-type: application/fhir+json' \
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.