Project context
GLAX pipeline — project context & roadmap
Results-anchored, stat-informed rankings for Missouri girls HS lacrosse. Complements powerwise_lax (published Powerwise pairwise method); see docs/ALGORITHMS.md. Connects team strength, individual production, and next-season / scheduling risk.
Keep this document updated as tasks complete. Domain math lives in composite_score.py; data lives in source-data/; runnable pipeline is run_glax_analysis.py.
Why we are building this
Problem
-
Team strength is often summarized by win–loss and ranking systems (Powerwise, RPI, etc.), but coaches and ADs also care about how a team wins (offense, defense, possession) and whether margins are sustainable.
-
Graduation and roster turnover change competitive balance. A team ranked 16th can look very different in August if its top three scorers and starting goalie graduate. We need a structured way to estimate how much team power walks off the field, not just “they were 16th last year.”
-
Scheduling should reduce predictable blowouts where possible. That requires an estimate of expected score delta between two teams after roster change—not only last year’s final rank.
What success looks like
| Audience | Question we answer |
|---|---|
| Coach / AD | If our seniors leave, how much does our team power drop, and where might we land in the league? |
| League / scheduler | Which matchups project to large margins given projected 2027 strengths? |
| Analysis | Are player stats meaningful (earned vs strong opponents) or padded vs weak schedules? |
Design principles (do not break these)
-
Anchor strength on game results only — finish tiers (1–4, 5–8, 9–16, 17–32) from
finish_2026.tsv/finish_2025.tsv. League rank and schedule margins do not use composite (Phases A–B, 2026). Box-score domains remain for player impact and board columns only. Seedocs/PHASE_C_ROADMAP.mdfor retiring composite from coach exports. -
Two separate metrics - Team power — three domains (offense, defense, possession). - Player impact on team power — SOS-weighted share of production + defense (goalies).
-
Context weighting — stats vs weak opponents count less (tier SOS × game leverage).
-
Subtract → adjust rank off prior — graduation uses departure loss % (not composite drop) with capped moves vs prior finish.
-
Scheduling — projected margin ratings (results-only) → expected margin & blowout-risk matrix.
-
Board ranking packet — default recommended rank = roster-adjusted outlook; teams flagged when metrics conflict. Flagged cases get secondary regression (surge, assists, manual notes) before the board votes. See
ranking_board_report_*.md.
Run
cd glax-pipeline
python3 fetch_awards.py 2026 # optional: refresh awards_2026.csv
python3 run_glax_analysis.py # 2025-26 → output/
python3 run_glax_analysis.py --season both # + backtest → output/backtest_2024_25/
python3 tune_roster_predictor.py # goal-seek board-line MAE (holdout)
python3 tune_possession_weights.py # draw / GB / CTO weight grid
Model overview
finish_2026.tsv / finish_2025.tsv → tier SOS weights
↓
team-game stats + results → team power (calibrated possession baseline)
↓
player-game stats → SOS-weighted impact → subtract seniors → T′
↓
league re-rank + schedule_risk_matrix (expected margin, blowout flag)
Possession baseline: league p90 weighted events/game (~42 for 2025–26), not 10.
Tier SOS: 1.0 / 0.80 / 0.55 / 0.30 for finish tiers 1–4.
Outputs
| File | Description |
|---|---|
output/team_strength_2025_26.csv |
Coach strength index (margin, RPI, ranks, turnover, domains) |
output/team_power_2025_26.csv |
Domain stats + margin (composite optional via CLI flag) |
output/player_impact_2025_26.csv |
Raw + SOS shares, power points per player |
output/projected_power_2027.csv |
Post–2026 grad projection |
output/league_rerank_2027.csv |
League order before/after grad losses |
output/ranking_recommendations_2027.csv |
Board packet: recommended rank + defensible metrics (all teams) |
output/ranking_outliers_2027.csv |
Flagged teams only — secondary regression / manual review |
output/ranking_board_report_2027.md |
Executive brief: methodology, flag rules, flagged table |
output/graduation_player_loss_2025_26.csv |
Per-departing-player loss shares |
output/board_overrides_2027.csv |
Template for board voted rank + notes |
output/schedule_risk_matrix_2027.csv |
Pairwise expected margin (projected T′) |
output/sanity_report_2025_26.md |
Composite vs finish vs Powerwise |
output/backtest_2024_25/ |
Same artifacts for 2024–25 season |
output/backtest_2024_25/backtest_report.md |
Projected 2025–26 vs actual finish_2026 + #0 naive benchmark |
output/backtest_2024_25/backtest_benchmark_by_tier.csv |
MAE / ±3 by finish tier (1–4) per predictor |
output/backtest_2024_25/backtest_team_rank_errors.csv |
Per-team rank errors (prior, roster, adjusted, composite) |
output/benchmark_2027.md |
Forward: model proj rank vs prior-finish carry-forward |
Task tracker
Phase 1 — Foundation
- [x] Wire GLAX CSVs (
glax_data.py,run_glax_analysis.py) - [x] Fix domain aggregation ×100 bug
- [x]
PROJECT.md+finish_2026.tsv+finish_2025.tsv
Phase 2 — Anchor & calibration
- [x] Finish tier map +
anchor.pytier SOS - [x] Possession baseline from league median
- [x] SOS × leverage on player-game rows
Phase 3 — Two metrics
- [x]
team_power_2025_26.csv - [x]
player_impact_2025_26.csv(raw + SOS + power points) - [x] Goalie/defense slice in player impact & graduation
Phase 4 — League projection & scheduling
- [x]
projected_power_2027.csv - [x]
league_rerank_2027.csv - [x] Margin coefficient
kinrun_meta.json - [x]
schedule_risk_matrix_2027.csv
Phase 5 — Polish
- [x]
sanity_report_2025_26.md - [x] README updated
- [x] RPI+AGD from results (
results_anchor.py,--anchor finish|rpi_agd|blend)
Phase 6 — Backtest
- [x] Full 2024–25 data wired
- [x]
output/backtest_2024_25/pipeline run - [x]
backtest_report.mdvsfinish_2026.tsv - [x] Graduation: default damping=1.0 + domain caps (ablation MAE 6.8) +
ablation.py - [x]
tests/test_anchor.py— Francis Howell + defense regression tests - [x]
composite_score.py+ScoringConfig(no module-global mutation) - [x] Position-aware player impact + goalie minutes weighting
Code map
| Module | Role |
|---|---|
anchor.py |
Load finish TSV, tier SOS, name resolution |
glax_data.py |
Season config, loaders, player impact, graduation |
analysis.py |
Full pipeline, sanity + backtest reports |
ranking_outliers.py |
Board recommendations, outlier flags, regression tracks |
secondary_regression.py |
Flagged-team adjustments → recommended_rank_adjusted |
roster_stats.py |
Late surge, assists/feeders, draw % for graduation |
board_override.py |
Optional voted rank + program notes CSV |
game_margin_stats.py |
MO-vs-MO margin profiles (top-16, consistency) |
results_anchor.py |
RPI+AGD, rolling snapshots, dated results loader |
awards.py / awards_{year}.csv |
STL awards ingest, roster match, graduation + board rollups |
fetch_awards.py |
Download season-results-{year}.json → CSV |
run_glax_analysis.py |
CLI |
composite_score.py |
Domain scoring engine |
Related files
docs/archive/— legacy notes (sample_code_legacy.py, design scratch)../README.md— Powerwise repobenchmark.py— prior-finish naive baseline + MAE cohortsdocs/VALIDATION.md— process & statistics audit guidedocs/COACH_EXPLAINER.md— coach-friendly explainer of the approachdocs/presentations/— slide-deck prompts (coach onboarding, MAE slide, appendix glossary, conference realignment)
Prioritized roadmap (product order)
Work in this sequence. #0 is mandatory on every evaluation before claiming the model adds value.
#0 — Prior-year finish benchmark (default expectation)
- [x] Implemented:
benchmark.py;backtest_report.mdcompares Prior finish (2025) vs Graduation model with MAE for all / top-8 / top-16 / top-20;output/benchmark_2027.mdfor forward outlook vs naive carry-forward. - Definition: Next-year rank = last year’s
finish_*.tsvrank (what schedulers and coaches already use). - Why: Unfair to rebuild programs (strong senior class leaves → still scheduled as elite) and too easy for legacy programs (long-term contenders keep soft schedules on reputation). The model must beat or justify this baseline.
- Primary metric: Top-20 finish MAE (scheduling and league balance focus).
- Roster-adjusted model (prior + capped turnover) beats naive on top-20 / top-16 / top-8 in backtest; all-teams MAE still slightly behind naive (~3.1 vs 2.7).
#1 — Missouri-only league set (drop out-of-state from ranking)
- [x] League pool =
finish_*.tsvteams —team_power,player_impact, graduation,league_rerank, andschedule_risk_matrixare MO-only (e.g. Wentzville and all IL/KY/TN opponents excluded from ranks). - [x] OOS games excluded from ranking stats: player/team game rows and results used for composite, SOS/RPI, and leverage only when both teams are in
finish.tsv(in-state league). OOS exhibitions are dropped (often not played to win / incomplete rosters). - [x]
run_meta.jsonlistsexcluded_from_league_rankingandnon_league_opponents_in_results; marginkfit on MO vs MO games only. - Why: Rankings and blowout-risk are for the MO girls D1 scheduling pool, not national opponents.
#2 — Graduation, roster turnover & legacy team score (primary predictor path)
Rank outlook = prior finish + capped roster move, not full composite re-sort. Box-score domain weights and composite sort amplify noise; turnover + returning production + legacy explain adjustments off last year.
- [x] Roster-adjusted finish rank:
roster_rank_predictor.py;league_rerank_*.csvusesprojected_league_rank;projected_league_rank_compositekept for comparison. - [x] Returning production share (offense / possession / defense) in graduation output + projected CSV.
- [x] Replacement-level subtraction:
max(share − baseline[position], 0); baselines calibrated from 2024-25 + 2025-26 non-senior SOS shares (roster_projection.py,get_replacement_baselines()). - [x] Concentration risk: extra offense loss when a departing senior held ≥32% SOS goals.
- [x] Class-year depth: junior/soph SOS goal share reduces projected senior loss (up to ~30%).
- [x] Goalie continuity: boost defense loss when starter goalie leaves with <22% backup minutes.
- [x] Legacy program prior: multi-year
finish_*.tsv→legacy_program_priors.csv+ small composite nudge on projection (LEGACY_COMPOSITE_WEIGHT=0.12). - [x] Board outlier flags:
ranking_outliers.py→ recommendations CSV, outliers CSV,ranking_board_report_*.md(automated each pipeline run). - [x] Secondary regression on flagged teams:
secondary_regression.py→recommended_rank_adjusted(late surge, legacy pull, feeders, draw %). - [x] Manual program notes / board override:
board_overrides_{label}.csv→program_notes,board_voted_rank,final_rank. - [x] Late-season underclass surge:
roster_stats.pyin graduation + secondary (last-third goal share). - [x] Assist rate / feeder–scorer:
team_assist_rate,feeder_pair_departing, offense concentration. - [x] Draw control %:
returning_draw_pct/departing_draw_pct+ possession loss relief. - [x] Per-player departure loss:
graduation_player_loss_{season}.csv.
#3 — SOS accuracy for top-20 teams
- [x] Smooth SOS:
smooth_finish_sos_weight(log-scaled rank → [0.30, 1.00]); default--anchor finish. Use--anchor finish_tierfor legacy cliffs. - [x] Top-20 matchup boost: +8% context weight when both teams are finish rank ≤ 20 (
TOP20_SOS_BOOST). - [x] Metro-east IL in league pool: Belleville West, Edwardsville, O'Fallon in
finish_*.tsv(games count toward ranking). - [x] RPI fallback: SOS opponent rank from RPI strength for teams not in
finish.tsv(resolve_opponent_sos_rank); RPI pool = all results games (incl. OOS exhibitions). - [x] Rolling in-season RPI:
rolling_rpi_{season}.csv— snapshot after each date using games before that date. - [x] Game-rooted margin stats:
game_margin_stats.py→game_margin_stats_{season}.csv+ columns onteam_power_*/ board packet (avg_margin_vs_top16,margin_std, etc.). - [x] Margin k vs top-16:
margin_k_top16inrun_meta.json(schedule calibration on elite matchups only). - [x] Tier-conditioned MAE:
backtest_benchmark_by_tier.csv,backtest_team_rank_errors.csv, tier table inbacktest_report.md.
#4 — Offense / defense recalibration (after MO-only + top-20 SOS focus)
- [x] Percentile calibration per season (
domain_calibration.py); anchors inrun_meta.json. - [x] Soft squash offense / possession / defense (p50→~55, p90→~92, soft tail to 100).
- [x] Defense: shot volume + save % separately (45/55), MO
sfp20–p80 anchors. - [x] Skip
stat_type=combinedrows in team game aggregation. - [x] Domain weight sensitivity — not useful (top-20 MAE ~4.9–5.0 vs 5.0 default); high box-score variance. Deprioritized.
- [x] Roster-adjusted rank (
roster_rank_predictor.py): prior finish + capped turnover (returning %, legacy); composite re-sort is diagnostic only.
#5 — Player awards (STL Lacrosse Awards)
Independent recognition signal for individual talent (not team finish). Source: STL Lacrosse Awards — Results (2026 season is a static snapshot on that site; filter by conference, school, position).
| Level | What it is | Model use (planned) |
|---|---|---|
| All Conference | Committee selections by conference and team level | Depth / returning quality; conference strength context |
| All State | Official 1st Team + Honorable Mention (statewide) | Stronger individual prior; departing All-State → board flag |
| All American | USA Lacrosse area awards (coach ballots) | Highest tier; graduation loss weight + legacy program signal |
- [x] Ingest awards per season —
fetch_awards.py+awards_{year}.csvfromhttps://stl-lacrosse-awards.fly.dev/data/season-results-{year}.json(2026 live; prior years when published). - [x] Match to GLAX roster (
canonical_team_name+normalize_player_name+ last-name fallback). - [x] Team rollup:
awards_points,departing_awards_points,returning_awards_pointsonprojected_power_*.csvand board CSVs. - [x] Graduation: departing award boost + returning award relief on domain losses (
awards.py). - [x] Board packet:
departing_awards,returning_awards, flagaward_talent_loss(≥5 departing points). - [x] Backtest:
backtest_award_loss_analysis.csv+ section inbacktest_report.md(whenawards_{date_year}.csvexists).
Award tier weights (tuned on 2025-26 holdout → finish_2026): see awards_weights.TUNED_AWARD_WEIGHTS and python3 awards_tune.py.
| Honor | Points |
|---|---|
| All-Conference 1st | 2 |
| All-Conference 2nd / HM | 1 |
| Conference POY | 3 |
| All-State HM | 4 |
| All-State 1st | 8 |
| All-American | 16 (2× state 1st) |
Board flag award_talent_loss when departing points ≥ 10. Tune grid: output/awards_tune_results.csv.
Follow-up backlog (Opus review + analysis session)
Done since Opus review
- P0 canonicalization, defense GA=0, results-joined goals allowed
- p90 possession baseline, margin
k, ablation,ScoringConfig - Position + goalie minutes;
(#)player name strip; damping=1.0 + caps
Still open (reference)
| Item | Priority |
|---|---|
| Replacement-level graduation | #2 |
| Legacy / multi-year program prior | #2 |
| Out-of-state handling | #1 |
| Smooth / continuous SOS | #3 |
| Offense/defense recalibration | #4 |
| Bootstrap rank confidence intervals | P1 |
| Naive baseline on ablation grid | P1 (backtest has it) |
| HFA on schedule margins | P2 |
| Per-player % loss | Done → graduation_player_loss_*.csv |
stat_type=combined filter |
Done |
| Player awards (All Conference / State / American) | #5 — results |
| Tier-conditioned MAE CSV | Done (#3) |
| Bootstrap rank confidence intervals | P1 |
Game-rooted predictors (for #2–#4)
- [x] Capped margin vs top-16 (
avg_margin_vs_top16,margin_k_top16) - [x] Margin consistency (
margin_std,margin_std_vs_top16) - [ ] Goals per possession (efficiency diagnostic)
- [ ] SOS-weighted GA vs elite opponents only
- [ ] H2H nudge within tight composite clusters (optional, results-only)