[Operations 4] Profile Changes Do Not Rewrite Confirmed Visit Commitments

A patient profile has changed in the CRM. Because that change may affect appointment eligibility, the appointment service may need to review a current proposal or hold. But if the appointment service stores the CRM’s source profile, or silently moves a visit that the patient and clinic have already agreed on, the ownership boundary is lost.
The conclusion is simple:
A CRM profile change is an input signal for reservation reevaluation, not a command to rewrite a confirmed visit commitment. Automatically reevaluate only
PROPOSEDandHELD; protect the current appointment, consent, and resource allocation ofCONFIRMED.
This article does not explain how the CRM calculates an assessment. It focuses on the minimum event the appointment service accepts, the durable job and outcome model, fair dispatch across clinics, failure recovery, and the next action shown to STAFF. The implementation and the operations runbook are the source of those boundaries.
The operations screen should show job state, not the profile itself
Section titled “The operations screen should show job state, not the profile itself”When STAFF investigates a profile change, the first useful facts are not the patient’s name or score. The screen should first answer how many jobs are waiting, what is running now, which jobs are waiting for another attempt, and which failures require operator attention.
The operations-screen concept places the following values from ProfileReevaluationOperationalSnapshot at the top:
PENDING: reevaluation jobs that have not been processed yetRUNNING: jobs currently owned by a worker leaseRETRY_WAIT: jobs waiting for their next attempt after a transient failureFAILED: jobs that exhausted automatic recovery and need an operator- active lease count and the age of the oldest backlog item
- lease-renewal failure count and consecutive assessment-failure count
drainState:ACTIVE,DRAINING, orDRAINED
The action queue is not just another list. A list describes the current state; an action queue helps STAFF decide what to inspect
first. Each row therefore includes targetRevision, priorityClass, dueAt, nextAttemptAt, the latest failure code, and
outcome counts. The queue makes the HELD_PRESENT-before-PROPOSED_ONLY priority explicit.

This is a concept based on the implemented operations snapshot and endpoint contract. The numbers are illustrative. It deliberately omits patient names, contact details, the source profile, and assessment content. STAFF can read and preview; only ADMIN can execute a redrive within an authenticated scope.
The detail panel keeps only the values needed for the next operational decision:
- tenant group and clinic scope
- the target
profileRevision targetPolicyRefandtargetPolicyGeneration- current job state and the latest outcome
dueAt,nextAttemptAt, the latest failure code, and outcome counts
It does not show the source profile, feature values, scores, explanations, assessment body, patient name, or phone number. Those values are not needed to choose the next operation, and the appointment service is not their source of truth.
A CRM event starts reevaluation with a minimum payload
Section titled “A CRM event starts reevaluation with a minimum payload”The implemented event model is PatientSchedulingAssessmentChanged. As the name suggests, it signals that a CRM assessment
relevant to scheduling has changed; it does not carry the profile itself.
data class PatientSchedulingAssessmentChanged( val eventId: String, val tenantGroupId: Long, val clinicId: Long, val patientReferenceFingerprint: String, val profileRevision: Long, val materialChange: Boolean, val assessmentRef: String, val assessmentHash: String, val occurredAt: Instant,)The event crosses the appointment-service boundary with:
eventIdfor deduplication and audittenantGroupIdandclinicIdfor the processing scope- a lower-case SHA-256
patientReferenceFingerprintthat does not directly identify the patient profileRevisionfor freshness checksassessmentRefandassessmentHashfor locating and verifying the assessment resultmaterialChangeto indicate whether the change can affect a scheduling decisionoccurredAtfrom the source event
The event contains no raw profile, features, scores, explanations, or corrections. Possessing a fingerprint does not let the appointment service recover the patient’s identity. This keeps the CRM as the source of truth for profile and assessment data, while the appointment service owns appointment state and resource allocation.
assessmentHash does not mean “trust whatever the CRM sent.” It is a contract value that lets the appointment service verify that
the referenced assessment belongs to the revision named by the event. Events that fail trust, schema, or scope validation are not
turned into reevaluation jobs; they are isolated for investigation or quarantine.
Merge the latest revision, then target only PROPOSED and HELD
Section titled “Merge the latest revision, then target only PROPOSED and HELD”Several profile-change events can arrive for the same patient scope within seconds. Starting a full reservation scan for every event could let an older revision overwrite a newer decision. The storage model therefore separates a scope-level latest head from durable jobs for individual revisions.
tenantGroupId + clinicId + patientReferenceFingerprint ↓ latestRevision head ↓ revision-specific reevaluation jobProfileReevaluationScope combines tenant group, clinic, and fingerprint. The fingerprint must use the lower-case SHA-256 form.
ProfileReevaluationHeadRecord points to the newest revision, assessment reference, and hash observed for that scope, while
ProfileReevaluationJobRecord processes one particular revision durably.
The job states expose the processing lifecycle:
| State | Meaning | Next STAFF action |
|---|---|---|
PENDING | Waiting to be processed | Check the target revision and dueAt |
RUNNING | A worker owns a lease | Check the active lease and elapsed time |
RETRY_WAIT | Waiting for a cooldown after a transient failure | Check nextAttemptAt and the failure code |
COMPLETED | Processing for the latest revision is complete | Check outcome counts and the outbox record |
STALE | A newer revision arrived, so this job will not continue | Move to the latest job |
FAILED | Automatic recovery was exhausted | Classify the cause, then preview or investigate |
The eligibility rule for one appointment is deliberately narrower:
val AppointmentCommitmentStatus.isProfileReevaluationEligible: Boolean get() = this == AppointmentCommitmentStatus.PROPOSED || this == AppointmentCommitmentStatus.HELDThe absence of CONFIRMED is a protection rule, not an implementation gap. A CONFIRMED appointment is a visit commitment
already agreed by the patient and clinic, and it is tied to resources such as a provider, time, or device. If the profile change
requires a new decision, the system can create a separate proposal; patient consent and an operator confirmation must precede a
change to the confirmed commitment.
Record assessment lookup and the final state decision as one job
Section titled “Record assessment lookup and the final state decision as one job”A PROPOSED or HELD appointment is not written to a new state immediately. The worker claims the job through fair dispatch
across clinics, fetches the assessment referenced by the event, and verifies the policy reference and generation. The job keeps
heldTarget, proposedTarget, targetPolicyRef, targetPolicyGeneration, nextAttemptAt, lease data, and cursors.
Fair dispatch is not a repeated ORDER BY id LIMIT N. ClaimProfileReevaluationJobs takes both a global limit and a per-clinic
limit, while ProfileReevaluationClinicCursor resumes keyset ordering from the clinic after the last one served. A large backlog
in one clinic therefore cannot permanently starve clinics with smaller identifiers.
Once the assessment result arrives, each appointment receives an explicit outcome:
| Outcome | Meaning |
|---|---|
PROPOSAL_SUPERSEDED | Replace the existing proposal with a new one |
HOLD_KEPT | Keep the current hold because it is still valid |
HOLD_REPLACED | Atomically replace the existing hold |
FALLBACK_TO_PROPOSED | Release the hold and return to a proposal when no replacement is available |
SKIPPED_INELIGIBLE | Skip because the current state is not eligible for automatic reevaluation |
SKIPPED_UNCHANGED | Leave the appointment unchanged because the evaluated input is the same |
These outcomes must not be collapsed into “the assessment passed” or “the appointment was confirmed.” HOLD_REPLACED means that
held capacity changed; it does not create a CONFIRMED visit. SKIPPED_INELIGIBLE can be a successful protection of the current
state rather than an error.

Final State Decision is an explicit node. PROPOSED and HELD can record reevaluation outcomes, while CONFIRMED follows a protected path.Separate RETRY_WAIT from QUARANTINE
Section titled “Separate RETRY_WAIT from QUARANTINE”Putting every failure into automatic retry makes it impossible for operators to distinguish a temporary dependency problem from a data, authorization, or privacy incident.
Transient dependency failure
Section titled “Transient dependency failure”If the CRM assessment endpoint is temporarily slow or the database returns a transient error, the job can move to RETRY_WAIT.
It retains attemptCount, nextAttemptAt, lastFailureCode, lease data, and redrive lineage. Automatic redrive is limited by
configured attempts and cooldown.
Trust, privacy, or contract failure
Section titled “Trust, privacy, or contract failure”If signature, issuer, audience, schema, or tenant/clinic scope cannot be verified, if a raw profile appears to have crossed the boundary, or if quarantine repeats, the job is not released through generic retry. Stop the consumer, preserve the evidence, and investigate with the CRM and security on-call teams.
The FAILED rows on the operations screen show only a bounded failure code, never exception text or patient data. For example,
ASSESSMENT_TIMEOUT may be a limited retry candidate, while PRIVACY_BOUNDARY requires investigation first.
Separate STAFF read access from ADMIN execution
Section titled “Separate STAFF read access from ADMIN execution”Operational state and redrive live in a Spring Actuator endpoint rather than the regular appointment API.
GET /actuator/profileReevaluationPOST /actuator/profileReevaluationThe read operation returns ProfileReevaluationOperationalSnapshot. The write operation accepts
ProfileReevaluationAdminAction, a reason, an idempotency key, tenant/clinic scope, target revision, and a limit. It does not
trust an actor written into the request body. ProfileReevaluationAdminActorResolver obtains the audit actor only from the
authenticated SchedulingUserPrincipal, then verifies issuer, token ID, authentication time, and the allowed clinic list.
The runbook sequence is:
- Verify the
ADMINrole andSCOPE_profile-reevaluation:operatepermission. - Require
tenantGroupIdandclinicId, then verify that the authenticated principal is allowed to operate on that clinic. - Use
PREVIEWto inspect the actual target and target revision. - Execute only when the preview matches the approved scope.
- Re-read the new job lineage, outcomes, backlog, and whether any
CONFIRMEDappointment changed.
This is also why the screen has no “retry everything” button. A failure could be a privacy incident, CRM latency, or a policy mismatch for one revision. Redriving a broad scope without classifying the cause can repeat the problem.
Rollout and rollback are separate from implementation complete
Section titled “Rollout and rollback are separate from implementation complete”Having a job model and endpoint in code does not mean that every clinic is ready to use them. The runbook expands mutation mode in this order:
DISABLED → DRY_RUN → APPLY_PROPOSED → APPLY_PROPOSED_AND_HELDThe default targets are HELD=5m and PROPOSED=30m. They are queue-wait objectives, not guarantees that every job will finish in
that time. A clinic-specific override cannot move an already-created job’s processing time backward.
Rollback is not a bulk reversal of already successful reservation transactions. It is a procedure that stops new mutation and checks these invariants:
mutation-mode=DISABLEDorenabled=falsedrainState=DRAINEDandactiveLeases=0- zero changes to
CONFIRMEDappointments - valid
HELDallocation is not lost except through a normal atomic replacement - job, outcome, inbox, quarantine, outbox, and audit rows are preserved
Do not mark a rollback as proof that completed jobs returned to their original state. Re-read state and history to determine which jobs finished and which remain.
Distinguish current implementation, approved design, and operational readiness
Section titled “Distinguish current implementation, approved design, and operational readiness”| Area | What this article covers |
|---|---|
| Current implementation | Minimal CRM event, scope-level latest head, durable job states and outcomes, PROPOSED/HELD eligibility, fair dispatch across clinics, an operational snapshot, and a redrive endpoint that verifies authentication and clinic scope |
| Approved operational design | PREVIEW before scoped EXECUTE, DISABLED → DRY_RUN → APPLY_PROPOSED → APPLY_PROPOSED_AND_HELD, separate retry and quarantine handling, and protection of CONFIRMED during rollback |
| Operational readiness | Real clinic allowlists, permission and redrive drills, assessment saturation alerts, drain rehearsals, privacy-incident response, and a read-only snapshot comparison before rollout |
| Follow-up improvements | A proposal→acceptance→confirmation flow with consent evidence, a notification contract for new patient proposals, and an audit link between operational outcomes and CRM assessments |
Keeping implementation and readiness separate matters. The endpoint’s existence does not prove that a real clinic has practiced an
ADMIN redrive. Conversely, the eligibility rule and outcome records that protect CONFIRMED are current code behavior and should
not be described as merely future work.
A profile change still requires a final visit-commitment check
Section titled “A profile change still requires a final visit-commitment check”The operational flow can be summarized as the following next actions:
- The CRM sends a minimum event with a revision, fingerprint, assessment reference, and hash instead of the source profile.
- The appointment service merges the latest revision and reevaluates only current
PROPOSEDandHELDcommitments. - Assessment lookup failures become bounded
RETRY_WAITjobs; trust, privacy, and contract failures becomeQUARANTINEcases. - Outcomes are recorded as
PROPOSAL_SUPERSEDED,HOLD_KEPT,HOLD_REPLACED,FALLBACK_TO_PROPOSED, orSKIPPED_*. CONFIRMEDis not changed automatically; a new proposal, patient consent, and operator confirmation are required when a change is needed.- STAFF reads the snapshot and preview, while ADMIN can redrive only within an authenticated clinic scope.
- After rollback or drain, re-read completed jobs, outcomes, and allocations to establish the final state.
An operations screen should provide more clarity, not merely more information. In one view, STAFF should be able to distinguish whether a job is eligible, whether it can run now, whether a failure can be retried or must be investigated, and whether the confirmed visit commitment was protected.
For the underlying design boundaries, see the CRM profile change and reservation reevaluation visual companion. The visual companion presents the state, permission, and privacy boundaries as a separate board; this article translates them into STAFF’s next operational action.
References
Section titled “References”- clinic-appointment repository
- Profile change and reservation reevaluation design
- Profile reevaluation implementation plan
- Profile reevaluation operations runbook
PatientSchedulingAssessmentChangedProfileReevaluationModelProfileReevaluationRecordsProfileReevaluationHealthIndicatorProfileReevaluationEndpoint- Recovering appointments changed by clinic conditions
Comments
Leave a note or reaction with your GitHub account.