Skip to content
ML Academy

project-016 · Forecasting · aviation

Flagship: final bookings per departure from booking-curve snapshots

For every departure of the fictional carrier's 19 flights, what will final bookings be when the forecast is issued 30, 14 and 7 days before departure, and how uncertain is that number? The revenue-management (RM) analyst uses it for one decision: keep the lowest fare level (L) open for this departure, or close it to protect seats for later, higher-fare demand.

Dataset
Synthetic airline sample database (airline_sample.sqlite: booking_curve, flight_day, calendar)
Licence
Synthetic data generated by the academy's own simulator (project code); no third-party data.
Reference run
executed

At a glance

  • Data

    Synthetic airline sample database (airline_sample.sqlite: booking_curve, flight_day, calendar) (synthetic) · booking_curve 270,712 rows (19 flights x 1,096 departure dates x 13 snapshots); flight_day 20,824 rows; 2022-01-01 to 2024-12-31; about 19 MB SQLite (row counts checked by query on 2026-10-11).

    Details on data
  • Baseline

    Two baselines, both computed with the same point-in-time rule as the candidates: Seasonal naive by flight: final bookings of the most recent already departed departure…

    Details on baseline
  • Validation

    Rolling-origin backtest with an expanding training window: 12 monthly origins (first day of each month in 2024).

    Details on validation
  • Metric

    WAPE and MAE in seats per horizon d; bias; MASE scaled by the seasonal-naive-by-flight in-sample error; 80% interval coverage and mean width;

    Details on metric

Success criterion

On a rolling-origin backtest over departures in 2024 (synthetic), the chosen method must:

  1. beat the better of the two baselines on WAPE at each issue horizon (d = 30, 14, 7) by more than twice the standard error of the per-origin differences (so the gain is not origin-to-origin noise);
  2. not be worse than that baseline by more than 5% WAPE in any pre-declared slice (market, holiday window, flights that hit the bookable limit, IST-ADB/IST-AYT/IST-FRA after 2024-03-01);
  3. deliver 80% intervals whose empirical coverage lies between 75% and 85% at every horizon;
  4. come with a written L-class decision rule whose expected cost under the stated hypothetical costs is lower than "never close L" and "close L when the point forecast reaches capacity". If no candidate meets 1-2, the deliverable is the better baseline plus the evidence for that choice.

Context

This is the learner's flagship for Weeks 1-6 and is built across the guided weekend studios; Day 28 is the defence. Airline RM forecasts final bookings from the booking curve: cumulative bookings observed at fixed snapshots before departure. The classic baseline is the pickup method (current bookings plus historical pickup from this point to departure), which remains a strong benchmark in the RM literature. The simulator deliberately includes an unobserved demand shock that RM pricing partly reacts to (confounded fares), capacity that truncates bookings (censored demand), Turkish public holidays, destination events and a competitor-capacity step on IST-ADB, IST-AYT and IST-FRA from 2024-03-01. Reference implementation python/reference/r04_airline_demand_backtest (project-017) provides a reusable rolling-origin harness, but the snapshot features, decision rule, failure analysis and transfer task are the learner's own work.

Data

Dataset
Synthetic airline sample database (airline_sample.sqlite: booking_curve, flight_day, calendar) (synthetic)
Licence
Synthetic data generated by the academy's own simulator (project code); no third-party data.
Size
booking_curve 270,712 rows (19 flights x 1,096 departure dates x 13 snapshots); flight_day 20,824 rows; 2022-01-01 to 2024-12-31; about 19 MB SQLite (row counts checked by query on 2026-10-11).
Verification
executed

Access

Built locally, no network or login: cd python && uv run python -m capstone.airline_sim writes python/artifacts/airline_sample.sqlite (deterministic, seed 42). All rows are synthetic; flight numbers ZX1xx are fictional and airport codes are labels only. Latent truth (unconstrained demand, the unobserved shock) is not in the database; it is returned by capstone.airline_sim.simulate() for teaching checks only.

Grain

booking_curve: one row per flight_no x departure date x days_to_departure snapshot (90, 60, 45, 30, 21, 14, 10, 7, 5, 3, 2, 1, 0). A snapshot with days_to_departure = k becomes known on date - k. flight_day: one row per flight_no x departure date; bookings_final equals the d = 0 cumulative value. Forecast unit: flight_no x departure date x issue horizon d.

ColumnMeaning
booking_curve.dateDeparture date (ISO text), not the date the snapshot was taken
booking_curve.days_to_departureSnapshot offset k; the row is observable from date - k onward
booking_curve.cumulative_bookingsBookings on hand at the snapshot; cancellations are reported only at departure level in flight_day
booking_curve.bookingsIncrement since the previous (earlier) snapshot
flight_day.bookings_finalTarget: bookings at departure, capped at floor(1.06 x capacity) by the overbooking limit (censored)
flight_day.capacitySeats (160, 180 or 280); constant per route in the sample
flight_day.avg_fare / fare_level / revenue / passengers / load_factorRealised outcomes of the departure; unknown at issue time and must not be features for that departure
calendar.is_holiday / eventTurkish public holidays and destination events; treated as known in advance (state this assumption)

EDA and data-quality rules

  • Uniqueness: exactly 13 snapshots per flight_no x departure date; fail the build on duplicates or gaps.

  • Monotonicity: cumulative_bookings must be non-decreasing as days_to_departure falls; count and report violations instead of silently sorting.

  • Consistency: the d = 0 cumulative value equals flight_day.bookings_final for every departure; any mismatch is a join or grain error (Day 7).

  • Censoring flag: mark departures with bookings_final = floor(1.06 x capacity); in the seed-42 sample this is roughly 17-26% of flight-days depending on gauge, so it is a core slice, not an outlier.

  • Break check: plot weekly mean bookings for IST-ADB, IST-AYT, IST-FRA around 2024-03-01 before choosing a training window.

Baseline

Two baselines, both computed with the same point-in-time rule as the candidates:

  • Seasonal naive by flight: final bookings of the most recent already departed departure of the same flight_no on the same weekday (for d = 30 that is at least five weeks back).
  • Additive pickup: cumulative bookings at snapshot d for this departure plus the mean pickup from d to 0 over the last eight departed same-flight, same-weekday departures. This is the RM benchmark to beat.

Features

For departure D issued at T = D - d, every feature must be computable from rows observable on or before T:

  • this departure's snapshots with days_to_departure of at least d: cumulative bookings at d, pace over the last available interval (for example cum at 14 minus cum at 21 when d = 14), ratio of cum at d to the historical same-flight mean at d;
  • history of other departures only if they had departed by T (final bookings, historical pickup curves);
  • calendar known in advance: weekday, month, holiday window, event flag, days to the next holiday;
  • flight_no, route and market as categorical identifiers. Excluded and documented: avg_fare, fare_level, revenue, passengers, load_factor and cancellations of departure D (outcomes); competitor_price and competitor_capacity of D (the column has no observation timestamp, so its availability at T cannot be shown; use values from departed flights only if at all). Every feature is built by one function features_as_of(T) that is unit-tested against a hand-checked departure.

Candidate methods

  • Regression pickup (linear model of final bookings on cumulative bookings at d, per market and d)

    Keeps the pickup structure but learns the slope, so a strong early pace scales up rather than adding a fixed increment. Cheap, explainable to RM analysts, and a fair test of whether nonlinearity is needed.

  • LightGBM global model per horizon d on snapshot, history and calendar features

    One model across 19 flights can learn interactions (holiday x market, pace x weekday) that pickup ignores. It is only worth its complexity if it clears the success criterion; trees cannot extrapolate beyond seen booking levels, which matters after the 2024 competitor break.

Validation design

Rolling-origin backtest with an expanding training window: 12 monthly origins (first day of each month in 2024). At origin O the model is trained only on departures with D before O (their final bookings are known), then forecasts departures in [O, O + 28 days) at each issue horizon d in {30, 14, 7}, with features built as of T = D - d. Departures whose issue date T falls before O would use information the model did not have at O; either skip them or rebuild features as of T and document which you chose. Hyperparameters are tuned only on 2023 origins and frozen before the 2024 evaluation (Day 15). Report per-origin and pooled metrics.

Leakage checks

Assert that no feature row for (D, d) reads a snapshot with days_to_departure below d or a departure after T. Run a deliberate-leak control: add the d = 0 snapshot as a feature and confirm the error collapses; if your honest model is close to that control, you have a leak. Confirm that RM-set fares are excluded, because they react to the unobserved demand shock and encode the outcome.

Bias and representativeness

Report bias (mean signed error) per slice. Expect systematic under-forecast on departures that hit the bookable limit, because the target is truncated; do not "fix" it by inflating forecasts without saying the model is then predicting something other than bookings.

Robustness checks

Re-run with the training window starting 2023-03-01 and with the 2024-03-01 routes excluded from training to see whether the choice of winner changes. Perturb the pace feature by plus or minus 10% to check the model is not brittle on one input. Verify that results are identical across two runs with the same seed.

Technical metrics

WAPE and MAE in seats per horizon d; bias; MASE scaled by the seasonal-naive-by-flight in-sample error; 80% interval coverage and mean width; pinball loss at the 0.1 and 0.9 quantiles.

Business interpretation

Expected cost of the L-class rule with hypothetical unit costs: spoilage (an empty seat) valued at the average L fare, displacement (an L seat that later H demand would have bought) valued at H minus L fare. Compare three rules: never close, close when the point forecast reaches capacity, close when the forecast probability of reaching the bookable limit exceeds the cost ratio. State that closing L changes future bookings, so the backtest cost is an approximation (the forecast was trained under historical RM behaviour).

Error slices

  • Issue horizon d = 30 vs 14 vs 7 (does the candidate's advantage shrink as the booking curve fills?).

  • Departures at the bookable limit vs below it (censoring).

  • Holiday windows and destination-event dates vs ordinary days.

  • IST-ADB, IST-AYT, IST-FRA after 2024-03-01 (competitor capacity step) vs other routes.

  • Market: domestic, intl-short, intl-long (different booking-curve shapes).

Implementation steps

  • Build the database, write the grain and availability table (column, observable from, owner) and get it reviewed before any modelling.

  • Implement features_as_of(T) and its unit test on one departure computed by hand in SQL.

  • Implement both baselines and the rolling-origin harness (reuse the r04 harness design); produce a per-horizon baseline table.

  • Fit regression pickup, then LightGBM; tune only on 2023 origins; freeze configurations in a YAML file.

  • Run the 2024 backtest, compute metrics with origin-level standard errors and every pre-declared slice.

  • Produce 80% intervals (quantile LightGBM or residual quantiles by d and market) and check coverage.

  • Run the L-class decision analysis with the hypothetical costs and a sensitivity table over the cost ratio.

  • Write the report: decision, evidence, the censored-demand limitation, failures found, and the transfer task.

Reproduction

cd python
uv run python -m capstone.airline_sim          # rebuild the synthetic DB (seed 42)
uv run python -m reference.r13_booking_curve_final_bookings.run   # optional: reference implementation (recorded run in RESULTS.md)

The learner's own repository must expose one entry point that rebuilds features, runs the backtest and writes report.md, metrics_by_horizon.csv and slices.csv, pinned by uv.lock and a fixed seed.

Limitations

  • The target is bookings truncated at floor(1.06 x capacity), not demand. On roughly one departure in five the model learns the cap, so the forecast must not be used to size capacity or to estimate how much demand was turned away (project-017 shows the gap against simulator truth).

  • Synthetic data: the booking-curve shape, shock structure and holiday effects come from a simulator with known parameters; results show method behaviour, not real-airline accuracy.

  • No fare-class split: the sample has departure-level bookings only, so the L-class rule is a proxy for a real nested-class control.

  • The backtest cost of a closure rule ignores the feedback from closing a class to future bookings.

Production and monitoring

Daily batch after the snapshot load: forecasts for every open departure at each snapshot horizon, versioned by issue date so later evaluation uses the forecast that was actually issued. Monitor per-d bias on departed flights (labels arrive at departure, so a d = 30 forecast is graded 30 days later), share of departures at the bookable limit, pace anomalies versus historical curves, and feature freshness. Fall back to additive pickup when the feature job fails or the candidate's rolling WAPE exceeds the baseline for two consecutive weeks. Analysts can override for known events; overrides are logged and evaluated separately.

Interview questions

  • Why is the d = 0 snapshot a leak at d = 14 but a valid feature for a departure that has already flown?

  • Your LightGBM wins pooled WAPE but loses on holiday departures. What do you ship and how do you explain it to the RM lead?

  • Bookings on full flights equal the cap. What does your forecast mean on those flights, and what would you need to forecast demand instead?

  • How would you show that the 2024 competitor change, rather than model decay, explains the error on IST-FRA?

Postmortem and alternatives

Write it after the defence. Typical findings to look for: a feature silently used a snapshot taken after T because the departure date was joined instead of the snapshot date; the candidate's gain sat entirely in d = 30 and vanished at d = 7, where pickup is hard to beat; coverage collapsed on capped departures. Credible alternatives to record: ship additive pickup with a calibrated interval; forecast unconstrained demand with an explicit censoring model (Tobit or an RM unconstraining method) once the simulator truth has validated it.

Reference implementation

Status: executed — outputs below are from the recorded run

cd python && uv run python -m reference.r13_booking_curve_final_bookings.run

r13 results - final bookings per departure from booking-curve snapshots (project-016)

Recorded run of cd python && uv run python -m reference.r13_booking_curve_final_bookings.run (full mode). Every number below is copied from the files in outputs/ written by that run (summary.json, data_checks.csv, metrics_by_horizon.csv, criterion1_paired.csv, per_origin_wape.csv, slices.csv, intervals.csv, interval_coverage_by_slice.csv, decision_costs.csv, decision_sensitivity.csv, criterion4_decision.csv, robustness.csv, leak_controls.csv, censoring_note.csv, success_criteria.csv, report.md). The data are synthetic (academy airline simulator, seed 42); fares and costs in the decision analysis are hypothetical.

ItemValue
Run date2026-10-12
Seedsdatabase 42; LightGBM random_state=42, deterministic=True, n_jobs=1 (a second LightGBM seed, 7, is a robustness row)
Python / numpy / pandas / scipy / scikit-learn / lightgbm3.12.13 / 2.5.3 / 3.0.6 / 1.18.1 / 1.9.1 / 4.7.0
MachineApple M5, macOS arm64, CPU only
Runtime of the recorded run166.4 s (shared laptop; other full runs during development took 100 to 120 s). --smoke: 8 s.
Databaseairline_sample.sqlite, sha256 a364e86b2c27990888ace9486e90e53ca5df4535816f68586a47dcf7447eb284; flight_day 20,824, booking_curve 270,712, calendar 1,096 rows (match the project record)
Evaluation12 monthly origins in 2024 (expanding window), 19 flights, issue horizons d = 30, 14, 7; 6,384 / 6,688 / 6,821 evaluated forecasts
Tuning4-cell LightGBM grid on the 12 monthly origins of 2023, using only departures dated before 2024-01-01; frozen in outputs/frozen_config.yaml before 2024 was scored
Determinismthe repeat run inside robustness.csv is bit-identical (determinism_max_abs_diff = 0.0)
Testsuv run pytest tests/test_r13_booking_curve_final_bookings.py (see the end of this file)

Findings

  1. The data satisfy the five quality rules of the record with zero violations. 13 snapshots for each of 20,824 departures, no duplicate keys among 270,712 rows, no decreasing cumulative curve among 249,888 consecutive snapshot pairs, the d = 0 snapshot equals bookings_final on all 20,824 departures, and each of the 19 flights has 1,096 dates. 19.3% of departures (4,013) end at the bookable limit floor(1.06 x capacity), so the target is truncated bookings, not demand (26.3% on the 280-seat flights, 21.1% on 160 seats, 17.4% on 180 seats).

  2. Additive pickup is the better baseline at every horizon, and the candidates beat it by a wide margin. Pooled 2024 WAPE:

    dseasonal naiveadditive pickupregression pickupLightGBM
    300.1800.0840.0280.026
    140.1720.0440.0160.013
    70.1470.0250.0130.009

    MASE (scaled by the in-sample seasonal-naive error) of LightGBM is 0.138, 0.066 and 0.048. Against additive pickup, LightGBM lowers WAPE by 68.4%, 71.7% and 64.2%; the mean per-origin difference is 34.2, 42.9 and 61.2 standard errors (12 of 12 origins better at every horizon), so success criterion 1 passes with a large margin. LightGBM was also the candidate chosen on 2023 at all three horizons.

  3. Most of the gain comes from a one-line model; LightGBM adds a small, consistent increment. Regression pickup (final on cum at d, one line per market) already reaches 0.028, 0.016 and 0.013. LightGBM improves on it by 0.0014, 0.0031 and 0.0036 WAPE points on the per-origin mean (SE 0.0002 each, 12 of 12 origins better), a relative gain of 5%, 19% and 28%. The extra gain is real in this data but small in absolute seats: mean absolute error 4.30, 2.04 and 1.47 seats for LightGBM against 4.53, 2.55 and 2.06 for regression pickup.

  4. No pre-declared slice is worse than the baseline. Relative to additive pickup the LightGBM change in WAPE ranges from -65.8% to -81.3% (d = 30), -64.7% to -84.9% (d = 14) and -55.2% to -81.8% (d = 7) over the six criterion-2 slices (three markets, holiday window or event, ex post at the limit, IST-ADB/AYT/FRA after 2024-03-01). On the three break routes after the competitor step LightGBM WAPE is 0.030, 0.014 and 0.010 (all departures: 0.026, 0.013, 0.009), so the step is visible as a mild degradation, not as a failure. The slice "at the bookable limit" is defined by the outcome and is reported because the project record names it, but it cannot be used at issue time.

  5. The 80% intervals reach the target coverage overall and over-cover where bookings are capped. Coverage of the shipped LightGBM intervals is 79.8%, 81.0% and 80.9% at d = 30, 14 and 7; mean width is 13.0, 6.4 and 4.6 seats. By slice the range is 75.7% to 92.9% (d = 30), 77.4% to 92.8% (d = 14) and 76.8% to 94.6% (d = 7); the highest value is always the "at the bookable limit" slice (93% to 95%), where the forecast is capped and the residual is one-sided. Intervals are relative residual quantiles from matured forecasts by horizon and market, rebuilt for every origin from departures dated before that origin, plus the 2023 pool.

  6. The first decision rule failed; a second, calibrated rule was added afterwards and is cheaper than both references. Costs are in hypothetical currency per departure, with the fares of decision_fares_hypothetical.csv (spoilage = L fare, displacement = H - L fare; domestic 79.0 and 24.7).

    dnever close Lclose when point >= capacityrule 4a: P(limit) from residual quantilesrule 4b: calibrated P(limit given f / limit bin)share closed by 4boracle
    3010.2515.0710.257.6213.2%0
    1410.0813.9010.085.8320.2%0
    710.0113.5710.014.9722.2%0

    Rule 4a never closes a class, so it equals "never close L": pooled relative residuals are roughly symmetric and about half of them are non-negative, so at a forecast equal to the limit the estimated probability is about 0.5, below the break-even probability L / H (0.76 for domestic). Rule 4b estimates P(final bookings reach the limit) in six fixed bins of forecast / limit (0.9, 0.95, 0.98, 0.99, 0.999) from matured forecasts only, and closes L when the estimate exceeds the break-even probability. In the 2024 forecasts of d = 14 the bins reach the limit in 0.0%, 0.3%, 34.9%, 70.0%, 86.9% and 94.1% of cases from the lowest bins upward.

  7. The cost ranking depends on the cost ratio. For d = 14, with the displacement cost multiplied by 0.25, 0.5, 1, 2, 4, 8, 16: never close costs 2.52, 5.04, 10.08, 20.16, 40.32, 80.65, 161.3; rule 4b costs 2.47, 4.24, 5.83, 7.90, 9.50, 12.48, 13.36 and closes 3.8%, 10.9%, 20.2%, 23.8%, 24.6%, 25.4%, 30.3% of departures; rule 4a only starts closing at a multiplier of 4 (cost 25.97). At 0.25 the improvement of 4b over never-close is 2%, which is small enough that I would not rely on it. Scaling the residual spread by 0.5 to 2 left 4a unchanged (it closes nothing at multiplier 1).

  8. The controls behave as expected, with one that shows nothing. Adding the d = 0 snapshot (c0 / cum_d) drops WAPE to 0.0007, 0.0005 and 0.0004, which is what a leak looks like. Adding the realised fare of the departure changes WAPE from 0.0265 to 0.0257 (d = 30), 0.0126 to 0.0125 (d = 14) and not at all at d = 7: in this simulator the fare adds almost nothing beyond the booking curve, so the control does not demonstrate a leak and the exclusion is justified by availability (the fare is an outcome of the departure), not by a measured effect. The entity-unsafe positional shift reads another flight on 665, 399 and 266 rows (the first weeks of each flight) and leaves WAPE unchanged to three decimals, so it is a correctness defect that the unit test catches and the accuracy table does not.

  9. Robustness. An alternative training window starting 2023-03-01, excluding the three break routes from training, a different LightGBM seed and +-10% on the pace feature at prediction time all keep LightGBM ahead of the better baseline at every horizon (WAPE 0.026 to 0.027, 0.013 to 0.014, 0.009; maximum absolute forecast difference from the base run 0.9 to 4.3 seats). The four tuning cells differ by at most 0.0002 WAPE in 2023, so the grid is within noise and the "tuned" configuration is a default in practice.

  10. Censoring. On departures that ended at the limit, the mean unconstrained demand (simulator truth, used for this table only) is 226 against 198 observed bookings, and the shipped forecast is 12.6% to 13.6% below unconstrained demand while only 0.4% to 1.5% below observed bookings. On the other departures the forecast is within 0.5% of both. The model forecasts what the booking system will record, not what customers wanted.

Success criteria (record of project-016)

#CriterionResultDetail
1beat the better baseline by more than 2 SE at every horizonmetd = 30: 34.2 SE; d = 14: 42.9 SE; d = 7: 61.2 SE
2no pre-declared slice worse than the baseline by more than 5% WAPEmetworst slice -65.8%, -64.7%, -55.2% (lower is better)
380% interval coverage between 75% and 85% at every horizonmet79.8%, 81.0%, 80.9%
4athe first written rule (residual-quantile P(limit)) is cheaper than never-close and point >= capacitynot metit equals never-close at all three horizons
4ba second rule, added after 4a was seen to fail (calibrated P(limit given forecast / limit bin)), is cheaper than bothmet, post hoc7.62, 5.83, 4.97 against 10.25, 10.08, 10.01 and 15.07, 13.90, 13.57

Criterion 4 is met only by a rule that was designed after the first rule failed on the 2024 numbers. Its bins were fixed in advance of seeing its own costs and nothing was tuned on 2024, but the choice to try it was informed by 2024 results, so the 4b row is evidence that a calibrated rule is feasible, not an out-of-sample confirmation.

What this does not prove

  • Synthetic data. The simulator draws final bookings from a smooth booking curve with a known noise structure; a WAPE of 1% to 3% is a property of that generator (every snapshot is a noisy share of the final count), not a benchmark for real airline data. Pickup methods are known to be strong in real revenue-management data too, but real curves, group bookings, cancellations and schedule changes are absent here.
  • The decision cost is a stylised proxy. One marginal seat per departure, spoilage equal to the full L fare, hypothetical fares from 2022-2023 averages of the synthetic carrier. Closing a class changes later bookings; the backtest forecasts were produced under historical RM behaviour and do not model that feedback. A nested-class control would change both the rule and the cost.
  • One year, one seed, 12 origins. The standard errors are over 12 monthly origins, which are not independent (overlapping seasonal patterns, the same 19 flights). The interval estimates do not include seed or data-generation uncertainty.
  • Calendar facts are assumed known in advance (holidays and announced destination events). The competitor columns have no observation timestamp and are excluded; with timestamps they could be tested as features.
  • Tuning and candidate choice are tiny. Four cells that differ by 0.0002 WAPE, one candidate choice per horizon. The first 7, 14 or 30 days of 2024 are not evaluated (issue dates in 2023; evaluation_coverage.csv: 570, 266 and 133 departures).
  • Rule 4b was chosen after seeing 2024. See above. The residual-quantile rule 4a and the calibrated rule 4b both ignore that probabilities are estimated from the same 19 flights.

Reproduce

cd python
uv run python -m reference.r13_booking_curve_final_bookings.run          # about 2 to 3 minutes, writes outputs/
uv run python -m reference.r13_booking_curve_final_bookings.run --smoke  # about 10 seconds, temp directory
uv run pytest tests/test_r13_booking_curve_final_bookings.py

Builder-authored reference. It is not learner evidence and is never credited to your portfolio.

Rubric5 criteria

CriterionMeetsExceeds
Point-in-time correctnessAvailability table exists; features_as_of(T) is tested; the deliberate-leak control is reported.A second reviewer reproduces one departure's features by hand in SQL and they match exactly.
Baselines and candidatesSeasonal naive and additive pickup are implemented with the same availability rules; at most two candidates.Shows where pickup is unbeatable (by d and slice) and explains why from the booking-curve shape.
Backtest and slices12 rolling origins, per-horizon metrics with origin-level uncertainty, all declared slices.Shows that the winner is stable under the alternative training window and the break-route exclusion.
Uncertainty and decision80% coverage checked per horizon; L-class rule costed with labelled hypothetical costs.Sensitivity of the rule to the cost ratio and to coverage errors, with a recommendation that names its breaking point.
Limitations and defenceStates the censored-demand limitation and the synthetic-data scope explicitly.Completes the transfer task (for example, a hotel-arrivals variant) and revises one decision when challenged.

References7