Table of Contents generated with DocToc
- Unreleased
- CFB — loaders for 6 published-but-unreachable dataset releases
- CFB —
load_cfb_ratingsdataset loader - NBA / WNBA — CTG play context (T3.6): possession/shot/lineup/player tables + start-type oracle
- Fixes
- Dependencies
- Release utilities —
sportsdataverse.release(sportsdataversedata R-package port) - PWHL — coordinate-based xG (T5.3b): xg_method default flips quality → coords
- PWHL — per-strength xG calibration + geometry hardening (T5 follow-up)
- CFB — advanced-efficiency spine (opponent-adjusted efficiency/explosiveness/havoc → field position → adjusted tempo)
- NFL — NGS over-expected tracking spine (YAC-OE → RYOE → separation-OE → man/zone rates)
- NFL — scheme & special teams spine (play-call model → game script → kicker/punter value → line grades)
- NFL — projection & draft spine (player projections → usage shares → availability → draft model)
- NFL — ratings & market spine (power ratings → win prob → spread/total → player props)
- CFB — recruiting & roster-projection spine (talent composite → returning production → wins projection → transfer impact → draft projection)
- NBA / WNBA / G-League — shot-value spine (xPoints → context make-prob → talent → selection → zone maps)
- MBB / WBB — shot-quality spine (xPoints → shot selection → shooter talent)
- MBB / WBB — player-value & projection spine (box-BPM → archetypes → recruiting → transfer → draft)
- Recruiting — ESPN NCAA recruiting family + On3 rankings
- MBB / WBB — prediction & tournament stack (ratings → pregame → in-game WP → résumé → bracketology → Monte Carlo)
- NBA — external concurrent validity + walk-forward retrodiction (WP3)
- NBA — RAPM variants (WP2)
- NBA — through-date ratings panel, WAR, and single-game BPM (WP4)
- NBA — v3-to-v2 play-by-play adapter (
nba_v3_to_v2_pbp) - NBA / WNBA — stats.nba.com / stats.wnba.com flat-API family (
nba_stats/wnba_stats) - NBA — possession event-detail columns, per-shooter shooting frame,
game_date - NBA — faithful possession boundaries (pbpstats parity)
- NBA — quarter-box on-court lineup seeding +
lineup_source="quarter_box"
- 0.0.71 Release: June 24, 2026
- 0.0.70 Release: June 24, 2026
- 0.0.69 Release: June 23, 2026
- 0.0.68 Release: June 23, 2026
- CFB — completion-probability (
cp/cpoe) + expected-pass (xpass/pass_oe) surface - CFB — spread-free (naive) win-probability surface (
wp_*_naive) - CFB — QBR model retrained on the full 2004–2025 history
- CFB — fourth-down decision surface (
get_4th_down_probs, cfb4th port) - CFB — two-point-conversion decision surface (
get_2pt_probs, cfb4th port) - CFB — rule-era QBR / FG / fourth-down models +
spread_timesign fix - CFB — pre-2014 play-text player-name extraction
- NFL — expected pass (
xpass/pass_oe) + nfl4th fourth-down decision surface - NFL — self-trained XGBoost field-goal model in the fourth-down surface
- NFL —
load_nfl_espn_qbr(ESPN QBR loader, nflreadpy parity) - NFL — bundled self-derived xpass model (offline, no first-use download)
- CFB — completion-probability (
- 0.0.67 Release: June 17, 2026
- 0.0.66 Release: June 17, 2026
- 0.0.65 Release: June 17, 2026
- 0.0.64 Release: June 17, 2026
- 0.0.63 Release: June 16, 2026
- 0.0.62 Release: June 16, 2026
- 0.0.61 Release: June 16, 2026
- 0.0.60 Release: June 15, 2026
- 0.0.59 Release: June 13, 2026
- CFB — cross-source crosswalk loaders (
load_cfb_*_crosswalk) - ESPN — NCAA men's & women's college hockey (
espn_mch_*,espn_wch_*) - ESPN — NCAA college baseball + softball (
espn_college_baseball_*,espn_college_softball_*) - ESPN — UFL, XFL, and CFL (
espn_ufl_*,espn_xfl_*,espn_cfl_*) - ESPN — soccer/cricket param families + soccer headline aliases (
espn_soccer_*(league=),espn_cricket_*(league=),espn_epl_*,espn_ucl_*,espn_mls_*, ...)
- CFB — cross-source crosswalk loaders (
- 0.0.58 Release: June 12, 2026
- Loaders — NHL core + new NBA/MBB datasets aligned to
sportsdataverse-datareleases - Robustness & infrastructure — typing, CI gates, HTTP, deprecation policy
- The Odds API wrappers (
sportsdataverse.odds,toa_*) - Yahoo Sports college football wrappers (
yahoo_cfb_*) - NFL —
api.nfl.comwrappers cut over to generated; "NFL.com API" docs grouping - NFL — automatic
api.nfl.comtoken caching +NFL_ACCESS_TOKENoverride - Documentation —
api.nfl.comOpenAPI spec - Bug fixes
- Internal — Fox data key single-sourced
- Loaders — NHL core + new NBA/MBB datasets aligned to
- 0.0.57 Release: June 10, 2026
- 0.0.56 Release: June 9, 2026
- HockeyTech — live multi-league scraper (PWHL + AHL/OHL/WHL/QMJHL) + on-ice/Corsi/TOI analytics
- NFL — Next Gen Stats (
nfl_ngs_*) + api.nfl.com football/v2 (nfl_*) modules - NFL — restored the api.nfl.com game schedule + play-by-play wrappers
- ESPN — remove always-erroring endpoint variants + NFL R-parity
- Documentation — per-league Python ↔ R parity tables
- Documentation — example notebooks repaired, expanded, and rendered on-site
- NHL / PWHL — loader naming-parity aliases + games-manifest loaders (fastRhockey parity)
- Documentation — NFL return-table descriptions mined from nflverse
- Documentation — class methods rendered on autodoc pages (CFB / NFL)
- Documentation — accuracy-audit fixes
- 0.0.55 Release: June 8, 2026
- 0.0.54 Release: June 8, 2026
- 0.0.53 Release: June 8, 2026
- ESPN — declarative codegen + factory retirement
- NHL native — codegen cutover + clean names (api-web; in progress)
- Dataset loaders — release manifest + drift audit
- Generated documentation — reference pages + drift gate
- CFB — advanced box score expansion (
create_box_score) - CFB — box-score attribution correctness + ESPN-sourced totals (
create_box_score) - CFB — play-type reclassification: interception-return-fumble guard (
__add_new_play_types) - CFB — blocked-kick turnover flags + ESPN native-flag tripwires
- CFB — pre-2014 era support (
CFBPlayProcess) - Removed — NCAA bracketology
- 0.0.52 Release: June 3, 2026
- 0.0.51 Release: May 30, 2026
- User-facing quality-of-life additions
- New: MLB module (greenfield)
- New: NHL —
api-web.nhle.commigration + EDGE / Stats REST / Records - New: ESPN cross-league port
- New: NCAA bracketology
- New:
_common_espn_parsers.py(polars / pandas parser layer) - New:
return_parsed=Truedispatch shim - New:
nhl_edge_parsers.py - New: Site v2 summary dispatcher (20 sub-parsers)
- New: 100% ENDPOINT_PARSERS coverage (121/121)
- New: weekly cron live-test drift detector
- New: MLB Stats API parser layer
- New: NHL Stats REST + Records parser layers
- New: NHL api-web parser layer
- Bug fixes
- New: NFL drive-plays parser (true PBP parity)
- Test infrastructure
- Documentation
- 0.0.50 Release: May 7, 2026
- Packaging modernization
- Conda installability
- Linting & pre-commit modernization
- Documentation toolchain
- Runnable docstring examples (~190 functions)
- Example notebooks
- Contributor docs and templates
- NFL — nflreadpy parity
- NFL — caching and configuration
- NFL — static datasets
- NFL — pickcenter / odds modern path
- NFL —
load_nfl_scheduleparquet port - WBB / WNBA — new ESPN scrape modules
- CFB —
cfb_play_participantsand__add_player_colscollapse - CFB — pandas → polars 1.x bug-fix reconciliation (
0.36-live→main) - Infrastructure and tooling
- Bug fixes
- Deprecations
- 0.0.40 Release: December 6, 2025
- 0.0.38-39 Release: August 28, 2023
- 0.0.36-37 Release: July 9, 2023
- 0.0.34-35 Release: May 7-9, 2023
- 0.0.18 Release: July 25, 2022
- 0.0.17 Release: July 9, 2022
- 0.0.15 Release: May 8, 2022
- 0.0.14 Release: March 16, 2022
- 0.0.12 Release: February 24, 2022
- 0.0.5 Release: October 20, 2021
Unreleased
CFB — loaders for 6 published-but-unreachable dataset releases
cfbfastR-cfb-data publishes 18 dataset tags to sportsdataverse-data; sdv-py
had loaders for 9. These six were built, backfilled, and published for years
with no way to read them from Python:
| loader | tag | seasons | cols |
|---|---|---|---|
load_cfb_passing | espn_cfb_passing | 2014– | 43 |
load_cfb_rushing | espn_cfb_rushing | 2014– | 28 |
load_cfb_receiving | espn_cfb_receiving | 2014– | 32 |
load_cfb_percentiles | espn_cfb_percentiles | 2014– | 27 |
load_cfb_team_summaries | espn_cfb_team_summaries | 2014– | 383 |
load_cfb_model_pbp | espn_cfb_model_pbp | 2004– | 43 |
The first five are the season-level "Binion Box Score" tables (opponent-adjusted
team summaries + passing/rushing/receiving leaderboards + per-metric
percentiles); load_cfb_model_pbp is the EP/WP/QBR-enriched play-by-play built
by that repo's model pipeline. Every returns-schema is introspected from the
real published parquet rather than derived, and a live-gated contract test
asserts the declared schema against the published asset so a producer-side
rename or dtype flip can't leave the docs table lying.
espn_cfb_injuries is intentionally not loadable: its release exists but
ships zero assets (write_dataset skips 0-row frames and ESPN's CFB injuries
feed yields nothing), so a loader would 404 on every season.
CFB — load_cfb_ratings dataset loader
load_cfb_ratings(seasons)— loads the publishedcfb_ratingsrelease (one row per team per season, 2004–): opponent-adjusted offensive / defensive / special-teams EPA, FEI,games,off_pace, denseoff_rank/def_rank/net_rank, andnet_z. The tag is produced bycfbfastR-cfb-data'scfb_model_publish ratingsbuilder running sdv-py's own :func:sportsdataverse.cfb.cfb_ratingsover the releasedespn_cfb_pbpplay-by-play, so the loader's returns-schema is the compute function's output schema — a contract test pins the two together (both column order and dtype) so a producer change can't silently leave the published returns-table lying. Like every release loader it is 404-safe: seasons with no published asset are skipped with a warning rather than raising.
NBA / WNBA — CTG play context (T3.6): possession/shot/lineup/player tables + start-type oracle
nba_play_context/wnba_play_context— Cleaning the Glass recreation on the shipped possession engine. Per-possession context (possession_start_typecoarse family +possession_start_type_detailzone-split + the fivepossession_start_type_ctgbuckets,is_transition/transition_source,seconds_to_first_play,is_garbage_time/garbage_time_basis,is_heave_possession) and per-shot context (ctg_shot_zone,is_putback,is_second_chance_shot,shot_context).wnba_play_contextis a real shim (wnba_engine), byte-identical to the NBA core on WNBA fixtures.lineup_play_context/player_play_context— on/off possessions + points per 5-man unit and per player, sharing one aggregation core; the OFF side is derived by subtraction so the on/off split is exact by construction.starters_on_court_counts— implements CTG's garbage-time "<=2 starters on floor" clause; when starter data is joinedgarbage_time_basisupgrades frommargin_onlytomargin+starters(containment-verified against margin-only).- Faithful pbpstats possession start-type — boundary-only timeout detection
(a port of
possession_has_timeout/previous_possession_has_timeout, incl. the asymmetric FT-sandwich technical carve-out), exact CTG shot-zone boundaries from the legacy coordinates (the v3shot_distancecolumn isInt64, rounded to whole feet), and ateam_id == 0team-rebound discriminator (the v3 feed stuffs the team id intoperson_id). Validated like-for-like against pbpstats-live: 99.50% coarsepossession_start_typeagreement (592/595) across the committed fixtures (test_nba_play_context_oracle.py, gated onSDV_PBPSTATS_ROOT).
Fixes
- fix(cfb):
cfb_ratings()now works against the releasedespn_cfb_pbpasset it documents itself as loading. The function advertises that it pulls play-by-play viaload_cfb_pbp, but that path had never been exercised — the module was built and gated entirely against a 14-column fixture carrying cfbfastR-canonical names, while the published asset is ESPN-shaped. Every real call (cfb_ratings(2023)) raisedKeyErroronpos_team_id/def_pos_team_id/home/neutral_site, then onplay_type/drive_id. The orchestrator now normalizes the released field names (start.pos_team.id,start.def_pos_team.id,homeTeamId,type.text,drive.id, plusneutral_siteoff the schedule join), aliasing only when the canonical name is absent so callers passing an already-canonical frame are unchanged. The HFA term is now guarded onpos_team/homedtype agreement — it derives frompos_team == home, which across mismatched namespaces silently marked every play a road play rather than failing. Verified on the real 2023 asset (153,625 plays → 227 teams); the oracle gates hold on released data (adj_netvs FPI 0.926, vs SP+ 0.936;adj_offvs SP+ off 0.846;adj_defvs SP+ def 0.793). - fix(codegen): all codegen/capture writers now emit LF explicitly
(
newline="\n"), matchinggenerate.py's convention. On Windows the text-mode default translated\nto CRLF, so every codegen-test run (which re-runs the pff / 247 site-pages generators) left ~65 endpoint + schema YAMLs dirty with line-ending-only churn against the LF-normalized index. - feat(nfl): PFF Premium auth can now auto-refresh from a saved Playwright
storage_state. PointSDV_PY_PFF_STORAGE_STATEat astorage_stateJSON captured once from a headed login andpff_runtimereplays it headlessly so Clerk re-mints the short-lived__sessioncookie, extracting fresh_premium_key+__sessionand caching them in-process forSDV_PY_PFF_STORAGE_STATE_TTLseconds (default 300 — one browser launch per window, not per request). It slots in as auth tier 3 (explicitcookies=>SDV_PY_PFF_*env cookies > storage_state), so existing callers are unaffected. Needs the new optionalplaywrightextra (pip install sportsdataverse[pff]thenplaywright install chromium); a missing install raises a clearImportError. The browser refresh is injectable (refresher=) so the wiring is fully offline-testable. - fix(nfl): the DynastyProcess CSV loaders (
load_nfl_ff_playerids,load_nfl_ff_rankings) retry with exponential backoff on transient upstream errors (HTTP 429/5xx) instead of failing on the first hit — the raw-GitHub host rate-limits parallel CI runners. - fix(codegen): reference-doc Returns prose no longer mangles docstrings
whose return description is an inline
col: dtype, ...code span.docstring_parsersplits a Google-styleReturns:body on the first colon, so a colon inside the span was mistaken for the type/description separator — dropping the leading column/key and leaving a stray unbalanced backtick. The renderer now recombines the mis-split fragment (only when the parsed type contains a backtick, so legit Google types likepl.DataFrameare untouched), fixing ~15 rendered Returns across the cfb/mbb/wbb/nba/nfl/mlb/wnba reference pages. - fix(dl_utils):
download()now retries transient status codes (403/408/429/500/502/503/504) with the sameRetry-After-aware backoff it already used for connection failures — previously a 429/403/5xx came back as a normalResponseand was returned without a retry (the root cause the DynastyProcess loader-level retry worked around). The retryable set is configurable via the newretry_statuses=param; when the budget is spent the last response is returned unchanged (callers still key on.status_code), and non-2xx responses are no longer cached. 403 is retried by default because ESPN's Core v2 API returns it under load —download()is the ESPN/nflverse gateway and does not serve the auth'd endpoints.
Dependencies
- chore(deps): dropped the unused
pyreadrdependency (runtime deps + themodels/allextras and the conda recipe). It had no live imports — its only reference was a long-commented.rdscontracts loader — and its removal also drops thelibbz2/liblzmasystem-header requirement that pyreadr's sdist build imposed on Python 3.9 Linux installs.
Release utilities — sportsdataverse.release (sportsdataversedata R-package port)
- feat(release): new
sportsdataverse.releasemodule — a Python port of thesportsdataversedataR package (v0.0.11) used to publish data assets tosportsdataverse/sportsdataverse-dataGitHub releases. Surface:sportsdataverse_save()(writes rds / csv / csv.gz / parquet — the R default set — withsportsdataverse_type+sportsdataverse_timestampstamped into parquet metadata and as R attributes on the rds frame, plus R-parityseason/weekinteger coercion),sportsdataverse_upload()(addstimestamp.*/package_function.*sidecars; retries the whole upload with the sameSPORTSDATAVERSE.UPLOAD.*env-var backoff config the R package reads), plusgh_cli_release_upload/gh_cli_release_tags/gh_cli_release_assets/gh_cli_rate_limits/gh_cli_available. Backed by golden-fixture parity tests generated by running the real R functions (tests/fixtures/release/). Deliberate divergences (documented in the module docstring): onegh release uploadinvocation per file (the multi-file form silently drops large assets), the R-onlyqsformat raisesValueError, unpaddedsize_string, no.tokenargument (gh CLI auth;GH_TOKENfalls back toGITHUB_PAT). - feat(release): native
.rdswriting without R or any new dependency —sportsdataverse/_rds.pyimplements RDS version-2 (XDR) serialization for data.frames of atomic columns (integer / double / logical / character / Date / POSIXct, with NA fidelity, UTF-8 strings, int64→double promotion beyond int32 range, and R symbol back-references). Output is validated byte-for-byte against R 4.5.3saveRDS()via the committedrds_golden.rdsfixture (serialization header excluded) and via a livereadRDS()oracle at fixture-capture time. Nested/list columns raiseValueError.
PWHL — coordinate-based xG (T5.3b): xg_method default flips quality → coords
- feat(pwhl)!:
pwhl_team_game_xg_rates/pwhl_ratings_from_proxygain anxg_methodparameter ("coords" | "quality") and the DEFAULT is the new coordinate distance/angle logistic xG (fit_pwhl_coord_xg, geometry fromhockeytech._analytics.add_shot_distance_angle) — output values change for existing callers that relied on the implicit categorical shot-quality proxy; passxg_method="quality"to keep the old behavior.LEAGUE_CONSTANTS["pwhl"].margin_sdre-fit 1.21 → 1.19 (coords-paired, 2025-only). Held-out 2026 (n=107): coords Brier 0.2444 vs quality 0.2449 vs naive 0.2500 (within noise; gates stay no-worse-than-naive + calibration).
PWHL — per-strength xG calibration + geometry hardening (T5 follow-up)
- feat(pwhl):
fit_pwhl_coord_xggainscalibrate_strength=True(default) — when the frame carries strength columns it fits a per-EV/PP/SH Platt recalibrator (PwhlCoordXGModel.strength_calibrators) that shrinks residual within-bucket per-strength calibration error at ~zero AUC cost (held-out LOSO SH 10-bin ECE 0.0130 → 0.0091, AUC 0.6962 both). Default xG output shifts slightly for strength-bearing frames; identity (unchanged) on 2-feature or thin frames, or withcalibrate_strength=False. Buckets are shooter-relative (is_pp/is_sh). - fix(pwhl,hockeytech): harden the shot-xG geometry against the dual-frame
coordinate footgun —
fit_pwhl_coord_xg/predictnow raise on a RAW-scale (0–600) enrich frame instead of silently scoring it withgoal_x=89, andhockeytech._analytics.add_shot_distance_angleassertsgoal_xis in a plausible rink range (default is the documented NHL-size-rink constant).
CFB — advanced-efficiency spine (opponent-adjusted efficiency/explosiveness/havoc → field position → adjusted tempo)
- feat(cfb):
cfb_advanced_stats— Connelly-style opponent-adjusted offense/defense efficiency, explosiveness (isoPPP), success rate, EPA/play, and havoc fromload_cfb_pbpwith garbage-time filtering. Validated on 2021 vs CFBD advanced stats + SP+: success-rate Spearman 0.954/0.959 (off/def), opponent-adjusted EPA ranks vs SP+ component ranks 0.866/0.843. - feat(cfb):
cfb_field_position— team drive-start field-position value model on a bundled EP-by-yardline curve (fit_field_position_ep/load_fp_curve); avg-start vs CFBD 2021 Spearman 0.897, MAE 0.67 yards. - feat(cfb):
cfb_adjusted_tempo— opponent-adjusted, situation-neutral pace (raw plays/game vs CFBD 2021 Spearman 0.902; adjusted pace validated as a monotone re-ordering of raw, Spearman 0.948). - feat(cfb):
cfb_advanced_constants— shared success/explosive/garbage-time flags + metric helpers and the iterative opponent-adjustment solver. - Committed 2021 CFBD advanced + SP+ oracle corpus under
tests/fixtures/cfb_advanced/with provenance README (hosted pbp covers 2002-2021; 2022+ is a producer-backfill gap).
NFL — NGS over-expected tracking spine (YAC-OE → RYOE → separation-OE → man/zone rates)
- feat(nfl):
nfl_ngs_yac_oe— receiving YAC-over-expected with empirical-Bayes shrinkage (weekly-σ² estimator); shrunk estimates are more next-season-stable than raw (stability oracle: corr(shrunk₂₀₂₂, raw₂₀₂₃) ≥ corr(raw, raw), n=80). - feat(nfl):
nfl_ngs_ryoe— rushing yards-over-expected per attempt with the same shrinkage machinery. The 2022→2023 RYOE stability gate is a documented strict xfail: the base year-over-year signal on that transition is statistically zero (raw→raw corr 0.045, n=33), so no estimator can beat it — escalation noted in the test. - feat(nfl):
nfl_ngs_separation_oe— expected-separation ridge (cushion + air-yards + alignment one-hots, intercept unpenalized) with a positive cushion coefficient by construction; the separation-OE stability gate is a strict xfail on the underpowered 2022→2023 transition. - feat(nfl):
nfl_ngs_man_zone_rates— team-level man/zone coverage snap rates (one row per season/defteam) from the NGS tracking panel. - feat(nfl):
nfl_ngs_constants— shared empirical-Bayes shrinkage, weekly-σ² identification, expected-separation ridge, and the dtype-guardednext_season_stabilityjoin (asserts amin_noverlap floor so a shrunken fixture cannot let a stability gate pass on a handful of players). - Committed NGS panel fixtures + fitting scripts under
tests/fixtures/anddev/nfl_ngs/.
NFL — scheme & special teams spine (play-call model → game script → kicker/punter value → line grades)
- feat(nfl):
nfl_playcall— run/pass + play-family classifier (bundlednfl_playcall.ubj, trained 2016-2021) that beats the shippedxpass_modelon held-out 2022-23 (log-loss 0.498 vs 0.518, AUC 0.822 vs 0.798) with PROE reconciling to pbppass_oeexactly; team tendency profiles included. - feat(nfl):
nfl_gamescript— expected plays / pace / game-script curves (held-out 2023 expected-plays MAE 1.77). - feat(nfl):
nfl_kicker_rating— environment-adjusted FG make probability + empirical-Bayes FGOE (held-out 2019-23 decile calibration gap 0.038; the systematic bias traced to nfl4th's long-kick decision clamp and corrected with a fitted term). - feat(nfl):
nfl_special_teams— per-unit ST EPA decomposition (sums reconcile to team ST EPA exactly) + punter net-over-expected (YoY stability 0.62/0.55). - feat(nfl):
nfl_line_grades— OL/DL pressure-based grades from pbp + PFR advstats (pbp-vs-PFR pressures Spearman 0.794). - feat(nfl):
nfl_scheme_constants— shared metrics/constants + as-of split. - Committed fixture corpus + fitting scripts under
tests/fixtures/anddev/nfl_scheme/; known upstream issue flagged:load_nfl_pbp_participationcrashes on multi-season loads (cross-season schema drift) — work around per-season withhow="diagonal_relaxed".
NFL — projection & draft spine (player projections → usage shares → availability → draft model)
- feat(nfl):
nfl_projection— next-season player stat projections with integer-age aging curves and fitted per-position damping (holdout-2024 Spearman QB/RB/WR/TE 0.61/0.72/0.66/0.73; beats naive carry-forward for QB/WR/TE, RB shortfall shipped as a documented strict xfail). - feat(nfl):
nfl_usage_projection— team-internal target/carry share projections that sum to 1.0 by construction (share Spearman RB/WR/TE 0.73/0.65/0.74 on the 2024 holdout). - feat(nfl):
nfl_availability— expected games played from historical availability (MAE 3.54, decile calibration gap 0.049). - feat(nfl):
nfl_draft_model— combine + college-production draft-position model (Spearman 0.587 vs realized draft slots, n=1269; hit-rate calibration gap 0.08) withw_avcareer labels (car_avupstream is all-null). - feat(nfl):
nfl_projection_constants— shared metrics, league constants, and the as-of split the backtests enforce; constants fitted on 2022/2023 as-of folds only (single-evaluation 2024 holdout). - Committed fixture corpus under
tests/fixtures/nfl_projection/with provenance README.
NFL — ratings & market spine (power ratings → win prob → spread/total → player props)
- feat(nfl):
nfl_ratings— opponent-adjusted ridge team ratings (offense/defense/net EPA per play + HFA) fromload_nfl_pbp, validated vs ESPN FPI (Spearman 0.890) and raw team EPA (0.965, 32/32 matched). - feat(nfl):
nfl_market— pregame win probability (Brier 0.232, quintile calibration gap 0.036), spread and total projections (MAE 2.96 / 3.24 vs closing lines over 208 as-of games, weeks 5-18), fitted constants (points-per-net 23.36, HFA 3.12, margin sd 13.02) from committed fitting scripts. - feat(nfl):
nfl_player_props— empirical-Bayes player projection distributions for passing/rushing/receiving yards with over-probabilities (MAE 70.5/21.1/21.4 vs realized; p_over calibrated against lagged pseudo-lines — ESPN purges historical propbets, documented in-test). - feat(nfl):
nfl_prediction_constants— shared metrics (Brier, log-loss, Spearman, calibration tables), league constants, and the as-of season/week split helper the backtests enforce. - Committed fixture corpus under
tests/fixtures/nfl_prediction/with provenance README.
CFB — recruiting & roster-projection spine (talent composite → returning production → wins projection → transfer impact → draft projection)
- feat(cfb):
load_recruit_classes— per-recruit signing classes from the 247 RDB feed (signed institution with committed fallback; stars, composite grade, position, player name; idsUtf8). - feat(cfb):
blue_chip_ratio+cfb_roster_talent— Bud Elliott blue-chip ratio over a trailing 4-class window and a 247-style class-recency-weighted team talent composite (optionalcomposite_247snapshot override). Oracle-gated vs the 2023 247 Team Talent snapshot (Spearman 0.896, 196/196 teams name-matched) + a percentile champion blue-chip invariant. - feat(cfb):
cfb_returning_production— Connelly-style returning production (offense = attributed yardage; defense = splash events) from the hosted per-play player-stats parquet + rosters; unit weights fitted on FBS 2018-2023 (offense-only; retention gate Spearman 0.229). - feat(cfb):
cfb_recruiting_projection— on-demand ridge projecting wins / scoring margin from preseason features (talent, blue-chip ratio, returning production, prior wins) with the as-of season boundary enforced internally. Backtest 2019-2023: pooled wins MAE 2.19, beating prior-year (2.46) and league-mean (2.34) baselines. - feat(cfb):
cfb_transfer_moves+cfb_transfer_impact— transfer-portal moves from roster year-over-year diffs (name-matched recruit talent points) and a net-talent win-delta ridge. The predictive gate is a documented strict-xfail: net transfer talent shows no team-level win-delta signal on 2018-2023 data (escalation: position-specific values + PFF NCAA grades). - feat(cfb):
load_draft_outcomes+cfb_draft_projection— NFL draft labels from the nflverse picks dataset (the ESPN season-draft endpoint 404s) and an as-of logistic draft-probability model (stars, talent points, career production, class year) with per-team expected-picks roll-up. Holdout AUC 0.78-0.82 (2022-2024 drafts); team draft-capital Spearman 0.62 observed. - Committed oracle fixtures under
tests/fixtures/cfb_projection/(results, 247 talent + 39.6k recruits 2014-2023, returning production, team map, nflverse draft picks, per-player production, net transfer talent) with provenance README.
NBA / WNBA / G-League — shot-value spine (xPoints → context make-prob → talent → selection → zone maps)
- feat(nba):
score_shot_xpoints+xpoints_baseline— per-shot expected points from theLeagueAverageszone-FG% table thatnba_stats_shotchartdetailreturns for free (no bundled artifact, compute-on-demand). Asserts the three zone join keys share dtype before joining;game_idstaysUtf8(zero-padded),player_id/team_idInt64. - feat(nba):
make_prob_by_context+make_prob_joint— FG% by defender distance and shot clock (aggregateplayerdashptshotsbuckets, the only form the public API exposes) plus an independence-combined joint via odds multipliers. - feat(nba):
shooter_talent— regressed make%-above-expected per shooter (split-half-fitted shrinkagek; NBA/G-League 70.1). - feat(nba):
shot_selection_quality(player expected value per shot vs the league) +zone_value_map(per-player per-zone points / expected points). - feat(nba):
nba_shot_valueorchestrator (fetch → score → all five models,include_context=) +nba_shot_value_lineups(shotchartlineupdetailvariant). One league-agnostic core switched byleague_id("00"NBA,"10"WNBA,"20"G-League); court geometry + shrinkage constants keyed by league. - feat(wnba):
wnba_shot_valueby-reference shim (league_id="10") + the five model functions re-exported; G-League isnba_shot_value(..., league_id="20").
MBB / WBB — shot-quality spine (xPoints → shot selection → shooter talent)
- feat(mbb): canonical shot frame + dual-source adapter (
mbb_shot_data,espn_shots_to_canonical,shot_events_to_frame, geometry classifiers) — normalizes the ESPN shots release (basket-anchoredcoordinate_*_rawhalf-court grid, court scale FITTED from rim-make origins + made-three distances, int32-sentinel rows dropped) and the NCAA HTML shot charts (create_shot_event_dataoutput; source axes swapped to the canonical lateral/up-court orientation) into one schema with pinnedUtf8ids. - feat(mbb):
mbb_shot_quality_model+mbb_shot_quality— compute-on-demand empirical-Bayeszone × typemake-rate/xPoints table (cells shrunk toward the parent-zone mean) and the per-shotxmake/xpointsscorer. No bundled artifact. Oracle-gated offline: temporal train/holdout calibration (Σ xpoints / Σ actual ≈ 1.00, per-zone bands ≤ 0.03) and blended 2P/3P within ±0.02 of observed Barttorvik national aggregates. - feat(mbb):
mbb_shot_selection— per shooter/team expected points per attempt vs a league-average shot mix (selection_value, attempt-weighted zero-sum by construction). - feat(mbb):
mbb_shooter_talent+fit_shrinkage_k— per-shooter make%-over-expected regressed by a split-half-fittedk(mens 233.2, womens 92.4); reliability gated on splits the fit never saw. - feat(wbb): by-reference shims for all of the above
(
wbb_shot_data/wbb_shot_quality*/wbb_shot_selection/wbb_shooter_talent) with era-matched women's gates (season 2026 — thewbb_shotsrelease floor).
MBB / WBB — player-value & projection spine (box-BPM → archetypes → recruiting → transfer → draft)
- feat(mbb):
mbb_box_bpm— team-constrained box Plus/Minus from a game-level minutes-weighted fit (lineup-free APM identification); bundled ridge artifact; oracle-gated vs Barttorvik BPM (Spearman 0.88 mens / 0.91 womens) plus an independent 125-game NCAA stint-RAPM validation at ~95% of the grain's noise ceiling. - feat(mbb):
mbb_archetypes— bundled KMeans role clusters (k=6 mens, k=8 womens incl. women-specific "midrange big" / "slashing guard"), gated on bootstrap ARI ≥ 0.70 + hand-labeled role-certain players. - feat(mbb):
mbb_recruiting_projection— expected freshman box-BPM from pre-arrival composite/rank/height (as-of safe); LOSO-gated ≥ 0.45 per held-out class. - feat(mbb):
mbb_transfer_projection+transfer_cohort— post-transfer box-BPM projection over the boxscore-discontinuity cohort (the roster release under-reports moves ~70×); beats the naive post=pre baseline. - feat(mbb):
mbb_draft_projection— dual-head draft probability (AUC 0.97+) + log-pick projection with tier bucketing; WNBA pick head's data floor documented as an xfail at the unlowered gate. - feat(wbb): by-reference shims + women's artifacts for all five models.
Recruiting — ESPN NCAA recruiting family + On3 rankings
- feat(espn): Core v2
recruitingfamily —espn_{league}_recruiting_years/espn_{league}_recruiting_players/espn_{league}_recruiting_rankingsacross the 7 NCAA-scope leagues (cfb, mbb, wbb, college baseball/softball, m/w college hockey).recruiting/{year}/athletesships inline athlete objects that flatten to a 33-column recruit frame (identity, grades, recruiting class); all three shorts route throughparse_itemsvia thereturn_parsedshim. Live-captured MBB fixtures + offline tests. - feat(cfb): On3 recruiting rankings stem (
on3_player_rankings,on3_industry_player_rankings,on3_team_rankings,on3_industry_team_rankings) over on3.com's Next.js data routes — the only public JSON surface; industry = On3/Rivals/247Sports/ESPN consensus, including NIL valuations and commitment / transfer status.on3_runtime._getauto-discovers the rotating Next.jsbuildIdfrom the rankings page and refreshes it once on the stale-buildId 404 (an unchanged buildId is treated as an authoritative miss). Returns-schemasnative/on3/*with all 224 column descriptions authored; trimmed real-capture fixtures + offline runtime, parser, and wiring tests. - feat(cfb): 247Sports Recruit Database stem (11 wrappers) over
ipa.247sports.com/rdb/v1/—sports247_recruits(individual recruit rankings: 247 + industry-composite ratings/stars/ranks, commit status),sports247_transfers(transfer portal),sports247_coaches,sports247_target_predictions(expert "crystal ball"),sports247_institution_rankings/sports247_teams/sports247_composite_team_ranking_feed/sports247_transfer_portal_team_feed/sports247_transfer_portal_player_feed/sports247_sport_years/sports247_tags_autocomplete. One genericparse_sports247_result_setcovers every payload shape (bare array /{players|results|rankings|list: [...]}envelope / scalar array / single object). The Fastly edge fingerprint-blocks plainrequests, so the runtime uses lazy-optionalcurl_cffiChrome impersonation with an injectable transport (thenba_statspattern) and normalizes slash-less paths (the RDB 301s them). Most routes need anAuthorization: Bearerguest JWT —GET https://247sports.com/mints one with no login (~12 h TTL); the runtime mints/caches/refreshes it automatically (re-mints once on a 401/403). The ~14 remaining routes stay 403 even with the guest token (logged-in/premium) and are not wrapped. Returns-schemasnative/sports247/*with all 211 column descriptions authored; real-capture fixtures + offline parser/runtime/wiring tests.
MBB / WBB — prediction & tournament stack (ratings → pregame → in-game WP → résumé → bracketology → Monte Carlo)
- feat(mbb): opponent-adjusted team ratings (
mbb_team_ratings: AdjO/AdjD/AdjEM/AdjTempo KenPom-style fixed points) oracle-gated vs barttorvik 2024 (Spearman 0.990, MAE 2.37). - feat(mbb): closed-form pregame predictions (
predict_margin/win_prob_from_margin/predict_total+ vectorizedmbb_predict_games) with backtest-fitted constants (em_scale/HFA/sigma/tempo-anchor, joint least squares on a leakage-free as-of-date walk of 2024); gates: Brier beats the ESPN BPI predictor (0.2006 vs 0.2031), spread MAE 1.95 / total MAE 2.90 vs the closing line. - feat(mbb): in-game win probability (
in_game_features+mbb_in_game_win_prob) from a bundled shallow-xgboost artifact trained on 2023 pbp; out-of-sample 2024 decile calibration max gap 0.0298 (gate ≤ 0.03). - feat(mbb): strength of schedule + NET-style Quad 1–4 résumé + Wins Above Bubble
(
mbb_strength_of_schedule); SoS Spearman vs ESPN BPI SOS 0.923. - feat(mbb): bracketology (
mbb_bracketology/project_bracket+ conference auto-bids) — committee-style résumé blend, 68-team field selection, seeds; seed-order Spearman vs the actual 2024 committee 0.938. - feat(mbb): season + bracket Monte Carlo (
mbb_season_sim/mbb_bracket_sim/simulate_game, seeded + deterministic); neutral-site calibration slope 1.03 on 413 neutral games (includes the NCAA tournament). - feat(wbb): full women's parity — six thin shims (
wbb_team_ratings,wbb_predict_games,wbb_in_game_win_prob,wbb_strength_of_schedule,wbb_bracketology,wbb_season_sim/wbb_bracket_sim) over the league-agnostic mbb cores with women's fitted constants + a women's-trained WP artifact; every oracle gate re-run on WBB 2024 at the same thresholds (Torvik 0.995, SoS 0.985, seed-order 0.976, WP deciles 0.0224). - test(mbb,wbb): committed 2024 oracle corpora under
tests/fixtures/{mbb,wbb}_prediction/(results/team-box/barttorvik/ESPN BPI/predictor/odds samples/pbp WP samples/actual NCAA tournament seeds) with provenance READMEs; all gates run offline in CI.
NBA — external concurrent validity + walk-forward retrodiction (WP3)
- feat(nba): model-zoo v2 WP3 — Oracle 5 external concurrent validity
(
external_validity, five published-metric loaders innba_oracle_data.py: Ryan Davis RAPM, Dunks & Threes EPM, LEBRON season/daily, DARKO DPM, Dunks & Threes ewins) and Oracle 6 walk-forward retrodiction (walk_forward, time-ordered "predict tomorrow" with carry-forward and random-fold baselines) innba_model_validation.py.
NBA — RAPM variants (WP2)
- feat(nba): RAPM variants (
nba_rapm_variants) — luck-adjusted (nba_la_rapm), four-factor (nba_four_factor_rapm), and time-decay (nba_decay_rapm) RAPM, all reusing the plain-RAPM design matrix; concurrent-validity vs the Ryan Davis oracle CSVs gated onSDV_PY_NBA_ORACLE_DIR.
NBA — through-date ratings panel, WAR, and single-game BPM (WP4)
- feat(nba): through-date ratings panel (
nba_ratings_panel+ratings_as_ofprimitive, leakage-free by construction — works with any harness model), WAR layer (nba_war+calibrate_pts_per_win/calibrate_replacement_levelcalibration helpers), andnba_bpm(granularity="game")single-game BPM 2.0.
NBA — v3-to-v2 play-by-play adapter (nba_v3_to_v2_pbp)
New sportsdataverse/nba/nba_v3_v2_adapter.py ports hoopR's .v3_to_v2_format() to Python:
nba_v3_to_v2_pbp(pbp_v3, box_v3, *, return_as_pandas=False) turns a playbyplayv3 payload +
boxscoretraditionalv3 boxscore into the full 61-column v2-schema frame (NBA retired playbyplayv2,
which now returns 0 rows for every season — this restores v2-dataset compatibility from the live v3 feed).
- Recovers the secondary players v3 drops: assist via the
(Name N AST)description parenthetical, block/steal via the standaloneactionType == ""rows (the blocker/stealer ships aspersonId, associated to the shot/turnover at the same period+clock), sub-in viaSUB: X FOR Y, and jump-ball viavs. / Tip to— each resolved through a 4-tier roster name-match (family → name_i → "F. Family" → fuzzy). Validated 1-to-1 against the cdn live feed's structuredassistPersonId/blockPersonId/stealPersonIdfields: 100% agreement on all three committed fixture games. Documented gap: the foul-drawn player is unrecoverable from v3 (fouls carry nullplayer2/player3). - v2 schema faithful to hoopR — event/action-type codes (EVENTMSGTYPE/EVENTMSGACTIONTYPE),
home/neutral/visitor description split by
location, forward-filledscore/score_margin/team_leading, person types, time columns from the ISO clock, string-typed ids (leading zeros preserved), plus the v3 passthrough columns. One deliberate divergence:player2/player3are enriched by id from the extraction rather than hoopR's name re-resolution (which can mismatch on family-name collisions). - pbpstats interop: a
stats_nbafeed shim (resultSetsenvelope) lets the adapted frame drive thepbpstatslibrary's v2 provider. A gated round-trip test feeds our v3-derived output through pbpstats-stats_nbaand matches pbpstats' ownliveprovider on the same games (possession counts within 0–2, period starters exact 8/8). The round-trip surfaced two fixes:PLAYER1_TEAM_IDserializes as null (not 0) on team-rebound rows, and the"Transition Take"foul subtype (EVENTMSGACTIONTYPE 31, added with the 2022-23 transition take foul rule) joined the foul table. Opt in locally withSDV_PBPSTATS_ROOT=<path to a pbpstats checkout>. - Six cdn oracle fixtures committed under
tests/fixtures/nba_engine/{gid}/cdn_{playbyplay,boxscore}.json(provenance documented in the fixtures README); exported assportsdataverse.nba.nba_v3_to_v2_pbp.
NBA / WNBA — stats.nba.com / stats.wnba.com flat-API family (nba_stats / wnba_stats)
Two new codegen-generated flat-API stems wrap the official stats API surface:
nba_stats(sportsdataverse/nba/nba_stats.py) — 112 wrappers targetingstats.nba.com. League routing is a singleleague_idparameter on each endpoint:"00"→ NBA,"20"→ G-League,"15"→ Summer League. Namednba_stats_<slug>(e.g.nba_stats_leaguedashplayerstats,nba_stats_playercareerstats,nba_stats_boxscoreplayertrackv3).wnba_stats(sportsdataverse/wnba/wnba_stats.py) — 95 wrappers targetingstats.wnba.com(WNBALeagueID=10), namedwnba_stats_<slug>. Implemented as a thin shim re-exporting the NBA-stats runtime with the WNBA host.- Codegen surface = capture-confirmed live, non-deprecated endpoints only. The wrapper count is driven by a live capture sweep (committed under
sdv-internal-refs/nba/): endpoints that annba_api/hoopR/wehoopsource marks deprecated (lifecycle::deprecate_*, runtime warnings, or release-note retirements — 26 endpoints) and endpoints with no capture confirming a populated table (untested/barren/deadfor that league) are excluded. The full active/dying/barren/dead matrix lives insdv-internal-refs/nba/ENDPOINT_HEALTH.md. - One generic parser
parse_nba_stats_result_sets(raw, result_set=None, *, return_as_pandas=False)handles the uniform{resultSets: [{name, headers, rowSet}]}envelope. Returns a singlepolars.DataFramewhen aresult_setname is given or the payload has one set; returnsdict[str, DataFrame]for multi-set payloads (e.g.playercareerstats). Empty / malformed payloads return a zero-row frame; columns are snake-cased viadl_utils.underscore. It also handles the two non-uniform shapes in this family: the shot-location endpoints (leaguedash{player,team}shotlocations) whoseresultSetsis a single dict with 2-level grouped headers (flattened to composite columns likeless_than_5_ft_fgm), andscoreboardv3whose data lives underscoreboard.games(one row per game, home/away team objects inlined).parse_wnba_stats_result_setsis a re-export alias. - Browser-TLS runtime:
stats.nba.comTLS/JA3-fingerprint-blocks plainrequests(silent timeout, not an IP block). The runtime_getusescurl_cffiwithimpersonate="chrome".curl_cffiis a lazy optional import shipped under thetestsandallextras — not a hard runtime dep. A clearImportErrorguides users topip install curl_cffi(orpip install sportsdataverse[all]). The HTTP transport is injectable so wrappers and tests can run fully offline. - Wrappers default to
return_parsed=True(tidy polars DataFrame). Passreturn_parsed=Falsefor the rawDictorreturn_as_pandas=Truefor pandas. There is no user-facingheaders=param — the TLS impersonation is handled inside the runtime, not via a user token. - Generated from the enriched canonical catalog (
tools/codegen/gen_nba_stats.py) and registered inFLAT_APISintools/codegen/generate.py. Paramdefault/examplevalues are mined from the hoopR/wehoop roxygen signatures +@examples. Returns-table descriptions are authored for the pilot slugs and back-filled by column name from the SDV R-package docs (_r_col_desc); the remaining un-authorednative/nba_stats+native/wnba_statscolumns are a tracked follow-up exempted from the coverage ratchet viaextract_residual_columns._DEFERRED_BUCKETS(surfaced bydeferred_columns()).
NBA — possession event-detail columns, per-shooter shooting frame, game_date
- feat(nba): possession event-detail columns (
fg2a/fg2m/fg3a/fg3m/fta/ftm/oreb/tov), per-shooterbuild_possession_shootingcompanion frame, andgame_dateoncompile_nba_seasonoutput (possession cachePIPELINE_VERSION1 -> 2).
NBA — faithful possession boundaries (pbpstats parity)
- feat(nba):
_build_possession_groupsrewritten to pbpstatsstats_nbais_possession_ending_eventsemantics (and-1 + FT-trip exceptions, real-rebound and no-turnover filtering, jump-ball logic); technical FTs are inline again with team-filtered event detail (per-possession points identity preserved exactly). - feat(nba): possessions gain
dreb,number_in_period,possession_start_type(coarse vocabulary),count_as_possession; shooting frame gainsteam_id(possession cachePIPELINE_VERSION2 -> 3). - test(nba): pbpstats-live oracle gate — like-for-like possession counts +
boundary-by-boundary diff on the committed cdn fixtures (
SDV_PBPSTATS_ROOT).
NBA — quarter-box on-court lineup seeding + lineup_source="quarter_box"
- feat(nba): exact quarter-box on-court seeding —
players_on_court_from_quarter_boxscoresandlineup_source="quarter_box"onnba_possessions(auto chain: rotation -> quarter_box -> pbp), seeded from per-periodboxscoretraditionalv3range payloads. - fix(nba):
players_on_court_from_quarter_boxscoresgains an optionalraw_boxfull-game-boxscore name-map source (mirrorsplayers_on_court_from_pbp's own signature), closing a mid-period name-resolution gap that regressed one fixture to 0.8817 gamerotation-agreement; withraw_boxthreaded through, quarter_box now matchesplayers_on_court_from_pbpexactly on all 3 fixture games (0.9689 / 0.9686 / 0.9662).
0.0.71 Release: June 24, 2026
CFB — opponent-adjusted EPA (cfb_adjusted_epa): season + walk-forward
sportsdataverse.cfb.cfb_adjusted_epa() and cfb_adjusted_epa_by_game() add a reusable ridge / RAPM-style opponent-adjustment primitive — separating a team's per-play EPA from its schedule with a ridge regression on offense/defense team indicators (plus home-field), fit over the competitive (0.1 ≤ wp_before ≤ 0.9) pass and rush plays. The season function returns one row per team (adjusted off / def / net EPA + ranks); the walk-forward function returns one row per team-game and is point-in-time — each week is adjusted using opponent strengths fit only on prior weeks, so the values are leak-free and valid as in-season power-rating or model inputs (week 1 has no prior, so its adjustments are null; not-yet-seen opponents fall back to the league baseline, the intended early-season shrinkage). This is an in-sample per-season estimator lifted out of the cfb-data team_summaries builder — not a bundled .ubj artifact. scikit-learn is now a runtime dependency.
NFL — era-aware decision models + both-path (ESPN + nflverse) model parity
Ships the era-aware NFL model suite and brings both PBP construction paths to model parity. Rule-era one-hots (era0..era4, cuts 2001/2005/2013/2017) are added to the xpass / fourth-down / fg models so the curves are era-aware across all of 1999–2025 (fourth-down 14-feature, fg 7-feature, xpass 19-feature), and the bundled nfl/models/* are refreshed to the 1999–2025 retrain (two-point on 2010–2025).
Both builders now produce the same modeled columns: the ESPN path (NFLPlayProcess) gains qb_epa, wp / vegas_wp (+ def_wp / home_wp / away_wp), and xpass / pass_oe, wired into run_processing_pipeline in nflfastR order; the nflverse path (enrich_nfl_pbp) gains the per-play QBR EPA components. The fourth-down decision surface is default-on in both builders, scored on its play-type subset and merged back by play id, with each model applied on its correct play-type shape (xpass on scrimmage dropbacks, cp/xyac on pass + air-yards, fourth-down on down == 4). xYAC remains the documented null stub on the ESPN path (no air_epa). A latent bug the era refresh introduced is fixed: calculate_xpass now backfills the era0/era1 features _make_cp_mutations did not build. Pairs with the nfl-data 1999–2025 retrain that produced the artifacts.
0.0.70 Release: June 24, 2026
CFB — qbr / fg / wp_spread models refreshed on the consensus-odds full-corpus reprocess
The bundled CFB qbr_model, fg_model, and wp_spread XGBoost artifacts are retrained on the full 2004–2025 play-by-play corpus after it was re-reprocessed with two upgraded modeling inputs: the cfb_line_odds multi-book consensus pregame spread/total (replacing ESPN's single pickcenter as the EPA/WPA odds source) and roster-backed pre-2014 player IDs. Feature contracts are byte-identical to the shipped models (qbr 10-feature incl. era0–3, fg 5-feature, wp_spread 13-feature), so this is a drop-in artifact refresh — no model-application changes.
Leave-one-season-out CV over all 22 seasons confirms the gains: qbr RMSE 17.60 → 17.29 (r² 0.598 → 0.612), fg logloss 0.5265 → 0.5247, and wp_spread baseline logloss 0.3616 → 0.3486 — the win-probability model improves most, since the consensus odds sharpen the spread_time feature directly (the signal the rule-era one-hot dummies previously had to recover). The fourth_down model is intentionally left unchanged: on the refreshed corpus its era variant no longer beats the consensus-odds baseline, so it was not promoted.
0.0.69 Release: June 23, 2026
CFB — roster-backed {type}_player_id + player-name cleanup fixes
CFBPlayProcess now emits a {type}_player_id for every extracted {type}_player_name, resolved team-aware against the game roster: each player type maps to the team that fielded it (offense pos_team / defense def_pos_team / special-teams kicking_team / return_team / recovery), so identical names on opposing rosters don't collide; a globally-unique name is the fallback. Ids resolve for all years — pre-2014 (no structured participants[] array) via the roster, 2014+ from the clean participant names.
- New
CFBPlayProcess(game_roster=, participants=)constructor params let offline rebuilds pass the stored roster + participants — fetch-free, and keeping 2014+ clean names whenjoin_participantsis off.__join_participantsnow accepts a caller-supplied participant frame /{"data": [...]}/ row list instead of always fetching. - Player-name cleanup fixes the roster-match exposed: the receiver state-abbrev strip (
ST/GA/FL/…, with the leading space) is anchored to a trailing standalone token so it can't corrupt real names (it used to eat the " St" inside "Stewart" → "ewart"); a garbage guard nulls obvious play-text artifacts ("bea loss of") before the id-join.
0.0.68 Release: June 23, 2026
CFB — completion-probability (cp/cpoe) + expected-pass (xpass/pass_oe) surface
CFBPlayProcess now emits per-play completion-probability and expected-pass columns, mirroring nflfastR's cp/cpoe and xpass/pass_oe.
- Two new bundled models —
cfb/models/cfb_cp_model.ubj(8-featbinary:logistic:down,distance,yards_to_goal,score_diff,seconds_remaining,is_home,period,passing_down) andcfb/models/xpass_model.ubj(7-featbinary:logistic:down,distance,yards_to_goal,pos_score_diff,TimeSecsRem,era,period). Both are ~400 KB and ship via the existingcfb/models/*package-data glob (no download-on-demand). - New per-play columns —
cp= P(complete) on pass plays withcpoe = 100 * (completion - cp)(percentage-point scale, null on non-pass plays);xpass= P(pass) on scrimmage rush-or-pass plays withpass_oe = 100 * (pass - xpass)(null elsewhere). Added as two pipe steps (__process_cpoe/__process_xpass) after the EPA/WPA steps inrun_processing_pipeline(); each degrades to null columns rather than raising when a source column is absent.
CFB — spread-free (naive) win-probability surface (wp_*_naive)
CFBPlayProcess now emits a second, spread-free win-probability surface alongside the existing spread WP, completing the play-level model handoff begun in 0.0.67 (which retrained EP + spread WP on the full 2004–2025 history).
- New bundled model
cfb/models/wp_naive.ubj— the faithful cfbscrapR "naive" recipe (12-feat =wp_final_namesminusspread_time,binary:logistic, 65 rounds), retrained on the same full-history corpus (2,219,607 plays, 2004–2025) as the spread model. Ships via the existingcfb/models/*package-data glob. - New per-play columns
wp_before_naive/wp_after_naive/wpa_naive(plusdef_/home_/away_analogues), mirroring the spread columns under a_naivesuffix. The naive surface answers "given only game state, who wins?" while the spread surface bakes in the pregame line; the two correlate ~0.90, diverging most early-game where the market prior carries the most information. - Refactor (no behavior change to the spread surface): the win-probability prediction + game-logic derivation in
__process_wpawas factored into shared_wp_predict/_apply_wp_derivationhelpers routed once per model. The spread (un-suffixed) output is byte-identical to the prior release — verified against a captured per-play baseline.
CFB — QBR model retrained on the full 2004–2025 history
The bundled cfb/models/qbr_model.ubj (6-feat XGBoost: qbr_epa / sack_epa / pass_epa / rush_epa / pen_epa / spread) was retrained on the full-history corpus, replacing the legacy 2020-lineage model.
- Decisively better against the ESPN raw-QBR reference. On a 2021–2025 holdout (out-of-sample for the legacy model): RMSE 23.2 → 16.1 (−31%), MAE 18.7 → 12.5, R² 0.29 → 0.66, correlation 0.69 → 0.82. The retrained model's honest leave-one-season-out metrics (RMSE 17.9, R² 0.585) confirm the gains are real generalization, not in-sample fit.
- Drop-in swap — same 6-feature contract, ships via the existing
cfb/models/*package-data glob; no caller changes.
CFB — fourth-down decision surface (get_4th_down_probs, cfb4th port)
A full college-football fourth-down decision surface, a faithful Python port of cfb4th's add_4th_probs(), against this package's bundled EP / WP-spread boosters.
sportsdataverse.cfb.get_4th_down_probs(pbp_df)scores all three options on a frame of fourth-down situations and adds:go_wp/first_down_prob/wp_succeed/wp_fail(go),punt_wp(punt),fg_make_prob/make_fg_wp/miss_fg_wp/fg_wp(field goal), afourth_down_recommendation∈ {go,punt,field_goal} (max-WP choice), per-option*_wp_diff, andgo_boost(cfb4th's headline100·(go_wp − max(fg_wp, punt_wp))).CFBPlayProcess.add_fourth_down_probs()applies the same to a processed game's fourth-down rows afterrun_processing_pipeline().- New models:
fg_model.ubj(CFB-native field-goal make-probability by distance, trained on 42.6k attempts) andpunt_distribution.parquet(punt end-yardline distribution) are bundled undercfb/models/; the 6-feat / 76-classfd_model.ubj(yards-gained, with the ordinal CFB rule-era factor) is download-on-demand (~16 MB, fetched from theespn_cfb_model_artifactsrelease and cached under~/.cache/sportsdataverse/cfb_models/, mirroring the NFL xYAC pattern; override withSDV_PY_CFB_MODEL_DIR). The go path reuses the reviewed cfb-data decision-layer machinery; punt/FG mirror cfb4th's possession-flip + end-game scoring.
CFB — two-point-conversion decision surface (get_2pt_probs, cfb4th port)
The extra-point vs go-for-2 decision, a faithful Python port of cfb4th's get_2pt_wp(), against this package's bundled EP / WP-spread boosters and a new bundled CFB two-point model.
sportsdataverse.cfb.get_2pt_probs(pbp_df)treats each row as "the scoring team just made a touchdown; decide". For each of the three point outcomes (0/1/2) it subtracts the points, flips to the opponent's ensuing kickoff-return drive (1st-&-10 at the 25,yards_to_goal = 75), scores EP → WP, and flips WP back to the scoring team. It addstwo_pt_wp(=prob_2pt·wp(2) + (1−prob_2pt)·wp(0)),xp_wp(=prob_xp·wp(1) + (1−prob_xp)·wp(0)),prob_2pt, atwo_pt_recommendation∈ {go_for_2,kick_xp} (go for 2 ifftwo_pt_wp > xp_wp), andtwo_pt_wp_diff(=two_pt_wp − xp_wp, positive ⇒ go for 2). The ensuing-drive frame reuses the reviewed 4th-down state machinery (_flip_team_state+ EP/WP scorers).CFBPlayProcess.add_2pt_probs()applies the same to a processed game's point-after / two-point-conversion rows (those withpointAfterAttempt.textpresent) afterrun_processing_pipeline(); every other row carries nulls.- New model:
two_pt_model.ubj(abinary:logistic4-feature booster —posteam_spread,posteam_total,pos_score_diff, ordinalera) is bundled undercfb/models/.prob_2ptcomes from this model (cfb4th hardcodes 0.45);prob_xpis the empirical CFB extra-point make rate0.9851(cfb4th derives XP from its FG GAM, but the empirical rate is more accurate for CFB).
CFB — rule-era QBR / FG / fourth-down models + spread_time sign fix
The QBR, field-goal, and fourth-down (yards) models gain one-hot rule-era dummies (era0..era3, cuts 2006/2013/2020) where they improve out-of-fold, and the bundled boosters are swapped to the era-augmented versions.
- QBR —
qbr_varsgainsera0..era3(LOSO RMSE 17.9 → 17.4);__process_qbrinjects the per-game era one-hot before prediction; bundledqbr_model.ubjswapped to the 10-feature era model. - Fourth-down —
fd_model.ubjswitched to the 9-feature one-hot era model (first-down cal-MAE 0.0035 → 0.0027) and bundled in the package (was download-on-demand);fg_model.ubjswapped to the 5-feature era model. - WP-spread — bundled
wp_spread.ubjretrained on the odds-backfilled frame (the ~2,167 missing-spread games now carry real consensus spreads; LOSO logloss 0.362 → 0.352; same 13-feature contract, no inference change). spread_timesign fix —_predict_wpcomputedspread_time = −pos_team_spread·exp(…), inverted vs the trained-on convention (favorites scored as underdogs inget_go_wp/get_fg_wp/get_punt_wp); corrected to+pos_team_spread·exp(−4·elapsed_share).- Decision surfaces on by default —
run_processing_pipeline(fourth_down_probs=True, two_pt_probs=True)now appends the fourth-down and two-point decision columns to a processed game by default.
CFB — pre-2014 play-text player-name extraction
CFBPlayProcess now recovers per-play player names for 2004–2013 games, where ESPN ships no structured per-play participants array (only teamParticipants). Two latent bugs in the play-text regex extraction were fixed: a multi-alternative str.extract group-index bug (the matched branch's name landed in a non-default capture group, returning null for ESPN "rush" / "Punt by" / "on-side" / "returned by" phrasings) and a \d-escaping bug (a literal backslash instead of a digit, which broke field-goal-kicker extraction). Pre-2014 games now populate rusher / passer / receiver / sack / fg-kicker / punter / returner / fumble player names (all null before); 2014+ output is unchanged (the structured-participants overwrite still wins).
NFL — expected pass (xpass / pass_oe) + nfl4th fourth-down decision surface
calculate_xpass adds xpass (P(dropback)) and pass_oe = 100·(pass − xpass) to the enriched NFL PBP, plus a faithful Python port of nfl4th's fourth-down decision surface (nfl/nfl_fourth_down.py) scoring go / field-goal / punt win probability + a recommendation.
calculate_xpass— the self-derived dropback booster;xpass/pass_oemirror nflfastR'sadd_xpass.- nfl4th surface — go / FG / punt WP via the download-on-demand
fd_model/wp_modelartifacts (cached on first use), mirroring nfl4th'sadd_4th_probs.
NFL — self-trained XGBoost field-goal model in the fourth-down surface
get_fg_wp / get_2pt_wp switched from the mgcv-GAM prediction grid to a self-trained binary:logistic XGBoost FG model (fg_model.ubj, features yardline_100 / fg_roof / fg_era) with the unchanged nfl4th long-kick clamps. Oracle parity (2022): fg_wp 0.9995, go_wp 0.9998, punt_wp 0.9996.
NFL — load_nfl_espn_qbr (ESPN QBR loader, nflreadpy parity)
New load_nfl_espn_qbr (also aliased load_espn_qbr) — the last nflreadpy dataset without an sdv-py loader. summary_type= season|week, 2006+ floor, source= dual (nflverse espn_data release or the SDV-native nfl_espn_qbr release, 2006–2025), read-once-then-filter, with a 23-column returns-schema.
NFL — bundled self-derived xpass model (offline, no first-use download)
xpass_model.ubj (the self-derived dropback booster, 1121 trees, 7.4 MB) moves from download-on-demand to bundled under nfl/models/, so calculate_xpass works offline. It is the same model the release ships — xpass output is unchanged; removed from _MODEL_URLS (the bundled path wins in _load_model's resolution order).
0.0.67 Release: June 17, 2026
Documentation — return-table column descriptions filled (~3,061 columns)
Every generated reference page renders a col_name | type | description returns table; ~3,061 of those cells previously rendered blank because the column name had no entry in the R-package-mined dictionary that backfills descriptions at render time (sdv-py-/provider-specific columns: ESPN Site v2, MLB Stats API, NHL api-web / EDGE, nflverse Shield, HockeyTech, etc.). Those cells are now filled.
- New hand-curated source
tools/codegen/manual_column_descriptions.yaml, keyed by the schema'sschema:field (with a_globaltable-agnostic fallback), consumed at render time bygenerate.py:_table_cell_desc. Resolution order: captured-stored value →manual[schema][col]→manual._global[col]→ R-dict mined fill → empty. Descriptions live only here (theschemas/**.yamlare clobbered blank on every capture), so they survive re-capture. - Coverage: NFL (1,158 — nflverse / Next Gen Stats / Pro Football Reference / ESPN), MLB (599 — Stats API + ESPN), NHL (588 — api-web / EDGE / ESPN), CFB (177 — ESPN + cfbfastR), plus the ESPN cross-league game summary (sport-agnostic), NBA/WNBA/MBB/WBB, PWHL + CHL junior hockey (OHL/QMJHL/WHL/AHL), and the shared
standings/leaders/team_roster/news/team_scheduleschemas. - Regression guard:
tools/codegen/extract_residual_columns.pycomputes the render-blank residual;tests/codegen/test_manual_descriptions.pyasserts it stays at 0 (a newly-captured undocumented column fails CI until authored), plus an orphan guard (no stale dict keys) and a filler-lint (rejects terse/generic descriptions). Every bucket was adversarially accuracy-reviewed; corrections included PFRrec_br, MLBbase_on_balls, NHL EDGE goalie goal-differential / pbp assist totals, and the long-formatload_cfb_betting_linescolumns.
Documentation — doctest-prompt cleanup, native returns-tables, new tutorials
- No more raw
>>>doctest prompts. The generated ESPN-wrapper + loader docstring templates emitted>>> callunderExample:(whichsphinx.ext.doctestwould try to verify); both emission sites now produce the napoleonQuick start::literal block, clearing ~3,559 generated hazards. The remaining ~55 hand-written prompts (NFL NGS / parsers, The Odds API,find/discover, etc.) were converted in source. - 78 new native returns-tables. Wired
returns_schemafor NHL api-web (9), stats-rest (10), records (37), EDGE (15), and MLB Stats API (8) endpoints that previously rendered no return table — captured from live fixtures; the 676 new columns are fully described. (24 endpoints were skipped: off-season EDGE top-10 leaderboards, retired record paths, and auth-gated MLB endpoints.) refresh_return_schemasno longer writes 0-column per-league schemas — an emptycolumns: []file shadowed and suppressed the genericschemas/{name}.yamlfallback, leaving some leagues with no table; it now skips them so the generic table renders.- Three new intro tutorials under
examples/notebooks/(rendered todocs/docs/tutorials/): Soccer (espn_soccer_*(league=)+ headline aliases), Cricket (espn_cricket_*+ the 8-section matchcard summary), and Other ESPN leagues (UFL/XFL/CFL, college baseball/softball, NCAA M/W hockey).
NFL — PBP ETL ↔ nflfastR alignment + faithful model artifacts
enrich_nfl_pbp()lead-diff orchestrator computes nflverse-native EP/EPA/WP/WPA/CP/xYAC on a real nflverse PBP frame, aligned to nflfastR; runs on live nflverse data.- Shared derivations
calculate_epa()/calculate_wpa()lifted intosportsdataverse/nfl/ep_wp.py; the NFL EP/WP constants + shared column contract centralized insportsdataverse/nfl/model_vars.py. - Faithful NFL model artifacts replace the byte-identical CFB 8-feature placeholders that previously shipped under
nfl/models/:ep_model.ubj(18 features),wp_spread.ubj(12),wp_naive.ubj(11),cp_model.ubj(18) — resolving the long-standingxgboost num_feature >= num_col (8 vs 18)mismatch that left the NFL model path red. - New test coverage:
tests/nfl/gains enrich, enrich-derive, EPA, WPA, and column-contract suites.
CFB — EP + WP models retrained on the full 2004–2025 history
- Canonical
cfb/models/ep_model.ubjandcfb/models/wp_spread.ubjretrained on the complete cfbfastR-cfb-raw finals — 2,219,607 cleaned/labeled/weighted plays, seasons 2004–2025 — now that the raw backfill is complete. Shipped XGBoost recipes unchanged (EPmulti:softprob7-class/525 rounds; WP-spreadbinary:logistic/760 rounds). - Leave-one-season-out validated (22 folds, out-of-fold): EP mlogloss 1.233 / accuracy 0.500 / EP-value calibration MAE 0.014 pts; WP logloss 0.362 / Brier 0.118 / AUC 0.916 / weighted-cal-error 0.0147. Drop-in safe (feature names/order match
cfb_pbp.ep_final_names/wp_final_names). QBR is intentionally unchanged (LOSO R² 0.585 — remains the Dec-2020 canonical model).
0.0.66 Release: June 17, 2026
CFB — cfb_pbp sparse-game ColumnNotFoundError guard (end.team.id et al.)
Sparse pre-2010 games (e.g. 2005 game 252440154) crashed CFBPlayProcess.run_processing_pipeline() with polars.exceptions.ColumnNotFoundError: unable to find column "end.team.id". The per-play start.*/end.*/period.*/clock.*/type.* columns are produced only by pd.json_normalize flattening the plays array, so when no play in a game carries a given nested object the column is never created — and the downstream with_columns chain dereferences it via pl.col(...) unconditionally, which raises at plan time before the existing fill_null / when-otherwise logic can substitute a value.
Added a column-materialization guard in __helper_cfb_pbp_features (after the early-return length checks, before the main play chain) that diffs the 15 unconditionally-referenced json_normalize-origin columns against the live frame and creates any missing one as a Null literal:
- String-typed source columns (
clock.displayValue,type.text,text,start.downDistanceText) are created aspl.lit(None, dtype=pl.String)because the chain runs.str.*ops on them (an untyped Null column raisesSchemaError). - Numeric/bool columns stay untyped
pl.lit(None)so their explicit downstream.cast(...)owns the final dtype. - The guard is a no-op for healthy games —
with_columnsis skipped when nothing is missing, so output is byte-identical (verified: 5 control games reprocessed to identical 406-column frames and exact play counts). Resolves all 7 known-failing 2005 games.
0.0.65 Release: June 17, 2026
Namespace — minor/alias leagues nested under sport-group packages
- refactor(namespace): nest minor/alias leagues under sport-group packages (
sportsdataverse.soccer.epl,.hockey.ahl,.football.ufl,.baseball.college_baseball); the 8 majors + pwhl/soccer/cricket stay top-level. Legacy names (sportsdataverse.epl,import sportsdataverse.ufl) still resolve with aDeprecationWarning. NOTE:discover.function_count()/list_functions()keep flat-leaf keys (function_count(league="ufl")works);import sportsdataversenow eagerly loads the 12 soccer-alias submodules to support attribute access.
All sports — espn_*_game_rosters vectorized logo extraction
Pre-2010 ESPN team payloads omit the logos key entirely, causing helper_{sport}_team_items to raise polars.exceptions.ColumnNotFoundError: "logos" not found. The row-by-row item-assignment fallback (teams_df[row, "logo_href"] = ...) also triggers TypeError: the truth value of a Series is ambiguous in polars 1.x because the row-index selector internally evaluates Series.__bool__.
Replaced the logos block in all seven espn_*_game_rosters modules (cfb, mbb, nba, nfl, nhl, wbb, wnba) with vectorized with_columns:
if "logos" in teams_df.columns:guard handles pre-2010 payloads where the key is absent.pl.col("logos").list.get(i).struct.field("href").fill_null("")— expression-engine extraction, null-safe, no Python-level row iteration.except Exception:fallback to empty-string literals if the logos payload doesn't match the expectedList(Struct)shape.
MLB — mlb_api_* renamed to mlb_*
The 64 Stats API wrapper functions in mlb_api.py (generated via tools/codegen/endpoints/mlb_api.yaml) and the 15 hand-written functions in mlb_api_extra.py were renamed from mlb_api_{short} to mlb_{short} — parallel to the statcast_* → mlb_statcast_* rename in 0.0.64. The _api_ infix was a disambiguation artifact from when multiple backends shared the module; it is now redundant. No aliases — update call sites accordingly.
0.0.64 Release: June 17, 2026
MLB — comprehensive Baseball Savant / Statcast surface (mlb_statcast_*, 43 endpoints)
Expanded the Baseball Savant integration from a 12-endpoint representative slice to the full ~43-endpoint catalog under the mlb_statcast_<family>_<name> naming (search / leaderboard / gamefeed / player), with every endpoint parsed to a tidy frame by default (return_parsed=False / raw=True for the raw payload).
- 39 codegen-generated wrappers — 37 leaderboards (expected stats, sprint speed, bat tracking, pitch arsenals/movement/tempo, OAA, arm strength, catcher framing/blocking/throwing, baserunning, park factors, …) plus
mlb_statcast_gamefeed(one row per pitch) andmlb_statcast_schedule(one row per game). Savant mixes CSV / JSON / HTML, so the family uses a content-type-aware getter (dictfor JSON,strfor CSV/HTML); the two HTML-embedded leaderboards (fielding-run-value,statcast-park-factors) are parsed from their embeddeddata[]blob. - Hand-written search —
mlb_statcast_search(+_minors,_wbc) auto-chunks the 25,000-row Savant cap and translates friendly filters (season,pitch_type,at_bat_result,batters_lookup, …) to Savant'shf*params.mlb_statcast_playerparses a player page'sserverValssection (defaultstatcast, ~260 metrics) to a tidy frame (section=for others,raw=Truefor HTML). - Returns-schemas (
col_name | type | description) for every frame-returning function, andexamples/notebooks/09_mlb_intro.ipynbmodernized to the new surface. - The pre-0.0.64
statcast_*names were renamed (no aliases) to themlb_statcast_*convention.
Documentation — nfl_api (NFL.com Shield) returns-schema tables
Added live-captured col_name | type | description returns-schemas for all 11 api.nfl.com endpoints (standings, rosters, teams_history, team, weeks, weeks_by_date, combine_profiles, draft_picks, injuries, game_summaries, weekly_game_details), wired via returns_schema: into nfl_api.yaml and rendered into the reference docs — bringing nfl_api to parity with the other six native API families (mlb_api, nhl_*). Docs/codegen-metadata only; no runtime change.
0.0.63 Release: June 16, 2026
All sports — espn_*_game_rosters diagonal per-team concat (fixes silent roster loss)
The per-team roster concat in espn_wbb/wnba/nba/mbb/nfl/cfb_game_rosters used pl.concat(..., how="vertical"), which hard-fails with polars.exceptions.ShapeError when a game's two teams ship different roster columns (e.g. one entry list has jersey, the other didNotPlay). The whole game then errored and was discarded as empty despite having roster data. Switched to how="diagonal" (union + null-fill), matching nhl_game_rosters and the teams/athletes concats in the same modules.
HTTP — download() no longer retries a definitive 404
sportsdataverse.dl_utils.download retried a NoESPNDataError (ESPN 404 / code:404 body) for the full num_retries budget — wasting ~51s of backoff and N requests per genuinely-absent resource, amplifying load against a rate-limited host. A 404 is definitive "no data", so it now fails fast (one attempt) instead of retrying. Connection/timeout/5xx errors still retry as before.
0.0.62 Release: June 16, 2026
All sports — espn_*_game_rosters robust to long-tail ESPN payloads
Applies the two espn_cfb_game_rosters robustness fixes from 0.0.61 to every sibling rosters builder — espn_wbb_game_rosters, espn_wnba_game_rosters, espn_nba_game_rosters, espn_mbb_game_rosters, espn_nhl_game_rosters, and espn_nfl_game_rosters — which were templated from the same source and shared both bugs verbatim:
statistics_hrefstrict-rename of the competitors payload now renames only keys actually present, so older games that omit the team-levelstatistics$refno longer raisepolars.exceptions.ColumnNotFoundError.- Per-team roster 404 is now tolerated: a single team's missing
/roster(NoESPNDataError) no longer fails the whole game; the other team's roster is recovered, andNoESPNDataErroris raised only when every team is empty.
Adds parametrized offline regression tests across all six modules (tests/test_sibling_game_rosters.py).
0.0.61 Release: June 16, 2026
CFB — espn_cfb_game_rosters robust to long-tail ESPN payloads
Surfaced by the 2004–2023 cfbfastR-cfb-raw backfill, two deterministic failures used to empty a game's rosters entirely (then get caught upstream and banked as empty "hollow" extras):
statistics_hrefstrict-rename. Older games (e.g. pre-2021) omit the team-levelstatistics$refin the competitors payload, sostatistics_hrefnever exists and the unconditionalitems.rename({..., "statistics_href": "team_statistics_href"})raisedpolars.exceptions.ColumnNotFoundErrorfor the whole game. The renamed column is unused downstream, so the rename now applies only to keys actually present.- Per-team roster 404. A single team's
/rostersub-endpoint can 404 (NoESPNDataError) — common for older games and FCS opponents — while the other team's roster exists. The per-team loop now skips a 404 team and recovers the other, raisingNoESPNDataErroronly when every team is empty (genuinely no roster data).
Adds offline helper unit tests (tests/cfb/test_cfb_game_rosters.py, no network).
0.0.60 Release: June 15, 2026
NFL — expected points, win probability, completion probability (CP/CPOE), and expected YAC (XYAC) models
sportsdataverse.nfl.ep_wp gains nflfastR-parity modeling functions — calculate_expected_points, calculate_win_probability, calculate_completion_probability (CP + CPOE), and calculate_xyac (four XYAC sub-models: mean/median/SD yardage + completion probability) — fed by ESPN-adapter feature builders and wired into NFLPlayProcess. Ships the bundled XGBoost .ubj model files.
CFB — espn_cfb_schedule guards null-competitor placeholder events
ESPN's 2010 and 2014 college-football scoreboards include placeholder events with null competitions/competitors. espn_cfb_schedule now skips those events instead of raising TypeError: 'NoneType' object is not subscriptable and failing the entire season.
0.0.59 Release: June 13, 2026
CFB — cross-source crosswalk loaders (load_cfb_*_crosswalk)
New 404-safe dataset loaders read pre-built CFB identity crosswalks from the cfb_crosswalk release tag on sportsdataverse-data, so callers can translate ids across providers without re-scraping every source. They cache the output of the live cfb_teams_crosswalk / cfb_schedule_crosswalk / cfb_rosters_crosswalk builders (ESPN × Fox × Yahoo, keyed on an aggressively-normalized team name; see sportsdataverse.cfb.cfb_crosswalk) — a full-season schedule build otherwise fans out hundreds of requests across three providers.
ESPN — NCAA men's & women's college hockey (espn_mch_*, espn_wch_*)
- feat(espn): add NCAA men's & women's college hockey (espn_mch_, espn_wch_)
ESPN — NCAA college baseball + softball (espn_college_baseball_*, espn_college_softball_*)
- feat(espn): add NCAA college baseball + softball (espn_college_baseball_, espn_college_softball_)
ESPN — UFL, XFL, and CFL (espn_ufl_*, espn_xfl_*, espn_cfl_*)
- feat(espn): add UFL, XFL, and CFL (espn_ufl_, espn_xfl_, espn_cfl_*)
ESPN — soccer/cricket param families + soccer headline aliases (espn_soccer_*(league=), espn_cricket_*(league=), espn_epl_*, espn_ucl_*, espn_mls_*, ...)
-
feat(espn): add league-parameterized soccer + cricket families (espn_soccer_(league=), espn_cricket_(league=)) + soccer headline aliases (espn_epl_, espn_ucl_, espn_mls_*, ...)
-
feat(soccer): full-parity soccer parsers — scoreboard→matches, standings→league table (group column), summary→11-section dispatcher (header/lineups/key_events/team_stats/commentary/leaders/standings/head_to_head/last_five/game_info/shootout), teams, roster — routed via per-sport codegen overrides; feat(cricket): cricket parsers — scoreboard, standings, summary→8-section matchcard dispatcher (batting/bowling/partnerships)
-
load_cfb_teams_crosswalk(seasons=)andload_cfb_schedule_crosswalk(seasons=)are per-season (min_season2014) — teams and schedules are genuinely historical, published per year for 2014–2025. -
load_cfb_rosters_crosswalk()is season-less: ESPN's and Fox's team-roster endpoints expose only the current roster, so the artifact is a single snapshot (noseasons=argument) rather than a misleading per-season series.
All accept return_as_pandas=. Artifacts are produced by cfbfastR-cfb-data/scripts/build_cfb_crosswalk.py (the rosters table fans the per-team cfb_rosters_crosswalk out over the current season's ESPN↔Fox team-id pairs and concatenates them with espn_team_id / fox_team_id provenance). The companion on-demand builder cfb_odds_events_crosswalk (The Odds API event-id ↔ ESPN game-id) remains live-only — near-term events aren't worth caching.
0.0.58 Release: June 12, 2026
Loaders — NHL core + new NBA/MBB datasets aligned to sportsdataverse-data releases
The four core NHL loaders (load_nhl_pbp, load_nhl_player_boxscore, load_nhl_team_boxscore, load_nhl_schedule) now read the SDV-native sportsdataverse-data releases (nhl_pbp_full, nhl_player_boxscores, nhl_team_boxscores, nhl_schedules) instead of the legacy R fastRhockey-data branch — gaining the 2010 season (min_season 2011 → 2010). Added loaders for NBA/MBB datasets that were already published but had no loader, bringing them to parity with the WBB/WNBA surface: load_nba_player_season_stats, load_nba_team_season_stats, load_nba_draft, load_nba_rosters, and load_mbb_standings, load_mbb_player_season_stats, load_mbb_team_season_stats, load_mbb_rosters, load_mbb_officials, load_mbb_game_rosters — each with a generated return-schema table. Also fixed the --audit-releases drift check to key on the release tag (it parsed the human-readable title), which had been falsely flagging valid releases as missing.
Robustness & infrastructure — typing, CI gates, HTTP, deprecation policy
A package-wide hardening pass with no change to public data outputs:
- Typing + CI: ships a PEP 561
py.typedmarker; a newquality.ymlCI gate runsruff+ruff format --check+mypyon every PR, with a[tool.mypy] filesratchet (modules join the strict gate as they reach clean typing), and the test workflow now emits coverage. - Errors + logging: a
SportsDataverseErrorbase class (withSeasonNotFoundError/NoESPNDataErrorre-parented under it) and a package logger with aNullHandler; previously-silentexceptpaths now log. - HTTP layer:
dl_utils.download()reuses a module-level pooledrequests.Sessionand backs off honoringRetry-After(numeric and RFC 7231 HTTP-date, clamped non-negative, 120s ceiling) instead of a fixed sleep. - Deprecation policy: a centralized
sportsdataverse._deprecation(warn_deprecated+@deprecated) with a documented removal window; the 11 per-type NFL loader aliases migrated to it. - Codegen determinism: generator output is LF-only on every platform and the ruff format pass is pinned to the project's ruff (no CRLF phantom diffs); idempotency tests lock it in.
- Tests: a VCR-style record/replay harness (committed cassettes, secret-scrubbing) exercises the real
download()→ parser call path offline.
The Odds API wrappers (sportsdataverse.odds, toa_*)
New sportsdataverse.odds module wrapping The Odds API v4 — live + historical sports betting odds, scores, events, markets and participants across a wide range of bookmakers. Mirrors the sister R package oddsapiR's toa_* surface: toa_sports, toa_sports_odds, toa_sports_scores, toa_sports_events, toa_event_odds, toa_event_markets, toa_sports_participants, the three *_history snapshot variants, and toa_usage (cached quota, no network). The odds endpoints return tidy long-format frames (one row per event × bookmaker × market × outcome). Auth resolves from the ODDS_API_KEY env var (same variable as oddsapiR) or an api_key= argument; the call routes through the shared dl_utils.download() gateway. Same return_parsed / return_as_pandas contract (polars by default). Built from the the_odds_api OpenAPI spec.
Yahoo Sports college football wrappers (yahoo_cfb_*)
Read-only Yahoo Sports wrappers for college football over Yahoo's shangrila stats graph (graphite-secure.sports.yahoo.com/v1/query/shangrila) and editorial feed (api-secure.sports.yahoo.com/v1/editorial/s): yahoo_cfb_player_season_stats, yahoo_cfb_team_season_stats, the legacy per-category *_season_stats_legacy variants, yahoo_cfb_scoreboard, and a yahoo_cfb_boxscore scaffold. Same return_parsed / return_as_pandas contract (polars by default).
NFL — api.nfl.com wrappers cut over to generated; "NFL.com API" docs grouping
The hand-written sportsdataverse.nfl.nfl_api wrappers (nfl_standings, nfl_rosters, nfl_injuries, …) are now generated from tools/codegen/endpoints/nfl_api.yaml, like the NHL/MLB native families. The flat-API codegen gained getter_module + auth support so an authenticated family (the NFL.com WEB_DESKTOP bearer token) can be generated; the auth getter lives in nfl_api_runtime.py and the per-endpoint record extraction in nfl_api_parsers.py. As a result the NFL docs index now lists a dedicated "NFL.com API" reference grouping (11 functions) instead of burying those wrappers in "Additional functions". Wrapper signatures gain return_parsed / **kwargs.
NFL — automatic api.nfl.com token caching + NFL_ACCESS_TOKEN override
The api.nfl.com bearer token is now minted once and cached in-process, then auto-renewed just before its JWT exp — so back-to-back nfl_* / nfl_api_* calls reuse a single token instead of POSTing to /identity/v3/token on every call, with no setup and no manual refresh. A new optional NFL_ACCESS_TOKEN env var injects a pre-minted bearer token verbatim (skipping the mint + cache); the existing NFL_CLIENT_KEY / NFL_CLIENT_SECRET credential overrides still apply. nfl_clear_token_cache() forces a fresh mint, and nfl_token_gen(force_refresh=True) re-mints on demand.
Documentation — api.nfl.com OpenAPI spec
Added an OpenAPI 3.1 description of the modern NFL.com "Shield" data API (api.nfl.com: /identity/v3/token device-token auth + /football/v2/* + /experience/*) to the reference repos (sdv-internal-refs/nfl/, sdv-swagger/nfl_api_openapi.yaml).
Bug fixes
load_nfl_players()now reads the nflverse players release (players/players.parquet) on both the polars and pandas paths; the default polars path previously returned the officials dataset by mistake.- The generated
api.nfl.comwrappers route their HTTP call through the sharedsportsdataverse.dl_utils.download()gateway (retries + cache + ESPN-aware error handling) like every other wrapper, instead of callingrequests.get()directly. Boolean query flags and thenfl_weeksseason/season_typepath params are hardened soNonecan no longer leak onto the wire.
Internal — Fox data key single-sourced
sportsdataverse.cfb.cfb_fox_ext.FOX_DATA_KEY is now imported from sportsdataverse._fox_layout.DATA_KEY so the bundled public Fox key and its SDV_PY_FOX_DATA_KEY env override live in exactly one place instead of being duplicated.
0.0.57 Release: June 10, 2026
Fox Sports Bifrost wrappers (CFB, NBA, MBB, NHL, MLB)
Read-only Fox Sports "Bifrost" wrappers (fox_<sport>_*) over api.foxsports.com/bifrost/v1/<sport>/*, complementing the espn_<sport>_* families. The Bifrost API is a layout API (sections → tables → rows → cells) that is uniform across sports; a shared parsing layer (sportsdataverse/_fox_layout.py) backs every league module. Same return_parsed / return_as_pandas contract (polars by default).
CFB (cfb module): fox_cfb_pbp (quarters → drives → plays), fox_cfb_boxscore, fox_cfb_odds, fox_cfb_team_roster, fox_cfb_team_stats, fox_cfb_team_gamelog, fox_cfb_standings, fox_cfb_league_leaders.
NBA / MBB / NHL (nba / mbb / nhl modules): the same eight wrappers per sport (fox_<sport>_pbp, _boxscore, _odds, _team_roster, _team_stats, _team_gamelog, _standings, _league_leaders). Play-by-play is period-based (QUARTER / HALF / PERIOD → plays); boxscore is tidy long per player-stat.
MLB (mlb module): fox_mlb_team_roster, fox_mlb_team_stats, fox_mlb_team_gamelog, fox_mlb_standings, fox_mlb_league_leaders, fox_mlb_odds. Fox does not expose MLB play-by-play or boxscore via event/{id}/data, so those two are intentionally omitted.
Live-tested (gated behind SDV_PY_LIVE_TESTS=1). Reverse-engineering notes + an OpenAPI 3.1 spec live in the sdv-internal-refs repo. Parallels the cfbfastR / hoopR / fastRhockey / baseballr fox_* families.
CFB — Fox as a backup source for the EPA/WPA play processor (fox_cfb_play_process)
Where fox_cfb_pbp returns the raw Fox play rows, fox_cfb_play_process runs Fox data through the same CFBPlayProcess pipeline ESPN games use — producing EPA / WPA / advanced box score — as a backup/alternative when ESPN is unavailable. The new module sportsdataverse.cfb.cfb_pbp_fox adapts a Fox cfb/event/{id}/data payload into the ESPN-summary shape the processor consumes (fox_to_espn_summary), so the 6,000-line pipeline runs unmodified.
fox_cfb_play_process(event_id)— fetch + adapt +run_processing_pipeline(orprocess=Falsefor cleaning-only,raw=Truefor the adapted summary). Returns the processed payload taggedsource="fox".fox_to_espn_summary(fox_data)— the adapter (modalPlay.events[].yardStart→ yards-to-goal, play title → down/distance,events[].text→ ESPNtype.textvocab, team logo → possession).- Validated offline (5 tests) on a captured blowout (FSU 66-10 → FSU +0.50 vs Kent −0.94 EPA/play — game-consistent). High fidelity on the structured/numeric path (down/distance/yards-to-goal/EPA/WPA); text-grammar features (detailed player attribution, penalty yards) degrade vs ESPN. Archive-format Fox games (no
modalPlaygeometry) are detected and rejected. A Fox event id differs from an ESPN game id; backing up a specific ESPN game needs matching by teams + date.
0.0.56 Release: June 9, 2026
HockeyTech — live multi-league scraper (PWHL + AHL/OHL/WHL/QMJHL) + on-ice/Corsi/TOI analytics
A new sportsdataverse.hockeytech core powers live wrappers over the HockeyTech
feeds, alongside the existing offline load_pwhl_* loaders:
- PWHL (
sportsdataverse.pwhl): 20 livepwhl_*()functions at fastRhockey output parity —pwhl_schedule,pwhl_scorebar,pwhl_game_info,pwhl_game_summary,pwhl_pbp,pwhl_player_box,pwhl_teams,pwhl_team_roster,pwhl_standings,pwhl_player_info,pwhl_player_stats,pwhl_player_game_log,pwhl_player_search,pwhl_stats,pwhl_leaders,pwhl_streaks,pwhl_transactions,pwhl_playoff_bracket,pwhl_season_id, andmost_recent_pwhl_season. - AHL / OHL / WHL / QMJHL (
sportsdataverse.{ahl,ohl,whl,qmjhl}): per-league families (schedule, pbp, standings, teams, team_roster, player_stats, leaders, game_summary, season_id,most_recent_<lg>_season) over one shared core. - Analytics across all five leagues:
<lg>_game_shifts,<lg>_player_toi, and<lg>_game_corsi(player-level on-ice Corsi/Fenwick), reconstructed from the shift tables via countdown-clock interval matching.<lg>_pbpis enriched to a superset (coordinate transforms, clock columns, shot distance/angle, scoring chances, on-ice players, game-meta join,blocked_shot/hitevents). - Corsi/Fenwick caveat: the HockeyTech feed has no missed-shot event, so both
metrics are computed from shots-on-goal + blocked + goals and every analytics
output carries
corsi_includes_missed = False. - All returned columns are snake_case; PWHL columns match fastRhockey exactly. A companion fastRhockey (R) release mirrors this surface, verified by a cross-language parity test pinning identical Corsi/TOI numbers.
NFL — Next Gen Stats (nfl_ngs_*) + api.nfl.com football/v2 (nfl_*) modules
- New
sportsdataverse/nfl/nfl_ngs.py— token-free Next Gen Stats wrappers overnextgenstats.nfl.com/api(browser session, no auth). 10 functions / 21 endpoints:nfl_ngs_statboard(passing/receiving/rushing),nfl_ngs_statboard_leaders,nfl_ngs_leaders(speed/distance/time-to-sack + completion/ery/yac expectation, season & week),nfl_ngs_league_schedule[_current],nfl_ngs_league_teams,nfl_ngs_gamecenter_overview,nfl_ngs_microsite_chart[_players],nfl_ngs_play_is_highlight. The/live/*NGS endpoints are anonymous-403 (need elevated auth) and are documented as omitted. - New
sportsdataverse/nfl/nfl_api.py—api.nfl.com/football/v2+/experiencewrappers on the bearer token (reusesnfl_headers_gen). 11 functions:nfl_standings,nfl_rosters,nfl_teams_history,nfl_team,nfl_weeks,nfl_weeks_by_date,nfl_combine_profiles,nfl_draft_picks,nfl_injuries,nfl_game_summaries,nfl_weekly_game_details. - Both return tidy polars DataFrames by default (
return_as_pandassupported) and are documented on the NFL reference pages. Catalogued from a full crawl of the NFL API surface (api.nfl.com + NGS). - Captured autodoc return-column tables for all 23 new NFL functions (live
introspection ->
schemas/autodoc/nfl/*.yaml+autodoc_example_args.yaml), so each renders acol_name | type | descriptiontable on its reference page.
NFL — restored the api.nfl.com game schedule + play-by-play wrappers
nfl_game_schedule/nfl_game_detailswere broken because NFL.com retired the old/v1/rerouteclient-credentials token endpoint (404 ->JSONDecodeError). Rebuiltsportsdataverse/nfl/nfl_games.pyon the modern flow the NFL.com web app (and nflverse'snflapi) now use:nfl_token_gen()mints a bearer token from/identity/v3/token(form-encoded device grant,X-Domain-Id: 100);nfl_game_schedule()reads/football/v2/games/season/{s}/seasonType/{t}/week/{w};nfl_game_details()reads/experience/v1/gamedetails/{id}and unwraps the shielddata.viewer.gameDetailobject (plays, drives, scoring summaries, line scores).- Auth uses the NFL.com public
WEB_DESKTOPweb-client credentials as defaults, overridable viaNFL_CLIENT_KEY/NFL_CLIENT_SECRETenv vars or function args (no personal account; the token carries the anonymousfreeplan). Verified live: 16 games for 2024 REG wk1, 194 plays / 20 drives for the opener. - Added a parsed surface over the raw dicts:
nfl_game_pbp(game_id)returns a tidy polars/pandas DataFrame (one row per play, withgame_id/home_team/visitor_teamcontext), andnfl_week_games(season, season_type, week)returns one row per game. (Named to avoid colliding with thenfl_pbp/nfl_schedulesubmodules.)
ESPN — remove always-erroring endpoint variants + NFL R-parity
- Removed dead ESPN endpoint variants (all leagues). A live health sweep found
these generated wrappers 404 /
NoESPNDataErrorat ESPN for every league and season: the season-lessespn_*_coacheslist (/leagues/{league}/coaches) and the fourespn_*_calendar_{offseason,regular_season,postseason,ondays}sub-paths. They are dropped from the codegen so the package no longer ships endpoints that always raise. The working counterparts remain:espn_*_season_coaches(/seasons/{season}/coaches), the coach-detail endpoints (espn_*_coach, ...), and the baseespn_*_calendar. (~40 dead functions removed across 8 leagues.) - NFL Python ↔ R parity. Added curated
r_parity_aliases.yamlentries mapping the canonicalload_nfl_*loaders to their nflreadr equivalents (e.g.load_nfl_pbp→load_pbp,load_nfl_schedule→load_schedules), so the NFL parity table links both naming styles (nfl rows 26 → 49). Theload_nfl_*/ bareload_*dual-naming itself was verified already consistent (intentional nflreadpy parity; the only unaliasedload_nfl_*are deprecated or sdv-specific).
Documentation — per-league Python ↔ R parity tables
- Each league's
index.mdnow carries a Python ↔ R parity table mapping everysportsdataversefunction to its equivalent in the sister R package (cfbfastR / hoopR / wehoop / baseballr / fastRhockey), linking the Python doc page and the R pkgdown reference. Driven by a newtools/codegen/build_r_exports.pyminer (NAMESPACE → committedr_exports.yaml, so links never 404 and the offline--checkstays deterministic) plus a curatedr_parity_aliases.yamlfor divergent names (e.g.mlb_api_*→ baseballrmlb_*, +36 verified). Coverage: nhl 202, mlb 107, wnba 83, nba/wbb 74, mbb 69, cfb 55, nfl 26, pwhl 15. - Fixed a self-referential codegen bug the parity table exposed:
render_autodoc_pagecomputed "already documented" against a corpus that included the index, so the index's parity table (which names autodoc functions) caused those functions to be dropped fromadditional.mdand their parity links to 404. It now uses the reference-pages corpus only, matching the autodoc-name count used for the index.
Documentation — example notebooks repaired, expanded, and rendered on-site
- Repaired the example notebooks. Live execution (
nbclient) surfaced runtime schema/usage drift that import/compile checks miss: ESPN schedule team columns renamed tohome_display_name/away_display_name;espn_*_pbp()['plays']is a raw list using dot-notation keys (period.number,clock.displayValue,scoringPlay,shootingPlay,coordinate.x/.y) built viapl.DataFrame(...); ESPN scores are strings (cast before arithmetic);espn_cfb_scheduletakesdates=notseason=; ESPN team rosters usefull_name;espn_*_team_statsreturns a dict{Averages, Totals, Misc}; some hardcoded dates had no games. All notebooks now execute clean end-to-end. - Split + expanded the suite to ten notebooks. The combined
wbb_wnbanotebook was split into separate05_wbb_introand08_wnba_intro, both expanded; the NHL notebook gained an ESPN-NHL section alongside the native api-web surface; and two new notebooks were added:09_mlb_intro(MLB Stats API + Statcast + ESPN MLB) and10_pwhl_intro(PWHL loaders). - On-site rendered Tutorials. New
tools/codegen/render_notebooks.pyexecutes each notebook and renders it (with real outputs, as clean monospace tables) to a themed page underdocs/docs/tutorials/, surfaced in a new Tutorials sidebar section. Execution is quarantined to the weeklylive-tests-cronworkflow, which now re-executes + renders and opens a refresh PR (main is branch-protected); the normal offline docs build just consumes the committed pages. Each league index's Examples section now links the on-site tutorial pages instead of GitHub.
NHL / PWHL — loader naming-parity aliases + games-manifest loaders (fastRhockey parity)
- Added 4 NHL short-name aliases in
sportsdataverse/nhl/nhl_loaders.py:load_nhl_team_box→load_nhl_team_boxscore,load_nhl_player_box→load_nhl_player_boxscore,load_nhl_skater_box→load_nhl_skater_boxscores,load_nhl_goalie_box→load_nhl_goalie_boxscores. - Added 5 PWHL short-name aliases in
sportsdataverse/pwhl/pwhl_loaders.py:load_pwhl_team_box→load_pwhl_team_boxscores,load_pwhl_player_box→load_pwhl_player_boxscores,load_pwhl_skater_box→load_pwhl_skater_boxscores,load_pwhl_goalie_box→load_pwhl_goalie_boxscores,load_pwhl_schedule→load_pwhl_schedules. - Added
load_nhl_games()(noseasonsarg) reading the NHL games-in-data-repo manifest parquet from thenhl_schedulesrelease asset (primary URL verified working:sportsdataverse-data/releases/download/nhl_schedules/nhl_games_in_data_repo.parquet). - Added
load_pwhl_games()(noseasonsarg) reading the PWHL games-in-data-repo manifest parquet from thepwhl_schedulesrelease asset (primary URL verified working:sportsdataverse-data/releases/download/pwhl_schedules/pwhl_games_in_data_repo.parquet). - Added
tests/test_loader_parity.pycovering importability,__all__membership, docstring-based forwarding assertions, and live alias shape-parity + manifest tests (gated behindSDV_PY_LIVE_TESTS=1).
Documentation — NFL return-table descriptions mined from nflverse
- Extended
tools/codegen/build_r_col_descriptions.pywith two nflverse source parsers:mine_csv_dictionaries()reads nflreadr's canonicaldata-raw/dictionary_*.csvfield docs (delimiter-sniffing for the semicolon-delimited NGS file, BOM-stripping forroster_status, and case-insensitive Field/Description column resolution across 6 header variants), andmine_item_list()reads nflfastR'sdata-raw/variable_list.txt(\item{Field}{Description}form). Yieldsnflreadr(941 columns) andnflfastR(372 columns) dictionaries inr_column_descriptions.yaml. - Mapped
nfl → nflreadringenerate.py's_LEAGUE_R_PACKAGE; nflfastR's fields still contribute via the_mergedcross-package fallback. - NFL generated reference-page description fill rose from ~36% to ~85%. The
enlarged
_mergedunion (7.3k → 8.1k columns) also backfilled previously-blank shared football/stat columns on the CFB and MLB reference pages (e.g.passing_yards,receptions,kicker_player_name,name_short).
Documentation — class methods rendered on autodoc pages (CFB / NFL)
- Hand-written classes (
CFBPlayProcess,NFLPlayProcess) previously rendered on theadditionalreference pages as a bare constructor signature with no description and an empty parameter table — their public methods, returns, and examples were omitted entirely. The autodoc renderer now treats a class specially:_doc_view()attaches a per-method doc-view list (via_augment_class_view()), and theautodoc_page.md.jinjatemplate renders each public method as a nested#### Class.method(...)entry with its description, parameters, returns, and example. Both classes now document all 7 of their public methods (espn_*_pbp,*_pbp_disk,*_pbp_json,corrupt_pbp_check,create_box_score,run_cleaning_pipeline,run_processing_pipeline). - Constructor parameter descriptions are backfilled from the class's
__init__docstring when the class object itself carries none (CFBPlayProcessdocuments its ctor args on__init__), so the constructor parameter table now renders with descriptions instead of blank cells. - Added a class-level docstring to
CFBPlayProcess(it had none) mirroringNFLPlayProcess, so the class entry leads with an overview + runnable example instead of aNo description available.placeholder.
Documentation — accuracy-audit fixes
- Stable autodoc anchors. Every autodoc function/class heading now carries an
explicit
{#name}id, so it is reliably deep-linkable instead of relying on a signature-derived slug. This fixes a broken cross-link inecosystem.md(espn_nhl_teamsnow resolves to itsadditionalpage entry) and future-proofs any reference to a hand-written wrapper. - Invalid example code.
_clean_example()mis-handled reST literal-block intros that wrap across multiple prose lines (only the line ending in::was recognized), leaking a prose sentence into the rendered```pythonblock as a broken statement. It now absorbs the preceding contiguous intro lines into the step comment. Fixes theNflConfigandespn_wbb_team_statsexamples; all non-REPL doc examples now compile. - Notebook reachability.
ecosystem.mdnow links all seven example notebooks individually (previously only01_quickstartwas linked; the per-sport intros 02–07 were an un-linked "for your league" mention). Each league's generatedindex.mdlanding page also gained an Examples section linking the quickstart plus that sport's intro notebook (render_league_index+ a league→notebook map ingenerate.py); mlb/pwhl show the quickstart until they get a dedicated intro.
0.0.55 Release: June 8, 2026
Documentation — richer per-function reference
- Autodoc "Additional functions" pages now render full Parameters tables (name/type/default/description), Returns, and runnable Example blocks parsed from each function's docstring (previously just a signature + one line).
- Endpoint reference pages gained a Description column on the parameter table; shared query params carry authored descriptions.
- Function Returns are now
col_name | type | descriptiontables: endpoint pages from introspected per-sport schemas, and autodoc DataFrame functions from a newgenerate.py --autodoc-schemaslive-introspection pass (best-effort, with prose fallback where a function can't be introspected offline). - Return-table column descriptions are filled by column name from the sibling
SDV R packages'
@returndocs (cfbfastR / hoopR / wehoop / baseballr), mined totools/codegen/r_column_descriptions.yamland applied at render time (hand-curated descriptions take precedence; unmatched columns stay blank).
Bug fixes
espn_mbb_game_rosters/espn_wbb_game_rosters/espn_nfl_game_rosters: fixed aShapeError(positional column rename broke when ESPN ships extra*_$reffields); columns are now renamed by source key.espn_nhl_schedule: fixed'NoneType' object has no attribute 'get'with default args (a helper was missing itsreturn event).- The
espn_*_game_rostersrename-by-source-key fix is additive: NBA/WNBA roster frames now include ateam_alternate_ids_sdrcolumn when ESPN ships it (the old positional rename would have raised once that field appeared).
0.0.54 Release: June 8, 2026
Per-sport return schemas (correctness)
@return tables are now derived per league by running the real parsers against
captured per-sport fixtures (generate.py --schemas), replacing the previous
sport-agnostic schemas that showed (e.g.) basketball boxscore columns on MLB/NHL
pages. Native API pages (nhl_api_web/nhl_edge/nhl_records/nhl_stats_rest/
mlb_api) gained accurate return schemas. Schemas are now introspected truth,
gated by generate.py --check.
BREAKING — parser-backed wrappers return a DataFrame by default
return_parsed now defaults to True for the parser-backed wrappers; they
return a tidy polars DataFrame instead of the raw Dict. Pass
return_parsed=False to recover the raw Dict; return_as_pandas=True switches
polars→pandas. Wrappers without a registered parser are unchanged (still Dict).
The sportsdataverse.parsed.{league} mirror modules are unaffected.
Docs coverage gate + autodoc
Every user-facing function now reaches the docs. A new generate.py --coverage
audit enumerates in-scope exported functions per league and fails --check if
any is undocumented (allowlist for cross-cutting internals in
tools/codegen/coverage_allowlist.yaml). ~180 hand-written wrappers/loaders/
statcast/utility functions that the endpoint-YAML codegen never documented are
now rendered into per-league "Additional functions" reference pages
(autodoc from live signatures + docstrings).
MLB - full MLB Stats API coverage
The codegen now wraps the full statsapi.mlb.com surface: 38 previously
unwrapped endpoints were added (home run derby, all-star ballots, conferences,
free agents, game pace, jobs/datacasters/official-scorers, team coaches/
personnel, schedule variants, seasons/all, sport, teams history/stats, etc.).
28 are publicly serviceable and ship with captured fixtures + introspected
return schemas; the remaining handful are auth-gated/internal MLBAM feeds
(analytics/guids/color), wrapped with valid example args for if/when access
exists.
Deprecations
sportsdataverse.parsed.{league}is deprecated (since the default modules now return parsed DataFrames by default). Importing aparsed.*module emits aDeprecationWarning; it still works and will be removed in a future release. Migrate tofrom sportsdataverse.{league} import <fn>directly.
0.0.53 Release: June 8, 2026
ESPN — declarative codegen + factory retirement
The runtime "magic" that mass-registered each league's espn_<league>_* ESPN
wrappers at import time (_common_espn.make_league_module / _bind + the
_UNIVERSAL_WRAPPERS / _NCAA_WRAPPERS / _FOOTBALL_WRAPPERS / _MLB_WRAPPERS
tables + ~127 private _site_v2_* / _espn_* / _core_v2_* core functions) has
been replaced by a declarative codegen pipeline (tools/codegen/). Endpoint
metadata lives in tools/codegen/endpoints/*.yaml; generate.py renders concrete,
fully-documented wrapper modules into sportsdataverse/<league>/<league>_espn_ext.py.
- New
espn_nhl_*surface (115 functions). NHL previously had no ESPN cross-league wrappers; it now gets the full Site v2 / Web v3 / Core v2 surface, andfind()works for NHL for free. - Identical behavior, real signatures. Every generated function builds a
byte-identical URL + query string to the function it replaced (verified by a
URL+params parity gate across all scopes), but now exposes concrete parameter
names, type hints, and docstrings instead of an opaque
*args, **kwargsshim. - Names aligned to the R sister packages (universal, token-level convention).
Across all eight leagues the generated
espn_*names follow the cfbfastR/hoopR/wehoop taxonomy (behavior unchanged). The rename is applied at the underscore-token level (not just prefixes), soathlete/eventconvert in every position incl. plurals:athlete -> player(athlete_vs_athlete -> player_vs_player,athletes_index -> players_index,season_athletes -> season_players),event -> game(bareevent -> game,events -> games,event_* -> game_*,season_week_events -> season_week_games). Two combined mappings run first:event_competitor* -> game_team*(a competitor is the game's team) andevent_competition -> game_competition/event_competition_* -> game_*. Compound tokens likeeventlogare preserved (athlete_eventlog -> player_eventlog). cfb additionally getsseason_*cleanups vs cfbfastR (futures/groups/recruits/week_rankings;powerindex -> team_powerindex). Rule engine:generate._convention_rename; cfb-specific exceptions:tools/codegen/espn_rename_map.yaml. - Collision-guarded. Renames that would clash with a hand-written sibling or
another generated name are skipped automatically:
teams_site(raw endpoint, != parsedespn_*_teams) andespn_cfb_season_{team,awards,coaches}(vs the catalog). SAME-endpoint duplicates are dropped: the generated rawespn_{wbb,wnba}_game_officialsis suppressed (viaespn_rename_map.yamldrop:) because the hand-written parsedespn_{wbb,wnba}_game_officials(renamed fromevent_officials, core-api officials with ids) exposes the same endpoint. One->many splits (e.g.summary) remain for curation (seedocs/superpowers/specs/espn-r-naming-worksheet.md). - Versioned collision rule (dynamic, "one stays bare"). When a generated name
would collide with an existing function but they hit different endpoints, both are
kept: ONE keeps the bare name and the larger/newer one is version-qualified. This is
now decided dynamically by the generator (
_league_module_sourcepass 2 +_versioned_on_collision), not hard-coded. The web-common-v3/athletes/{id}/statsendpoint wants the bareplayer_stats; it is version-qualified toespn_*_player_stats_v3only when a hand-written bareplayer_statsalready claims the name — a league without that sibling would get the bare name automatically (no orphaned*_v3). - Cross-league
player_statsparity (core-v2 season) for ALL eight ESPN leagues. Every league now exposes a bareespn_<league>_player_stats(core-v2/seasons/{season}/types/{type}/athletes/{id}/statisticsseason line) returning one wide, self-describing row (athlete identity + season line as{category}_{stat}columns +team_*identity), plus the generatedespn_<league>_player_stats_v3(web-v3 comprehensive) — matching the hoopR/wehoop/cfbfastR convention exactly. nba, mbb, nfl, nhl, mlb, and cfb gain new hand-written wrappers; wnba/wbb were already converted. A single sport-aware core (sportsdataverse._common_espn_player_stats._espn_player_stats) backs all eight (basketball/football/baseball/hockey share the core-v2splits.categories[].stats[]shape and athlete/team$refgraph). Newseason_type("regular"/"postseason") andtotalparams mirror the wehoop signature. BREAKING:espn_wnba_player_stats/espn_wbb_player_statspreviously hit web-v3 and returned adictof category frames; they now return a single core-v2 seasonDataFrame(the web-v3 payload moved to*_player_stats_v3). _get/_csvsingle source. The HTTP + coercion helpers now live insportsdataverse._codegen_runtime(shared by all generated wrappers);_common_espnre-exports them. Note for test authors: mocksportsdataverse._codegen_runtime.download(not_common_espn._get) to intercept the generated wrappers.- Drift guard.
python tools/codegen/generate.py --check(and thesdv-codegenpre-commit hook) fail if the committed wrappers fall out of sync with the endpoint metadata.
BREAKING (internal): sportsdataverse._common_espn no longer exposes the
factory (make_league_module / _bind / the _*_WRAPPERS tables) or the private
_site_v2_* / _core_v2_* core functions. Public espn_<league>_* wrappers are
unchanged in name and behavior.
NHL native — codegen cutover + clean names (api-web; in progress)
The hand-written NHL native modules are being regenerated from endpoint specs
(via tools/codegen/extract_native.py -> flat-API YAML -> generate.py) with
clean, R-aligned names, family by family. First family: nhl_api_web.
- BREAKING renames (
nhl_web_* -> nhl_*where the clean name is free; the qualifier is kept only on collision with a hand-written composite, sonhl_web_pbpandnhl_web_scheduleare unchanged): e.g.nhl_web_boxscore -> nhl_boxscore,nhl_web_standings -> nhl_standings,nhl_web_roster -> nhl_roster,nhl_web_scoreboard -> nhl_scoreboard(26 functions; full map intools/codegen/rename_map.yaml). Behavior (URL + params + parser) is identical -- faithfulness was verified bytest_parity_nativebefore the swap. nhl_scoreboard(the 3-way team/date/now branch) stays hand-written innhl_api_web_extra.py-- the single-URL-builder codegen can't represent it.- Removed the deprecated
sportsdataverse.nhl.nhl_apimodule (targeted the retiredstatsapi.web.nhl.com); usenhl_api_web/nhl_pbpinstead. nhl_edge(family 2) andnhl_stats_rest(family 3) are now generated too. Both keep their meaningful API namespaces (nhl_edge_*,nhl_stats_rest_*) so they are non-breaking codegen-ifications (35 + 21 functions). stats_rest's arbitrary**filterspower feature (cayenneExp/sort/limit/...) is preserved via a newpassthrough_queryengine mode that forwards None-filtered**kwargsas query params;return_parsedis additionally wired where a parser exists.nhl_records(family 4) is generated too -- keptnhl_records_*(distinct records.nhl.com product), non-breaking (50 functions: 44 generated +passthrough_query, 6 value-embedded/scope-conditional ones preserved hand-written innhl_records_extra.py).mlb_api(family 5, final) is generated too -- keptmlb_api_*(the raw MLB Stats API namespace, distinct from the curatedmlb_*composites), non-breaking (41 names: 26 generated +passthrough_queryfor hydrate/fields, 15 conditional-_csv/ multi-param //api/v1.1/-host functions preserved hand-written inmlb_api_extra.py).- All five native families are now codegen-generated. Only
nhl_api_webwas a breaking rename (itswebqualifier was host-noise); the other four kept their meaningful API namespaces. The codegen engine gained flat-API collision resolution (FlatApi.qualifier+resolve_name),passthrough_query, and abuild_flat/--checkdrift gate.test_parity_nativelocked in each family's faithfulness before its swap.
Dataset loaders — release manifest + drift audit
releases.yamlmanifest expanded 24 -> 92 loaders, seeded from the live sportsdataverse-data release list: every release tag shipping season-partitioned*.parquetassets gets a 404-safe loader entry whose URL is derived from the actual asset names (verified to resolve). New coverage: WNBA (espn_wnba_*+wnba_stats_*), PWHL (15 datasets, a new league), NHL (fullnhl_*family incl. EDGE/lite/boxscores), WBB, NBA, MBB.generate.py --audit-releasescompares the manifest against the live release list (gh CLI) and reports tags with no loader (gaps) + orphans -- a CI-oriented drift gate (separate from the offline--check).tests/codegen/fixtures/release_tags.txtsnapshots the live tags for offline coverage tests.- Release tags that don't yet ship parquet (empty / csv-only / season-less -- e.g.
several
espn_cfb_*advanced-box tags,nba_stats_*boxscores) are intentionally absent and surfaced by the audit until parquet lands. @returncolumn tables (Task 4): every non-stub loader's parquet footer is introspected intotools/codegen/schemas/loader_schemas.yaml(92 datasets) and rendered as a|col_name|type|table in the generated loader docstrings (reproducible viagenerate.py --loader-schemas).- All loader modules are now generated (Task 5 complete). The new
sportsdataverse.pwhlleague (15 loaders) plus the six existing leagues (cfb/mbb/nba/nhl/wbb/wnba) are rendered from the manifest into{league}/{league}_loaders.py-- expanding from 4 hand-written loaders per league to the full release-backed set (nhl 24, wnba 25, wbb 11, nba 9, pwhl 15, ...), each with@returncolumn tables. Zero loss (verified before/after): the season-less / helper functions the loop template can't express are preserved hand-written in{league}_loaders_extra.pyresiduals --cfb:load_cfb_betting_lines+get_cfb_teams;nhl:nhl_teams. The codegenbuild/--checkdrift gate covers all generated loader modules (_GENERATED_LOADER_LEAGUES). Verified live:load_pwhl_pbp(2024)-> 10,456 rows,load_nhl_pbp_lite(2010)-> 400,512,load_wnba_shots(2024)-> 45,480.