Skip to main content

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:

  1. Hashes externalEventId against the idempotency store; returns the existing case if one matches.
  2. Groups passengers by PNR; preserves family/group associations.
  3. Computes the stay plan — check-in, check-out, nights — from the next flight's scheduled departure and policy buffers (late checkout, early check-in).
  4. Classifies each passenger by tier: ECONOMY, PREMIUM_ECONOMY, BUSINESS, FIRST, CREW.
  5. Emits case.created and 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: true and the rooms requested fit inside maxAutomaticRoomsPerWave, the demand moves straight to APPROVED.
  • Manual: otherwise an OPS_SUPERVISOR must call POST /demand/:urn/approve.

Approval enqueues nexa-allocation with { caseUrn, demandRequestUrn, actor }.

Allocation

The allocation worker:

  1. Loads the active policy for the airport / airline / country context.
  2. 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.
  3. Filters candidates by policy constraints (stars, distance, price cap, amenities, excluded chains).
  4. Scores survivors per tier with a multi-objective weighted sum: cost (dominant), distance, consolidation bonus.
  5. Assigns groups to hotels honoring PNR integrity, special-needs requirements, and contract-direct preferences.
  6. Persists an allocation wave with every candidate, input score, assignment, and total cost for auditability.

Wave outcome drives status:

  • COMPLETEDBOOKING
  • PARTIALBOOKING (what fits) + MANUAL_REVIEW item for the rest
  • FAILEDMANUAL_REVIEW

Booking

For each allocation the booking orchestrator:

  1. Checks idempotency on (caseUrn, waveUrn, groupId). If a CONFIRMED reservation already exists, returns it.
  2. Calls the booking provider for the primary hotel with up to 5 retries, exponential backoff (2, 4, 8, 16, 32 seconds).
  3. On exhaustion, falls back to the next 3 ranked hotels with one attempt each.
  4. Adjusts direct-contract inventory on success.
  5. 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:

CategoryPriorityTypical trigger
ALLOCATION_FAILEDHIGHNo hotels matched the policy
BOOKING_FAILEDHIGHAll fallbacks exhausted
PAYMENT_FAILEDMEDIUMPSP rejection
PARTIAL_FULFILLMENTMEDIUMBooked some, manual rest
POLICY_VIOLATIONLOWNeeds 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_RESIDENT untouched.

Everything is recorded in audit with the operator and reason that triggered it.

Was this helpful?