NBA Playoff AI Match Analysis System
An end-to-end full-stack ML application that compares NBA teams using advanced stats, simulates matchup outcomes, and supports real-time "What-If" scenario testing through interactive sliders.
This project includes:
- A Python data pipeline to pull NBA data and store it in Supabase
- A trained Random Forest model for team strength/championship probability
- A FastAPI backend for team data + prediction endpoints
- A React + Vite frontend dashboard for matchup selection, sliders, and visualization
- Pulls multi-season NBA team advanced metrics (offense, defense, net, pace, efficiency)
- Trains a model on historical seasons to estimate team-level championship probability
- Converts two team probabilities into head-to-head matchup percentages
- Lets you tweak team inputs with sliders (offense/defense/pace) and re-runs predictions instantly
- Shows transparent stat comparison tables so predictions are explainable
I built this project in phases:
-
Data first
Started withnba_apito fetch regular-season advanced team stats and standings labels. -
Storage + repeatability
Pushed season rows into Supabase with upserts (team_id,season) so I could re-run ingestion safely. -
Model training
Trained aRandomForestClassifieron historical team-season features with championship labels. -
API layer
Wrapped model + data lookup with FastAPI endpoints (/api/teams,/api/team/{team_id}/stats,/api/predict). -
UI + interaction
Built a dense dashboard in React with team selection, prediction bar, stat table, and adjustment sliders. -
Calibration fixes
Added season-awareness (2025-26 support/fallback) and softened extreme 0/100 outputs in matchup conversion.
The model is trained to predict:
won_championship(binary classification at team-season level)
The model uses these features:
off_ratingdef_ratingnet_ratingpacets_pctw_pct
Training logic lives in:
data_pipeline/scripts/train_model.py
Key training details:
- Model:
RandomForestClassifier n_estimators=300max_depth=6min_samples_leaf=2class_weight="balanced"- Champion rows are intentionally duplicated in training to handle severe class imbalance
- Saved model path:
data_pipeline/models/random_forest_v1.pkl
The core model predicts championship probability, not direct game-win probability. Matchup % shown in the UI is a transformed comparison of two team probabilities (see next section).
Backend logic:
backend/app/services/simulator.py
Flow:
- Load Team A + Team B stats for the effective season
- Apply slider adjustments to each team
- Recompute
net_rating = off_rating - def_ratingafter adjustments - Predict each team's championship probability via
model.predict_proba - Convert the two probabilities into matchup percentages using:
- probability clipping floor/ceiling
- log-odds conversion
- temperature softening
- blend toward 50/50 to avoid unrealistic extremes
This is why outputs are now more realistic (not hard 0%/100% in typical cases).
Frontend sends this payload:
{
"team_a_id": 1610612738,
"team_b_id": 1610612747,
"adjustments": {
"team_a_off_rating_pct": 10,
"team_a_def_rating_pct": -5,
"team_a_pace_pct": 5,
"team_b_off_rating_pct": 0,
"team_b_def_rating_pct": 0,
"team_b_pace_pct": 0
}
}Interpretation:
off_rating_pct: % boost/penalty to offensive ratingdef_rating_pct: % change to defensive rating (lower defensive rating is better)pace_pct: % change to possessions/game pace
Impact:
- Sliders directly alter model input features before inference
- Every slider change triggers a debounced re-predict call
- This lets you simulate hypothetical performance shifts and see probability movement instantly
API season behavior:
backend/app/services/routes.py- Uses
NBA_TARGET_SEASONif present in DB - Otherwise falls back to latest available season in
team_season_stats /api/predictreturns theseasonfield so UI/results are explicit
Data ingestion season behavior:
data_pipeline/scripts/fetch_nba_stats.py- Dynamic season generation using:
NBA_TARGET_SEASON(default inferred current NBA season)NBA_START_YEAR(for range control)
Example (single-season refresh):
NBA_START_YEAR=2025 NBA_TARGET_SEASON=2025-26 python data_pipeline/scripts/fetch_nba_stats.pyNbaplayoffs-main/
├── backend/
│ └── app/
│ ├── services/routes.py
│ ├── services/supabase.py
│ ├── services/simulator.py
│ └── main.py
├── data_pipeline/
│ ├── models/random_forest_v1.pkl
│ └── scripts/
│ ├── fetch_nba_stats.py
│ ├── train_model.py
│ └── predict_champion.py
└── frontend/
└── src/
├── App.jsx
├── services/api.js
└── components/
- Python
nba_apipandas,numpyscikit-learnjoblib
- FastAPI
- Uvicorn
- Supabase Python client
python-dotenv
- React
- Vite
- Tailwind CSS
- Axios
use-debounce
- Supabase (PostgreSQL)
Base URL: http://127.0.0.1:8000
-
GET /
Health check -
GET /api/teams
Returns teams for effective current season -
GET /api/team/{team_id}/stats
Returns one team's season stats -
POST /api/predict
Returns head-to-head matchup percentages + raw model probabilities
Interactive docs:
http://127.0.0.1:8000/docs
Clone the repo and open it in your terminal/editor. All commands below are relative to the project root.
Create and activate a virtual environment:
macOS/Linux:
python3 -m venv venv
source venv/bin/activateWindows (PowerShell):
python -m venv venv
.\venv\Scripts\Activate.ps1Install backend dependencies and run API:
pip install fastapi uvicorn python-dotenv supabase joblib scikit-learn nba_api pandas
cd backend
uvicorn app.main:app --reloadIn a second terminal:
cd frontend
npm install
npm run devOpen:
- Frontend:
http://localhost:5173 - API docs:
http://127.0.0.1:8000/docs
# from project root, with venv activated
python data_pipeline/scripts/fetch_nba_stats.py# from project root, with venv activated
python data_pipeline/scripts/train_model.pycd backend
uvicorn app.main:app --reload- Matchup win % is derived from a championship model, not trained directly on game-level head-to-head outcomes
- Results are regular-season-stat driven and may not capture injuries, rotations, fatigue, or playoff matchup schemes
- Class imbalance is significant for championship labels, so probabilities should be treated as directional, not betting-grade certainty
- Train a dedicated game-level or series-level matchup model
- Add injury and roster context features
- Add model versioning and experiment tracking
- Add unit/integration tests around prediction calibration
- Add CI checks and deployment scripts for backend/frontend
- Param Patel
- Email:
parampatel2007@gmail.com
