Technical report · Snapcount

Snapcount Methodology, v1.0

July 2026 · the exact-how companion to How we predict
Abstract. Snapcount projects NFL player-weeks as calibrated distributions, not points. This document specifies the system exactly: the scoreboards and their formulas, the data and what is structurally forbidden from it, the per-regime models with their masks and hyperparameters, the validation protocol, and the current honest standing — including the head-to-heads we lose. Every number here is reproduced verbatim from the internal research record; where a result is a hint rather than evidence, it is labeled a hint. Backtest scope throughout: seasons 2021–2024 (training panels reach back to 2016–2018 depending on module); the 2025 season is sealed and appears only as a frozen-model holdout replay.

1.Scoreboards

1.1 The internal yardstick: TD-free, position-pure slate Spearman

The north-star internal metric is weekly ranking quality: within each (position × week) slate, rank players by a projected score, and measure the Spearman rank correlation against the realized ordering, alongside Top-N hit rate and start/sit accuracy. The score is deliberately touchdown-free:

QB score = pass_yd × 0.04 + rush_yd × 0.10
RB score = (rush_yd + rec_yd) × 0.10 + receptions
WR score = rec_yd × 0.10 + receptions
TE score = rec_yd × 0.10 + receptions

Per-position slate sizes (Top-N): QB 12 · RB 24 · WR 36 · TE 12

Three design choices, each load-bearing:

A result "counts" only under the robustness contract of snapcore/eval/ranking_eval.py: beats the recency baseline in ≥3 of 4 seasons (2021–2024), beats a flat constant, and survives a label-shuffle placebo.

1.2 Fantasy-points variants (the public-facing candidate)

Because the market is judged in fantasy points, a parallel scoreboard (snapcore/eval/fantasy_points_eval.py) mirrors the same position-pure slate-Spearman machinery on actual fantasy points — half-PPR primary, standard and full-PPR secondary (interceptions/fumbles/2-pt conversions excluded from truth: no predictor in the stack projects them, documented). How much do TDs reorder reality? Spearman between TD-free truth and fantasy truth:

PositionTruth-vs-truth ρTop-N membership overlap
QB0.8510.736
RB0.9460.781
WR0.9520.796
TE0.9230.672

So a TD-blind predictor concedes real headroom (most at QB and TE) on fantasy truth — which is exactly what the prospective E[TD] layer (§7) is measured against. The internal modeling metric stays TD-free; the fantasy-points scoreboard is the candidate public yardstick.

1.3 Benchmarks are fenced, never blended

RotoWire (via Sleeper's public API, company="rotowire" on the wire) and ESPN's weekly projections are scored as benchmarks only. They are structurally fenced in code (BENCHMARK_ONLY flag, separate registry, an assert_not_benchmark() guard on every feature path): no third-party projection is ever an input, feature, anchor, or blend component of our predictions. The same rule holds for betting prop lines (eval-only since 2026-06-08). Game-level environment (spread/total from schedules) is the one market signal that is feature-legal — and it tested dead for within-position ranking anyway (§6).

2.Data and the anti-leakage doctrine

2.1 Sources

All ingestion comes from the nflverse open-data ecosystem via nflreadpy (play-by-play, weekly box scores, rosters, depth charts, schedules with pregame lines, NGS weekly aggregates, draft, contracts, trades). Raw pulls are schema-validated (drift detection against expected schemas) before any feature build. No paid data feeds are in the model today (§5.3).

2.2 Feature layers

Features are built in eight composable layers, each contributing named columns; the canonical entry point composes them and then passes every column list through the anti-leakage gate:

LayerFamily
0Identifiers and keys
1Lagged player box-score aggregates
2EPA / CPOE efficiency context
3NGS weekly aggregates (pregame-usable)
4Game context (schedules, environment)
5Player attributes and draft
6Injury status
7Injury intelligence (practice-report refinement)

2.3 The block-list: what can never reach a live prediction

The validator (snapcore/eval/leakage.py) is mandatory on every live feature build and fail-closed. It block-lists five surfaces:

The doctrine is precise to the column name: per-play time_to_throw (participation, post-game charted) is blocked, while avg_time_to_throw (NGS weekly aggregate, released within ~24h of games and available pregame the following week) is allowed — and there is a unit test asserting exactly that distinction.

3.Models, per season regime

3.1 The core: walk-forward opportunity projection (W5+, RB/WR/TE)

The production ranking model decomposes each player-week into projected volume × shrunken efficiency (snapcore/models/opportunity_model.py). Per position, the supervised volume targets and feature sets are:

Volume targets      QB: pass att + rush att   RB: carries + targets   WR: targets   TE: targets

CANONICAL_FEATURES  QB: lag_att, lag_car, lag_snap_share, depth_chart_order
                    RB: lag_car, lag_car_share, lag_tgt, lag_tgt_share, lag_snap_share, depth_chart_order
                    WR: lag_tgt, lag_tgt_share, lag_snap_share, depth_chart_order, lag_airyds
                    TE: lag_tgt, lag_tgt_share, lag_snap_share, depth_chart_order, lag_airyds

Every lagged feature is an exponentially-decayed trailing window computed strictly on prior weeks: shift(1).ewm_mean(half_life=2.5) within (player, season) — the shift(1) guarantees week W reads only weeks < W. The training panel spans seasons 2018–2024; evaluation seasons are 2021–2024.

Each volume is projected by its own walk-forward regression. For the ranking lane the estimator is a standardized RidgeCV (alphas 0.1, 1, 10, 100, 1000) — the feature set is small and collinear by design, so a regularized linear model selects among proxies rather than hand-stacking them. Missing feature cells are imputed with training-set medians (computed on pre-eval rows only). The training mask for a season-S, weeks-9–18 target is:

train:   (season < S)  OR  (season == S AND week ≤ 8)     — with week ≥ 2 and a non-null lag guard
predict: (season == S) AND (week ∈ eval_weeks)

Projected volumes are recombined through the position's TD-free efficiency decomposition (yards-per-attempt / per-carry / per-target and catch rate), producing proj_score. An oracle variant that keeps actual volumes measures the achievable ceiling (§5.2).

For the displayed yards median (the number on the board, not the ranking), the estimator is a gradient-boosted quantile median — HistGradientBoostingRegressor(max_iter=300, max_depth=3, learning_rate=0.05, l2_regularization=1.0, random_state=0, loss="quantile", quantile=0.5) — a frozen configuration, adopted after paired MAE wins over the ridge base (RB −1.19 / WR −0.94 / TE −0.21 displayed yards, pooled sign-test p ≈ 2e-20 over 120 cells) with interval coverage unchanged. This is an L1-versus-L2 structural improvement, not a modeling breakthrough, and we quote it as such.

On top of the model score, the ranking lane applies a walk-forward blend with the recency baseline (models/recency_blend.py, RB/WR/TE only; blend weight re-selected per position and season on strictly prior data). Current canonical pooled Spearman (2021–2024, W9–18, own population):

PosModel onlyRanking lane (blend)RotoWire benchmarkGapSeasons held
RB.698.700.741−.0414/4
WR.677.680.690−.010 (statistically unresolved)4/4
TE.623.623.637−.0144/4

Honest headline: the gap to the market narrowed and WR is statistically unresolved — that is parity-adjacent, not a win. QB is absent from this table by design (§3.3).

3.2 Efficiency shrinkage (empirical Bayes)

Trailing per-opportunity efficiency mean-reverts; carrying a hot streak forward hurts ranking. Each player's trailing efficiency is therefore shrunk toward a position prior:

shrunk_eff = w · trailing_eff + (1 − w) · prior        w = n / (n + k)

where n is the trailing opportunity count and k is chosen by argmin reconstruction-MAE on an out-of-sample calibration cohort (each season's weeks 1–8), then frozen before scoring weeks 9–18. Low-n players (noisy ratios) are pulled hard toward the prior; high-n players keep their own signal. Shrinkage is applied to RB/WR/TE only: it measurably hurts QB ranking, so QB efficiency stays raw — a finding from the Phase-2 validation, kept as a hard rule.

3.3 QB: no model is claimed

Every QB ranking model built to date fails the season-consistency gate (0/4 seasons vs the recency baseline for the promoted-model family; one later side-flag candidate resolved NULL under paired testing against the stronger ewma5 incumbent — mean lift fails Bonferroni, 0/4 seasons, unstable weights). QB therefore serves recency_ewma5 — an exponentially weighted average of prior TD-free scores — and the projection grid leaves the QB model score deliberately NULL. We publish QB numbers under this baseline and do not represent them as model output.

3.4 The early-season regime: W1–4 (promoted 2026-07-12)

The W5+ production training mask is illegal for W1–4 targets — it would train on the very weeks being predicted. The early lane therefore uses a strict mask, and Week 1 gets its own construction:

The promotion also closed a latent leakage path: before it, an early-week request could reach the W9+ production mask. The W1 numbers were independently hand-traced to raw stats (agreement to 1e-9) during adversarial verification. Known caveat, recorded: the W1–4 boom/bust and interval columns are not yet early-week-validated.

3.5 Touchdown models: distributions, then calibration

TDs are never regressed as counts (symmetric-percentage error on 0-vs-0.4 outcomes is a math artifact, not signal). Instead, multinomial XGBoost (multi:softprob) predicts a distribution over discrete outcomes, binned per target from the empirical distribution:

passing TDs:    classes [0, 1, 2, 3+]   E[Y] values [0, 1, 2, 3.3]
rushing TDs:    classes [0, 1, 2+]      E[Y] values [0, 1, 2.2]
receiving TDs:  classes [0, 1, 2+]      E[Y] values [0, 1, 2.2]

E[TD] = Σ class_value × P(class)

Two calibration fixes were merged 2026-07-03 after a three-fold bootstrap verification (all folds p < .001, coefficients re-derived independently to 4 decimal places):

MetricBeforeAfter
any-TD precision.4915.5872
any-TD recall.2985.4734
multi-TD precision.2097.2452
multi-TD recall.1960.7035
any-TD ECE.0529.0308

Recorded caveats: the QB any-TD fraction .75 is grid-capped below its .82 base rate; the multi-TD board expands 186 → 571 predicted rows (QB-dominated); 2022 any-TD precision is .535 on the thinnest calibration fold.

3.6 Conformal intervals: the floor–ceiling range

Every displayed projection is a 70%-target central interval on yards (snapcore/serving/projection_intervals.py):

median  = walk-forward gbm_q50 projected yards (§3.1)
raw_lo  = median − σ        raw_hi = median + σ        σ = lagged trailing SD of the player's yards
floor   = clip(raw_lo − q̂_lo, 0)                       ceiling = raw_hi + q̂_hi

The asymmetric offsets (q̂_lo, q̂_hi) are conformal corrections calibrated walk-forward on prior-season out-of-sample residuals (each median is itself an out-of-sample projection, so no eval rows leak into calibration). Target coverage 0.70 (α = 0.30); measured coverage post-merge:

PosCoverageBand width (yds)In target band [0.65, 0.75]
RB0.69959.7yes
WR0.71253.0yes
TE0.70838.5yes

The per-player width is the honest product form of the boom/bust research: a steady every-down role gets a tight band, a volatile role a wide one.

3.7 Boom / bust probabilities (leave-one-season-out)

Per player-week, calibrated P(boom) and P(bust) on the TD-free score (snapcore/models/boom_bust.py): boom = deviation from the pregame expectation in the top tercile of the per-position residual distribution, bust = bottom tercile (terciles, not quintiles — TE slates carry only ~12 players, and a ~33% base rate keeps calibration bins populated). Tercile thresholds are fit only on calibration seasons, never eval rows. The classifier is a standardized logistic regression on lagged volatility/role features (air yards, target share, snap share, depth-chart order, trailing residual volatility, trailing score CV); its output is calibrated by a Platt / isotonic / beta calibrator selected by a pre-declared 0.6·Brier + 0.4·ECE criterion, fit on out-of-fold predictions, leave-one-season-out over 2021–2024. Scoring follows a reliability contract (AUC ≥ 0.60, ECE ≤ 0.05, BSS > 0) against climatology and a naive-volatility floor. Positioning is explicit: this is a confidence layer, not an accuracy edge — an adversarial re-check showed much of the raw bust signal is mean-reversion a naive variance ranker also captures.

3.8 Cell-scoped exception: the RB-receptions count head

One cell (RB receptions spike/bust probabilities) is served by a negative-binomial count head: a Poisson-loss gradient-boosted mean on the RB feature set, NB2 dispersion via method-of-moments, spike probability from the NB CDF, shipped only behind a strict-prior isotonic calibration map (verified ECE 0.021). Gate evidence: paired lift over the incumbent provider in 3/3 seasons (p = .0215 / .0025 / .009; pooled 0.6206 vs 0.5105 spike AUC, Δ +0.110, p = 0.0015). Adoption conditions are recorded and enforceable: the incumbent runs in parallel through 2026 with auto-fallback on any trailing-8-week regression, and the head is scoped to this single cell — it was tested for TE receptions and made things significantly worse (−0.152 spike AUC), which is exactly why cell-scoping is a rule and not a vibe.

4.Validation protocol

4.1 Walk-forward cross-validation, and nothing else

The only valid evaluation shape for this domain (snapcore/eval/harness.py::walk_forward_splits): to score predictions at (season S, week W), train on (season < S) OR (season == S AND week < W), predict on (season == S AND week == W), iterate chronologically. Random k-fold is invalid — it trains on future weeks of the same season. Feature columns are validated against the leakage block-list before the first fold yields.

4.2 What it takes for a change to count

4.3 Adversarial verification

Before any promotion, an independent verification pass — instructed to refute, not confirm — re-derives the numbers and attacks the construction. This step has teeth; its record includes:

4.4 The sealed-2025 protocol and pre-registration

5.Honest limitations

5.1 We trail the market on yards-ranking, at every position, today

The public market scoreboard (market_scoreboard.json, rendered on the 2024 replay page) grades us against RotoWire and ESPN on identical player populations — a player-week counts only if truth, our board, and every provider cover it. 2024 W9–18, 40 graded cells:

PosSnapcountRotoWireESPNBestGapOur cells W–L vs bestsign_p / wilcoxon_p
QB0.4160.5460.516RotoWire−0.1301–90.022 / 0.010
RB0.7300.7710.767RotoWire−0.0422–80.109 / 0.037
WR0.7150.7290.745ESPN−0.0301–90.022 / 0.014
TE0.6430.6660.682ESPN−0.0383–70.344 / 0.065
ALL0.6260.6780.678−0.052

Readings, verbatim from the internal tracker: we are #1 at zero positions today. QB is a statistically resolved loss and 3–4× the size of any other gap. TE is the closest to unresolved (sign_p 0.344; we already take 3–4 of 10 weekly slates). These all-common numbers run higher than the own-population numbers elsewhere in this document because the common population drops market-uncovered fringe players — compare only within one table, never across scoreboards. The claim rule is encoded in the artifact itself: "best at a position" requires beating every provider on the identical population, with paired tests agreeing, over consecutive graded periods. By that rule we claim nothing today.

5.2 The free-data ceiling is real and measured

Headroom map (pooled Spearman, recency → our model → opportunity oracle, W9–18): RB .675 → .688 → .932; WR .643 → .666 → .876; TE .574 → .609 → .861; QB .474 → .457 → .751 (the model loses to recency at QB, hence §3.3). Against the corrected, honest recency anchor (ewma5), our model's edge is ~+0.006–0.008 Spearman at RB/WR/TE — roughly 3% of the oracle headroom, not the 5–12% an earlier weak-anchor comparison suggested. The ceiling was stress-tested: a richer gradient-boosted configuration plus every additional free lagged signal we could construct (team pace, pass rate, red-zone shares) added approximately nothing, replicated per position. Free public data is exhausted for this problem on both the ranking and boom/bust axes; the honest description of our current edge is "beats naive baselines, trails paid-analyst market projections."

5.3 What paid data would change (and the gate on buying it)

The oracle gap decomposes mostly into this-week volume information we cannot see in free data. The #1 scoped paid lever is in-season route participation (charted, e.g. FTN/PFF-class feeds), with an evidence bar attached: a Stage-A partial-correlation probe grades it +.251 (TE) / +.186 (RB) against residuals, and the expected gain is +0.03–0.08 Spearman on WR/TE. The spend rule is pre-registered: a paid sample must beat the free RotoWire benchmark on WR/TE (> 0.687 / > 0.636 on the own-population yardstick) in ≥3 of 4 seasons — beating our own recency baseline is not enough — and a $0 public-tracking-data pre-validation is mandatory before any vendor contract is signed.

5.4 Other stated limits

6.The improvement loop (ledger excerpts)

Every research iteration appends a verdict — with numbers — to a ledger; NULL verdicts are recorded with the same detail as promotions, and "never re-quote as support" flags are attached to near-misses. Excerpts from the Week-1 2026 program (five iterations, 2026-07-11 → 07-12) and the preceding loop campaign:

A1 · Prior-season carry mechanicsNULL

Made the model emit at W1 by carrying prior-season volume and EB-shrunk efficiency forward. The standalone W1 model scored 0.513 — worse than both dumb baselines (recency 0.550, prior-season mean 0.554). Adversarial read: the blend's apparent edge decomposed ~80% into a QB-substitution effect containing zero model information. Real product: the harness and a sharpened weakness map.

A2 · Betting-market priors (spread, total, win totals)PARTIAL — W1 NULL, W4 hint

W1: nothing there — the in-model variant lost to the model-free book (−0.023 paired), and the tilt selector chose zero tilt where it had data. W4: a market-attributable window (beats the plain model 4/4 seasons at season level, paired sign_p 0.004 vs recency) — carried forward as a hint, not promoted.

A3 · Roster continuity + draft capitalPARTIAL — first real in-model signal

Gate: 2/4 seasons — candidates rejected. But the same-pipeline probe shows the features genuinely move the model (+0.009, 3/4 seasons), and the subgroup analysis produced the program's key insight: the "movers break the model" thesis was partially a phantom — prior-season aggregates carry across team changes far better than assumed; the real mover weakness belongs to recency predictors.

A4 · Coaching-change graphNULL at W1 (W4 window again)

Hand-curated OC/DC table, 32 teams × 2015–2026, per-row citations, 15/15 adversarial spot-audit. W1 model: 1/4, lost to the book by −0.016 paired. Conclusion: coaching-change information does not move W1 within-position ranking on free features. Durable assets: the cited coordinator table and a third consecutive family showing a W4 window.

A5 · Slow-start priors + new-contract recencyPARTIAL — one construct refuted, one real

The slow-start prior (F5) is a construct-level NULL: across 127 same-staff consecutive-season pairs, early-vs-late form anti-persists (Pearson −0.217) — the "slow-start team" trait does not exist at season-mean granularity in free data, and we retired it. New-contract recency (F7) is the program's strongest in-model W1 signal (+0.017, 4/4 seasons) but both A5 candidates failed the charter gate; the model-free book keeps the W1 seat.

W1 book → serving

The surviving incumbent — the model-free book (+0.021 vs the recency family at W1) plus the strict prior-seasons-only model at W2–4 — was promoted into week-aware serving, with W5+ byte-identity re-proven independently and the W1 numbers hand-traced to raw stats.

Selected reverts from the wider campaign, kept on the record so they stay dead: ranking-loss objectives (significantly worse than the squared-loss default); removal of an "obviously wrong" /3.0 divisor (significantly negative — it was beneficial implicit shrinkage); RB share pooling (significantly negative, Wilcoxon .017); the TE transfer of the RB count head (−0.152 spike AUC); player-level red-zone share features for TDs (null beyond lagged TD rate + volume); and four consecutive linear book-tilt grids (A2 tilt, A3 discount, A4 delta, A5 z-tilt — all ≈ net zero; the idea is retired). The QB side-flag that briefly suggested a promotable QB model resolved NULL under paired re-testing and is quarantined.

7.Roadmap