Building Your Workflows on Oystehr
Scheduling

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.


Time Slotting Tracking Board

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

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.

Create a Slot
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.

Search a Schedule for open slots between tomorrow and next month
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();

Appointment Picker

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 Slot status, create an Appointment, and send a confirmation email
// 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