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 dataBaseline
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 baselineValidation
Rolling-origin backtest with an expanding training window: 12 monthly origins (first day of each month in 2024).
Details on validationMetric
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:
- 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);
- 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);
- deliver 80% intervals whose empirical coverage lies between 75% and 85% at every horizon;
- 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.
| Column | Meaning |
|---|---|
| booking_curve.date | Departure date (ISO text), not the date the snapshot was taken |
| booking_curve.days_to_departure | Snapshot offset k; the row is observable from date - k onward |
| booking_curve.cumulative_bookings | Bookings on hand at the snapshot; cancellations are reported only at departure level in flight_day |
| booking_curve.bookings | Increment since the previous (earlier) snapshot |
| flight_day.bookings_final | Target: bookings at departure, capped at floor(1.06 x capacity) by the overbooking limit (censored) |
| flight_day.capacity | Seats (160, 180 or 280); constant per route in the sample |
| flight_day.avg_fare / fare_level / revenue / passengers / load_factor | Realised outcomes of the departure; unknown at issue time and must not be features for that departure |
| calendar.is_holiday / event | Turkish 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
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.
| Item | Value |
|---|---|
| Run date | 2026-10-12 |
| Seeds | database 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 / lightgbm | 3.12.13 / 2.5.3 / 3.0.6 / 1.18.1 / 1.9.1 / 4.7.0 |
| Machine | Apple M5, macOS arm64, CPU only |
| Runtime of the recorded run | 166.4 s (shared laptop; other full runs during development took 100 to 120 s). --smoke: 8 s. |
| Database | airline_sample.sqlite, sha256 a364e86b2c27990888ace9486e90e53ca5df4535816f68586a47dcf7447eb284; flight_day 20,824, booking_curve 270,712, calendar 1,096 rows (match the project record) |
| Evaluation | 12 monthly origins in 2024 (expanding window), 19 flights, issue horizons d = 30, 14, 7; 6,384 / 6,688 / 6,821 evaluated forecasts |
| Tuning | 4-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 |
| Determinism | the repeat run inside robustness.csv is bit-identical (determinism_max_abs_diff = 0.0) |
| Tests | uv run pytest tests/test_r13_booking_curve_final_bookings.py (see the end of this file) |
Findings
-
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_finalon 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). -
Additive pickup is the better baseline at every horizon, and the candidates beat it by a wide margin. Pooled 2024 WAPE:
d seasonal naive additive pickup regression pickup LightGBM 30 0.180 0.084 0.028 0.026 14 0.172 0.044 0.016 0.013 7 0.147 0.025 0.013 0.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.
-
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.
-
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.
-
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.
-
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).d never close L close when point >= capacity rule 4a: P(limit) from residual quantiles rule 4b: calibrated P(limit given f / limit bin) share closed by 4b oracle 30 10.25 15.07 10.25 7.62 13.2% 0 14 10.08 13.90 10.08 5.83 20.2% 0 7 10.01 13.57 10.01 4.97 22.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.
-
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).
-
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. -
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.
-
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)
| # | Criterion | Result | Detail |
|---|---|---|---|
| 1 | beat the better baseline by more than 2 SE at every horizon | met | d = 30: 34.2 SE; d = 14: 42.9 SE; d = 7: 61.2 SE |
| 2 | no pre-declared slice worse than the baseline by more than 5% WAPE | met | worst slice -65.8%, -64.7%, -55.2% (lower is better) |
| 3 | 80% interval coverage between 75% and 85% at every horizon | met | 79.8%, 81.0%, 80.9% |
| 4a | the first written rule (residual-quantile P(limit)) is cheaper than never-close and point >= capacity | not met | it equals never-close at all three horizons |
| 4b | a second rule, added after 4a was seen to fail (calibrated P(limit given forecast / limit bin)), is cheaper than both | met, post hoc | 7.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.pyRubric5 criteria
| Criterion | Meets | Exceeds |
|---|---|---|
| Point-in-time correctness | Availability 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 candidates | Seasonal 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 slices | 12 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 decision | 80% 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 defence | States 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
- The Theory and Practice of Revenue Management
Booking-curve forecasting, pickup methods, unconstraining and fare-class control in RM.
- A comparison of forecasting methods for hotel revenue management
Pickup methods were among the most accurate or robust in an RM forecasting comparison; justifies pickup as the baseline to beat.
- Better unconstraining of airline demand data in revenue management systems for improved forecast accuracy and greater revenues
Why capacity-limited bookings understate demand and how unconstraining is evaluated.
- Point-in-time joins (Feast documentation)
Point-in-time join semantics behind features_as_of(T).
- Out-of-sample tests of forecasting accuracy: an analysis and review
Rolling-origin design and multiple horizons in out-of-sample forecast evaluation.
- Another look at measures of forecast accuracy
Scaled errors (MASE) relative to a naive benchmark.
- Probabilistic Forecasts, Calibration and Sharpness
Judge intervals by calibration first, then sharpness.