stathead 0.3.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.1 → stathead-0.3.2}/.gitignore +7 -0
- {stathead-0.3.1 → stathead-0.3.2}/PKG-INFO +2 -1
- {stathead-0.3.1 → stathead-0.3.2}/README.md +1 -0
- {stathead-0.3.1 → stathead-0.3.2}/pyproject.toml +1 -1
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/__init__.py +3 -1
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/projections.py +57 -0
- {stathead-0.3.1 → stathead-0.3.2}/tests/test_smoke.py +14 -0
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/_fetch.py +0 -0
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/_renames.py +0 -0
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/adp.py +0 -0
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/crosswalk.py +0 -0
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/dynasty.py +0 -0
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/features.py +0 -0
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/polars.py +0 -0
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/predictions.py +0 -0
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/prospects.py +0 -0
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/sql.py +0 -0
- {stathead-0.3.1 → stathead-0.3.2}/src/stathead/stats.py +0 -0
|
@@ -41,6 +41,7 @@ public/data/clay-team-projections-*.json
|
|
|
41
41
|
!public/data/ktc-forecasts-*.json
|
|
42
42
|
!public/data/redraft-projections.json
|
|
43
43
|
!public/data/projection-base-*.json
|
|
44
|
+
!public/data/weekly-projections-*.json
|
|
44
45
|
!public/data/model-eval-*.json
|
|
45
46
|
!public/data/team-metrics-*.json
|
|
46
47
|
!public/data/coach-tendencies.json
|
|
@@ -106,6 +107,12 @@ __pycache__/
|
|
|
106
107
|
.venv/
|
|
107
108
|
pdfs/
|
|
108
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
|
+
|
|
109
116
|
# Proprietary inputs (Clay PDFs + parsed dataset) — never committed.
|
|
110
117
|
/private/
|
|
111
118
|
# Local-only Clay projection set consumed at runtime by the "Consensus" preset.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stathead
|
|
3
|
-
Version: 0.3.
|
|
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
|
|
@@ -154,6 +154,7 @@ available here as a pandas DataFrame.
|
|
|
154
154
|
| Function | Returns | Shape |
|
|
155
155
|
|---|---|---|
|
|
156
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 |
|
|
157
158
|
| `load_ppg_projections()` | Model-predicted PPG for established players | ~250 × 4 |
|
|
158
159
|
| `load_adp_value_model()` | VOR vs ADP, hit probability, confidence interval | ~153 × 10 |
|
|
159
160
|
| `load_volume_projections()` | Team pass/rush/target volumes with low/high bands | ~153 × ~14 |
|
|
@@ -126,6 +126,7 @@ available here as a pandas DataFrame.
|
|
|
126
126
|
| Function | Returns | Shape |
|
|
127
127
|
|---|---|---|
|
|
128
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 |
|
|
129
130
|
| `load_ppg_projections()` | Model-predicted PPG for established players | ~250 × 4 |
|
|
130
131
|
| `load_adp_value_model()` | VOR vs ADP, hit probability, confidence interval | ~153 × 10 |
|
|
131
132
|
| `load_volume_projections()` | Team pass/rush/target volumes with low/high bands | ~153 × ~14 |
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "stathead"
|
|
7
|
-
version = "0.3.
|
|
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" }
|
|
@@ -35,13 +35,14 @@ from .projections import (
|
|
|
35
35
|
load_share_projections,
|
|
36
36
|
load_taxi_predictions,
|
|
37
37
|
load_volume_projections,
|
|
38
|
+
load_weekly_projections,
|
|
38
39
|
)
|
|
39
40
|
from .polars import load_polars, to_polars
|
|
40
41
|
from .prospects import load_prospect_grades
|
|
41
42
|
from .sql import list_tables, query, register
|
|
42
43
|
from .stats import load_player_stats
|
|
43
44
|
|
|
44
|
-
__version__ = "0.3.
|
|
45
|
+
__version__ = "0.3.2"
|
|
45
46
|
|
|
46
47
|
__all__ = [
|
|
47
48
|
"__version__",
|
|
@@ -67,6 +68,7 @@ __all__ = [
|
|
|
67
68
|
"load_share_projections",
|
|
68
69
|
"load_taxi_predictions",
|
|
69
70
|
"load_volume_projections",
|
|
71
|
+
"load_weekly_projections",
|
|
70
72
|
"get_player",
|
|
71
73
|
"resolve_player",
|
|
72
74
|
"query",
|
|
@@ -45,6 +45,63 @@ def load_redraft_projections() -> pd.DataFrame:
|
|
|
45
45
|
return _stamp_keys(df)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
def load_weekly_projections() -> pd.DataFrame:
|
|
49
|
+
"""Per-week 2026 projections — the season projection split across the
|
|
50
|
+
schedule, one row per player per scheduled game (17 rows/player; byes
|
|
51
|
+
omitted).
|
|
52
|
+
|
|
53
|
+
Weekly points = season PPG x opponent defense-vs-position multiplier
|
|
54
|
+
(prior-season PPR allowed per game vs league average, heavily regressed)
|
|
55
|
+
x home/away nudge, normalized per team so the 17 games sum back to the
|
|
56
|
+
season line. Points assume the player plays; ``gp`` carries the season
|
|
57
|
+
games (health) discount. Half/Std conversion: weekly receptions scale
|
|
58
|
+
with the same multiplier, so ``rec_w = recPG * proj_ppr / ppg``.
|
|
59
|
+
|
|
60
|
+
Columns: ``player_key``, ``name``, ``position``, ``team``, ``week``,
|
|
61
|
+
``opp``, ``home``, ``matchup_mult``, ``proj_ppr``, ``ppg``, ``recPG``,
|
|
62
|
+
``gp``, ``season``.
|
|
63
|
+
|
|
64
|
+
Metadata on ``df.attrs``: ``meta`` (generatedAt + method note) and
|
|
65
|
+
``def_vs_pos`` (per-team defense-vs-position multiplier table).
|
|
66
|
+
"""
|
|
67
|
+
data = fetch_json("public/data/weekly-projections-2026.json")
|
|
68
|
+
team_weeks = {
|
|
69
|
+
team: {g["w"]: g for g in games}
|
|
70
|
+
for team, games in (data.get("teamWeeks") or {}).items()
|
|
71
|
+
}
|
|
72
|
+
def_vs_pos = data.get("defVsPos") or {}
|
|
73
|
+
rows = []
|
|
74
|
+
for p in data.get("players") or []:
|
|
75
|
+
sched = team_weeks.get(p["team"], {})
|
|
76
|
+
for i, pts in enumerate(p["wk"]):
|
|
77
|
+
week = i + 1
|
|
78
|
+
game = sched.get(week)
|
|
79
|
+
if pts is None or game is None:
|
|
80
|
+
continue
|
|
81
|
+
rows.append({
|
|
82
|
+
"name": p["name"],
|
|
83
|
+
"position": p["pos"],
|
|
84
|
+
"team": p["team"],
|
|
85
|
+
"week": week,
|
|
86
|
+
"opp": game["opp"],
|
|
87
|
+
"home": game["home"],
|
|
88
|
+
"matchup_mult": def_vs_pos.get(game["opp"], {}).get(p["pos"]),
|
|
89
|
+
"proj_ppr": pts,
|
|
90
|
+
"ppg": p["ppg"],
|
|
91
|
+
"recPG": p["recPG"],
|
|
92
|
+
"gp": p["gp"],
|
|
93
|
+
})
|
|
94
|
+
df = pd.DataFrame(rows)
|
|
95
|
+
df["season"] = data.get("season")
|
|
96
|
+
df = _stamp_keys(df)
|
|
97
|
+
df.attrs["meta"] = {
|
|
98
|
+
"generatedAt": data.get("generatedAt"),
|
|
99
|
+
"note": data.get("note"),
|
|
100
|
+
}
|
|
101
|
+
df.attrs["def_vs_pos"] = def_vs_pos
|
|
102
|
+
return df
|
|
103
|
+
|
|
104
|
+
|
|
48
105
|
def load_ppg_projections() -> pd.DataFrame:
|
|
49
106
|
"""Model-predicted points-per-game for established players.
|
|
50
107
|
|
|
@@ -131,6 +131,20 @@ def test_redraft_projections():
|
|
|
131
131
|
assert {"player_key", "name", "position", "ppg", "recPG"}.issubset(df.columns)
|
|
132
132
|
|
|
133
133
|
|
|
134
|
+
def test_weekly_projections():
|
|
135
|
+
df = stathead.load_weekly_projections()
|
|
136
|
+
assert not df.empty
|
|
137
|
+
assert {"player_key", "name", "position", "team", "week", "opp", "home",
|
|
138
|
+
"matchup_mult", "proj_ppr", "ppg", "gp"}.issubset(df.columns)
|
|
139
|
+
# 17 scheduled games per player (byes omitted), weeks within 1-18.
|
|
140
|
+
assert (df.groupby("name")["week"].count() == 17).all()
|
|
141
|
+
assert df["week"].between(1, 18).all()
|
|
142
|
+
# Normalization: weekly points sum back to ppg * 17 for every player.
|
|
143
|
+
sums = df.groupby("name").agg(total=("proj_ppr", "sum"), ppg=("ppg", "first"))
|
|
144
|
+
assert ((sums["total"] - sums["ppg"] * 17).abs() < 1.0).all()
|
|
145
|
+
assert isinstance(df.attrs.get("def_vs_pos"), dict)
|
|
146
|
+
|
|
147
|
+
|
|
134
148
|
def test_ppg_and_adp_value_model():
|
|
135
149
|
ppg = stathead.load_ppg_projections()
|
|
136
150
|
adp = stathead.load_adp_value_model()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|