Skip to main content
Glama
example-data.ts11.4 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { createReference, getReferenceString } from '@medplum/core'; import type { BotEvent, MedplumClient } from '@medplum/core'; import type { Appointment, Bundle, BundleEntry, CodeableConcept, Encounter, Patient, Practitioner, Reference, Schedule, Slot, } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; export async function handler(medplum: MedplumClient, event: BotEvent<Practitioner>): Promise<Bundle> { const practitioner = event.input as Practitioner; // Note that the Schedule resource is created in the App.tsx file const schedule = await medplum.searchOne('Schedule', { actor: getReferenceString(practitioner) }); if (!schedule) { throw new Error('Schedule not found'); } const entries: BundleEntry<Patient | Slot | Appointment | Encounter>[] = [...createPatientEntries()]; const practitionerReference = createReference(practitioner); const homerReference = { reference: 'urn:uuid:bd90afcc-4f44-4c13-8710-84ddc1bce347', display: 'Homer Simpson' }; const margeReference = { reference: 'urn:uuid:eca66352-415c-4dab-add1-e4ed8a156408', display: 'Marge Simpson' }; const scheduleReference = createReference(schedule); const today = new Date(); // Create slots and appointments for the previous week, this week, and next week createWeekSlots(today, -1, entries, scheduleReference, practitionerReference, [margeReference, homerReference]); createWeekSlots(today, 0, entries, scheduleReference, practitionerReference, [margeReference, homerReference]); createWeekSlots(today, 1, entries, scheduleReference, practitionerReference, [margeReference, homerReference]); const bundle: Bundle = { resourceType: 'Bundle', type: 'batch', entry: entries, }; return bundle; } function createPatientEntries(): BundleEntry<Patient>[] { return [ { fullUrl: 'urn:uuid:bd90afcc-4f44-4c13-8710-84ddc1bce347', request: { method: 'PUT', url: 'Patient?name=homer' }, resource: { resourceType: 'Patient', active: true, name: [{ family: 'Simpson', given: ['Homer'] }], gender: 'male', birthDate: '1956-05-12', }, }, { fullUrl: 'urn:uuid:eca66352-415c-4dab-add1-e4ed8a156408', request: { method: 'PUT', url: 'Patient?name=marge' }, resource: { resourceType: 'Patient', active: true, name: [{ family: 'Simpson', given: ['Marge'] }], gender: 'female', birthDate: '1958-03-19', }, }, ]; } // Create slots and appointments for a week // - Create free slots for Monday and Wednesday // - Create appointments for Monday and Wednesday // - Create a busy-unavailable slot for Friday function createWeekSlots( baseDate: Date, weekOffset: number, entries: BundleEntry[], scheduleReference: Reference<Schedule>, practitionerReference: Reference<Practitioner>, patientReferences: [Reference<Patient>, Reference<Patient>] ): void { const [routinePatient, emergencyPatient] = patientReferences; // Calculate Monday, Wednesday, and Friday of the week const monday = new Date(baseDate); monday.setDate(monday.getDate() + weekOffset * 7 - ((baseDate.getDay() + 6) % 7)); const wednesday = new Date(monday); wednesday.setDate(wednesday.getDate() + 2); const friday = new Date(monday); friday.setDate(friday.getDate() + 4); // Create free slots and appointments for Monday and Wednesday [monday, wednesday].forEach((day, index) => { for (let hour = 9; hour < 17; hour++) { // Skip lunch hour if (hour === 12) { continue; } const startTime = new Date(day); startTime.setHours(hour, 0, 0, 0); const endTime = new Date(startTime); endTime.setHours(hour + 1, 0, 0, 0); const freeSlotEntry = createSlot(scheduleReference, startTime, endTime, 'free'); if (weekOffset === -1 && index === 0 && hour === 9) { // Create a cancelled routine appointment on Monday at 9am + a replacement free slot const busySlotEntry = createSlot(scheduleReference, startTime, endTime, 'busy'); entries.push(busySlotEntry); entries.push( createAppointment( practitionerReference, routinePatient, busySlotEntry, 'cancelled', appointmentTypeMap.routine, [serviceTypeMap.consultation] ) ); entries.push(freeSlotEntry); } else if (weekOffset === 0 && index === 0 && hour === 10) { // Create a fulfilled routine appointment on Monday at 10am const busySlotEntry = createSlot(scheduleReference, startTime, endTime, 'busy'); entries.push(busySlotEntry); const fulfilledAppointment = createAppointment( practitionerReference, routinePatient, busySlotEntry, 'fulfilled', appointmentTypeMap.routine, [serviceTypeMap.consultation] ); entries.push(fulfilledAppointment); entries.push(createEncounter(practitionerReference, routinePatient, fulfilledAppointment)); } else if (weekOffset === 0 && index === 1 && hour === 15) { // Create an emergency appointment on Wednesday at 3pm const busySlotEntry = createSlot(scheduleReference, startTime, endTime, 'busy'); entries.push(busySlotEntry); entries.push( createAppointment( practitionerReference, emergencyPatient, busySlotEntry, 'booked', appointmentTypeMap.emergency, [serviceTypeMap.emergencyRoomAdmission] ) ); } else if (weekOffset === 1 && index === 0 && hour === 11) { // Create an upcoming followup appointment on Monday at 11am const busySlotEntry = createSlot(scheduleReference, startTime, endTime, 'busy'); entries.push(busySlotEntry); entries.push( createAppointment( practitionerReference, routinePatient, busySlotEntry, 'booked', appointmentTypeMap.followup, [serviceTypeMap.consultation], 'Followup appointment to assess the exam results' ) ); } else if (weekOffset === 1 && index === 1 && hour === 15) { // Create an upcoming routine checkup appointment on Wednesday at 3pm const busySlotEntry = createSlot(scheduleReference, startTime, endTime, 'busy'); entries.push(busySlotEntry); entries.push( createAppointment( practitionerReference, emergencyPatient, busySlotEntry, 'booked', appointmentTypeMap.routine, [serviceTypeMap.consultation], 'Routine checkup after the emergency room visit' ) ); } else { // Create a free slot entries.push(freeSlotEntry); } } }); // Create busy-unavailable slot for Friday from 9am to 5pm const busyUnavailableSlot = createSlot( scheduleReference, new Date(new Date(friday).setHours(9, 0, 0, 0)), new Date(new Date(friday).setHours(17, 0, 0, 0)), 'busy-unavailable' ); entries.push(busyUnavailableSlot); } function createSlot(schedule: Reference<Schedule>, start: Date, end: Date, status: Slot['status']): BundleEntry<Slot> { return { fullUrl: `urn:uuid:${randomUUID()}`, request: { url: 'Slot', method: 'POST' }, resource: { resourceType: 'Slot', schedule, status, start: start.toISOString(), end: end.toISOString(), }, }; } function createAppointment( practitioner: Reference<Practitioner>, patient: Reference<Patient>, slotEntry: BundleEntry<Slot>, status: Appointment['status'], appointmentType: Appointment['appointmentType'], serviceType: Appointment['serviceType'], comment?: Appointment['comment'] ): BundleEntry<Appointment> { const slot = slotEntry.resource as Slot; const slotReference = { reference: slotEntry.fullUrl }; const cancelationReason = status === 'cancelled' ? { coding: [ { system: 'http://terminology.hl7.org/CodeSystem/appointment-cancellation-reason', code: 'prov', display: 'Provider', }, ], } : undefined; return { fullUrl: `urn:uuid:${randomUUID()}`, request: { url: 'Appointment', method: 'POST' }, resource: { resourceType: 'Appointment', status, slot: [slotReference], start: slot.start, end: slot.end, appointmentType, serviceType, participant: [ { actor: patient, status: 'accepted' }, { actor: practitioner, status: 'accepted' }, ], comment, cancelationReason, }, }; } function createEncounter( practitioner: Reference<Practitioner>, patient: Reference<Patient>, appointmentEntry: BundleEntry<Appointment> ): BundleEntry<Encounter> { const appointment = appointmentEntry.resource as Appointment; const appointmentReference = { reference: appointmentEntry.fullUrl }; const duration = new Date(appointment.end as string).getTime() - new Date(appointment.start as string).getTime(); return { fullUrl: `urn:uuid:${randomUUID()}`, request: { url: 'Encounter', method: 'POST' }, resource: { resourceType: 'Encounter', status: 'finished', subject: patient, appointment: [appointmentReference], class: { system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode', code: 'VR', display: 'virtual', }, serviceType: appointment.serviceType?.[0], period: { start: appointment.start, end: appointment.end, }, length: { value: Math.floor(duration / 60000), unit: 'minutes', }, participant: [ { individual: practitioner, type: [{ coding: [{ system: 'http://terminology.hl7.org/CodeSystem/v3-ParticipationType', code: 'ATND' }] }], }, ], }, }; } // Subset of http://terminology.hl7.org/ValueSet/v2-0276 const appointmentTypeMap: Record<string, CodeableConcept> = { followup: { coding: [ { system: 'http://terminology.hl7.org/CodeSystem/v2-0276', code: 'FOLLOWUP', display: 'A follow up visit from a previous appointment', }, ], }, routine: { coding: [ { system: 'http://terminology.hl7.org/CodeSystem/v2-0276', code: 'ROUTINE', display: 'Routine appointment - default if not valued', }, ], }, emergency: { coding: [ { system: 'http://terminology.hl7.org/CodeSystem/v2-0276', code: 'EMERGENCY', display: 'Emergency appointment', }, ], }, }; // Subset of http://example.com/appointment-service-types const serviceTypeMap: Record<string, CodeableConcept> = { consultation: { coding: [ { system: 'http://snomed.info/sct', code: '11429006', display: 'Consultation', }, ], }, emergencyRoomAdmission: { coding: [ { system: 'http://snomed.info/sct', code: '50849002', display: 'Emergency room admission', }, ], }, };

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