Skip to content

Latest commit

 

History

49 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Banner

✈️ Dynamic Flight Price Classifier

LIVE APP

Given a route and a departure date, is this fare cheap or expensive — and when should you book?

A Streamlit app that pulls live fares from Google Flights across many departure dates and classifies each one as Cheaper or Costlier with a logistic-regression scorecard. No API key required — clone it and it runs.


🚀 Features

  • Real fares across a date range, not a single snapshot — one query per departure date
  • Booking-curve analysis: how fare moves with time to departure
  • Logistic Regression classifier with interpretable coefficients
  • Leakage-free evaluation: split grouped by departure date, stability reported across 8 splits
  • Threshold diagnostics: what the decision cut-off costs and buys

📦 Tech Stack

  • Frontend: Streamlit, Matplotlib, Seaborn
  • Data: fast-flights (Google Flights), no credentials
  • ML: scikit-learn — logistic regression (L1), grouped splitting, ROC/PR analysis

📊 Results

Kolkata → Bangalore, 1,067 fares across 90 departure dates:

metric value
ROC-AUC 0.930
Accuracy (threshold 0.50) 82.1%
F1 — Cheaper / Costlier 0.832 / 0.809
Log loss 0.336

Evaluation splits by departure date, so no date appears in both train and test.


🔍 Two findings worth reading

1. Time-to-departure has almost no linear relationship with price — but it is the strongest predictor.

window correlation of Days Out with the label
30 days 0.576
90 days 0.120

The correlation collapses as the window widens, while model performance holds at ~0.93 AUC. The reason is that the booking curve is U-shaped — expensive last-minute, cheapest around 30–60 days out, expensive again further ahead. Pearson correlation only sees linear association, so a U-shape reads as roughly zero, and a raw linear term would learn nothing. Binning Days Out into bands recovers the signal. The fitted coefficients then trace the curve directly, e.g. relative to booking inside a week: 7–14 days −1.42, 14–21 days −2.05, 21–30 days −3.21 — steadily cheaper, exactly as a traveller experiences it.

2. Tuning the decision threshold did not help — tested twice, rejected.

run 0.50 (default) tuned on train winner
90 dates, 1,067 fares 82.1% acc, F1 0.809 0.58 → 80.7%, F1 0.784 default
30 dates, 344 fares 91.9% acc, F1 0.901 0.58 → 87.2%, F1 0.820 default

The cut-off maximising Youden's J on the training set performs worse on held-out dates, in both runs. With a median-split label the classes are balanced by construction, so 0.50 is already near-optimal and the tuning was fitting training noise. The app therefore uses 0.50 and reports the tuned value beside it rather than burying the comparison.

The direction of that choice matters: 0.50 was fixed before seeing any data. Picking whichever threshold scored best on the test set would be selecting an operating point after seeing the answers — the same class of error as the leakage described below, in miniature.


⚠️ A note on the earlier version

An earlier version of this app reported 90–98% accuracy. That figure was an artifact: it fetched one day of fares and copied them across up to 90 simulated future days, changing only the date. A random train_test_split then scattered near-identical rows across both sides, so the classifier scored highly by recognising rows it had already seen.

The current version fetches each departure date for real and splits by date. The honest number is ~0.93 ROC-AUC, and it holds up on dates the model has never seen.


⚡ Quickstart

git clone https://github.com/bravo2024/FLIGHT.git
cd FLIGHT

python -m venv .venv && source .venv/bin/activate   # optional

pip install -r requirements.txt
streamlit run classify.py

No API keys, no secrets.toml. Fares are fetched live at run time.

Tip: in the classification tab, set the day range to 90. Fewer departure dates make the grouped evaluation unstable — at 6 dates a test fold is one or two dates and AUC swings wildly; at 18+ it settles.

On live-request limits. Each departure date is one request to the source, and Google throttles bursts from shared datacentre IPs — measured on Streamlit Cloud, ~90 rapid requests returns almost nothing while ~18 succeeds. The app fetches every date you ask for and adapts its pacing rather than thinning the range: it opens at 0.3s spacing and widens (×2.5 per refusal, up to 3s) only once the source actually starts refusing. A clean 90-date run finishes in about 4 minutes; one that hits throttling slows itself to roughly 8 rather than returning empty.

Refused dates are retried twice with exponential backoff, and anything still missing is named rather than quietly dropped. Successful dates are cached for the session, so re-running the same query is instant.


📁 Layout

classify.py        # the whole app: live fetch, features, model, diagnostics
requirements.txt
DOCS/              # screenshots

Limitations

  • Single-origin snapshot: all dates are observed on one day, so the model sees the booking curve as it stands today, not how a given flight's price drifts as departure approaches. Running the app on several days would build that panel.
  • "Cheap" is defined relative to the median fare of the dates fetched, so the label moves with the query window.
  • fast-flights parses Google Flights HTML; upstream markup changes can break collection until the library is updated.

About

Real time flight price classifier using the Amadeus API and machine learning with a Streamlit dashboard

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages