stathead 0.2.1__tar.gz → 0.3.2__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.2/.gitignore +125 -0
- {stathead-0.2.1 → stathead-0.3.2}/PKG-INFO +101 -4
- {stathead-0.2.1 → stathead-0.3.2}/README.md +100 -3
- {stathead-0.2.1 → stathead-0.3.2}/pyproject.toml +1 -1
- {stathead-0.2.1 → stathead-0.3.2}/src/stathead/__init__.py +31 -1
- {stathead-0.2.1 → stathead-0.3.2}/src/stathead/crosswalk.py +36 -0
- stathead-0.3.2/src/stathead/dynasty.py +192 -0
- stathead-0.3.2/src/stathead/polars.py +65 -0
- stathead-0.3.2/src/stathead/projections.py +173 -0
- stathead-0.3.2/src/stathead/sql.py +162 -0
- stathead-0.3.2/src/stathead/stats.py +56 -0
- stathead-0.3.2/tests/test_smoke.py +322 -0
- stathead-0.2.1/.gitignore +0 -63
- stathead-0.2.1/tests/test_smoke.py +0 -150
- {stathead-0.2.1 → stathead-0.3.2}/src/stathead/_fetch.py +0 -0
- {stathead-0.2.1 → stathead-0.3.2}/src/stathead/_renames.py +0 -0
- {stathead-0.2.1 → stathead-0.3.2}/src/stathead/adp.py +0 -0
- {stathead-0.2.1 → stathead-0.3.2}/src/stathead/features.py +0 -0
- {stathead-0.2.1 → stathead-0.3.2}/src/stathead/predictions.py +0 -0
- {stathead-0.2.1 → stathead-0.3.2}/src/stathead/prospects.py +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
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/weekly-projections-*.json
|
|
45
|
+
!public/data/model-eval-*.json
|
|
46
|
+
!public/data/team-metrics-*.json
|
|
47
|
+
!public/data/coach-tendencies.json
|
|
48
|
+
!public/data/player-metrics-*.json
|
|
49
|
+
!public/data/pbp-slim-*.json.gz
|
|
50
|
+
!public/data/prospect-boom-bust.json
|
|
51
|
+
!public/data/prospect-grades-*.json
|
|
52
|
+
!public/data/depth-order-2026.json
|
|
53
|
+
!public/data/career-2027.json
|
|
54
|
+
!public/data/cfbd-college-stats.json
|
|
55
|
+
!public/data/cfbd-sp-ratings.json
|
|
56
|
+
!public/data/cfbd-recruiting.json
|
|
57
|
+
!public/data/cfbd-games.json
|
|
58
|
+
!public/data/cfbd-team-talent.json
|
|
59
|
+
!public/data/cfbd-player-usage.json
|
|
60
|
+
!public/data/cfbd/
|
|
61
|
+
!public/data/cfbd/*.json
|
|
62
|
+
!public/data/pdf-prospect-features.json
|
|
63
|
+
!public/data/pdf-prospect-features-merged.json
|
|
64
|
+
!public/data/rsp-historical-rankings.json
|
|
65
|
+
!public/data/rsp-qb-features.json
|
|
66
|
+
!public/data/manual-cfbd-overrides.json
|
|
67
|
+
!public/data/player-crosswalk.json
|
|
68
|
+
!public/data/player-id-map.json
|
|
69
|
+
!public/data/player-aliases.json
|
|
70
|
+
!public/data/player-promotions.json
|
|
71
|
+
!public/data/player-conflicts.json
|
|
72
|
+
!public/data/player-buzz.json
|
|
73
|
+
# Raw upstream sources committed as .csv.gz (~35 MB vs ~200 MB raw).
|
|
74
|
+
# TS readLocalFile auto-decompresses; Python runs `bash scripts/extract-data.sh`
|
|
75
|
+
# once to materialize the .csv. See scripts/pull-all-data-sources.sh.
|
|
76
|
+
!public/data/*.csv.gz
|
|
77
|
+
!public/data/ffc_adp_ppr_*.json
|
|
78
|
+
!public/data/ffc_adp_2qb_*.json
|
|
79
|
+
!public/data/espn_adp_*.json
|
|
80
|
+
!public/data/espn-nfl-ids.json
|
|
81
|
+
!public/data/fantasycalc_*.json
|
|
82
|
+
!public/data/fantasycalc_history_*.json
|
|
83
|
+
!public/data/sleeper-players.json
|
|
84
|
+
!public/data/sleeper-adp-*.json
|
|
85
|
+
!public/data/ktc-fc-rescale.json
|
|
86
|
+
# Aggregated, anonymized expert activity (no usernames) — committed/deployed.
|
|
87
|
+
# Built by scripts/build_expert_data.py from the gitignored private/ list.
|
|
88
|
+
!public/data/expert-ownership.json
|
|
89
|
+
!public/data/expert-adds.json
|
|
90
|
+
!public/data/expert-trades.json
|
|
91
|
+
!public/data/expert-graph.json
|
|
92
|
+
!public/data/expert-league-graph.json
|
|
93
|
+
!public/data/expert-candidates.json
|
|
94
|
+
# expert-names.json (index -> username) is intentionally NOT un-ignored: it stays
|
|
95
|
+
# local-only so the social graph / rankings can show names in dev but never ship.
|
|
96
|
+
# Its passphrase-encrypted form (scripts/encrypt-expert-names.mjs) IS committed:
|
|
97
|
+
# the deployed Social Graph decrypts it in-browser after an "unlock" passphrase.
|
|
98
|
+
# The ciphertext is public, so the passphrase is the only protection — keep it strong.
|
|
99
|
+
!public/data/expert-names.enc.json
|
|
100
|
+
.claude/*
|
|
101
|
+
!.claude/commands/
|
|
102
|
+
!.claude/commands/**
|
|
103
|
+
CLAUDE.local.md
|
|
104
|
+
__pycache__/
|
|
105
|
+
.nflverse-cache/
|
|
106
|
+
.cache/
|
|
107
|
+
.venv/
|
|
108
|
+
pdfs/
|
|
109
|
+
|
|
110
|
+
# Footballguys SFB cheatsheet (paid product) — LOCAL-ONLY, never committed.
|
|
111
|
+
# Regenerate from a PDF you own:
|
|
112
|
+
# pdftotext -layout <cheatsheet.pdf> - | npx tsx scripts/parse-sfb-cheatsheet.ts > public/data/sfb16-cheatsheet.json
|
|
113
|
+
# (already covered by the public/data/* ignore; listed here for discoverability)
|
|
114
|
+
public/data/sfb16-cheatsheet.json
|
|
115
|
+
|
|
116
|
+
# Proprietary inputs (Clay PDFs + parsed dataset) — never committed.
|
|
117
|
+
/private/
|
|
118
|
+
# Local-only Clay projection set consumed at runtime by the "Consensus" preset.
|
|
119
|
+
# Never committed — absent in the public deploy so Clay's numbers ship nowhere.
|
|
120
|
+
/public/data/clay/
|
|
121
|
+
|
|
122
|
+
# Downloaded headshot images
|
|
123
|
+
headshots.zip
|
|
124
|
+
headshots_part*.zip
|
|
125
|
+
!public/data/depth-starters-*.json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stathead
|
|
3
|
-
Version: 0.2
|
|
3
|
+
Version: 0.3.2
|
|
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,16 +137,61 @@ 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_weekly_projections()` | Per-week matchup-adjusted projections, one row per player-game (opp, home, matchup mult, PPR pts; `df.attrs['def_vs_pos']`) | ~445 × 17 rows/player |
|
|
158
|
+
| `load_ppg_projections()` | Model-predicted PPG for established players | ~250 × 4 |
|
|
159
|
+
| `load_adp_value_model()` | VOR vs ADP, hit probability, confidence interval | ~153 × 10 |
|
|
160
|
+
| `load_volume_projections()` | Team pass/rush/target volumes with low/high bands | ~153 × ~14 |
|
|
161
|
+
| `load_share_projections()` | Predicted target + rush share | ~153 × 6 |
|
|
162
|
+
| `load_taxi_predictions()` | Taxi-squad roster/drop probabilities (+ `df.attrs['meta']`) | ~96 × 6 |
|
|
163
|
+
|
|
164
|
+
**Market & stats**
|
|
165
|
+
|
|
166
|
+
| Function | Returns | Shape |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `load_player_stats(season=None)` | Per-player per-week NFL box scores 2010-present | ~400k × ~50 |
|
|
169
|
+
| `load_dynasty_values()` | In-house blended dynasty value (1QB + Superflex) | ~500 × 10 |
|
|
170
|
+
| `load_dynasty_value_history()` | Blended daily dynasty value history | variable |
|
|
92
171
|
| `load_adp_historical()` | Model-training ADP 2010-2025 | 4507 × 10 |
|
|
93
172
|
| `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
|
-
|
|
173
|
+
|
|
174
|
+
**Identity & raw**
|
|
175
|
+
|
|
176
|
+
| Function | Returns | Shape |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `load_player_crosswalk()` | Canonical cross-source player IDs | ~10k × ~20 |
|
|
179
|
+
| `resolve_player(name, position=None)` / `get_player(key)` / `load_player_profile(key)` | Name → `player_key` helpers | — |
|
|
95
180
|
| `load_feature_matrix()` | Raw `feature-matrix.json` (dict) | — |
|
|
96
181
|
| `load_manual_overrides()` | Manual CFBD usage overrides (dict) | — |
|
|
97
182
|
|
|
183
|
+
Every row-shaped loader carries a `player_key` column that joins to
|
|
184
|
+
`load_player_crosswalk()`.
|
|
185
|
+
|
|
186
|
+
### Dynasty values are the app's in-house blend
|
|
187
|
+
|
|
188
|
+
`load_dynasty_values()` returns the same blended value the web app displays:
|
|
189
|
+
KTC rankings rescaled into FantasyCalc's scale via a per-player ratio
|
|
190
|
+
(`fc_value / ktc_value`, with a positional-median fallback below a value
|
|
191
|
+
floor). The ratio snapshot is built offline and committed as
|
|
192
|
+
`public/data/dynasty-fc-rescale.json`; the loader just applies it. No raw KTC
|
|
193
|
+
value is exposed — only the rescaled blend.
|
|
194
|
+
|
|
98
195
|
## Feature columns
|
|
99
196
|
|
|
100
197
|
Career-prediction and backtest rows include flattened model features
|
|
@@ -11,8 +11,8 @@ pip install stathead
|
|
|
11
11
|
Optional extras:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
pip install "stathead[polars]" # for .to_polars() helpers
|
|
15
|
-
pip install "stathead[duckdb]" # for local SQL querying
|
|
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
16
|
```
|
|
17
17
|
|
|
18
18
|
## Quick start
|
|
@@ -34,6 +34,58 @@ adp = sh.load_adp_historical()
|
|
|
34
34
|
adp[(adp.season == 2023) & (adp.adp <= 24)]
|
|
35
35
|
```
|
|
36
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
|
+
|
|
37
89
|
## Pinning to a specific version
|
|
38
90
|
|
|
39
91
|
Loaders resolve against the upstream GitHub repo. Pin to a commit SHA, tag,
|
|
@@ -57,16 +109,61 @@ cache directory or call `clear_cache()` to force a refresh.
|
|
|
57
109
|
|
|
58
110
|
## Available loaders
|
|
59
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
|
+
|
|
60
117
|
| Function | Returns | Shape |
|
|
61
118
|
|---|---|---|
|
|
62
119
|
| `load_career_predictions_2026()` | 2026 rookie predictions | ~77 × ~80 cols |
|
|
63
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_weekly_projections()` | Per-week matchup-adjusted projections, one row per player-game (opp, home, matchup mult, PPR pts; `df.attrs['def_vs_pos']`) | ~445 × 17 rows/player |
|
|
130
|
+
| `load_ppg_projections()` | Model-predicted PPG for established players | ~250 × 4 |
|
|
131
|
+
| `load_adp_value_model()` | VOR vs ADP, hit probability, confidence interval | ~153 × 10 |
|
|
132
|
+
| `load_volume_projections()` | Team pass/rush/target volumes with low/high bands | ~153 × ~14 |
|
|
133
|
+
| `load_share_projections()` | Predicted target + rush share | ~153 × 6 |
|
|
134
|
+
| `load_taxi_predictions()` | Taxi-squad roster/drop probabilities (+ `df.attrs['meta']`) | ~96 × 6 |
|
|
135
|
+
|
|
136
|
+
**Market & stats**
|
|
137
|
+
|
|
138
|
+
| Function | Returns | Shape |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `load_player_stats(season=None)` | Per-player per-week NFL box scores 2010-present | ~400k × ~50 |
|
|
141
|
+
| `load_dynasty_values()` | In-house blended dynasty value (1QB + Superflex) | ~500 × 10 |
|
|
142
|
+
| `load_dynasty_value_history()` | Blended daily dynasty value history | variable |
|
|
64
143
|
| `load_adp_historical()` | Model-training ADP 2010-2025 | 4507 × 10 |
|
|
65
144
|
| `load_adp_ffc(season=None)` | FFC PPR raw ADP (per season as fetched) — data via [Fantasy Football Calculator](https://fantasyfootballcalculator.com/adp/ppr) | variable |
|
|
66
|
-
|
|
145
|
+
|
|
146
|
+
**Identity & raw**
|
|
147
|
+
|
|
148
|
+
| Function | Returns | Shape |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `load_player_crosswalk()` | Canonical cross-source player IDs | ~10k × ~20 |
|
|
151
|
+
| `resolve_player(name, position=None)` / `get_player(key)` / `load_player_profile(key)` | Name → `player_key` helpers | — |
|
|
67
152
|
| `load_feature_matrix()` | Raw `feature-matrix.json` (dict) | — |
|
|
68
153
|
| `load_manual_overrides()` | Manual CFBD usage overrides (dict) | — |
|
|
69
154
|
|
|
155
|
+
Every row-shaped loader carries a `player_key` column that joins to
|
|
156
|
+
`load_player_crosswalk()`.
|
|
157
|
+
|
|
158
|
+
### Dynasty values are the app's in-house blend
|
|
159
|
+
|
|
160
|
+
`load_dynasty_values()` returns the same blended value the web app displays:
|
|
161
|
+
KTC rankings rescaled into FantasyCalc's scale via a per-player ratio
|
|
162
|
+
(`fc_value / ktc_value`, with a positional-median fallback below a value
|
|
163
|
+
floor). The ratio snapshot is built offline and committed as
|
|
164
|
+
`public/data/dynasty-fc-rescale.json`; the loader just applies it. No raw KTC
|
|
165
|
+
value is exposed — only the rescaled blend.
|
|
166
|
+
|
|
70
167
|
## Feature columns
|
|
71
168
|
|
|
72
169
|
Career-prediction and backtest rows include flattened model features
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "stathead"
|
|
7
|
-
version = "0.2
|
|
7
|
+
version = "0.3.2"
|
|
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,25 @@ 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
|
+
load_weekly_projections,
|
|
39
|
+
)
|
|
40
|
+
from .polars import load_polars, to_polars
|
|
29
41
|
from .prospects import load_prospect_grades
|
|
42
|
+
from .sql import list_tables, query, register
|
|
43
|
+
from .stats import load_player_stats
|
|
30
44
|
|
|
31
|
-
__version__ = "0.2
|
|
45
|
+
__version__ = "0.3.2"
|
|
32
46
|
|
|
33
47
|
__all__ = [
|
|
34
48
|
"__version__",
|
|
@@ -37,13 +51,29 @@ __all__ = [
|
|
|
37
51
|
"set_ref",
|
|
38
52
|
"load_adp_ffc",
|
|
39
53
|
"load_adp_historical",
|
|
54
|
+
"load_adp_value_model",
|
|
55
|
+
"load_career_2027",
|
|
40
56
|
"load_career_backtest",
|
|
41
57
|
"load_career_predictions_2026",
|
|
58
|
+
"load_dynasty_value_history",
|
|
59
|
+
"load_dynasty_values",
|
|
42
60
|
"load_feature_matrix",
|
|
43
61
|
"load_manual_overrides",
|
|
44
62
|
"load_player_crosswalk",
|
|
45
63
|
"load_player_profile",
|
|
64
|
+
"load_player_stats",
|
|
65
|
+
"load_ppg_projections",
|
|
46
66
|
"load_prospect_grades",
|
|
67
|
+
"load_redraft_projections",
|
|
68
|
+
"load_share_projections",
|
|
69
|
+
"load_taxi_predictions",
|
|
70
|
+
"load_volume_projections",
|
|
71
|
+
"load_weekly_projections",
|
|
47
72
|
"get_player",
|
|
48
73
|
"resolve_player",
|
|
74
|
+
"query",
|
|
75
|
+
"register",
|
|
76
|
+
"list_tables",
|
|
77
|
+
"to_polars",
|
|
78
|
+
"load_polars",
|
|
49
79
|
]
|
|
@@ -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
|
|