Back
LightGBMGitHub ActionsEvidently AIMLflowDagsHubHopsworksAWS S3PyArrowNext.jsLeafletDocker

Citi Bike Demand Forecaster:Live 24-Hour Prediction Pipeline

Live NYC Citi Bike demand forecaster serving a 28-hour recursive-multistep LightGBM forecast for the 3 busiest stations. A two-phase Recursive Bridge walks the ~20-day public-data lag forward to the present, so the model can predict tomorrow from data that stops three weeks ago.

Core Impact

Turns a dataset published ~20 days late into a live 28-hour forecast at MAE 2.94 trips/hour, fully automated through 3 scheduled GitHub Actions workflows and served at $0 backend cost.

Citi Bike Demand Forecaster: Live 24-Hour Prediction Pipeline

2.94

MAE, trips per hour

28 hr

Recursive forecast horizon

$0

Backend cost, S3 Parquet direct

The Problem

Citi Bike publishes its trip data about 20 days behind, which quietly makes the public dataset useless for real-time prediction. Any model trained on it is forecasting from a picture of three weeks ago. I wanted an operator-facing forecast of the next 28 hours anyway, which meant solving the lag itself before the forecasting even starts.

The Approach

01

Why a two-phase Recursive Bridge

Phase one walks the model forward hour by hour from the last real observation to now, feeding its own predictions back in as lag features. Phase two forecasts the actual 28 hours ahead from that bridged present. Without the bridge there is simply no way to predict tomorrow from data that stops three weeks ago.

02

Why LightGBM instead of a neural model

The signal here is lag features and station identity, which gradient boosting handles directly, including categoricals with no encoding layer. It trains in seconds inside a GitHub Actions runner and lands at MAE 2.94 trips/hour, so a heavier architecture would cost more and buy nothing.

03

Why one model for three stations

Each station gets 28 lag features plus its own categorical ID, so a single model learns the shared temporal shape while still separating station-specific demand. Three separate models would triple the retrain and registry surface for no accuracy gain.

04

Why three separate Actions workflows

Feature ETL and retraining run monthly, inference runs hourly. Splitting them means a failed retrain never blocks the hourly forecast, and each has its own schedule and its own failure surface instead of one fragile cron doing everything.

05

Why S3 Parquet with no API

Predictions are written to S3 as Parquet and the Next.js frontend reads them directly. Nothing stays running between forecasts, so operating cost is roughly zero, and the model registry in Hopsworks with MLflow lineage on DagsHub still gives full reproducibility.

Technical Deep Dive

01

Built a live demand forecaster predicting NYC Citi Bike usage on a 28-hour recursive horizon, driven by 3 independent GitHub Actions workflows (monthly feature ETL, monthly retrain, hourly inference) with zero manual intervention.

02

Engineered a two-phase Recursive Bridge that closes the ~20-day public data lag, feeding predictions back as features to walk from the last real observation to the present before forecasting forward; achieved MAE 2.94 trips/hour on LightGBM.

03

Engineered per-station 28-lag features with native LightGBM categorical encoding across the 3 busiest stations, so a single model serves all of them while preserving station-specific demand patterns.

04

Built a Champion/Challenger registry with minimum-MAE promotion from the Hopsworks Model Registry and full lineage in MLflow on DagsHub; predictions archived to AWS S3 as Parquet.

05

Served the whole thing at $0 backend cost: the Next.js frontend parses S3 Parquet directly with PyArrow-written files, so there is no always-on API to pay for.

Systems Analysis Concluded

© 2026 · Marian Glen Louis

Engineered with Next.js, Tailwind v4 & Framer Motion