Powerwise

Coach explainer

Why this works — a coach's explainer

Plain-English version of the model, with no math beyond what you'd write on a whiteboard. If something here doesn't square with how you think about the season, that's a flag worth raising.


The one-line idea

Start with where you finished. Adjust for who's leaving and who's coming back. Flag the cases where the data and the eye test disagree, and let the board talk those out.

That's the whole engine. Everything below is a defensible answer to how we adjust.


What this is and isn't

This is.

This isn't.


What we use as truth

We trust results. We do not trust raw stats to rank teams.

We do not rank a team higher because their goals total was big. The only way box stats move you is relative to your opponents, and only when the opponent was actually a tough matchup.


Why "where did you finish?" isn't enough

If we just carry forward last year's finish, two things happen and both are unfair:

  1. Rebuilds get punished. A program that had a senior-heavy top-5 team last year and graduates 8 starters is still scheduled like a top-5 team. Their younger kids get dragged through a brutal schedule, lose interest, and the program slides further.
  2. Legacy programs get a free pass. A multi-year power that returns very little is still treated as elite, gets a softer schedule than it deserves, and beats up on rebuilds.

Both are bad for the league. The model exists to adjust off the prior finish — not to throw it out.


How we measure team strength (three buckets)

For each team, every game produces three 0–100 numbers. We then average across the season, weighted so close games against good opponents matter more than blowouts against weak ones.

Bucket What it captures Coach intuition
Offense Goals per possession (not per game) Are we efficient when we have the ball?
Defense Shots faced + save % and ground balls + interceptions + caused turnovers Goalie play plus field pressure that ends possessions.
Possession Draw controls Do we win the draw and start with the ball?

We don't count goals allowed twice (so a great goalie isn't double-credited and a leaky D isn't double-penalized). Each bucket is calibrated to the actual Missouri distribution, so the top of the league lands in the 80s/90s instead of everyone pegging at 100.

The composite score is 0.35×offense + 0.35×defense + 0.30×possession. That number is a diagnostic — a quick read on a team's profile. It's not how we rank the league.


How we measure player impact

A player's "impact" is their share of team production, weighted by who they did it against.

This gives us per-player scores like offense_share_sos = 0.21 ("she generated 21% of our scoring against the schedule we played"). We can sum those up for the seniors graduating to get the share of team power that walks off the field.


How we project next year

Two layers, deliberately conservative:

Layer 1 — replacement-level subtraction

When a senior leaves, the next kid up the depth chart isn't zero — they're roughly a typical bench player at that position. So we subtract:

loss = max(senior's share − replacement baseline at her position, 0)

The replacement baselines come from real non-senior shares pooled across two seasons (run_meta.json → replacement_baselines).

Layer 2 — context multipliers

Then we tug that loss up or down for things scouts already think about:

Situation Direction
Senior held ≥ 32% of team's scoring (concentration risk) bigger loss
Strong returning juniors / sophomores (depth) smaller loss
Starting goalie leaves and backup played < 22% of minutes bigger defensive loss
Late-season underclass surge (last-third goal share) smaller loss
Departing assist-and-goal partner (feeder pair) bigger loss
Returning draw control share is high smaller possession loss
Departing All-State / All-American honors bigger loss
Returning All-State / All-American honors smaller loss
Multi-year legacy program (small composite floor) small protective bump

The award piece is tuned: All-Conference 1st = 2 points, State HM = 4, State 1st = 8, All-American = 16 (= 2× State 1st). Coaches' selections are an independent talent signal we don't get from box scores alone.


How we turn that into a finish rank

This is the part the board votes on, so we're explicit about it.

projected_rank = prior_finish + (capped move based on roster turnover)

There's a diagnostic column called composite_rank_diagnostic that re-sorts by box-score composite. We show it to the board, but it's not the vote line — re-sorting on box scores alone has more than 3× the rank error in our holdout test.


When the board should look harder

Every team in the recommendations CSV has a list of flags. If the flag list is empty, the model says "no surprises here, ship the proposed rank." If a team has flags, the board reads them aloud and decides:

Flag Plain English What to look at
large_move_vs_prior Model wants to move them ≥ 4 slots Confirm the roster turnover is real
low_returning_heavy_loss Lost a lot, returning is thin Underclass video; replacement candidates
high_returning_still_down Strong return but model still moves down Is the assist/feeder structure intact?
legacy_downgrade Long-term contender, model says down Coach interview, off-season program signals
award_talent_loss Multiple All-State / All-American departures Real talent loss vs. quality returning class
roster_composite_split Box-score composite disagrees with roster move Diagnostic only — usually composite is wrong
backtest_large_error (Backtest only) the model missed by ≥ 5 last year Worth a future audit

A "high priority" flag count of 1 doesn't mean the model is wrong; it means somebody at the table probably knows something the data doesn't, and it's worth asking before voting.

The board can also write program_notes and board_voted_rank into a CSV, and those overrides flow back into the final report.


How we know the model is doing something useful

We hold out a season we already know the answer to (2024-25 stats + 2025 grads → predict 2026 finish, where we know finish_2026). The numbers are in output/backtest_2024_25/backtest_report.md:

Predictor All teams Top-20 finish
"Same as last year" (naive) MAE 2.7 MAE 2.5
Roster-adjusted board line MAE 3.1 MAE 1.9
Re-sorted by box-score composite MAE 6.7 MAE 4.9

Reading: in the top half of the league — the part scheduling and league balance care about — the board line is meaningfully better than naive, and the box-score composite is meaningfully worse. That's why the model exists, and why we don't trust composite re-sort.

For the bottom of the league, the model and "same as last year" are roughly tied. That's fine; movement at the bottom is mostly noise.


What we don't claim


How to read a team's row in ranking_recommendations_*.csv

Take MICDS as a worked example (current 2027 forward outlook):

Column Value What it tells you
prior_finish_rank 4 Where they finished 2026
recommended_rank 2 First-pass roster model proposal
recommended_rank_adjusted 2 After secondary regressionboard vote line
composite_rank_diagnostic 1 Box-score re-sort says #1 (diagnostic only)
score_drop 4.86 Composite points lost to graduation
returning_production_share 0.774 ~77% of weighted production stays
legacy_prior 1.00 Top-tier multi-year program
departing_awards_points 13 Several big honors graduated
returning_awards_points 21 Strong honors returning
flag_codes (mostly empty) No required action
final_rank 2 Board vote line (after manual overrides if any)

So: high legacy, returning class is loaded with award winners, model carries them up one slot from prior finish. No flags, but composite_rank_diagnostic = 1 is a "hey, the box scores think they're #1" footnote — likely an MICDS top-5 group that the prior #1 (Burroughs) lost more from.


Process at a glance

1. Pull every game and stat for the season (already done)
2. Pull the awards from the STL site
3. Run the pipeline:  python3 run_glax_analysis.py --season both
4. Look at output/ranking_board_report_2027.md  ←  this is the board packet
5. For each "Action required" flagged team, the board talks it out
6. Edit board_overrides_2027.csv with notes / votes
7. Re-run the pipeline; final_rank reflects the votes; ship the report

If a team's projection feels off, it almost always shows up in the flags or in secondary_adjustment_reasons. Open the CSV and the answer is in plain English.


What you should push back on

The model is meant to be argued with. Things worth challenging out loud:

The validation guide in docs/VALIDATION.md walks through every CSV column and the math behind each adjustment. If something still feels off after reading both, that's a finding worth fixing — open a ticket.