1. The game
NE @ SEA
Wednesday, September 9, 2026 · 8:20 PM ET · Kickoff Game
Vegas line: SEA −3.5
Moneylines: home −205 / away +170
All four pick SEA. Our headline model (QBElo) is 7 percentage points more confident in SEA than the closing market. The rest of this page is how we got there.
2. Raw inputs from nflverse
Everything the model sees about this game starts in one row of schedules.parquet, plus all the prior team-games and plays that have already happened. No live feeds, no scrapers — just the public nflverse release.
The schedule row (selected fields)
| field | value | what it is |
|---|---|---|
| game_id | 2026_01_NE_SEA | nflverse’s composite key (season_week_away_home) |
| gameday | 2026-09-09 | kickoff date |
| gametime | 20:20 | kickoff (ET, 24h) |
| home_team / away_team | SEA / NE | 3-letter codes |
| spread_line | +3.5 | point spread (home perspective: >0 = home favored) |
| home_moneyline | −205 | American odds for SEA to win straight up |
| away_moneyline | +170 | American odds for NE |
| home_rest / away_rest | 7 / 7 | days since each team’s last game (Week 1 default = 7) |
| div_game | 0 | flag: divisional matchup (no — AFC East vs NFC West) |
| home_qb_id / away_qb_id | NaN / NaN | starting-QB IDs — not yet published for 2026 Week 1 |
| result | NaN | home_score − away_score, populated after kickoff |
What else the model has already seen by Sep 9
- All NFL games 2010–2025 (4,175 regular-season games) — team Elo + QBElo ratings have been updated through end of 2025 and regressed 1/3 of the way back to the mean at the off-season boundary.
- Every QB’s box-score history — QB ratings carry over un-regressed (skill is sticky); team QB baselines also carry over.
- Every play’s EPA from 2010–2025 — rolling 10-game form means for each team, shifted one game.
3. The features the model derives
The pipeline assembles one row per game. Here’s every column of that row for this matchup — each value derived from the inputs above.
a. Team Elo (pre-game)
Plain 538-style team rating. Updated game-by-game through end of 2025, regressed 1/3 toward 1505 at season boundary, then unchanged through the off-season.
| team | Elo |
|---|---|
| SEA (home) | 1675.91 |
| NE (away) | 1600.11 |
| SEA − NE | +75.80 |
b. QBElo team rating (pre-game)
Separate team-Elo subsystem inside QBElo — updated against the QB-adjusted prediction, so it drifts from plain Elo over time.
| team | QBElo team |
|---|---|
| SEA (home) | 1685.53 |
| NE (away) | 1578.64 |
| SEA − NE | +106.89 |
QB adjustment is 0 for both teams — starters haven’t been published, so we fall back to the team baseline.
c. Rolling EPA form (carries from end of 2025)
Trailing 10-game mean of EPA-per-play, shifted one game so the prediction can’t see itself. For Week 1 2026 the window is the last 10 games of 2025.
| team | off EPA/play | def EPA/play allowed |
|---|---|---|
| SEA | +0.0921 | −0.1539 |
| NE | +0.0418 | −0.1060 |
| SEA − NE | +0.0503 | −0.0479 |
SEA looks better on both sides of the ball over their last 10 games of 2025. (Negative def-EPA-allowed is good — opposing offenses scored fewer expected points per play.)
d. Schedule features
| feature | value | meaning |
|---|---|---|
| home_rest | 7 | standard week-1 rest |
| away_rest | 7 | same for NE |
| rest_diff | 0 | no rest edge either way |
| div_game | 0 | not a divisional matchup |
e. Everything merged: the one row that goes into the ML model
ml_model.assemble() stitches all of the above into a single row keyed by game_id. The 13 features below are exactly what the ML model consumes — same shape as every other game’s row.
| column | value | notes |
|---|---|---|
| elo_diff | +75.80 | SEA Elo − NE Elo |
| p_home_elo | 0.6710 | Elo’s home win prob (computed below) |
| p_home_qbelo | 0.7092 | QBElo’s home win prob |
| home_roll_off_epa | +0.0921 | SEA offense |
| home_roll_def_epa | −0.1539 | SEA defense (lower = better) |
| away_roll_off_epa | +0.0418 | NE offense |
| away_roll_def_epa | −0.1060 | NE defense |
| off_epa_diff | +0.0503 | SEA off − NE off |
| def_epa_diff | −0.0479 | SEA def − NE def |
| home_rest | 7 | |
| away_rest | 7 | |
| rest_diff | 0 | |
| div_game | 0 |
Note: p_home_mkt (the de-vigged Vegas implied probability) is also in the row but the ML model doesn’t use it — we leave Vegas out of training so it stays an honest benchmark.
4. Each model scores it
Elo — from team ratings to win probability
Standard 538-style Elo: add home-field advantage to the rating diff, then run through the Elo logistic.
The 400 in the denominator is the Elo scale (a 400-point gap = 10:1 odds). +48 of HFA means a home team beats an equally-rated road team about 56% of the time.
QBElo — Elo plus a QB adjustment
Same logistic, but the rating gap also includes qb_to_elo × (starter_rating − team_qb_baseline) for each side. With 2026 starters NaN, that adjustment is 0 here, so the math collapses to QBElo’s own team-Elo subsystem.
QBElo’s team-Elo subsystem (1685.53) is higher than plain Elo’s (1675.91) for SEA — the two rating systems update under different predictions over thousands of games and drift apart. Once 2026 starters are published, home_qb_adj and away_qb_adj become non-zero and QBElo can pull sharply if a backup is announced.
ML (Logit) — learned weights on the 13-feature row
The pipeline is a four-step sklearn.Pipeline: impute missing values with the median, standardize each feature, fit logistic regression, then Platt-scale on the 2025 season so the output is a calibrated probability.
In holdout testing the ML model gets slightly better straight-up accuracy than QBElo but worse Brier — the logistic is a touch more confident than it should be on lopsided games. Platt scaling on the most recent full season pulls it back toward calibrated.
Vegas — de-vigging the moneylines
Each American moneyline implies a raw probability that includes the book’s margin (the “vig”). The two sides sum to more than 1.0. We normalize so they sum to exactly 1.0 — the closest thing to an honest market probability we can extract for free.
All probabilities on this site labeled “Vegas” or “market” are de-vigged like this. The implementation lives in src/metrics.py (american_to_prob + market_home_prob).
5. Side by side
All four predictors, computed from the same row of inputs, summarized.
| model | SEA win % | NE win % | notes |
|---|---|---|---|
| Elo | 67.1% | 32.9% | team ratings + HFA only |
| QBElo ★ | 70.9% | 29.1% | independent team-Elo + QB adjustment (=0 here) |
| ML (Logit) | 74.4% | 25.6% | 13 features → logistic → Platt-calibrated |
| Vegas (de-vig) | 64.5% | 35.5% | closing-ish moneylines, vig removed |
All four agree SEA is the favorite; the disagreement is on how much. Vegas is the most cautious (64.5%); our ML is the most confident (74.4%). The 10-point gap between ML and Vegas is meaningful — the holdout backtest says Vegas is sharper on average, so betting markets “know something” we don’t (probably injury / camp news that hasn’t hit our data). After kickoff we’ll grade who was closer.
6. What happens after kickoff
Once the final score is in, every part of the pipeline flows forward automatically:
a. The raw data updates (Monday/Tuesday)
- nflverse publishes the final
result,home_score,away_scoreon the schedules row. - Player stats for the game are added to the weekly stats parquet (used for QB value).
- Play-by-play with EPA per play hits the pbp parquet (used for rolling form).
b. Our cron runs (Tuesday 16:00 UTC)
The GitHub Action re-runs the whole pipeline and pushes a refreshed web/replay_2026.json if any prediction changed. Vercel auto-deploys.
Each model gets graded on the games that have already been played:
Brier and log loss reward calibration (a confident wrong pick hurts more); accuracy is just who-picked-the-winner.
c. The ratings update online (next time the engines run)
Both Elo and QBElo update each team’s rating after every game. Worked example: if SEA wins NE@SEA by exactly 7 (covering the −3.5):
SEA gains ~13 points by winning “as expected.” A loss would’ve cost them more like 27 (because y − p_home would have flipped sign and grown in magnitude). Online updates like this are why Elo doesn’t need a re-train: it adapts every week.
d. The site updates itself
The refreshed JSON includes new cumulative and weekly Brier / log-loss / accuracy. The live tracker reads it next page load and the running scoreboard moves. The Week 1 page shows the result chip next to each pick.
Where each number on this page comes from
- section 2 — one row of
data/schedules.parquet, produced bysrc/pull_data.py. - 3a / 3b —
src/elo.pyandsrc/qbelo.pyrun chronologically over every game through Week 1 2026. - 3c —
data/epa_features.parquet, built bysrc/epa_features.py(now appends future-game placeholders so the rolling window carries through the off-season). - 3e —
ml_model.assemble()insrc/ml_model.py. - section 4 — the engines +
metrics.market_home_probfor the de-vig; the four numbers are exactly what’s inweb/replay_2026.jsonunder this game’spreds. - section 6 — the MOV multiplier and update rule are from
src/elo.py(mov_multiplier); the grading metrics are fromsrc/metrics.py.
Full pipeline overview: How it’s built →