Skip to main content
Glama
create-respond-to-message-task.ts6.31 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { getReferenceString, parseReference } from '@medplum/core'; import type { MedplumClient } from '@medplum/core'; import type { Bundle, BundleEntry, Communication, Practitioner, Reference, ResourceType, Task, } from '@medplum/fhirtypes'; /** * This bot creates a task for any messages that have not been responded to in 30 minutes. It is set to run every 15 minutes, and * only create one task per thread. If an employee has already responded to the thread, it will automatically be assigned to that * employee, otherwise it will be assigned to the Care Coordinator queue. * * @param medplum - The medplum client * @returns Promise<any> */ export async function handler(medplum: MedplumClient): Promise<boolean> { const currentDate = new Date(); const thirtyMinutesAgo = new Date(currentDate.getTime() - 30 * 60 * 1000).toISOString(); // Get all messages that are part of an active thread and older than 30 minutes. For more information on how threads work see https://www.medplum.com/docs/communications/organizing-communications const messages: Communication[] = await medplum.searchResources('Communication', { sent: `lt${thirtyMinutesAgo}`, 'part-of:Communication.status': 'in-progress', }); // Filter for messages sent by patients const patientMessages = messages.filter((message) => getMessageSenderType(message) === 'Patient'); // If no messages from patients, return if (patientMessages.length === 0) { console.log('No messages in the last 30 minutes that require a response.'); return false; } // Create and execute a batch search for fetch all thread messages const threadMessages = await fetchMessageThreads(patientMessages, medplum); if (!threadMessages.entry) { return false; } // Go through each thread and create a task if necessary for (const entry of threadMessages.entry) { const threadBundle = entry.resource as Bundle; const thread = threadBundle.entry; const mostRecentMessage = thread?.[0].resource as Communication; const threadHeader = mostRecentMessage.partOf?.[0].reference; // If the most recent message is not by a patient, the thread has been responded to, and no task should be created if (getMessageSenderType(mostRecentMessage) !== 'Patient' || !threadHeader) { continue; } // Check if there is already an existing task to respond to this message if (await checkNoExistingTask(medplum, threadHeader)) { // Get the most recent employee to respond to this thread, if anyone has const sender = getMostRecentResponder(messages) as Reference<Practitioner> | undefined; // Define a task to respond to the message const task: Task = { resourceType: 'Task', focus: { reference: threadHeader, }, code: { text: 'Respond to Message', }, status: 'ready', intent: 'order', }; // If somebody has already responded to this thread, assign the task to them, otherwise assign to care coordinator queue if (sender) { task.owner = sender; console.log('Assigned to most recent responder'); } else { task.performerType = [ { coding: [ { system: 'http://snomed.info/sct', code: '768820003', display: 'Care Coordinator', }, ], }, ]; console.log('Assigned to care coordinator queue'); } await medplum.createResource(task).then((result) => console.log(result)); console.log('Task created'); } else { console.log('Task already exists for this thread.'); } } return true; } // Gets the most recent practitioner to have responded to a thread function getMostRecentResponder(thread: Communication[]): Reference<Practitioner> | undefined { for (const message of thread) { const senderType = getMessageSenderType(message); if (senderType === 'Practitioner') { return message.sender as Reference<Practitioner>; } } return undefined; } // Checks that there isn't already a task assigned to the thread async function checkNoExistingTask(medplum: MedplumClient, threadHeader: string): Promise<boolean> { const existingTasks = await medplum.searchResources('Task', { focus: threadHeader, 'status:not': 'complete', }); if (existingTasks.length === 0) { return true; } else { return false; } } // Builds a search bundle to get all the messages from multiple threads in one batch request async function fetchMessageThreads(messages: Communication[], medplum: MedplumClient): Promise<Bundle> { const requestBundle: Bundle = { resourceType: 'Bundle', type: 'batch', }; const threads = new Set(); for (const message of messages) { if (!message.partOf) { continue; } // Get the parent communication representing the thread const partOf = message.partOf; const threadHeader = partOf.filter((reference) => parseReference(reference)?.[0] === 'Communication')[0]; const threadHeaderReference = getReferenceString(threadHeader); if (!threads.has(threadHeaderReference)) { threads.add(threadHeaderReference); // Query for all of the messages in the thread const searchQuery = `Communication?part-of=${threadHeaderReference}&_sort=-sent`; // Add the search to a bundle to execute a batch request to get all threads with messages that haven't been responded to const entry: BundleEntry = { request: { method: 'GET', url: searchQuery, }, }; if (requestBundle.entry) { requestBundle.entry.push(entry); } else { requestBundle.entry = [entry]; } } } const messageThreads = await medplum.executeBatch(requestBundle); return messageThreads; } // Returns the ResourceType that sent a message function getMessageSenderType(message: Communication): ResourceType | undefined { const sender = message.sender; if (!sender) { return undefined; } const senderReference = parseReference(sender); return senderReference?.[0]; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server