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.
@@ -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.1
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.1"
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.1"
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