Skip to main content
Glama
sync-patient.ts8.52 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { getCodeBySystem, getReferenceString } from '@medplum/core'; import type { BotEvent, MedplumClient, PatchOperation } from '@medplum/core'; import type { AllergyIntolerance, Identifier, MedicationRequest, Patient } from '@medplum/fhirtypes'; import type { CreatePatientVariables, PhotonAllergenInput, PhotonMedHistoryInput, PhotonPatient, } from '../photon-types'; import { NEUTRON_HEALTH, NEUTRON_HEALTH_PATIENTS } from './constants'; import { formatAWSDate, formatPhotonAddress, getSexType, getTelecom, handlePhotonAuth, photonGraphqlFetch, } from './utils'; export async function handler(medplum: MedplumClient, event: BotEvent<Patient>): Promise<PhotonPatient> { const patient = event.input; const CLIENT_ID = event.secrets['PHOTON_CLIENT_ID']?.valueString; const CLIENT_SECRET = event.secrets['PHOTON_CLIENT_SECRET']?.valueString; const PHOTON_AUTH_TOKEN = await handlePhotonAuth(CLIENT_ID, CLIENT_SECRET); const query = ` mutation createPatient( $externalId: ID, $name: NameInput!, $dateOfBirth: AWSDate!, $sex: SexType!, $gender: String, $email: AWSEmail, $phone: AWSPhone!, $allergies: [AllergenInput], $medicationHistory: [MedHistoryInput], $address: AddressInput ) { createPatient( externalId: $externalId, name: $name, dateOfBirth: $dateOfBirth, sex: $sex, gender: $gender, email: $email, phone: $phone, allergies: $allergies, medicationHistory: $medicationHistory, address: $address ) { id allergies { allergen { id rxcui } } medicationHistory { prescription { id } medication { codes { rxcui } } } } } `; if (!patient.birthDate) { throw new Error('Patient birth date is required to sync to Photon Health'); } if (!patient.name) { throw new Error('Patient name is required to sync to Photon Health'); } const variables: CreatePatientVariables = { externalId: patient.id as string, name: { first: patient.name?.[0].given?.[0] ?? '', last: patient.name?.[0].family ?? '', }, dateOfBirth: formatAWSDate(patient.birthDate), sex: getSexType(patient.gender), gender: patient.gender ?? 'unknown', phone: getTelecom('phone', patient), }; const allergies = await medplum.searchResources('AllergyIntolerance', { patient: getReferenceString(patient), }); const medicationHistory = await medplum.searchResources('MedicationRequest', { patient: getReferenceString(patient), }); const email = getTelecom('email', patient); const address = formatPhotonAddress(patient.address?.[0]); const allergyInputs = await createAllergyInputs(PHOTON_AUTH_TOKEN, allergies); const medHistoryInput = await createMedHistoryInputs(PHOTON_AUTH_TOKEN, medicationHistory); if (email) { variables.email = email; } if (address) { variables.address = address; } if (allergyInputs) { variables.allergies = allergyInputs; } if (medHistoryInput) { variables.medicationHistory = medHistoryInput; } const body = JSON.stringify({ query, variables }); const result = await photonGraphqlFetch(body, PHOTON_AUTH_TOKEN); await updatePatient(medplum, patient, result); return result.data.createPatient; } async function updatePatient(medplum: MedplumClient, patient: Patient, result: any): Promise<void> { const patientData = result.data.createPatient; const photonId = patientData.id; const photonIdentifier: Identifier = { system: NEUTRON_HEALTH_PATIENTS, value: photonId, }; const identifiers: Identifier[] = patient.identifier ?? []; identifiers.push(photonIdentifier); const patientId = patient.id as string; const op = patient.identifier ? 'replace' : 'add'; const ops: PatchOperation[] = [ { op: 'test', path: '/meta/versionId', value: patient.meta?.versionId }, { op, path: '/identifier', value: identifiers }, ]; try { await medplum.patchResource('Patient', patientId, ops); console.log('Success'); } catch (err) { console.error(err); } } export async function createMedHistoryInputs( authToken: string, medicationHistory?: MedicationRequest[] ): Promise<PhotonMedHistoryInput[]> { if (!medicationHistory) { return []; } const resourceIds: [MedicationRequest, string][] = []; for (const medicationRequest of medicationHistory) { const photonMedicationId = await getPhotonMedicationId(medicationRequest, authToken); if (photonMedicationId) { resourceIds.push([medicationRequest, photonMedicationId]); } } const medHistory = (await formatPhotonInput(resourceIds)) as PhotonMedHistoryInput[]; return medHistory; } export async function createAllergyInputs( authToken: string, allergies?: AllergyIntolerance[] ): Promise<PhotonAllergenInput[]> { const allergyInputs: PhotonAllergenInput[] = []; if (!allergies) { return []; } for (const allergy of allergies) { const photonAllergyId = await getPhotonAllergyId(allergy, authToken); if (!photonAllergyId) { continue; } const allergyInput = createInput(allergy, photonAllergyId) as PhotonAllergenInput; allergyInputs.push(allergyInput); } return allergyInputs; } export async function formatPhotonInput( resources: [MedicationRequest, string][] ): Promise<(PhotonAllergenInput | PhotonMedHistoryInput)[] | undefined> { const inputs: (PhotonAllergenInput | PhotonMedHistoryInput)[] = []; for (const [resource, photonId] of resources) { const input = createInput(resource, photonId); inputs.push(input); } return inputs; } function createInput( resource: AllergyIntolerance | MedicationRequest, photonId: string ): PhotonAllergenInput | PhotonMedHistoryInput { if (resource.resourceType === 'AllergyIntolerance') { const input: PhotonAllergenInput = { allergenId: photonId, comment: resource.note?.[0].text, }; if (resource.onsetDateTime) { input.onset = formatAWSDate(resource.onsetDateTime); } return input; } else { const input: PhotonMedHistoryInput = { medicationId: photonId, active: resource.status === 'active', comment: resource.note?.[0].text, }; return input; } } async function getPhotonAllergyId(allergy: AllergyIntolerance, authToken: string): Promise<string | undefined> { const photonId = getIdentifier(allergy.identifier); if (photonId) { return photonId; } const allergenName = allergy.code?.coding?.find( (code) => code.system === 'http://www.nlm.nih.gov/research/umls/rxnorm' )?.display; if (!allergenName) { return undefined; } const query = ` query allergens($filter: AllergenFilter) { allergens(filter: $filter) { id } } `; const variables = { filter: { name: allergenName } }; const body = JSON.stringify({ query, variables }); const result = await photonGraphqlFetch(body, authToken); const id = result.data.allergens?.[0]?.id as string; return id; } async function getPhotonMedicationId(medication: MedicationRequest, authToken: string): Promise<string | undefined> { const photonId = getIdentifier(medication.identifier); if (photonId) { return photonId; } if (!medication.medicationCodeableConcept) { return undefined; } const rxcui = getCodeBySystem(medication.medicationCodeableConcept, 'http://www.nlm.nih.gov/research/umls/rxnorm'); if (!rxcui) { return undefined; } const query = ` query medications( $filter: MedicationFilter, $after: ID, $first: Int ) { medications( filter: $filter, after: $after, first: $first ) { id } } `; const variables = { filter: { drug: { code: rxcui } } }; const body = JSON.stringify({ query, variables }); const result = await photonGraphqlFetch(body, authToken); const id = result.data.medications?.[0]?.id as string; return id; } function getIdentifier(identifiers?: Identifier[]): string | undefined { if (!identifiers) { return undefined; } const photonId = identifiers.find((id) => id.system === NEUTRON_HEALTH)?.value; return photonId; }

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