Appointments & Operatory Scheduling
v0.1.0Atomic multi-resource scheduling (chair, dentist, assistant), automated aerosol turnover disinfection buffers, and conflict detection.
Appointments & Operatory Scheduling
The Appointments & Operatory Scheduling bounded context coordinates clinic workflow logistics. It enforces atomic multi-resource reservations, automated aerosol room turnover buffers, and conflict detection across dental operatories, dentists, and dental assistants.
Atomic Multi-Resource Scheduling
Unlike standard medical appointments that book only a clinician, dental procedures require the concurrent coordination of three physical and human resources:
flowchart TD
APT["Appointment Booking"]
subgraph MultiResource["Atomic Multi-Resource Reservation"]
CHAIR["1. Operatory Chair<br/>(Physical dental operatory)"]
DENTIST["2. Attending Dentist<br/>(Clinician / Surgeon)"]
ASST["3. Dental Assistant<br/>(Chairside auxiliary)"]
end
BUFFER["Automated Aerosol Turnover Buffer<br/>(10-15 min room disinfection)"]
APT --> CHAIR
APT --> DENTIST
APT --> ASST
CHAIR --> BUFFER
If any single resource is unavailable or has an overlapping reservation, the entire transaction rolls back and throws a SchedulingConflictException.
Aerosol Room Turnover Buffer Invariant
Aerosol-generating dental procedures (e.g., high-speed air turbines, ultrasonic scalers, air polishers) release bio-aerosols into the operatory atmosphere.
To satisfy infection control mandates (CDC & OSHA), Obelawium Dental automatically computes and appends an aerosol disinfection buffer (configured via scheduling.aerosol_buffer_minutes, default 15 minutes) to the operatory chair schedule:
$$\text{Operatory Release Time} = \text{Appointment End Time} + \text{Aerosol Buffer Minutes}$$
- The attending dentist and assistant are released at
end_timeso they can attend to other patients. - The operatory chair remains locked until
aerosol_buffer_end_timeto allow room air evacuation and surface disinfection.
use Obelaw\Ium\Dental\Exceptions\SchedulingConflictException;
// Attempting to book an operatory during an active aerosol turnover buffer
// throws: SchedulingConflictException: Resource 'Operatory Chair' #1 is conflicted.
Appointment Lifecycle
use Obelaw\Ium\Dental\Enums\AppointmentStatus;
enum AppointmentStatus: string
{
case SCHEDULED = 'scheduled'; // Appointment booked on calendar
case CONFIRMED = 'confirmed'; // Confirmed via automated SMS / phone call
case CHECKED_IN = 'checked_in'; // Patient arrived in clinic waiting area
case IN_PROGRESS = 'in_progress'; // Seated chairside; treatment underway
case COMPLETED = 'completed'; // Dismissed; procedures finalized
case CANCELLED = 'cancelled'; // Cancelled prior to visit
case NO_SHOW = 'no_show'; // Patient failed to attend without notice
}
Fluent Service Operations
Scheduling workflows are orchestrated across ium()->dental()->scheduling() and ium()->dental()->appointments():
1. Booking an Appointment with Aerosol Buffer
use Obelaw\Ium\Dental\Data\BookAppointmentDto;
use DateTimeImmutable;
$appointment = ium()->dental()->scheduling()->bookAppointment(BookAppointmentDto::from([
'patient_id' => $patient->id,
'operatory_id' => '1', // Operatory 1 (South Wing)
'dentist_id' => 'DR-MARTINEZ', // Attending Dentist
'assistant_id' => 'ASST-JONES', // Chairside Assistant
'start' => new DateTimeImmutable('2026-10-15 10:00:00'),
'end' => new DateTimeImmutable('2026-10-15 11:00:00'),
'reason' => 'Root Canal Therapy on tooth 19',
'is_aerosol_generating' => true, // Automatically adds 15 min disinfection buffer
'aerosol_buffer_minutes' => 15,
'actor_id' => 'USER-RECEPTION',
]));
// Dentist released at: 11:00:00
// Operatory chair released at: 11:15:00
2. Confirming and Checking In Patients
// Patient confirms via SMS reminder
ium()->dental()->appointments()->confirm($appointment->id);
// Patient arrives at front desk
ium()->dental()->appointments()->checkIn($appointment->id);
3. Starting Encounter with Chairside Autoclave Verification
When seating the patient, the assistant scans the sterile instrument cassette barcode. The appointment can only transition to IN_PROGRESS if the cassette passes sterilization validation:
use Obelaw\Ium\Dental\ValueObjects\AutoclaveBarcode;
$cassetteBarcode = AutoclaveBarcode::from('CAS-2026-0941');
$activeAppointment = ium()->dental()->appointments()->start(
appointmentId: $appointment->id,
cassetteBarcode: $cassetteBarcode
);
4. Completing the Appointment
ium()->dental()->appointments()->complete($appointment->id);
5. Blocking Operatories for Maintenance or Lunch
$block = ium()->dental()->scheduling()->blockSlot(
resourceId: '1',
resourceType: 'operatory',
start: new DateTimeImmutable('2026-10-15 13:00:00'),
end: new DateTimeImmutable('2026-10-15 14:00:00'),
reason: 'Dental unit waterline shock chlorination and filter maintenance.'
);
6. Checking Available Resources
$available = ium()->dental()->scheduling()->availableResources(
start: new DateTimeImmutable('2026-10-15 14:00:00'),
end: new DateTimeImmutable('2026-10-15 15:00:00')
);
// Returns list of free operatories, available dentists, and free assistants
Emitted Domain Events
| Event Class | Trigger | Payload |
|---|---|---|
AppointmentScheduled | Fired when all resources are successfully reserved. | Appointment $appointment |
AppointmentCancelled | Fired when an appointment is cancelled and slots released. | Appointment $appointment |