Case Lifecycle
A case represents a single disruption event — one flight that was cancelled, rescheduled, or delayed long enough to trigger hotel accommodation. This page walks through every state transition a case goes through, what triggers it, and what side-effects each transition produces.
State machine
Entry: case creation
The airline posts a disruption event:
POST /cases
Authorization: Bearer <token>
{
"externalEventId": "EVT-MAD-2026-04-22-001",
"flight": { ... },
"nextFlight": { ... },
"passengerGroups": [ ... ]
}
Nexa:
- Hashes
externalEventIdagainst the idempotency store; returns the existing case if one matches. - Groups passengers by PNR; preserves family/group associations.
- Computes the stay plan — check-in, check-out, nights — from the next flight's scheduled departure and policy buffers (late checkout, early check-in).
- Classifies each passenger by tier:
ECONOMY,PREMIUM_ECONOMY,BUSINESS,FIRST,CREW. - Emits
case.createdand persists an audit entry.
Initial status: OPEN.
Demand approval
Before any allocation work runs, demand has to be approved. This intentional checkpoint prevents automated blast-radius when a policy mis-matches reality.
- Auto-approve: if the active policy says
autoApproveDemand: trueand the rooms requested fit insidemaxAutomaticRoomsPerWave, the demand moves straight toAPPROVED. - Manual: otherwise an
OPS_SUPERVISORmust callPOST /demand/:urn/approve.
Approval enqueues nexa-allocation with { caseUrn, demandRequestUrn, actor }.
Allocation
The allocation worker:
- Loads the active policy for the airport / airline / country context.
- Calls every configured hotel provider in parallel (
hotels.search). Results are deduplicated across providers using a 3-layer strategy: provider-id match → geo-proximity + star match → name+address exact match. - Filters candidates by policy constraints (stars, distance, price cap, amenities, excluded chains).
- Scores survivors per tier with a multi-objective weighted sum: cost (dominant), distance, consolidation bonus.
- Assigns groups to hotels honoring PNR integrity, special-needs requirements, and contract-direct preferences.
- Persists an allocation wave with every candidate, input score, assignment, and total cost for auditability.
Wave outcome drives status:
COMPLETED→BOOKINGPARTIAL→BOOKING(what fits) +MANUAL_REVIEWitem for the restFAILED→MANUAL_REVIEW
Booking
For each allocation the booking orchestrator:
- Checks idempotency on
(caseUrn, waveUrn, groupId). If aCONFIRMEDreservation already exists, returns it. - Calls the booking provider for the primary hotel with up to 5 retries, exponential backoff (2, 4, 8, 16, 32 seconds).
- On exhaustion, falls back to the next 3 ranked hotels with one attempt each.
- Adjusts direct-contract inventory on success.
- Generates a voucher and enqueues the notification job.
If every fallback fails, the case transitions to MANUAL_REVIEW with an item categorized as BOOKING_FAILED and a priority level.
Notifications
The notification worker renders a localized HTML template, sends through SendGrid (or the limited sandbox channel), and records a SENT / FAILED status per (groupId, type, channel, reservationUrn). Duplicate sends are blocked by the idempotency index — re-running the case will never double-email passengers.
Manual review
Manual-review items are the platform's escape hatch. Categories:
| Category | Priority | Typical trigger |
|---|---|---|
ALLOCATION_FAILED | HIGH | No hotels matched the policy |
BOOKING_FAILED | HIGH | All fallbacks exhausted |
PAYMENT_FAILED | MEDIUM | PSP rejection |
PARTIAL_FULFILLMENT | MEDIUM | Booked some, manual rest |
POLICY_VIOLATION | LOW | Needs policy relaxation |
Each item carries: attempted hotels, failure reasons, recommendations from the exception agent, and the actions permitted to the on-call operator (re-book, override, cancel).
Close
A case closes when:
- All reservations reach
CHECKED_OUT, or - The operator explicitly closes the case with a reason (e.g., airline re-routed pax without hotel needs).
Closure is a snapshot point for analytics and locks audit state for the case.
Re-accommodation
If the airline updates the next flight mid-flow, Nexa receives a case.reaccommodated event, recomputes the stay plan, and cascades changes:
- Shortens or extends active reservations where the provider supports it.
- Queues re-booking for passengers whose stay plan no longer matches current reservations.
- Leaves passengers classified as
LOCAL_RESIDENTuntouched.
Everything is recorded in audit with the operator and reason that triggered it.