一键导入
sdv-add-sport-parser
Add sport-specific tidy parsers for an ESPN sport (soccer/cricket pattern) — parser module + per-sport codegen routing + fixtures + TDD.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Add sport-specific tidy parsers for an ESPN sport (soccer/cricket pattern) — parser module + per-sport codegen routing + fixtures + TDD.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when porting R logic into the Python SportsDataverse package (sdv-py) — e.g. translating an nflfastR / cfbfastR / cfbscrapR / baseballr / hoopR R function, reconciling a fix from the pandas `0.36-live` branch into polars `main`, or re-implementing a modeling recipe (EP/WP/QBR/CPOE) from R. Enforces a parity-test-first workflow (golden fixture from the R output → failing parity test → port → green) and the polars-1x + ID-dtype + no-lookaround conventions. Also covers R numeric fidelity when outputs must match R bit-for-bit (R>=4.0 fround, 80-bit long-double sum, non-na.rm null-poisoning, dplyr group order), stale-fork sibling packages (wbigballR-style), and oracle integrity (a browser/rvest-captured oracle can be corrupt — fix the bug, don't match it). Invoke for "port this R function to polars", "port from nflfastR/bigballR", "reconcile 0.36-live into main", "translate this dplyr/np.select to polars", "re-implement the R model recipe in Python", or when a parity test fails on a handful of rounding-boundar
Use when shipping a change in sportsdataverse-py (sdv-py) — opening, pushing, or merging a PR. Runs the steps in the correct order (regenerate codegen docs, update changelog/docs/tutorials, lint, full pytest, commit + verify it landed, push, triage bot reviews CodeRabbit/Sourcery/Copilot immediately — in parallel with CI, not after it — wait for CI green, confirm merge, write a session note) and never cleans up a branch before the merge is confirmed. Invoke for "ship this", "open/merge the PR", "land this change", or any end-of-change release flow.
Use when executing a model-spine implementation plan (the ClaudeCowork specs/plans backlog — prediction stacks, ratings engines, projection/impact models) or building any oracle-gated analytics module in an SDV repo. Covers the full loop — isolated worktree + baseline, Phase-0 oracle harness (metrics/constants/leakage split/fixtures), per-task TDD with verified commits, oracle gates with the never-lower rule, league-shim parity, mypy/codegen close-out, reviewer pass, and the session restart prompt. Invoke for "implement T<x>", "start the <sport> model spine", "continue the prediction stack", or any multi-phase model build.
Use when capturing an external oracle/validation corpus into committed test fixtures — Torvik/KenPom ratings, ESPN BPI/predictor/odds, market closing lines, MoneyPuck, published RAPM/EPM, or any third-party reference a model gate will assert against. Covers column contracts, Utf8-id discipline, the team/player name-crosswalk recipe (contracting normalizer + candidate keys + alias table + match-rate reporting), rate-limit-aware sampling, and the mandatory provenance README. Invoke for "capture the oracle corpus", "commit the <source> fixtures", or any Task-0-style fixture capture in a model plan.
Use when porting Python logic from the SportsDataverse Python package (sdv-py) into a SportsDataverse R package — cfbfastR, hoopR, wehoop, baseballr, fastRhockey, softballR, etc. Enforces a parity-test-first workflow (golden fixture from the Python output → failing testthat test → port → green) plus the tidyverse/data.table idiom map and R package conventions (roxygen2 docs, snake_case, tibble returns, pkgdown reference coverage). Invoke for "port this to cfbfastR", "translate this polars logic to dplyr", "re-implement the sdv-py parser in R", or "bring this Python fix back to the R package".
Add a CBS Sports (NAPI) league/resource — capture + catalog in sdv-internal-refs/cbs.
| name | sdv-add-sport-parser |
| description | Add sport-specific tidy parsers for an ESPN sport (soccer/cricket pattern) — parser module + per-sport codegen routing + fixtures + TDD. |
| disable-model-invocation | true |
Adds tidy polars/pandas parsers for a sport that uses league_param: true mode (e.g. soccer, cricket) or any sport needing per-sport parser overrides in the ESPN cross-league surface.
polars.DataFrame by default; pandas via return_as_pandas=True.sportsdataverse.dl_utils.underscore.pandas.json_normalize for nested flattening, then convert to polars at the end._common_espn_parsers — circular import. Import only polars, pandas, typing, sportsdataverse.dl_utils.sportsdataverse/<sport>/<sport>_espn_parsers.py
Minimal skeleton:
from __future__ import annotations
from typing import Any
import pandas as pd
import polars as pl
from sportsdataverse.dl_utils import underscore
def _to_frame(records: list[dict], return_as_pandas: bool) -> pl.DataFrame | pd.DataFrame:
if not records:
df = pl.DataFrame()
return df.to_pandas() if return_as_pandas else df
pdf = pd.json_normalize(records)
pdf.columns = [underscore(c) for c in pdf.columns]
# stringify any list cells
for col in pdf.columns:
if pdf[col].apply(lambda x: isinstance(x, list)).any():
pdf[col] = pdf[col].astype(str)
frame = pl.from_pandas(pdf)
return frame.to_pandas() if return_as_pandas else frame
def parse_<sport>_scoreboard(
payload: dict[str, Any],
return_as_pandas: bool = False,
) -> pl.DataFrame | pd.DataFrame:
events = payload.get("events", [])
return _to_frame(events, return_as_pandas)
def parse_<sport>_summary(
payload: dict[str, Any],
section: str | None = None,
return_as_pandas: bool = False,
) -> dict[str, pl.DataFrame | pd.DataFrame] | pl.DataFrame | pd.DataFrame:
"""Dispatch all 21 sub-frames or a single named section."""
parsers = {
"header": _parse_summary_header,
"lineups": _parse_summary_lineups,
# add more sections here
}
if section is not None:
fn = parsers.get(section, lambda p, r: _to_frame([], r))
return fn(payload, return_as_pandas)
return {name: fn(payload, return_as_pandas) for name, fn in parsers.items()}
Add one _parse_summary_<section> private helper per section.
Open tools/codegen/generate.py. Add two entries:
_SPORT_PARSER_OVERRIDES["<sport>"] = {
"scoreboard": "parse_<sport>_scoreboard",
"summary": "parse_<sport>_summary",
# add remaining short names
}
_SPORT_PARSER_MODULE["<sport>"] = "sportsdataverse.<sport>.<sport>_espn_parsers"
The codegen's sport_parser_imports block picks these up and emits the correct import + routing in the generated ext module. Do not re-export from _common_espn_parsers.py.
tests/fixtures/espn/<sport>/<league>/<endpoint>.json
Capture from sdv-internal-refs or directly:
python c:\Users\saiem\Documents\sdv-internal-refs\espn\tools\espn_capture_league.py \
<sport> <league>
cp c:\Users\saiem\Documents\sdv-internal-refs\espn\inputs\sample_bodies\<league>\... \
tests/fixtures/espn/<sport>/<league>/
Add a README.md in each fixture directory documenting the URL + capture date.
Create tests/test_espn_<sport>_parsers.py:
import json, pathlib, pytest
import polars as pl
FIXTURE_DIR = pathlib.Path(__file__).parent / "fixtures" / "espn" / "<sport>"
def load(league: str, endpoint: str) -> dict:
return json.loads((FIXTURE_DIR / league / f"{endpoint}.json").read_text())
def test_parse_<sport>_scoreboard_columns():
from sportsdataverse.<sport>.<sport>_espn_parsers import parse_<sport>_scoreboard
payload = load("<league>", "scoreboard")
df = parse_<sport>_scoreboard(payload)
assert isinstance(df, pl.DataFrame)
assert df.height > 0
assert "id" in df.columns # adjust to actual schema
def test_parse_<sport>_scoreboard_empty():
from sportsdataverse.<sport>.<sport>_espn_parsers import parse_<sport>_scoreboard
df = parse_<sport>_scoreboard({})
assert isinstance(df, pl.DataFrame)
assert df.height == 0
def test_parse_<sport>_scoreboard_pandas():
import pandas as pd
from sportsdataverse.<sport>.<sport>_espn_parsers import parse_<sport>_scoreboard
payload = load("<league>", "scoreboard")
df = parse_<sport>_scoreboard(payload, return_as_pandas=True)
assert isinstance(df, pd.DataFrame)
Run to confirm failure, implement parsers, re-run to green:
uv run pytest tests/test_espn_<sport>_parsers.py -v
uv run python tools/codegen/generate.py
uv run python tools/codegen/generate.py --check
uv run pytest tests/codegen/ tests/test_espn_<sport>_parsers.py -q
The generated ext for this sport should now route return_parsed=True calls through the sport-specific parsers.
git add sportsdataverse/<sport>/<sport>_espn_parsers.py \
tests/test_espn_<sport>_parsers.py \
tests/fixtures/espn/<sport>/ \
tools/codegen/generate.py \
tools/codegen/_generated/
git commit -m "feat(<sport>): add ESPN <sport> tidy parsers (scoreboard, summary dispatcher)"