[Operations 3] Appointment Recovery Starts with Impact Discovery

On Monday morning, dermatology laser L-02 is taken out of service. Eight appointments scheduled through the afternoon
depend on that device. Six have a suitable replacement time within the next 14 days. Two have no available time that
matches both the doctor and treatment.
Equipment downtime is only one example. Physician leave, scheduled equipment maintenance or replacement, and a treatment-room closure can all make the people or resources required for an appointment unavailable. This article grounds its implementation discussion in the equipment-unavailability and clinic-closure paths present in the code, and treats the other causes as operational scenarios that need the same recovery boundary.
Reducing any disruption to “incident → change appointment time” removes the decisions that operations actually needs:
- Which appointments overlap the disruption’s time and scope?
- Did an original appointment change after candidate calculation finished?
- Who follows up when there is no candidate or when a progress stream disconnects?
- Is choosing a candidate the same as recording the patient’s consent to the change?
- Could a clinic-caused change count against the patient’s booking reliability?
Recovery is not an update to one date field. It separates impact discovery, candidate calculation, write-time revalidation, state transition, and STAFF verification into distinct stages.
Define the disruption’s impact before starting appointment recovery
Section titled “Define the disruption’s impact before starting appointment recovery”Start with a cause-specific query. For equipment downtime, EquipmentUnavailabilityService expands the effective ranges of a
registered unavailability schedule and returns appointments that use the same device and overlap those ranges. STAFF can
also call previewConflicts before saving a new unavailability schedule.
GET /api/{tenantCode}/clinics/{clinicId}/equipments/{equipmentId}/unavailabilities/{id}/conflictsPOST /api/{tenantCode}/clinics/{clinicId}/equipments/{equipmentId}/unavailabilities/preview-conflictsThese endpoints return appointments that may be affected. They do not move appointments to
PENDING_RESCHEDULE or create replacement candidates. Combining conflict discovery with reassignment would start writes
before STAFF has reviewed the scope and make equipment-schedule failures difficult to distinguish from recovery failures.
The clinic-closure recovery path is separate. ClosureRescheduleService reads active appointments on a given date,
calculates replacement candidates, and then rechecks current state before marking the appointments for recovery. Although
the current service is named around closures, an equipment-incident screen can use the same boundary as a reference after
its conflict scope has been approved. This does not mean that the equipment-conflict API automatically invokes the
closure-rescheduling service. A different cause such as physician leave needs its own cause record and candidate rules;
do not assume every cause automatically invokes this service.
Recheck appointment state and version before saving candidates
Section titled “Recheck appointment state and version before saving candidates”The synchronous processClosureReschedule path performs expensive candidate calculation outside the transaction:
- Verify that the clinic belongs to the requested tenant.
- Read active appointment IDs and snapshots for the closure date, and enforce the affected-appointment limit.
- Limit candidate work using the appointment count and
searchDays. - Cache matching doctor, service, and date lookups while calculating replacement times.
- Start the write transaction and read the active appointments again.
- Verify that the count, IDs,
status, andversionstill match the initial snapshots. - Use version-based compare-and-set updates to move each appointment to
PENDING_RESCHEDULE, then store history, status events, and candidates.
Calculating outside the transaction does not mean using stale results without protection. A patient may cancel or another staff member may change an appointment during candidate search. The service therefore compares every current row with the initial snapshot immediately before writing. If any appointment has changed, the entire synchronous batch rolls back.
This design keeps the transaction short while protecting one invariant: “Is the complete scope we first approved still the same?” Running candidate search inside a long transaction would hold database connections and locks for too long. Trusting an out-of-transaction calculation without revalidation could move an already cancelled or changed appointment back into recovery.
Synchronous failure rolls everything back; streaming failure keeps completed work
Section titled “Synchronous failure rolls everything back; streaming failure keeps completed work”The operations screen can start the same closure recovery as a synchronous request or an SSE stream. The difference is not limited to progress reporting.
| Boundary | Synchronous processClosureReschedule | Streaming streamClosureReschedule |
|---|---|---|
| Candidate calculation | Calculate candidates for the complete scope outside the transaction | Calculate candidates inside one transaction per appointment |
| Write scope | Save all affected appointments in one transaction | Save each appointment in a separate transaction |
| Concurrent change | Roll back the batch if the complete snapshot changed | Skip the affected appointment when its CAS fails |
| Progress event | Return the complete result at the end | Emit progress only after each appointment commits |
| Disconnect | Judge the request as one success or failure | Keep committed appointments; appointments not yet started may remain unprocessed |
The streaming path releases the database connection after each appointment transaction commits and calls onProgress
afterward. Slow SSE delivery therefore does not hold a connection. But if the stream disconnects, the last sequence number
visible in the browser cannot establish that the remaining work failed. STAFF must reconcile already committed and
unprocessed appointments against server state.
Use the synchronous path when the entire approved scope must change together or not at all. Streaming is useful when work takes longer and STAFF needs progress, but its per-appointment partial success must be part of follow-up operations. Describing both paths as having the same atomicity invites unsafe retries and duplicate confirmation.

Show appointment state and the next task together
Section titled “Show appointment state and the next task together”A time-ordered list forces STAFF to repeatedly distinguish appointments with candidates, appointments without candidates, and rows that have already changed. A useful screen summarizes the clinic incident first and separates items by the next task in an action queue.
The mockup brings these signals into one workflow:
- incident cause, affected time range, and candidate search window
- affected count, candidate-available count, and no-candidate count
- an action queue showing
PENDING_RESCHEDULE, candidate count, and next task - an anonymous appointment reference and the
currentVersionobserved by the screen - candidate priority, date, time, doctor, and equipment
- manual confirmation, automatic rescheduling, and a separate no-candidate task
- a visible boundary between current behavior and work that is not implemented yet
currentVersion explains the state that the screen read. It does not imply that the current confirmation endpoint accepts
a client-provided expectedVersion. The confirmation service reads the original appointment again and uses that current
version as its internal compare-and-set condition. Keeping that distinction visible prevents the UI contract from being
mistaken for the concurrency control implemented by the service.

This mockup is based on current states and operations; it is not a production screenshot. Values are illustrative, and no patient names, phone numbers, or clinical records are shown. STAFF separates candidate-ready appointments from exceptions, then reviews the selected appointment’s current state and candidate resources together.
Confirm manual and automatic selection in the same transaction
Section titled “Confirm manual and automatic selection in the same transaction”STAFF can choose one replacement time from an appointment’s candidate list. Automatic rescheduling is not a separate
optimization engine: it chooses the highest-priority current candidate and invokes the same confirmation logic. It returns
null when no candidate exists, so clicking the automatic action is not itself proof of success.
The confirmation transaction performs these operations together:
- Verify that the candidate and original appointment belong to the requested tenant and clinic scope.
- Verify that the candidate doctor belongs to the original appointment’s clinic.
- Create a new appointment at the candidate date and time with status
CONFIRMED. - Use the original appointment’s current version as a CAS condition and move it to
RESCHEDULED. - Record status history for the original appointment.
- Mark the selected candidate so it cannot be selected again.
- Write the change notification to the outbox.
If the original state or version changes concurrently, the CAS fails and the transaction does not complete. This prevents
a partial result in which only the replacement exists or only the original becomes RESCHEDULED.
The current confirmation boundary has an important limitation: it creates the replacement directly as CONFIRMED. A
separate proposal stage that records patient consent before confirmation is not connected to this path. The screen’s
“Reschedule to this candidate” action reflects current behavior; it does not prove that consent evidence has already been
captured.
A policy model alone cannot enforce patient consent
Section titled “A policy model alone cannot enforce patient consent”The policy model includes DisruptionRecoveryPolicy.
preserveConfirmedAppointment=true is a non-disableable rule stating that the existing confirmed visit must remain until
the customer accepts an alternative or explicitly cancels. The validator rejects a tenant default when this value is not
true, and clinic overrides cannot change it.
However, the current ClosureRescheduleService.confirmReschedule path does not read this policy. Confirming a candidate
immediately creates the replacement as CONFIRMED and changes the original to RESCHEDULED. These statements must remain
separate:
- Current policy model: expresses and validates the rule that an existing confirmed visit must not change without customer consent.
- Current confirmation path: immediately confirms a candidate without receiving the policy or consent evidence as input.
An operating procedure can require STAFF to contact the patient first. That is not the same as the system enforcing and preserving auditable consent evidence. Until the policy model is connected to the write path, this remains an operational readiness item.
Recheck versions before applying a Timefold result atomically
Section titled “Recheck versions before applying a Timefold result atomically”SolverService.optimizeReschedule can optimize several appointments at once. Seeing a proposed batch on the screen is not
enough to apply it, because another operation may change an original appointment after calculation.
applyOptimizedAssignments checks the source versions inside a locking transaction and updates every assignment with a
version-based CAS. If any result is stale or any CAS fails, the whole operation rolls back and returns false.
This is not the same API as candidate confirmation in ClosureRescheduleService. The latter stores candidates and creates
a replacement appointment selected by STAFF. The solver path applies a computed batch of existing appointments under
source-version fences. Combining both under one “automatic optimization” button would first require a shared result model
and shared failure semantics.
Decide clinic responsibility and patient compensation separately
Section titled “Decide clinic responsibility and patient compensation separately”Physician leave, equipment failure or maintenance, and clinic closure are not patient-created incidents. The booking reliability model includes CLINIC
and OPERATIONAL_EXCEPTION responsibility values, and only events confirmed as patient responsibility should support a
new-booking restriction. Treating a clinic-caused change like a no-show or late cancellation would make recovery create a
second penalty.
Compensation is a separate decision. The waitlist operations API can issue or revoke a recovery credit, but a closure, equipment conflict, or another operational disruption does not create one automatically.
POST /api/{tenantCode}/clinics/{clinicId}/waitlist/recovery-creditsPOST /api/{tenantCode}/clinics/{clinicId}/waitlist/recovery-credits/{recoveryCreditRef}/revokeClinic policy must define whether compensation is warranted, who approves it, and how long it remains valid. Creating it inside the recovery transaction would also require explicit answers for atomicity, duplicate grants, and revocation authority. The current design correctly treats it as an independent capability.
Separate current behavior from remaining operations work
Section titled “Separate current behavior from remaining operations work”| Category | Verified scope |
|---|---|
| Current implementation | Query equipment-unavailability conflicts; find appointments affected by a closure; calculate, revalidate, and atomically save one synchronous batch; stream per-appointment work; confirm candidates manually or automatically; atomically apply solver results |
| Current policy model | Configure automatic proposals and delay limits; require protection of existing confirmed visits; limit clinic-level overrides |
| Operations readiness | Approve the incident cause and affected scope; queue no-candidate work; reconcile state after a stream disconnect; contact patients and verify consent; classify clinic responsibility; approve compensation |
| Follow-up improvements | Add proposal → acceptance → confirmation with consent evidence; enforce DisruptionRecoveryPolicy in the confirmation path; preserve an auditable correlation across recovery, notification, and compensation |
The most important distinction is that a validated policy model does not mean the current confirmation path already enforces consent. The reverse mistake is also harmful: snapshot revalidation and rollback in the synchronous batch, and per-appointment commits in the streaming path, are implemented safeguards—not future plans.
Recovery ends only after the final state is verified
Section titled “Recovery ends only after the final state is verified”When clinic conditions force appointment changes, STAFF needs more than one reschedule button:
- Physician leave, equipment failure or maintenance, and other causes still require impact and responsibility checks before rescheduling.
- Equipment conflict queries identify affected appointments but do not reschedule them.
- Synchronous closure recovery calculates candidates outside the transaction, rechecks the complete snapshot, and saves the batch atomically.
- Streaming recovery commits per appointment, so a disconnect requires reconciliation of completed and remaining work.
- Manual and automatic candidate selection use the same confirmation transaction, which currently creates the replacement directly as
CONFIRMED. - The policy model protects existing confirmed visits, but the current confirmation path does not enforce patient-consent evidence.
- Solver application and recovery credits have separate boundaries.
- A clinic-caused incident must not count against patient booking reliability.
An operations screen should provide clearer information, not merely more information. It must show which appointments are affected, whether candidates exist, which operation is currently allowed, and what state must be read again afterward. That lets clinic staff, product owners, and developers distinguish current behavior from the next improvement while looking at the same screen.
Next article
Section titled “Next article”This article covered the recovery boundary for appointments changed by clinic conditions. The next Operations article examines which offers and holds should be reevaluated when a CRM profile changes, and which confirmed visits and consent evidence must remain protected.
References
Section titled “References”- clinic-appointment repository
ClosureRescheduleServiceRescheduleControllerRescheduleBatchStreamControllerEquipmentUnavailabilityServiceEquipmentUnavailabilityControllerSolverServiceOperationalSchedulingPoliciesSchedulingPolicyValidatorBookingReliabilityModelWaitlistOperationsControllerreschedule-list.component.ts- Reevaluating appointments after a CRM profile change
Comments
Leave a note or reaction with your GitHub account.