stathead 0.2.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stathead-0.3.1/.gitignore +118 -0
- {stathead-0.2.0 → stathead-0.3.1}/PKG-INFO +105 -9
- stathead-0.3.1/README.md +205 -0
- {stathead-0.2.0 → stathead-0.3.1}/pyproject.toml +1 -1
- {stathead-0.2.0 → stathead-0.3.1}/src/stathead/__init__.py +29 -1
- {stathead-0.2.0 → stathead-0.3.1}/src/stathead/_fetch.py +5 -1
- {stathead-0.2.0 → stathead-0.3.1}/src/stathead/crosswalk.py +36 -0
- stathead-0.3.1/src/stathead/dynasty.py +192 -0
- stathead-0.3.1/src/stathead/polars.py +65 -0
- stathead-0.3.1/src/stathead/projections.py +116 -0
- stathead-0.3.1/src/stathead/sql.py +162 -0
- stathead-0.3.1/src/stathead/stats.py +56 -0
- {stathead-0.2.0 → stathead-0.3.1}/tests/test_smoke.py +158 -0
- stathead-0.2.0/.gitignore +0 -61
- stathead-0.2.0/README.md +0 -109
- {stathead-0.2.0 → stathead-0.3.1}/src/stathead/_renames.py +0 -0
- {stathead-0.2.0 → stathead-0.3.1}/src/stathead/adp.py +0 -0
- {stathead-0.2.0 → stathead-0.3.1}/src/stathead/features.py +0 -0
- {stathead-0.2.0 → stathead-0.3.1}/src/stathead/predictions.py +0 -0
- {stathead-0.2.0 → stathead-0.3.1}/src/stathead/prospects.py +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
node_modules
|
|
2
|
+
dist
|
|
3
|
+
# ...except the published MCP server bundle (the distributable artifact).
|
|
4
|
+
!mcp/dist/
|
|
5
|
+
!mcp/dist/server.mjs
|
|
6
|
+
.DS_Store
|
|
7
|
+
*.local
|
|
8
|
+
.env
|
|
9
|
+
!.env.example
|
|
10
|
+
public/data/*
|
|
11
|
+
!public/data/schedule-*.json
|
|
12
|
+
!public/data/redraft-projections-presets.json
|
|
13
|
+
# Mike Clay's NFL Projection Guide (ESPN+) is a paid product — its raw extracts
|
|
14
|
+
# are LOCAL-ONLY (regenerate via scripts/extract_clay_*.py from a PDF you own).
|
|
15
|
+
# Never commit clay-*.json; only the derived/anonymized blends ship. See
|
|
16
|
+
# DATA_SOURCES.md ("Paid products — never redistribute the source").
|
|
17
|
+
public/data/clay-projections-*.json
|
|
18
|
+
public/data/clay-unit-grades-*.json
|
|
19
|
+
public/data/clay-matchups-*.json
|
|
20
|
+
public/data/clay-team-projections-*.json
|
|
21
|
+
!public/data/training-rows-cache-*.json
|
|
22
|
+
!public/data/trained-models-cache-*.json
|
|
23
|
+
!public/data/model-cache-*.json
|
|
24
|
+
!public/data/feature-store/
|
|
25
|
+
!public/data/score-store/
|
|
26
|
+
!public/data/ktc_rankings_*.json
|
|
27
|
+
!public/data/ktc_history.json
|
|
28
|
+
!public/data/nflverse_weekly_2025.json
|
|
29
|
+
!public/data/ppg-feature-ablation.json
|
|
30
|
+
!public/data/ppg-hyperparam-sweep.json
|
|
31
|
+
!public/data/residual-feature-ablation.json
|
|
32
|
+
!public/data/residual-hyperparam-sweep.json
|
|
33
|
+
!public/data/ktc-feature-ablation.json
|
|
34
|
+
!public/data/ktc-hyperparam-sweep.json
|
|
35
|
+
!public/data/pdf-career-ablation-predraft.json
|
|
36
|
+
!public/data/pdf-career-ablation-postdraft.json
|
|
37
|
+
!public/data/rsp-career-ablation-predraft.json
|
|
38
|
+
!public/data/rsp-career-ablation-postdraft.json
|
|
39
|
+
!public/data/pdf-only-career-test.json
|
|
40
|
+
!public/data/qb-beast-ablation.json
|
|
41
|
+
!public/data/ktc-forecasts-*.json
|
|
42
|
+
!public/data/redraft-projections.json
|
|
43
|
+
!public/data/projection-base-*.json
|
|
44
|
+
!public/data/model-eval-*.json
|
|
45
|
+
!public/data/team-metrics-*.json
|
|
46
|
+
!public/data/coach-tendencies.json
|
|
47
|
+
!public/data/player-metrics-*.json
|
|
48
|
+
!public/data/pbp-slim-*.json.gz
|
|
49
|
+
!public/data/prospect-boom-bust.json
|
|
50
|
+
!public/data/prospect-grades-*.json
|
|
51
|
+
!public/data/depth-order-2026.json
|
|
52
|
+
!public/data/career-2027.json
|
|
53
|
+
!public/data/cfbd-college-stats.json
|
|
54
|
+
!public/data/cfbd-sp-ratings.json
|
|
55
|
+
!public/data/cfbd-recruiting.json
|
|
56
|
+
!public/data/cfbd-games.json
|
|
57
|
+
!public/data/cfbd-team-talent.json
|
|
58
|
+
!public/data/cfbd-player-usage.json
|
|
59
|
+
!public/data/cfbd/
|
|
60
|
+
!public/data/cfbd/*.json
|
|
61
|
+
!public/data/pdf-prospect-features.json
|
|
62
|
+
!public/data/pdf-prospect-features-merged.json
|
|
63
|
+
!public/data/rsp-historical-rankings.json
|
|
64
|
+
!public/data/rsp-qb-features.json
|
|
65
|
+
!public/data/manual-cfbd-overrides.json
|
|
66
|
+
!public/data/player-crosswalk.json
|
|
67
|
+
!public/data/player-id-map.json
|
|
68
|
+
!public/data/player-aliases.json
|
|
69
|
+
!public/data/player-promotions.json
|
|
70
|
+
!public/data/player-conflicts.json
|
|
71
|
+
!public/data/player-buzz.json
|
|
72
|
+
# Raw upstream sources committed as .csv.gz (~35 MB vs ~200 MB raw).
|
|
73
|
+
# TS readLocalFile auto-decompresses; Python runs `bash scripts/extract-data.sh`
|
|
74
|
+
# once to materialize the .csv. See scripts/pull-all-data-sources.sh.
|
|
75
|
+
!public/data/*.csv.gz
|
|
76
|
+
!public/data/ffc_adp_ppr_*.json
|
|
77
|
+
!public/data/ffc_adp_2qb_*.json
|
|
78
|
+
!public/data/espn_adp_*.json
|
|
79
|
+
!public/data/espn-nfl-ids.json
|
|
80
|
+
!public/data/fantasycalc_*.json
|
|
81
|
+
!public/data/fantasycalc_history_*.json
|
|
82
|
+
!public/data/sleeper-players.json
|
|
83
|
+
!public/data/sleeper-adp-*.json
|
|
84
|
+
!public/data/ktc-fc-rescale.json
|
|
85
|
+
# Aggregated, anonymized expert activity (no usernames) — committed/deployed.
|
|
86
|
+
# Built by scripts/build_expert_data.py from the gitignored private/ list.
|
|
87
|
+
!public/data/expert-ownership.json
|
|
88
|
+
!public/data/expert-adds.json
|
|
89
|
+
!public/data/expert-trades.json
|
|
90
|
+
!public/data/expert-graph.json
|
|
91
|
+
!public/data/expert-league-graph.json
|
|
92
|
+
!public/data/expert-candidates.json
|
|
93
|
+
# expert-names.json (index -> username) is intentionally NOT un-ignored: it stays
|
|
94
|
+
# local-only so the social graph / rankings can show names in dev but never ship.
|
|
95
|
+
# Its passphrase-encrypted form (scripts/encrypt-expert-names.mjs) IS committed:
|
|
96
|
+
# the deployed Social Graph decrypts it in-browser after an "unlock" passphrase.
|
|
97
|
+
# The ciphertext is public, so the passphrase is the only protection — keep it strong.
|
|
98
|
+
!public/data/expert-names.enc.json
|
|
99
|
+
.claude/*
|
|
100
|
+
!.claude/commands/
|
|
101
|
+
!.claude/commands/**
|
|
102
|
+
CLAUDE.local.md
|
|
103
|
+
__pycache__/
|
|
104
|
+
.nflverse-cache/
|
|
105
|
+
.cache/
|
|
106
|
+
.venv/
|
|
107
|
+
pdfs/
|
|
108
|
+
|
|
109
|
+
# Proprietary inputs (Clay PDFs + parsed dataset) — never committed.
|
|
110
|
+
/private/
|
|
111
|
+
# Local-only Clay projection set consumed at runtime by the "Consensus" preset.
|
|
112
|
+
# Never committed — absent in the public deploy so Clay's numbers ship nowhere.
|
|
113
|
+
/public/data/clay/
|
|
114
|
+
|
|
115
|
+
# Downloaded headshot images
|
|
116
|
+
headshots.zip
|
|
117
|
+
headshots_part*.zip
|
|
118
|
+
!public/data/depth-starters-*.json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stathead
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Python client for the StatHead fantasy football model — rookie career predictions, historical ADP, and flattened feature matrices.
|
|
5
5
|
Project-URL: Homepage, https://github.com/dachhack/stathead
|
|
6
6
|
Project-URL: Source, https://github.com/dachhack/stathead
|
|
@@ -39,8 +39,8 @@ pip install stathead
|
|
|
39
39
|
Optional extras:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
pip install "stathead[polars]" # for .to_polars() helpers
|
|
43
|
-
pip install "stathead[duckdb]" # for local SQL querying
|
|
42
|
+
pip install "stathead[polars]" # for sh.to_polars() / sh.load_polars() helpers
|
|
43
|
+
pip install "stathead[duckdb]" # for local SQL querying (sh.query)
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
## Quick start
|
|
@@ -62,6 +62,58 @@ adp = sh.load_adp_historical()
|
|
|
62
62
|
adp[(adp.season == 2023) & (adp.adp <= 24)]
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
## SQL querying
|
|
66
|
+
|
|
67
|
+
With the `duckdb` extra, `sh.query()` runs SQL over the loaders — the same
|
|
68
|
+
surface as the site's Data Query tab, in Python. Every table joins on
|
|
69
|
+
`player_key`.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install "stathead[duckdb]"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import stathead as sh
|
|
77
|
+
|
|
78
|
+
sh.query("""
|
|
79
|
+
SELECT c.name, c.position, c.predictedCareerPPG, d.value_1qb
|
|
80
|
+
FROM career_2026 c
|
|
81
|
+
JOIN dynasty_values d USING (player_key)
|
|
82
|
+
WHERE c.percentile >= 80
|
|
83
|
+
ORDER BY d.value_1qb DESC
|
|
84
|
+
""")
|
|
85
|
+
|
|
86
|
+
sh.list_tables() # every queryable table name
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Tables load lazily — only the ones a query references are materialized, so a
|
|
90
|
+
query that never touches `player_stats` (~400k rows) doesn't pay for it.
|
|
91
|
+
Register your own DataFrame (a roster, a league export) to join against the
|
|
92
|
+
model tables:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
sh.register("my_roster", roster_df)
|
|
96
|
+
sh.query("SELECT * FROM career_2026 c JOIN my_roster r USING (player_key)")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Polars
|
|
100
|
+
|
|
101
|
+
Loaders return pandas. With the `polars` extra, convert any of them — handy on
|
|
102
|
+
the big tables like `load_player_stats` (~400k rows) where polars is faster.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pip install "stathead[polars]"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import stathead as sh
|
|
110
|
+
|
|
111
|
+
pl_df = sh.to_polars(sh.load_player_stats(2024))
|
|
112
|
+
|
|
113
|
+
# Or convert a loader by reference, without calling it yourself:
|
|
114
|
+
pl_df = sh.load_polars(sh.load_player_stats, 2024)
|
|
115
|
+
```
|
|
116
|
+
|
|
65
117
|
## Pinning to a specific version
|
|
66
118
|
|
|
67
119
|
Loaders resolve against the upstream GitHub repo. Pin to a commit SHA, tag,
|
|
@@ -85,22 +137,66 @@ cache directory or call `clear_cache()` to force a refresh.
|
|
|
85
137
|
|
|
86
138
|
## Available loaders
|
|
87
139
|
|
|
140
|
+
Every table on the [StatHead site](https://github.com/dachhack/stathead) is
|
|
141
|
+
available here as a pandas DataFrame.
|
|
142
|
+
|
|
143
|
+
**Predictions & prospects**
|
|
144
|
+
|
|
88
145
|
| Function | Returns | Shape |
|
|
89
146
|
|---|---|---|
|
|
90
147
|
| `load_career_predictions_2026()` | 2026 rookie predictions | ~77 × ~80 cols |
|
|
91
148
|
| `load_career_backtest()` | Historical rookies with pred + actual PPG | ~1087 × ~100 cols |
|
|
149
|
+
| `load_prospect_grades(year=2026)` | Scouting-report grades | ~200 × 7 |
|
|
150
|
+
| `load_career_2027()` | 2027 draft-class early board + college aggregates | ~200 × ~30 |
|
|
151
|
+
|
|
152
|
+
**Projections (model outputs)**
|
|
153
|
+
|
|
154
|
+
| Function | Returns | Shape |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `load_redraft_projections()` | Seasonal redraft PPG (PPR) + receptions/game | ~250 × 7 |
|
|
157
|
+
| `load_ppg_projections()` | Model-predicted PPG for established players | ~250 × 4 |
|
|
158
|
+
| `load_adp_value_model()` | VOR vs ADP, hit probability, confidence interval | ~153 × 10 |
|
|
159
|
+
| `load_volume_projections()` | Team pass/rush/target volumes with low/high bands | ~153 × ~14 |
|
|
160
|
+
| `load_share_projections()` | Predicted target + rush share | ~153 × 6 |
|
|
161
|
+
| `load_taxi_predictions()` | Taxi-squad roster/drop probabilities (+ `df.attrs['meta']`) | ~96 × 6 |
|
|
162
|
+
|
|
163
|
+
**Market & stats**
|
|
164
|
+
|
|
165
|
+
| Function | Returns | Shape |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `load_player_stats(season=None)` | Per-player per-week NFL box scores 2010-present | ~400k × ~50 |
|
|
168
|
+
| `load_dynasty_values()` | In-house blended dynasty value (1QB + Superflex) | ~500 × 10 |
|
|
169
|
+
| `load_dynasty_value_history()` | Blended daily dynasty value history | variable |
|
|
92
170
|
| `load_adp_historical()` | Model-training ADP 2010-2025 | 4507 × 10 |
|
|
93
171
|
| `load_adp_ffc(season=None)` | FFC PPR raw ADP (per season as fetched) — data via [Fantasy Football Calculator](https://fantasyfootballcalculator.com/adp/ppr) | variable |
|
|
94
|
-
|
|
172
|
+
|
|
173
|
+
**Identity & raw**
|
|
174
|
+
|
|
175
|
+
| Function | Returns | Shape |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| `load_player_crosswalk()` | Canonical cross-source player IDs | ~10k × ~20 |
|
|
178
|
+
| `resolve_player(name, position=None)` / `get_player(key)` / `load_player_profile(key)` | Name → `player_key` helpers | — |
|
|
95
179
|
| `load_feature_matrix()` | Raw `feature-matrix.json` (dict) | — |
|
|
96
180
|
| `load_manual_overrides()` | Manual CFBD usage overrides (dict) | — |
|
|
97
181
|
|
|
182
|
+
Every row-shaped loader carries a `player_key` column that joins to
|
|
183
|
+
`load_player_crosswalk()`.
|
|
184
|
+
|
|
185
|
+
### Dynasty values are the app's in-house blend
|
|
186
|
+
|
|
187
|
+
`load_dynasty_values()` returns the same blended value the web app displays:
|
|
188
|
+
KTC rankings rescaled into FantasyCalc's scale via a per-player ratio
|
|
189
|
+
(`fc_value / ktc_value`, with a positional-median fallback below a value
|
|
190
|
+
floor). The ratio snapshot is built offline and committed as
|
|
191
|
+
`public/data/dynasty-fc-rescale.json`; the loader just applies it. No raw KTC
|
|
192
|
+
value is exposed — only the rescaled blend.
|
|
193
|
+
|
|
98
194
|
## Feature columns
|
|
99
195
|
|
|
100
196
|
Career-prediction and backtest rows include flattened model features
|
|
101
197
|
under names like `collegeDominatorRating`, `relativeAthleticScore`,
|
|
102
198
|
`recruitRating`, `nflDraftPick`, plus two source-agnostic families
|
|
103
|
-
aggregated from the project's scouting pipeline:
|
|
199
|
+
aggregated from the project's scouting-report pipeline:
|
|
104
200
|
|
|
105
201
|
- **`scout*`** — single-scout grade signals (e.g. `scoutGradeDraft`,
|
|
106
202
|
`scoutTierOrdinal`, `scoutBreadthDraft`, `scoutNComps`).
|
|
@@ -109,9 +205,9 @@ aggregated from the project's scouting pipeline:
|
|
|
109
205
|
`guideNWeaknesses`, `guideSentimentNet`, …).
|
|
110
206
|
|
|
111
207
|
Both families are derived numeric features (counts, means, ordinals) —
|
|
112
|
-
no verbatim
|
|
113
|
-
flag missing-data so models can distinguish
|
|
114
|
-
"low score".
|
|
208
|
+
no verbatim scouting-report text is shipped. `hasScoutGrade` /
|
|
209
|
+
`hasGuideData` flag missing-data so models can distinguish
|
|
210
|
+
"no scout coverage" from "low score".
|
|
115
211
|
|
|
116
212
|
## Licensing & attribution
|
|
117
213
|
|
|
@@ -120,7 +216,7 @@ from the StatHead project's own modeling pipeline; upstream sources
|
|
|
120
216
|
(nflverse, FFC, CFBD, etc.) retain their own terms — see each source's
|
|
121
217
|
license before redistributing. Sources whose terms do not permit
|
|
122
218
|
third-party redistribution (e.g. KeepTradeCut dynasty values, verbatim
|
|
123
|
-
prose from paid scouting
|
|
219
|
+
prose from paid scouting reports) are intentionally not exposed by
|
|
124
220
|
this client.
|
|
125
221
|
|
|
126
222
|
ADP data exposed by `load_adp_ffc` is courtesy of
|
stathead-0.3.1/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# stathead
|
|
2
|
+
|
|
3
|
+
Python client for the [StatHead](https://github.com/dachhack/stathead) fantasy football model. Returns pandas DataFrames of rookie career predictions, historical ADP, and the flattened feature matrix used to train the models.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install stathead
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Optional extras:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install "stathead[polars]" # for sh.to_polars() / sh.load_polars() helpers
|
|
15
|
+
pip install "stathead[duckdb]" # for local SQL querying (sh.query)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import stathead as sh
|
|
22
|
+
|
|
23
|
+
# 2026 rookie class predictions (77 players × ~80 columns)
|
|
24
|
+
rookies = sh.load_career_predictions_2026()
|
|
25
|
+
rookies.nlargest(10, "percentile")[["name", "position", "predictedCareerPPG", "modelTier"]]
|
|
26
|
+
|
|
27
|
+
# Historical backtest — predicted vs actual for every drafted rookie 2010-2025
|
|
28
|
+
backtest = sh.load_career_backtest()
|
|
29
|
+
wr = backtest[backtest.position == "WR"]
|
|
30
|
+
wr.groupby("modelTier")[["actualPPG", "predictedPPG"]].mean()
|
|
31
|
+
|
|
32
|
+
# Historical ADP, every season fully populated
|
|
33
|
+
adp = sh.load_adp_historical()
|
|
34
|
+
adp[(adp.season == 2023) & (adp.adp <= 24)]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## SQL querying
|
|
38
|
+
|
|
39
|
+
With the `duckdb` extra, `sh.query()` runs SQL over the loaders — the same
|
|
40
|
+
surface as the site's Data Query tab, in Python. Every table joins on
|
|
41
|
+
`player_key`.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install "stathead[duckdb]"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import stathead as sh
|
|
49
|
+
|
|
50
|
+
sh.query("""
|
|
51
|
+
SELECT c.name, c.position, c.predictedCareerPPG, d.value_1qb
|
|
52
|
+
FROM career_2026 c
|
|
53
|
+
JOIN dynasty_values d USING (player_key)
|
|
54
|
+
WHERE c.percentile >= 80
|
|
55
|
+
ORDER BY d.value_1qb DESC
|
|
56
|
+
""")
|
|
57
|
+
|
|
58
|
+
sh.list_tables() # every queryable table name
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Tables load lazily — only the ones a query references are materialized, so a
|
|
62
|
+
query that never touches `player_stats` (~400k rows) doesn't pay for it.
|
|
63
|
+
Register your own DataFrame (a roster, a league export) to join against the
|
|
64
|
+
model tables:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
sh.register("my_roster", roster_df)
|
|
68
|
+
sh.query("SELECT * FROM career_2026 c JOIN my_roster r USING (player_key)")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Polars
|
|
72
|
+
|
|
73
|
+
Loaders return pandas. With the `polars` extra, convert any of them — handy on
|
|
74
|
+
the big tables like `load_player_stats` (~400k rows) where polars is faster.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install "stathead[polars]"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import stathead as sh
|
|
82
|
+
|
|
83
|
+
pl_df = sh.to_polars(sh.load_player_stats(2024))
|
|
84
|
+
|
|
85
|
+
# Or convert a loader by reference, without calling it yourself:
|
|
86
|
+
pl_df = sh.load_polars(sh.load_player_stats, 2024)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Pinning to a specific version
|
|
90
|
+
|
|
91
|
+
Loaders resolve against the upstream GitHub repo. Pin to a commit SHA, tag,
|
|
92
|
+
or branch for reproducibility:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
sh.pin_version("a6720e5") # or a tagged release
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Clear the local cache if you want to re-fetch:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
sh.clear_cache()
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Data freshness
|
|
105
|
+
|
|
106
|
+
Data files are cached under `~/.cache/stathead/<ref>/` after the first
|
|
107
|
+
download. Subsequent runs read from disk — no network roundtrip. Delete the
|
|
108
|
+
cache directory or call `clear_cache()` to force a refresh.
|
|
109
|
+
|
|
110
|
+
## Available loaders
|
|
111
|
+
|
|
112
|
+
Every table on the [StatHead site](https://github.com/dachhack/stathead) is
|
|
113
|
+
available here as a pandas DataFrame.
|
|
114
|
+
|
|
115
|
+
**Predictions & prospects**
|
|
116
|
+
|
|
117
|
+
| Function | Returns | Shape |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `load_career_predictions_2026()` | 2026 rookie predictions | ~77 × ~80 cols |
|
|
120
|
+
| `load_career_backtest()` | Historical rookies with pred + actual PPG | ~1087 × ~100 cols |
|
|
121
|
+
| `load_prospect_grades(year=2026)` | Scouting-report grades | ~200 × 7 |
|
|
122
|
+
| `load_career_2027()` | 2027 draft-class early board + college aggregates | ~200 × ~30 |
|
|
123
|
+
|
|
124
|
+
**Projections (model outputs)**
|
|
125
|
+
|
|
126
|
+
| Function | Returns | Shape |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| `load_redraft_projections()` | Seasonal redraft PPG (PPR) + receptions/game | ~250 × 7 |
|
|
129
|
+
| `load_ppg_projections()` | Model-predicted PPG for established players | ~250 × 4 |
|
|
130
|
+
| `load_adp_value_model()` | VOR vs ADP, hit probability, confidence interval | ~153 × 10 |
|
|
131
|
+
| `load_volume_projections()` | Team pass/rush/target volumes with low/high bands | ~153 × ~14 |
|
|
132
|
+
| `load_share_projections()` | Predicted target + rush share | ~153 × 6 |
|
|
133
|
+
| `load_taxi_predictions()` | Taxi-squad roster/drop probabilities (+ `df.attrs['meta']`) | ~96 × 6 |
|
|
134
|
+
|
|
135
|
+
**Market & stats**
|
|
136
|
+
|
|
137
|
+
| Function | Returns | Shape |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `load_player_stats(season=None)` | Per-player per-week NFL box scores 2010-present | ~400k × ~50 |
|
|
140
|
+
| `load_dynasty_values()` | In-house blended dynasty value (1QB + Superflex) | ~500 × 10 |
|
|
141
|
+
| `load_dynasty_value_history()` | Blended daily dynasty value history | variable |
|
|
142
|
+
| `load_adp_historical()` | Model-training ADP 2010-2025 | 4507 × 10 |
|
|
143
|
+
| `load_adp_ffc(season=None)` | FFC PPR raw ADP (per season as fetched) — data via [Fantasy Football Calculator](https://fantasyfootballcalculator.com/adp/ppr) | variable |
|
|
144
|
+
|
|
145
|
+
**Identity & raw**
|
|
146
|
+
|
|
147
|
+
| Function | Returns | Shape |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `load_player_crosswalk()` | Canonical cross-source player IDs | ~10k × ~20 |
|
|
150
|
+
| `resolve_player(name, position=None)` / `get_player(key)` / `load_player_profile(key)` | Name → `player_key` helpers | — |
|
|
151
|
+
| `load_feature_matrix()` | Raw `feature-matrix.json` (dict) | — |
|
|
152
|
+
| `load_manual_overrides()` | Manual CFBD usage overrides (dict) | — |
|
|
153
|
+
|
|
154
|
+
Every row-shaped loader carries a `player_key` column that joins to
|
|
155
|
+
`load_player_crosswalk()`.
|
|
156
|
+
|
|
157
|
+
### Dynasty values are the app's in-house blend
|
|
158
|
+
|
|
159
|
+
`load_dynasty_values()` returns the same blended value the web app displays:
|
|
160
|
+
KTC rankings rescaled into FantasyCalc's scale via a per-player ratio
|
|
161
|
+
(`fc_value / ktc_value`, with a positional-median fallback below a value
|
|
162
|
+
floor). The ratio snapshot is built offline and committed as
|
|
163
|
+
`public/data/dynasty-fc-rescale.json`; the loader just applies it. No raw KTC
|
|
164
|
+
value is exposed — only the rescaled blend.
|
|
165
|
+
|
|
166
|
+
## Feature columns
|
|
167
|
+
|
|
168
|
+
Career-prediction and backtest rows include flattened model features
|
|
169
|
+
under names like `collegeDominatorRating`, `relativeAthleticScore`,
|
|
170
|
+
`recruitRating`, `nflDraftPick`, plus two source-agnostic families
|
|
171
|
+
aggregated from the project's scouting-report pipeline:
|
|
172
|
+
|
|
173
|
+
- **`scout*`** — single-scout grade signals (e.g. `scoutGradeDraft`,
|
|
174
|
+
`scoutTierOrdinal`, `scoutBreadthDraft`, `scoutNComps`).
|
|
175
|
+
- **`guide*`** — multi-source draft-guide aggregations
|
|
176
|
+
(`guideRankMean`, `guideRankSpread`, `guideNStrengths`,
|
|
177
|
+
`guideNWeaknesses`, `guideSentimentNet`, …).
|
|
178
|
+
|
|
179
|
+
Both families are derived numeric features (counts, means, ordinals) —
|
|
180
|
+
no verbatim scouting-report text is shipped. `hasScoutGrade` /
|
|
181
|
+
`hasGuideData` flag missing-data so models can distinguish
|
|
182
|
+
"no scout coverage" from "low score".
|
|
183
|
+
|
|
184
|
+
## Licensing & attribution
|
|
185
|
+
|
|
186
|
+
Package code is MIT-licensed. The data this package retrieves is derived
|
|
187
|
+
from the StatHead project's own modeling pipeline; upstream sources
|
|
188
|
+
(nflverse, FFC, CFBD, etc.) retain their own terms — see each source's
|
|
189
|
+
license before redistributing. Sources whose terms do not permit
|
|
190
|
+
third-party redistribution (e.g. KeepTradeCut dynasty values, verbatim
|
|
191
|
+
prose from paid scouting reports) are intentionally not exposed by
|
|
192
|
+
this client.
|
|
193
|
+
|
|
194
|
+
ADP data exposed by `load_adp_ffc` is courtesy of
|
|
195
|
+
[Fantasy Football Calculator](https://fantasyfootballcalculator.com/) — please
|
|
196
|
+
preserve attribution when redistributing.
|
|
197
|
+
|
|
198
|
+
If you're building on these predictions, a link back to the StatHead repo
|
|
199
|
+
is appreciated but not required.
|
|
200
|
+
|
|
201
|
+
## Contributing
|
|
202
|
+
|
|
203
|
+
The package is small and focused — see
|
|
204
|
+
[`python/src/stathead/`](./src/stathead/) for the loader modules. Issues
|
|
205
|
+
and PRs welcome at the [main repo](https://github.com/dachhack/stathead).
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "stathead"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Python client for the StatHead fantasy football model — rookie career predictions, historical ADP, and flattened feature matrices."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -24,11 +24,24 @@ from .crosswalk import (
|
|
|
24
24
|
load_player_profile,
|
|
25
25
|
resolve_player,
|
|
26
26
|
)
|
|
27
|
+
from .dynasty import load_dynasty_value_history, load_dynasty_values
|
|
27
28
|
from .features import load_feature_matrix, load_manual_overrides
|
|
28
29
|
from .predictions import load_career_backtest, load_career_predictions_2026
|
|
30
|
+
from .projections import (
|
|
31
|
+
load_adp_value_model,
|
|
32
|
+
load_career_2027,
|
|
33
|
+
load_ppg_projections,
|
|
34
|
+
load_redraft_projections,
|
|
35
|
+
load_share_projections,
|
|
36
|
+
load_taxi_predictions,
|
|
37
|
+
load_volume_projections,
|
|
38
|
+
)
|
|
39
|
+
from .polars import load_polars, to_polars
|
|
29
40
|
from .prospects import load_prospect_grades
|
|
41
|
+
from .sql import list_tables, query, register
|
|
42
|
+
from .stats import load_player_stats
|
|
30
43
|
|
|
31
|
-
__version__ = "0.
|
|
44
|
+
__version__ = "0.3.1"
|
|
32
45
|
|
|
33
46
|
__all__ = [
|
|
34
47
|
"__version__",
|
|
@@ -37,13 +50,28 @@ __all__ = [
|
|
|
37
50
|
"set_ref",
|
|
38
51
|
"load_adp_ffc",
|
|
39
52
|
"load_adp_historical",
|
|
53
|
+
"load_adp_value_model",
|
|
54
|
+
"load_career_2027",
|
|
40
55
|
"load_career_backtest",
|
|
41
56
|
"load_career_predictions_2026",
|
|
57
|
+
"load_dynasty_value_history",
|
|
58
|
+
"load_dynasty_values",
|
|
42
59
|
"load_feature_matrix",
|
|
43
60
|
"load_manual_overrides",
|
|
44
61
|
"load_player_crosswalk",
|
|
45
62
|
"load_player_profile",
|
|
63
|
+
"load_player_stats",
|
|
64
|
+
"load_ppg_projections",
|
|
46
65
|
"load_prospect_grades",
|
|
66
|
+
"load_redraft_projections",
|
|
67
|
+
"load_share_projections",
|
|
68
|
+
"load_taxi_predictions",
|
|
69
|
+
"load_volume_projections",
|
|
47
70
|
"get_player",
|
|
48
71
|
"resolve_player",
|
|
72
|
+
"query",
|
|
73
|
+
"register",
|
|
74
|
+
"list_tables",
|
|
75
|
+
"to_polars",
|
|
76
|
+
"load_polars",
|
|
49
77
|
]
|
|
@@ -18,7 +18,11 @@ from typing import Any
|
|
|
18
18
|
from urllib.request import Request, urlopen
|
|
19
19
|
|
|
20
20
|
_BASE_URL = "https://raw.githubusercontent.com/dachhack/stathead"
|
|
21
|
-
|
|
21
|
+
# The repo's data + models live on its default branch (there is no `main`),
|
|
22
|
+
# and the every-2h refresh workflow commits there, so this is the ref that
|
|
23
|
+
# always has fresh feature-matrix / score-store / crosswalk files. Override
|
|
24
|
+
# per-session with pin_version() or per-shell with STATHEAD_REF.
|
|
25
|
+
_DEFAULT_REF = "claude/nfl-fantasy-workbench-6D1yd"
|
|
22
26
|
_ref: str = os.environ.get("STATHEAD_REF", _DEFAULT_REF)
|
|
23
27
|
|
|
24
28
|
|
|
@@ -72,6 +72,42 @@ def _crosswalk_cached() -> list[dict[str, Any]]:
|
|
|
72
72
|
return fetch_json("public/data/player-crosswalk.json").get("players") or []
|
|
73
73
|
|
|
74
74
|
|
|
75
|
+
@lru_cache(maxsize=1)
|
|
76
|
+
def key_by_name_pos() -> dict[tuple[str, str], str]:
|
|
77
|
+
"""``(_norm(name), position) -> player_key`` for every name a player has
|
|
78
|
+
gone by (display name, ``all_names``, aliases) crossed with every
|
|
79
|
+
position they've held. First writer wins so the canonical record is
|
|
80
|
+
preferred over an alias. Used by loaders that only carry name+position
|
|
81
|
+
(dynasty blend, projections) to stamp the canonical ``player_key``."""
|
|
82
|
+
out: dict[tuple[str, str], str] = {}
|
|
83
|
+
for rec in _crosswalk_cached():
|
|
84
|
+
key = rec.get("player_key")
|
|
85
|
+
if not key:
|
|
86
|
+
continue
|
|
87
|
+
names = {rec.get("display_name", "")}
|
|
88
|
+
names.update(rec.get("all_names") or [])
|
|
89
|
+
names.update(a.get("name", "") for a in rec.get("aliases") or [])
|
|
90
|
+
positions = set(rec.get("all_positions") or []) | {rec.get("position") or ""}
|
|
91
|
+
for nm in names:
|
|
92
|
+
n = _norm(nm)
|
|
93
|
+
if not n:
|
|
94
|
+
continue
|
|
95
|
+
for pos in positions:
|
|
96
|
+
out.setdefault((n, pos), key)
|
|
97
|
+
return out
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@lru_cache(maxsize=1)
|
|
101
|
+
def key_by_gsis() -> dict[str, str]:
|
|
102
|
+
"""``gsis_id -> player_key``. Used to stamp the canonical key onto
|
|
103
|
+
weekly ``player_stats`` rows (whose ``player_id`` is the GSIS ID)."""
|
|
104
|
+
return {
|
|
105
|
+
str(rec["gsis_id"]): rec["player_key"]
|
|
106
|
+
for rec in _crosswalk_cached()
|
|
107
|
+
if rec.get("gsis_id") and rec.get("player_key")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
75
111
|
def resolve_player(name: str, position: str | None = None) -> str:
|
|
76
112
|
"""Return the canonical ``player_key`` for a player name.
|
|
77
113
|
|