Availability control decides whether a request for seats in a given fare product can be accepted right now. Poor control leaks revenue (selling cheap seats on high value itineraries) or destroys conversion (over‑protecting inventory). For engineers, the challenge is translating revenue management (RM) output (forecasts, bid prices, protection levels) into deterministic APIs used by search and booking flows, while handling multi‑segment journeys where value is itinerary dependent.
Key tension: The seat is physical per leg, but value is per Origin & Destination (O&D) itinerary. O&D control resolves this by optimizing over displacement value of alternatives.
Three layers supply the answer “how many seats are open in booking class X for itinerary Y?”
Network vs Leg Control: Leg control opens classes solely based on leg forecast. O&D (network) control considers displacement: selling a seat A–B now might block a higher yield A–C passenger connecting through B later.
Two mainstream implementations:
Nesting defines hierarchical access of fare classes to shared seat inventory.
Type | Description | Pros | Cons / Risks |
---|---|---|---|
Traditional Classical Nesting | Ordered fare classes (Y highest → deep discount). Lower class opens only if all protected higher-demand seats satisfied. | Simplicity, easy to communicate | Over-protection if forecast variance high |
Virtual Nesting (Buckets) | Map fare products into aggregated value bands (virtual classes) rather than one-to-one with RBD. | Fewer control variables, dampens noise | Loss of granularity for micro-yield segments |
Hybrid / Dynamic Nesting | Periodically reassign fare products to nests based on updated bid values. | Adapts to demand shifts | Complex; requires atomic updates to avoid flicker |
Open Pricing / Continuous | Rather than discrete nests, dynamic price presented with availability gating still referencing minimum acceptance thresholds. | Revenue uplift via finer granularity | Harder explainability & auditing; bridging legacy systems |
Implementation Note: Maintain an immutable snapshot: fareProduct → nestID → protectionLevelVersion. Pricing and availability queries must reference the same version (or detect mismatch).
Bid price: Shadow (opportunity) cost of selling one more seat on that leg at that time. Acceptance rule (for itinerary with price P across legs L):
Accept if: P >= Σ (bidPrice[leg])
Else reject / close lower classes
Network control computes bid prices so that if you accept an itinerary, you expect non-negative incremental contribution vs keeping the seat for potential future higher-value demand.
Edge Cases: Bid price monotonicity across time-bands and cabins may need smoothing (avoid oscillating open/close states). Implement post-processing smoothing (e.g., moving average or convexity constraint) after raw optimization output.
Married segments enforce joint availability of multiple legs used together to prevent re-pricing or rebooking passengers at unintended values.
Implementation Tip: Expose an idempotent API to validate married segment integrity prior to ticketing and again during changes. Caching stale marriages is a common failure source.
Input: O&D request (A->C), travel date(s), cabin, constraints (max connects)
1. Enumerate feasible itineraries (A-B-C, A-C nonstop if exists).
2. Fetch leg inventory snapshots (A-B flight X, B-C flight Y).
3. Load control artifacts:
- Nest definitions & protection levels (or)
- Bid prices per leg time-slice
4. For each itinerary:
a. Compute controlling value threshold (sum bid prices).
b. Determine candidate fare products / RBD mapping.
c. Evaluate open condition:
(FareAmount >= threshold) AND (remainingPhysicalSeats > 0)
d. Apply married segment constraints (if multi-leg).
e. Apply blackout / channel overrides.
5. Produce availability vector:
- Per RBD: integer 0..maxDisplay (often capped at 9)
- Or priced offer list (for dynamic).
6. Attach provenance:
- InventorySnapshotVersion
- ControlVersion (bid price or protection timestamp)
- EvaluationTimestamp
7. Optionally pre-reserve (soft block) if hold logic required.
Fast Path Optimization: For large shopping queries, compress evaluation by grouping itineraries with identical leg sets and control thresholds to reuse computed open/close states.
LegKey = flightNumber + departureDate + boardPoint + offPoint
InventorySnapshot {
legKey -> { cabin: { RBD: { seatsRemaining, lastUpdate } } }
}
ControlVersion { id, generatedAt, method: 'BID_PRICE' | 'PROTECTION', smoothingParams }
BidPriceTable {
controlVersionId,
legKey -> { bidPriceCurrency, bidPriceValue }
}
ProtectionLevels {
controlVersionId,
legKey -> [ { nestId, protectSeats } ordered high->low ]
}
FareProduct {
id, fareBasis, cabin, bookingClass, anchorPrice, market O&D, brandId, channelPolicy
mapping: { bookingClass -> nestId }
}
MarriageGroup {
groupId, segmentLegKeys[], status, createdAt, version
}
AvailabilityResult {
requestId, od, itineraries:[
{ segmentLegKeys[], marriageGroupCandidate?, rbdAvailability:{ RBD: count },
provenance:{ inventoryVersion, controlVersionId, evalAt } }
]
}
Physical seats on Leg A-B: 20
Protection (high->low):
Nest H (Full Y): protect 5
Nest M: protect 8 (cumulative 13)
Nest L: protect 16 (cumulative 16)
Open seats logic for discount nest D:
Remaining seats = 20
Cumulative protection above D = 16
Open seats for D = max(0, 20 - 16) = 4
Display cap (9) -> show '4' (or '4' truncated if >9)
Leg A-B bidPrice = 70
Leg B-C bidPrice = 60
Itinerary A-B-C proposed fare: 115
Threshold = 70 + 60 = 130
115 < 130 => Reject / close low RBDs
If dynamic engine can adjust: raise fare to >=130 or suppress itinerary.
Booking: A-B-C (group G123). Later passenger attempts change:
Remove B-C only.
System:
Validate group G123: segment removal breaks marriage.
Reprice A-B alone -> A-B single-leg fare may be higher or closed.
If accepted: create new booking for A-B, retire group G123, charge difference.
Dilution: Selling a seat at a lower net contribution than an alternative expected demand would have provided. Drivers:
Detection Metric: Post-hoc compare realized average yield per leg vs forecast conditional yield distribution; deviation beyond tolerance triggers recalibration or anomaly alert.
Volatility: Availability changes frequently (bookings, cancellations, RM updates). Cache invalidation must be sub-minute for critical O&Ds in peak booking horizon.
Edge Strategy: For global distribution, replicate control artifacts (bid prices) to edge POPs; keep write path (booking commit) routed to central authority to perform atomic seat decrement and push diff events outward.
Failure | Symptom | Likely Root Cause | Mitigation |
---|---|---|---|
Phantom Availability | Itinerary appears bookable; fails at commit | Stale cache; marriage revalidation failure; race between hold and decrement | Short-lived availability tokens; commit-time host recheck |
Dilution Spike | Yield per seat drops vs forecast band | Missed bid price update; mis-nesting after RM file ingest error | Automated control version completeness check & fallback |
Oscillating Open/Close | Repeated class flicker within minutes | Un-smoothed bid price noise or demand forecast jitter | Smoothing algorithm; minimum hold window for state transitions |
Married Segment Abuse | High-yield seats vanish w/ unusual cancellations | Missing integrity check on partial cancel | Trigger reprice workflow; audit logs & anomaly detection |
Oversell (Above Target) | Leg load factor > planned oversell tolerance | Parallel booking race; control artifact lag > threshold | Atomic decrement service; sequence number gating |
POST /reserve
Body: { itineraryId, legSegments:[{legKey, rbd, seats:1}], requestId }
Process:
1. Validate seatsRemaining >= requested for each leg+rbd
2. Decrement atomically (optimistic CAS with version)
3. Emit event INVENTORY_RESERVED {requestId, versionAfter}
4. TTL hold; confirm (ticket) or release
function isOpen(itinerary, fareAmount, controlVersion):
threshold = sum(bidPrice[legKey])
return fareAmount >= threshold
function seatsOpenForClass(leg, class):
pl = cumulativeProtectionAbove(class)
return max(0, physicalSeats - pl)
Attach controlVersionId
to availability result; booking commit must either accept if currentVersion == responseVersion OR re-evaluate if changed.
// Keep open state at least MIN_WINDOW unless severe threshold violation
if (proposedState != currentState && timeSinceChange < MIN_WINDOW)
retain currentState
else
apply proposedState
As the industry migrates to offers & orders, availability logic moves closer to dynamic offer construction:
anchorThreshold
(sum bid prices) and marginAdd
.Conceptual, non-proprietary references used for framing (specific proprietary algorithms and data feeds intentionally not reproduced):
Consult licensed RM vendor / airline internal documentation for proprietary parameterization and optimization formulations.