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.
@@ -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.2.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
- | `load_prospect_grades(year=2026)` | Draft scouting grades | ~200 × 7 |
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 scout text is shipped. `hasScoutGrade` / `hasGuideData`
113
- flag missing-data so models can distinguish "no scout coverage" from
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 publications) are intentionally not exposed by
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
@@ -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.2.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.2.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
- _DEFAULT_REF = "main"
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