Skip to content

Scheduling — Overview

The Nexus EMR scheduling feature lets booking UIs discover bookable time windows for a clinic/provider and create FHIR Appointment resources that link back to the slot consumed. It complements the existing NexusEmrCoreAppointment profile (which models the booked appointment) by introducing two new profiles for availability: NexusEmrCoreSchedule and NexusEmrCoreSlot.

Why this exists

Before this feature, a booking UI had no FHIR-native way to ask:

  • "What time windows are open on this provider's calendar next week?"
  • "What's the duration of each available slot, and what service type does it support?"
  • "Is this slot bookable now, or held tentatively?"

The only options were to (a) reach into the source EMR's REST API directly (bypassing the FHIR layer), or (b) infer availability by querying for the absence of Appointment resources — which can't distinguish "no booking yet" from "no slot exists." Both are bad.

NexusEmrCoreSchedule + NexusEmrCoreSlot close that gap with a profile-level, queryable model.

Resources at a glance

Resource Profile Purpose
Schedule NexusEmrCoreSchedule The container — a provider's, room's, or service line's calendar
Slot NexusEmrCoreSlot An individual bookable time window with status (free / busy / busy-tentative / busy-unavailable)
Appointment NexusEmrCoreAppointment The booked appointment. As of IG 1.10.0, includes Appointment.slot 0..* MS → NexusEmrCoreSlot
AppointmentResponse NexusAppointmentResponse A participant's answer to a booking. Where a booking's confirmation state lives -- see Confirming a booking

Typical booking flow

  1. GET Schedule?actor=PractitionerRole/<id>&active=true — find the provider's calendar(s)
  2. GET Slot?schedule=Schedule/<id>&status=free&start=ge<date> — find free slots in a window
  3. User picks a slot in the UI; booking client PATCHes Slot.status = busy-tentative
  4. User confirms; booking client POSTs an Appointment with .slot[] = [Slot/<id>], then PATCHes Slot.status = busy
  5. (If user abandons) a hold-timeout job downgrades stale busy-tentative slots back to free

See Booking Flow Design for the full sequence + the writer-ownership contract.

Source data

Phase 1 source: OscarPro scheduletemplate family.

OscarPro represents recurring availability as templates: one scheduletemplate row per (provider, template_name) carries a timecode string where each character is a single-character code that maps to a duration via scheduletemplatecode. The Oscar-side converter materialises these templates into concrete daily Slot instances. See Oscar → FHIR Mapping for the detailed mapping.

Other EMRs (Juno, etc.) are explicitly out of scope for Phase 1 — see "Out of scope" below.

Design highlights

Flexible Schedule.actor

The actor element is sliced into three optional slices with an at-least-one invariant:

  • PractitionerRole 0..1 MS — most common; provider-owned calendar
  • Location 0..1 MS — room / equipment schedules (e.g. MRI bookings)
  • HealthcareService 0..1 MS — service-line schedules (e.g. walk-in clinic, telephone triage)

This avoids baking provider-only assumptions into the model and lets later EMR integrations represent room or service-line booking without a profile change.

Slot identifier strategy

Slots are materialised, not stored. To make them cacheable by reference, Slot.id and Slot.identifier.value use a deterministic pattern: <provider_no>-<yyyyMMdd>-<slot-index>. Re-running the converter for the same (provider, date) produces identical resources.

Slot.status writer ownership

The IG documents — but does NOT enforce — a writer-ownership contract:

  • Converter writes free / busy / busy-unavailable
  • Booking system writes busy-tentative (during holds)
  • Converter never writes busy-tentative

The contract lives in the published profile intro markdown so it ships with the IG, not just internal docs. If race conditions appear in production, If-Match optimistic concurrency on the booking system is the IG-level mitigation (not currently profile-enforced).

Confirming a booking

Correction: this page previously said confirmation was Appointment.status

Earlier releases listed AppointmentResponse as out of scope and said Phase 1 represented confirmation through Appointment status transitions. Both halves were wrong. The type is written into this estate today, and confirmation is derived from it rather than from Appointment.status. If you implemented against the earlier statement you are not reading confirmation state at all -- a confirmed booking, a reminded booking and an unanswered one all read booked. Read the answer alongside the appointment, as below.

A booking's confirmation state lives on AppointmentResponse, not on the appointment. The two are separate facts, and the answer never changes the booking: recording a refusal leaves the slot booked in the schedule until somebody acts on it.

What writes one. An integrating system outside the EMR, through the FHIR API. Nothing inside the EMR authors an answer, and that is what sets the shape a consumer should expect: a producer outside the estate supplies what it has and no more.

What reads one. Every surface that shows where a booking stands. The day sheet pairs each row with the latest answer to that appointment and derives the row's state from participantStatus; the chart timeline resolves the same derivation for its appointment rows, so the two surfaces cannot disagree about what an answer means.

How it combines with Appointment.status. The appointment decides first and the answer only refines. Read the appointment for where the visit stands -- booked, arrived, fulfilled, cancelled, no-show -- and consult the answer only for a booking still awaiting its visit. A late answer must not reach back and repaint a booking somebody has already arrived for: a patient who confirms after arriving is checked in, not confirmed.

participantStatus What a booking still awaiting its visit reads as
accepted Confirmed
tentative Reminder sent
declined Cancelled for display -- the Appointment is untouched and the slot stays booked
needs-action No change: "asked and not yet answered" is what an unanswered booking already says

An answer supersedes; it does not amend. A participant may answer the same booking more than once, and only the latest answer describes where it stands. Recency is meta.lastUpdated: R4 gives this resource no date of its own (start and end are a proposed new time for the appointment) and defines no date search parameter on it, so the server stamp is the only ordering available. A consumer must choose the latest itself rather than trust the order a search returned, because a server may ignore a _sort it does not support and answer unsorted without erroring. There is no delete: an answer is withdrawn by a newer answer.

tentative does not necessarily mean the participant is deciding

participantStatus is mandatory in R4, so a producer that omits it would have the whole resource rejected and see only a validation failure it could not act on. An omitted or blank status is therefore stored as tentative rather than refused, that being the code claiming the least. So tentative means either that the participant is still deciding or that the producer said nothing, and the two are indistinguishable from the resource. Do not read it as evidence that anybody was asked. A status a producer did supply is never rewritten, including one outside the value set.

actor is optional, and an absent actor narrows how an answer can be found. R4's patient search parameter on this type resolves through actor, so an answer that names nobody is unreachable by a patient-scoped read: the chart timeline will not find it, though the day sheet still will, because it searches by the appointment. Populate actor wherever the producing system knows who answered.

Out of scope (Phase 1)

Item Rationale
Juno scheduling OscarPro is the only source in Phase 1, matching the Phase 1 billing IG scope. The Juno device example already published in the IG is a billing-side artifact unrelated to this work.
Schedule recurrence FHIR R4 Schedule has no native recurrence element. Recurring weekly templates are materialised by the converter into concrete daily Slots — a series, not a recurrence rule. A recurrence extension is follow-up scope.
AppointmentResponse (no longer out of scope) The earlier entry here was wrong. It said this type was future scope and that Phase 1 represented confirmation through Appointment status transitions; in fact it is written and read today, and it is where confirmation state lives. See Confirming a booking.
Custom slot-window SearchParameter A composite range search (find slots overlapping a window) is deferred until a booking client confirms need. Base FHIR search params cover the MVP.
Patient-facing booking app implementation The IG is the spec; the patient/booking app is a separate effort. This IG provides the contract.

Implementation guide