Overbooking and refund policies, holds that take capacity and optional seats and expire on a passed-in clock, and a check-in close that seats groups together or bumps them with compensation.
The airline round, as in the lesson. Cabin, Flight, the OverbookingPolicy and RefundPolicy interfaces, NoOverbooking, the booking states with their LEGAL table, and the Booking with its move are given. You build the two policies and the BookingService that keeps the two limits apart: a count of passengers per cabin (sold) and an owner per seat (owner). There is no clock and no sleep: every method takes now in seconds, and money is whole cents.
PercentOverbooking(rate, comp_multiple=2): limit(capacity) is floor(capacity * (1 + rate)); compensation(fare) is fare * comp_multiple.
TieredRefund(tiers): tiers maps a fare class to (minimum hours before departure, percent) pairs in any order. refund(paid, fare_class, hours_before) takes the tier with the longest notice that hours_before still meets and returns paid * percent // 100; no tier met, or an unknown fare class, returns 0.
BookingService(flights, overbooking, refunds, hold_ttl=900) (owner and sold are keyed (flight id, seat label) and (flight id, cabin); booking numbers are P1001, P1002, ...):
| Method | Does |
|---|---|
expire(now) |
every HELD booking with expires_at <= now moves to EXPIRED and gives back its capacity and seats; returns how many |
hold(fid, cabin, fare_class, fare, passengers, now, seats=None) |
expire first; raise IllegalState if now >= departs_at; raise SoldOut if sold + n would exceed overbooking.limit(len(layout)); seats is one label per passenger (an entry may be None) or omitted: each label must be in the cabin and not repeated (ValueError, JavaScript RangeError; also the wrong number of seats) and not owned (SeatTaken). Only then create the booking (expires_at = now + hold_ttl), take the capacity and the seats. A failed hold changes nothing |
ticket(pnr, now) |
expire first; an EXPIRED booking raises HoldExpired; otherwise move to TICKETED and set ticketed_at = now |
cancel(pnr, now) |
expire first; IllegalState if the flight has left; the paid amount is fare * passengers for a TICKETED booking and 0 for a HELD one; move to CANCELLED, give back capacity and seats; return and store refunds.refund(paid, fare_class, (departs_at - now) // 3600) (0 when nothing was paid) in money |
close(fid, now) |
expire first. For each cabin, take the TICKETED bookings of the flight in order of (ticketed_at, pnr). If the free seats (in layout order) cover the seats the booking still needs (None entries), assign them in order and move to BOARDED; otherwise move to BUMPED, give back capacity and seats, and set money = fare * passengers + passengers * compensation(fare). Return {"boarded": [...], "bumped": [...]} of booking numbers |
In Python the service has bookings, owner, sold as dicts; in JavaScript and TypeScript they are Maps keyed "flight/seat" and "flight/cabin", with soldIn(fid, cabin) to read a count. JavaScript and TypeScript use camelCase (holdTtl, ticketedAt, expiresAt), Cabin.ECONOMY and BState.HELD constants and string states.
Write your code, then run 11 example tests and 5 hidden tests. Each run is limited to 8 seconds: an infinite loop gets stopped and the runtime restarts, your code stays.