Registration, Scheduling and Queuing
We group registration, scheduling and queuing functions (RSQ) together because collectively they represent the top of the patient funnel for most organizations. Organizations often want fine-grained control over RSQ because onboarding patients are at highest risk of attrition. Custom RSQ solutions generally integrate well with existing EHRs and the return on investment is measurable by tracking reduction in drop-offs, representing a low-risk, high-reward option for custom development.
In a perfect world, RSQ is self-scheduled and fully autonomous. Unfortunately, self-scheduling can be difficult to implement due to the real-world complexities of:
- Payer requirements that vary contract to contract
- State licensure
- Supporting different types of appointments and their specific requirements
- Working with provider preferences
- Managing visit capacity
As a result, at many clinical organizations scheduling is handled by a a staff scheduler. Only a small percentage of practices have true self-scheduling because there are too many factors influencing availability for traditional EHR environments to model.
In some cases, it is effectively impossible to allow full self-scheduling because appointment allocation is partially dependent on the intuition or lived-experience of the scheduler, rapidly changing staff allocation, or esoteric insurance contract details. However, the expressiveness of custom code affords the opportunity for more automated processes and workflows, even when a human is ultimately acting as the decision maker or handling exception cases.
All of the scheduling implementations described below are adaptable to a designated scheduler, self scheduling, or a hybrid approach.
Common Workflow Implementations
Time Slotting
Time slotting allocates specific time slots to individual patients often associated with specific providers. However, a given time can allocate multiple slots to a provider or group of providers. Patients tend to prefer time slot scheduling because they know exactly when they can expect to see a provider. Traditionally, time slot scheduling represented more work for clinical staff because it required managing cancellations and changes lest slots go empty. However, with Oystehr reserved time slots can roll into a custom queuing system or tracking board that provides guidance to staff for optimally allocating patients and providers on a rolling basis accounting for cancellations and staffing changes.
This tracking board, built with Oystehr, makes it easier to manage time slots.
Queuing (Urgency And Wave Scheduling)
There are several different scheduling models that build different queues. Wave scheduling creates patient groups by opening multiple slots at a given time and then creating individual queues among patients in a given group. Urgency scheduling has no time slots involved, but creates a queue based initially on registration timestamp (walk-in or online), modified by criticality, visit type and other factors.
Capacity Scheduling
Capacity scheduling attempts to open slots to patients based on expected provider capacity at a given time, and then continually modifies available slots based on changes in capacity, cancellations, and real-time wait time data.
FHIR Modeling
Key FHIR Resources
- Slot (opens in a new tab) — A block of time for booking an Appointment.
- Schedule (opens in a new tab) — A container for Slots. Every Slot must belong to exactly one Schedule.
- Appointment (opens in a new tab) — Details of an expected or planned meeting. Optionally associated with one or more Slots.
- Encounter (opens in a new tab) — An actual (rather than expected) interaction or meeting. Not usually directly relevant to scheduling.
Common Workflow Examples
Let's consider offering a patient self-scheduled slots for a telemed appointment to consult a pediatrician about a child with an ankle injury in a small practice. In this case, there are two methods of presenting available slots to the parent wishing to book an appointment. You can either generate slot resources representing availability of the provider, or you can present slots computationally without ever creating slot objects. If the provider's schedule is consistent and other factors (e.g. appointment type, insurance plan, location) don't excessively influence presentation of slots, it may be simpler to create slot resources. If slot presentation is influenced by a complex array of factors, it may be better to forgo creating slot resources and simply compute availability in custom code. In this example, we will assume the provider has a simple schedule, and pregenerate slots.
Generate slots
Slots (opens in a new tab) must be attached to a Schedule (opens in a new tab). In its most basic form, a Schedule only needs to specify the schedule's owner using the actor attribute. In this case, the Schedule belongs to the pediatrician, so the actor is a reference to a Practitioner resource.
Slots must begin and end at fixed points in time. If the presentation of availability requires more flexibility, you will need to forgo using Slot resources and present slots virtually. Here we create a slot, assign it a Schedule and set the start and end times.
async function createSlot(token: string, start: DateTime, end: DateTime): Promise<string> {
const oystehr = new Oystehr({ accessToken: '<your_access_token>' });
const slot = await oystehr.fhir.create<Slot>({
resourceType: 'Slot',
status: 'free',
schedule: {
reference: `Schedule/${FHIR_SCHEDULE_ID}`,
},
start: start.toISO(),
end: end.toISO(),
});
return slot.id;
}
Present Slots to the Patient
Slots are presented to the booker via a picker. The picker is populated with a FHIR search for available slots (slots that haven't been claimed) for the time frame or other criteria you specify.
const oystehr = new Oystehr({ accessToken: '<your_access_token>' });
const slots = (await oystehr.fhir.search<Slot>({
resourceType: 'Patient',
params: [
{ name: 'start', value: `ge${tomorrow}` },
{ name: 'start', value: `le${nextMonth}` },
{ name: '_count', value: '1000' },
{ name: '_sort', value: 'start' },
{ name: 'status', value: 'free' },
{ name: 'schedule', value: `Schedule/${FHIR_SCHEDULE_PRACTITIONER_ID}` },
],
})).unbundle();
Patient Selects Desired Slot and an Appointment is Created
Creating an appointment from the desired slot after gathering data from the booker can be a little tricky. For example, consider populating the participants attribute on the Appointment resource. You should include the Practitioner (pediatrician), any staff involved in the appointment, the parent as a RelatedPerson, the patient if they are attending, and less intuitively, the location of the visit should also be a participant. The patient is also the subject of the appointment, whether or not they are also attending the appointment as a participant.
// Update the Slot's status to busy
console.group('updateSlotStatus');
const updatedSlot = await updateSlotStatus({ slotId: slot.id, token, status: 'busy', secrets });
console.groupEnd();
console.debug(`updateSlotStatus success: updatedSlot = ${JSON.stringify(updatedSlot)}`);
// Create a new Appointment
console.group('createAppointment');
const fhirAppointment = await createAppointment({
slotId: slot.id,
fhirPatient,
patient,
updatedSlot,
token,
secrets,
ipAddress,
});
console.groupEnd();
console.debug(`createAppointment success: fhirAppointment = ${JSON.stringify(fhirAppointment)}`);
//Send a confirmation email
try {
console.group('sendConfirmationEmail');
const { start } = updatedSlot;
await sendConfirmationEmail({
language,
email: patient.email,
start,
timezone: slot.timezone,
secrets,
});
console.groupEnd();
} catch (error) {
console.log('failed to send a confirmation email, ', JSON.stringify(error));
}
Future Enhancements
More workflow examples are in the works for RSQ so check back soon:
- Schedule generation
- Incorporating wait time calculations and state licensure into your registration process
- Creating an Encounter from an Appointment