Mapblazer Wait Time Prediction
An end-to-end machine learning pipeline that predicts real-time theme park ride wait times — powering the Mapblazer routing engine by forecasting how crowds will shift hours into a guest's day. Beats the historical baseline by 53.8%.
Problem Statement
Mapblazer is an AI theme park routing app that builds a guest's optimal ride itinerary from their must-visit attractions, real-time wait times, and walking distances. The flaw: the optimizer relied on "current" wait times pulled from park APIs, but wait times are highly dynamic — a route optimized at 9:00 AM is already wrong by the time a guest reaches their 1:00 PM attraction, cascading small errors into a broken plan. The routing engine needed accurate forward predictions of wait times to anticipate how crowds shift through the day. Building those models meant solving two hard problems: the dataset spanned less than a full year, so tree-based models like XGBoost couldn't reliably extrapolate raw chronological datetime inputs, and irregular US holidays violently disrupt the normal daily and weekly crowd cycles. The challenge was to engineer features and architectures that learn the underlying rhythm of park crowds — accurately enough to forecast, not just describe.
Results & Impact
3.27 min
Prophet MAE
Down from a 7.08 min historical baseline
53.8%
Accuracy Gain
MAE reduction vs. baseline (Prophet)
90.38%
Error Containment
Predictions within ±10 min (vs. 74% baseline)
−62.9%
Severe Misses
Errors > 10 min: 25.9% → 9.6%
1.5M+
Records Processed
Wait-time telemetry points
3
ML Architectures
Prophet · XGBoost Local · XGBoost Global
Approach & Methodology
Domain-Aware Data Filtering
Raw wait-time telemetry is noisy: a "0-minute" wait can mean a true walk-on during operating hours or an artifact of an overnight park closure or a scraping outage. The ETL layer (data_utils.py) preserves genuine walk-on zeros while discarding closure zeros using per-park operating-hour windows and start dates, caps wait times below 900 minutes to drop sensor errors, and resamples every ride to a clean 30-minute grid to smooth high-frequency micro-fluctuations.
Cyclical Feature Engineering
With under a year of data, gradient-boosted trees can't extrapolate raw datetime values — they only learn splits inside the dates they've seen. I encoded continuous time into cyclical sin/cosine embeddings of the hour and month, so the model sees 23:59 and 00:00 (and Dec and Jan) as adjacent rather than maximally distant. These joined explicit dayofweek, is_weekend, and is_holiday flags to let the trees map the true daily and seasonal shape of crowd flow regardless of the specific calendar date.
Three Competing Architectures
I built and benchmarked three model families. Prophet: a dedicated time-series model per ride with flat growth, daily/weekly seasonality, and explicit US-holiday regressors to capture the irregular surges trees struggle with. XGBoost Local: an independent, RandomizedSearchCV-tuned gradient-boosted tree for every single ride, specialized to its unique pattern. XGBoost Global: one unified tree trained across all parks and rides, using park and ride names as native categorical features so it can borrow signal across attractions — and train far faster than hundreds of local loops.
Leakage-Free Chronological Validation
Every model uses a strict chronological 80/20 split rather than a random one — training only on the past and validating on the future, exactly mirroring how the model is used in production. Rides with insufficient historical mass (fewer than 50 resampled points) are skipped to avoid overfitting on thin data. Models were then evaluated on a 50,000+ sample holdout, measuring not just average error but the full error distribution and severe-miss rate.
Real-Time Inference Harness
A production-style harness synchronously hits the Queue-Times API for every mapped park, flattens the ride hierarchy across park "lands", computes live temporal features for the current timestamp, and runs inference across all three architectures plus the historical baseline simultaneously. It enforces the exact categorical ontology learned at training time and writes a side-by-side comparison matrix — making live model drift directly observable second by second.
Error-Distribution Analysis
Beyond MAE, I profiled the full signed error distribution (predicted − actual) for each architecture against the baseline on both standard holdout and high-traffic weekend/holiday data. This surfaced how each model controls the dangerous "fat tails" — the large misses that actually break a route — proving Prophet not only lowers average error but dramatically tightens the worst-case behavior the optimizer is most sensitive to.
Architecture
┌───────────────────────────────────────────┐
│ Raw Wait-Time Telemetry (1.5M+ rows) │
│ 6 SoCal parks │
└───────────────────────────────────────────┘
│
v
┌───────────────────────────────────────────────┐
│ data_utils.py - ETL │
│ keep walk-on 0s, drop closure / outage 0s │
│ per-park open / close hour windows │
│ cap < 900 min / resample to 30-min grid │
└───────────────────────────────────────────────┘
│
v
┌─────────────────────────────────────────────┐
│ Cyclical Feature Engineering │
│ hour & month -> sin / cos (continuous) │
│ dayofweek / is_weekend / is_holiday │
└─────────────────────────────────────────────┘
│
┌───────────────────────────────┼───────────────────────────────┐
│ │ │
v v v
┌─────────────────────┐ ┌───────────────────────┐ ┌─────────────────────┐
│ Prophet │ │ XGBoost Local │ │ XGBoost Global │
│ per-ride model │ │ per-ride model │ │ one unified tree │
│ flat growth + │ │ RandomizedSearchCV │ │ park & ride as │
│ daily / weekly + │ │ hyper-tuned │ │ native categories │
│ US holidays │ │ │ │ │
│ MAE 3.27 │ │ MAE 4.39 │ │ MAE 3.91 │
└─────────────────────┘ └───────────────────────┘ └─────────────────────┘
│ │ │
└───────────────────────────────┼───────────────────────────────┘
│
v
┌───────────────────────────────────────────────┐
│ Real-Time Inference Harness │
│ Queue-Times API -> live features │
│ 3 architectures + baseline, side by side │
│ -> live model-drift comparison matrix │
└───────────────────────────────────────────────┘Model Performance
7.08
MAE (min)
Baseline
Historical Average
4.39
MAE (min)
XGBoost Local
Individual Trees
3.91
MAE (min)
XGBoost Global
Unified Tree
3.27
MAE (min)
Prophet
Time Series

Signed error (Predicted − Actual) across the 50,000+ sample holdout set. The tighter and taller the spike around zero, the better. Prophet collapses the historical baseline's heavy fat tails into a narrow band — the large misses that actually break a route are cut by 62.9%.