Data Model
Nexa's data model is case-centric: a small number of aggregates hang off the case, and every aggregate is timestamped, URN-addressable, and audit-backed.
URNs
Every top-level entity has a URN of the form:
urn:nexa:<entity>:<shortid>
URNs are stable, opaque, and safe to log. They never contain PII. All inter-service references use URNs rather than internal database identifiers.
Core aggregates
The diagram above uses UML composition (*--) — every aggregate is owned by the case and shares its lifecycle. References between siblings (e.g. a reservation's groupId, an allocation's hotelUrn) are URN pointers, not embedded objects.
Case
The root aggregate.
| Field | Description |
|---|---|
urn | Case URN |
externalEventId | Airline-supplied event ID used for idempotency |
status | State from the case lifecycle |
flight | Original disrupted flight (IATA, schedule, delay reason) |
nextFlight | Reaccommodation details (used to calculate stay plan) |
passengerGroups[] | Grouped passengers — see below |
policyUrn | Policy active at allocation time |
airport | Anchor airport for the case |
createdAt, updatedAt | Standard timestamps |
Passenger Group
Passengers are always handled at the group (PNR) level. Groups never split across hotels unless explicitly overridden.
| Field | Description |
|---|---|
groupId | Stable within the case |
pnr | Booking reference |
tier | ECONOMY · PREMIUM_ECONOMY · BUSINESS · FIRST · CREW |
stayPlan | { checkIn, checkOut, nights, buffers } |
passengers[] | referenceId, type (ADT/CHD/INF), specialRequirements[] |
operationalStatus | UNASSIGNED · ASSIGNED · CHECKED_IN · CHECKED_OUT · LOCAL_RESIDENT |
Policy
Versioned and activated explicitly.
| Field | Description |
|---|---|
urn | Policy URN |
version | Monotonic; a new activation freezes the previous version |
context | { airport, airline, country } — used for matching |
tiers | Which tiers this policy applies to |
constraints | minStars, maxDistanceKm, maxNightlyRate, amenities, excluded chains |
buffers | lateCheckoutMinutes, earlyCheckInMinutes |
mode | BATCH (schedule waves) or ON_DEMAND (run immediately) |
autoApproveDemand | Boolean + maxAutomaticRoomsPerWave cap |
incrementalRequiresApproval | If true, incremental demand is never auto-approved |
Hotel (normalized offer)
Providers return wildly different shapes; Nexa normalizes them into one:
| Field | Description |
|---|---|
hotelUrn | Internal URN (stable across providers) |
providerOfferId | Provider-specific reference (required for booking) |
provider | AMADEUS · HOTELBEDS · CONTRACT |
name, address, location (lat/lon), stars | Basic attributes |
distanceKm | From the anchor airport |
nightlyRate, totalPrice, currency | Pricing |
amenities[] | Normalized amenity tags |
acceptedPayments | Payment methods the hotel will honor |
cachedAt, ttl | Inventory freshness |
savingsVsMarket | If this is a direct contract, vs cheapest GDS |
Reservation
| Field | Description |
|---|---|
urn | Reservation URN |
caseUrn, waveUrn, groupId | Traceability |
providerReservationId | Provider booking ID |
confirmationCode | Shown to the passenger |
status | REQUESTED · CONFIRMED · CANCELLED · FAILED |
checkIn, checkOut, rooms[] | Booked stay |
voucher | Rendered HTML and text payloads |
paymentInstructions | Corporate guarantee or card reference |
Audit
Append-only. One document per action:
| Field | Description |
|---|---|
entity | case, policy, demand, reservation, etc. |
entityUrn | Target URN |
actor | User URN or system actor |
action | e.g. approve, rebook, override |
reason | Free-text reason (required on manual actions) |
before, after | Snapshot of changed fields |
at | Immutable timestamp |