Under the hood
Six models compete. The most honest one wins.
Every model trains on the same three seasons (720 games) and is tested on 180 games it has never seen. The champion is chosen by probability quality, not by accuracy or complexity. In plain terms: when it says 70%, does the team actually win 70% of the time?
Leaderboard
| # | Model | Brier โ | Log loss โ | ROC-AUC โ | Accuracy | Calibration (ECE) |
|---|---|---|---|---|---|---|
| 1 | PyTorch Neural Network Champion | 0.1744 | 0.5162 | 0.815 | 73.0% | 0.0508overconfident |
| 2 | Random Forest | 0.1746 | 0.5132 | 0.815 | 72.9% | 0.0468overconfident |
| 3 | GRU Sequence Model | 0.1761 | 0.5170 | 0.811 | 72.5% | 0.0480overconfident |
| 4 | Gradient Boosting | 0.1784 | 0.5338 | 0.812 | 72.7% | 0.0551overconfident |
| 5 | Logistic Regression | 0.1802 | 0.5277 | 0.805 | 71.9% | 0.0508overconfident |
| 6 | Baseline Rule Model | 0.1837 | 0.5608 | 0.810 | 72.5% | 0.0845overconfident |
Note the gradient boosting row: strong accuracy, weak Brier score. A model can pick winners well while its percentages run too hot, and that is exactly why accuracy alone doesn't decide the champion.
Does 70% mean 70%?
Each dot buckets thousands of held-out predictions. On the dashed line, predicted probability matches reality. Below the line at the right edge means overconfidence.
How it's built
The full lifecycle, from raw play-by-play to the page you are reading, is one codebase.
A Python pipeline (pandas, scikit-learn, PyTorch) turns every NBA play into a game state, engineers 85 features covering time, score, momentum, possession, and team strength priors from the previous season, then trains all 6 models on identical data. Games are split at the game level so no moment from a test game ever leaks into training. A scheduled job repeats the whole cycle weekly during the season: download, retrain, re-select the champion, redeploy.
This site has no Python server. The champion network's weights are exported to JSON and executed in TypeScript, along with a full port of the feature pipeline. Every deploy runs a parity check proving the TypeScript output matches the original PyTorch model to eight decimal places. During live games, your browser fetches the play-by-play and computes win probability locally every ten seconds.
We also fit an isotonic calibration layer for the champion. On held-out games it did not improve probability quality (Brier 0.1750 raw vs 0.1771 calibrated), so it is deliberately not applied. The champion is already well calibrated.