Skip to main content
Glama
handle-order-event.ts7.87 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { createReference, normalizeErrorString } from '@medplum/core'; import type { BotEvent, MedplumClient } from '@medplum/core'; import type { MedicationDispense, MedicationRequest, Patient, Reference } from '@medplum/fhirtypes'; import type { Fill, OrderCreatedData, OrderData, PhotonEvent } from '../photon-types'; import { NEUTRON_HEALTH } from './constants'; import { handlePhotonAuth, photonGraphqlFetch } from './utils'; export async function handler( medplum: MedplumClient, event: BotEvent<PhotonEvent> ): Promise<MedicationDispense[] | undefined> { const body = event.input; if (body.type !== 'photon:order:created') { return undefined; } const PHOTON_CLIENT_ID = event.secrets['PHOTON_CLIENT_ID']?.valueString; const PHOTON_CLIENT_SECRET = event.secrets['PHOTON_CLIENT_SECRET']?.valueString; const PHOTON_AUTH_TOKEN = await handlePhotonAuth(PHOTON_CLIENT_ID, PHOTON_CLIENT_SECRET); const data = body.data; const photonPatientData = data.patient; const patient = await getPatient(photonPatientData, medplum); if (!patient) { throw new Error('No linked patient'); } const dispenses = handleOrderCreatedEvent(data as OrderCreatedData, PHOTON_AUTH_TOKEN, medplum, patient); return dispenses; } async function handleOrderCreatedEvent( data: OrderCreatedData, authToken: string, medplum: MedplumClient, patient: Patient ): Promise<MedicationDispense[]> { const fills = data.fills; const orderId = data.id; const dispenses: MedicationDispense[] = []; for (const fill of fills) { const id = fill.id as string; const processedDispense = await checkForDuplicateFill(medplum, id); if (processedDispense) { continue; } const fillData = await getFill(id, authToken); const prescriptionData = fill.prescription; const prescription: MedicationRequest | undefined = await getAuthorizingPrescription(medplum, prescriptionData?.id); if (!prescription) { throw new Error('Medication could not be dispensed as there is no authorizing prescription'); } const dispense = await createMedicationDispense(fillData, medplum, prescription, patient); await updatePrescription(prescription, medplum, orderId); dispenses.push(dispense); } return dispenses; } async function checkForDuplicateFill(medplum: MedplumClient, fillId: string): Promise<boolean> { const processedDispense = await medplum.searchOne('MedicationDispense', { identifier: NEUTRON_HEALTH + `|${fillId}`, }); return !!processedDispense; } async function updatePrescription( prescription: MedicationRequest, medplum: MedplumClient, orderId: string ): Promise<void> { const identifier = prescription.identifier ?? []; identifier.push({ system: NEUTRON_HEALTH, value: orderId }); const status = prescription.status === 'draft' ? 'active' : prescription.status; const udpatedPrescriptionData: MedicationRequest = { ...prescription, identifier, status, }; try { await medplum.updateResource(udpatedPrescriptionData); } catch (err) { throw new Error(normalizeErrorString(err)); } } /** * This creates new MedicationDispense resources to represent the fills included in a Photon order created event. * * @param fill - The fill data from the Photon order created event * @param medplum - The MedplumClient * @param request - The MedicationRequest that authorizes the MedicationDispense/Fill * @param patient - The patient that the dispense is for * @returns The created MedicationDispense */ async function createMedicationDispense( fill: Fill, medplum: MedplumClient, request: MedicationRequest, patient: Patient ): Promise<MedicationDispense> { // The partial fills from the created event only have ids, so we need to query for the expanded details from Photon // Search for the prescription linked to the fills const prescription = await medplum.searchOne('MedicationRequest', { identifier: NEUTRON_HEALTH + `|${fill.prescription?.id ?? ''}`, }); const patientRef = createReference(patient); const pharmacy = request.dispenseRequest?.performer; const performer: MedicationDispense['performer'] = []; if (pharmacy) { performer.push({ actor: pharmacy }); } const medication = request.medicationCodeableConcept ?? { coding: [ { system: 'http://www.nlm.nih.gov/research/umls/rxnorm', code: fill.treatment?.codes.rxcui, display: fill.treatment?.name, }, ], }; // Link the dispense to the prescription if it exists const authorizingPrescription: Reference<MedicationRequest>[] = []; if (prescription) { authorizingPrescription.push(createReference(prescription)); } // Build the MedicationDispense object const medicationDispenseData: MedicationDispense = { resourceType: 'MedicationDispense', status: getFillStatus(fill.state), identifier: [{ system: NEUTRON_HEALTH, value: fill.id }], authorizingPrescription, medicationCodeableConcept: medication, performer, subject: patientRef, }; if (fill.filledAt) { medicationDispenseData.whenPrepared = fill.filledAt; } // Create and return the MedicationDispense try { const medicationDispense = await medplum.createResource(medicationDispenseData); return medicationDispense; } catch (err) { throw new Error(normalizeErrorString(err)); } } export function getFillStatus(fillState: Fill['state']): MedicationDispense['status'] { switch (fillState) { case 'NEW': return 'in-progress'; case 'SCHEDULED': return 'preparation'; case 'SENT': return 'in-progress'; case 'CANCELED': return 'cancelled'; default: throw new Error('Invalid Fill state'); } } async function getFill(id: string, authToken: string): Promise<Fill> { const query = ` query fill($id: ID!) { fill(id: $id) { id treatment { id name codes { rxcui } } prescription { id externalId } state requestedAt filledAt order { id } } } `; const variables = { id }; const body = JSON.stringify({ query, variables }); const result = await photonGraphqlFetch(body, authToken); return result.data.fill; } /** * Takes the patient data from a Photon event and searches for that patient in your project, returning it if it exists. * * @param patientData - The partial patient data from a Photon order event * @param medplum - MedplumClient to search your project for a patient * @returns Your project's patient from the Photon event if it exists */ export async function getPatient( patientData: OrderData['patient'], medplum: MedplumClient ): Promise<Patient | undefined> { const id = patientData.externalId as string; const photonId = patientData.id; let patient: Patient | undefined; // Search for the patient based on the photon ID patient = await medplum.searchOne('Patient', { identifier: NEUTRON_HEALTH + `|${photonId}`, }); if (patient) { return patient; } if (!id) { return undefined; } // Search for the patient based on the medplum id try { patient = await medplum.readResource('Patient', id); return patient; } catch { return undefined; } } async function getAuthorizingPrescription( medplum: MedplumClient, photonId?: string ): Promise<MedicationRequest | undefined> { if (!photonId) { return undefined; } const authorizingPrescription = await medplum.searchOne('MedicationRequest', { identifier: NEUTRON_HEALTH + `|${photonId}`, }); return authorizingPrescription; }

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