Skip to main content
Glama
order-create.ts10.4 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { isResource } from '@medplum/core'; import type { BotEvent, MedplumClient } from '@medplum/core'; import type { Bundle, Coverage, Organization, Patient, Practitioner, ProjectSetting, QuestionnaireResponse, Reference, Resource, ServiceRequest, } from '@medplum/fhirtypes'; /** * Handles incoming BotEvent messages and processes ServiceRequest resources. * * @param medplum - An instance of the Medplum client for interacting with the FHIR server. * @param event - The BotEvent containing the incoming message. * * @returns A Promise that resolves to the response data (if successful) or an error message. */ export async function handler(medplum: MedplumClient, event: BotEvent<Resource>): Promise<any> { // Check if event.input is of type Resource if (!isResource(event.input)) { return false; } const resource = event.input as Resource; switch (resource.resourceType) { case 'ServiceRequest': { // Make sure to not duplicate the order if (resource?.identifier?.find((i) => i.system === 'vital-order-id')) { return true; } const bundle = await buildVitalOrder(medplum, resource); const patient = bundle.entry?.find((e) => e.resource?.resourceType === 'Patient')?.resource as | Patient | undefined; if (!patient) { throw new Error('Patient is missing'); } await createVitalUser(event.secrets, patient); const orderID = await createVitalOrder(event.secrets, bundle); await medplum.updateResource<ServiceRequest>({ ...resource, identifier: [ ...(resource.identifier || []), { system: 'vital-order-id', use: 'secondary', value: orderID, }, ], }); console.log('Vital order created:', orderID); return true; } default: return false; } } /** * Bundle containing patient, practitioner, service request, coverage, and questionnaire response resources * for creating a Vital order. */ type CreateOrderBundle = Bundle< QuestionnaireResponse | Organization | Practitioner | ServiceRequest | Coverage | Patient >; /** * Builds a Bundle containing patient, practitioner, service request, coverage, and questionnaire response resources * from the provided ServiceRequest. * * @param medplum - An instance of the Medplum client for interacting with the FHIR server. * @param sr - The ServiceRequest resource to use for building the Bundle. * @returns A Promise that resolves to the constructed Bundle. */ export async function buildVitalOrder(medplum: MedplumClient, sr: ServiceRequest): Promise<CreateOrderBundle> { if (!sr.subject || !sr.requester) { throw new Error('ServiceRequest is missing subject or requester'); } const patient = await medplum.readReference(sr.subject as Reference<Patient>); const practitioner = await medplum.readReference(sr.requester as Reference<Practitioner>); if (patient.resourceType !== 'Patient' || practitioner.resourceType !== 'Practitioner') { throw new Error('ServiceRequest subject or requester is not a Patient or Practitioner'); } const coverage = await getCoverage(medplum, sr); const questionnaries = await getQuestionnaires(medplum, sr.supportingInfo || []); const performer = await getPerformer(medplum, sr); return { resourceType: 'Bundle', type: 'transaction', entry: [ ...questionnaries.map((qs) => ({ resource: resourceWithoutMeta(qs) })), { resource: resourceWithoutMeta(practitioner) }, { resource: resourceWithoutMeta(sr) }, { resource: resourceWithoutMeta(coverage) }, { resource: resourceWithoutMeta(performer) }, { resource: { ...resourceWithoutMeta(patient), address: patient.address?.map((address) => ({ ...address, country: address.country || 'US', })), }, }, ], }; } /** * Returns a copy of the provided resource with the meta field removed. * * @param resource - The resource to remove the meta field from. * @returns A copy of the resource without the meta field. * * @throws An error if the provided resource is undefined. */ export function resourceWithoutMeta<T extends Resource>(resource: T): Omit<T, 'meta'> { const { meta: _, ...r } = resource; return r; } /** * Sends a POST request to the Vital API to create a vital order using the provided Bundle. * * @param secrets - An object containing project settings, including `VITAL_API_KEY` and `VITAL_BASE_URL`. * @param body - The Bundle containing the resources to create the order with. * @returns A Promise that resolves to the ID of the created order. */ export async function createVitalOrder( secrets: Record<string, ProjectSetting>, body: CreateOrderBundle ): Promise<string> { const apiKey = secrets['VITAL_API_KEY'].valueString; const baseURL = secrets['VITAL_BASE_URL']?.valueString || 'https://api.dev.tryvital.io'; if (!apiKey || !baseURL) { throw new Error('VITAL_API_KEY and VITAL_BASE_URL are required'); } const url = `${baseURL}/v3/order/fhir`; const resp = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/fhir+json', 'x-vital-api-key': apiKey, }, body: JSON.stringify(body), }); switch (resp.status) { case 200: { const { order } = (await resp.json()) as { order: { id: string } }; return order.id; } default: throw new Error('Vital API error: ' + (await resp.json())); } } /** * Response from the Vital API when creating a user. */ type CreateUserResponse = { client_user_id: string; user_id: string; }; /** * Response from the Vital API when a user already exists. */ type CreateUserConflictResponse = { detail: { error_type: string; error_message: string; user_id: string; created_on: string; }; }; /** * Sends a POST request to the Vital API to create a vital user using the provided Patient. * * @param secrets - An object containing project settings, including `VITAL_API_KEY` and `VITAL_BASE_URL`. * @param patient - The Patient resource to create the vital user with. * * @returns A Promise that resolves to the ID of the created vital user. */ export async function createVitalUser(secrets: Record<string, ProjectSetting>, patient: Patient): Promise<string> { const apiKey = secrets['VITAL_API_KEY'].valueString; const baseURL = secrets['VITAL_BASE_URL']?.valueString || 'https://api.dev.tryvital.io'; if (!apiKey || !baseURL) { throw new Error('VITAL_API_KEY and VITAL_BASE_URL are required'); } const url = `${baseURL}/v2/user`; const body = { client_user_id: patient.id, }; const resp = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-vital-api-key': apiKey, }, body: JSON.stringify(body), }); switch (resp.status) { case 400: { const data = (await resp.json()) as CreateUserConflictResponse; if (data.detail.user_id) { return data.detail.user_id; } throw new Error('Vital API create user error: ' + JSON.stringify(data)); } case 200: { const data = (await resp.json()) as CreateUserResponse; if (data.user_id) { return data.user_id; } throw new Error('Vital API create user error: ' + JSON.stringify(data)); } default: { const data = await resp.json(); throw new Error('Vital API error: ' + JSON.stringify(data)); } } } /** * Filters and retrieves QuestionnaireResponse resources from the provided references in the ServiceRequest's supportingInfo. * * @param medplum - An instance of the Medplum client for interacting with the FHIR server. * @param supportingInfo - An array of references potentially containing QuestionnaireResponse resources. * @returns A Promise that resolves to an array of QuestionnaireResponse resources found in the references. */ async function getQuestionnaires( medplum: MedplumClient, supportingInfo: Reference[] ): Promise<QuestionnaireResponse[]> { const questionnaires = [] as QuestionnaireResponse[]; for (const ref of supportingInfo) { if (ref.type !== 'QuestionnaireResponse' && !ref.reference?.startsWith('QuestionnaireResponse')) { continue; } const q = await medplum.readReference(ref as Reference<QuestionnaireResponse>); questionnaires.push(q); } return questionnaires; } /** * Finds the Coverage resource associated with the provided ServiceRequest. * * @param medplum - An instance of the Medplum client for interacting with the FHIR server. * @param sr - The ServiceRequest resource to search for insurance references. * @returns A Promise that resolves to the Coverage resource found in the insurance references, * or throws an error if no Coverage is found. */ async function getCoverage(medplum: MedplumClient, sr: ServiceRequest): Promise<Coverage> { const ref = (sr.insurance || []).find((r) => r.type === 'Coverage' || r.reference?.startsWith('Coverage')); if (!ref) { throw new Error('Coverage is missing'); } return medplum.readReference(ref as Reference<Coverage>); } /** * Finds the Organization resource associated with the provided ServiceRequest. * * @param medplum - An instance of the Medplum client for interacting with the FHIR server. * @param sr - The ServiceRequest resource to search for performer references. * @returns A Promise that resolves to the Organization resource found in the performer references, * * @throws An error if no Organization is found. */ async function getPerformer(medplum: MedplumClient, sr: ServiceRequest): Promise<Organization> { if (!sr.performer || sr.performer.length === 0) { throw new Error('Performer is missing'); } for (const ref of sr.performer) { if (ref.type === 'Organization' || ref.reference?.startsWith('Organization')) { const org = await medplum.readReference(ref as Reference<Organization>); const isLabTest = org.identifier?.find( (i) => i.system === 'https://docs.tryvital.io/api-reference/lab-testing/tests' ); if (isLabTest) { return org; } } } throw new Error('Performer is missing'); }

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