Skip to main content
Glama
find-matching-patients.ts4.32 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { createReference, getReferenceString, resolveId } from '@medplum/core'; import type { BotEvent, MedplumClient } from '@medplum/core'; import type { Patient, RiskAssessment, Task } from '@medplum/fhirtypes'; /** * This Bot listens for changes to a `Patient` resource and searches for potential patient duplicates. * The matching algorithm used is to search for exact matches: * - first name * - last name * - date of birth * - postal code * * See: https://www.medplum.com/docs/fhir-datastore/patient-deduplication#matching-rules * for more potential matching rules * * @param medplum - The Medplum client instance. * @param event - The BotEvent containing the Patient resource. */ export async function handler(medplum: MedplumClient, event: BotEvent<Patient>): Promise<void> { //This bot should only be triggered by a Patient resource Subscription only const srcPatient = event.input; // Read the patient's "Do Not Match" list. Assume there is only one applicable list const doNotMatchList = await medplum.searchOne('List', { code: 'http://example.org/listType|doNotMatch', subject: getReferenceString(srcPatient), }); console.info('Searching for matches for: ' + getReferenceString(srcPatient)); // Search for potential active duplicate patients by matching first name, last name, birthdate, and postal code. const candidateMatches = await medplum.searchResources('Patient', { 'family:exact': srcPatient.name?.[0]?.family, 'given:exact': srcPatient.name?.[0]?.given?.join(' '), birthdate: srcPatient.birthDate, 'address-postalcode': srcPatient.address?.[0]?.postalCode, // only search for patients that are 'active' (have not already been) active: true, // Exclude the source patient, '_id:not': srcPatient.id, }); console.info(`Found ${candidateMatches.length} matches`); /* For each potentialMatch: - Check if it is on the "Do Not Match" list - If so, skip the match - Otherwise create a RiskAssessment resource that represents the candidate match - Create a Task for someone to review the match */ for (const candidate of candidateMatches) { // Check if "Do Not Match" list contains the candidate match const blockedIds = doNotMatchList?.entry?.map((entry) => resolveId(entry.item)) ?? []; const shouldMatch = !blockedIds.some((blockedId) => blockedId === candidate.id); // If there are no lists marked with 'doNotMatch' for the potential duplicate patient, create a RiskAssessment and Task. if (shouldMatch) { // Create a RiskAssessment to represent the candidate match const riskAssessment = await medplum.createResource<RiskAssessment>({ resourceType: 'RiskAssessment', subject: createReference(srcPatient), basis: [createReference(candidate)], status: 'final', code: { coding: [ { system: 'http://example.org/riskAssessmentType', code: 'patientDuplicate', }, ], }, method: { coding: [ { system: 'http://example.org/deduplicationMethod', code: 'name-dob-zip', }, ], }, prediction: [ { probabilityDecimal: 90, qualitativeRisk: { text: 'Almost Certain', }, }, ], }); // Create a Task for a human to review the match await medplum.createResource<Task>({ resourceType: 'Task', code: { text: 'Review Potential Duplicate', coding: [ { system: 'http://example.org/taskType', code: 'patientDuplicate', }, ], }, performerType: [ // US SOC { text: 'Customer Service Representative', coding: [ { code: '43-4050', system: 'https://www.bls.gov/soc', }, ], }, ], focus: createReference(riskAssessment), intent: 'proposal', status: 'requested', }); } } }

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