THE BENCH

Coaching Operations Center
Projections
point-in-time · no future data
Loading season data...
Drag the playhead (or range handles in Season) · ◀ ▶ step · Space to play · Click panels to drill down

How this dashboard works

Formulas · Caveats · Honesty lines

Design principles

Every signal on this dashboard is built to answer the same coach's question: what is my team actually doing well or badly, separated from what the score says? The score conflates process, luck, and finishing. These metrics try to keep them apart.

Two rules the whole tool follows:

  • Impact and hotness are separate. A team or player can be good and cold, or mediocre and hot. Merging them recreates the score trap.
  • Labels tell the truth about what's underneath. When the data doesn't support "impact," the label says "scoring output" or "reference — not rated" instead. Different leagues get different metrics if their data cuts differently — we don't force one number when the data means two things.

Time windows

Two modes control what "recent" means:

  • Game mode — a point-in-time view after game N. The primary window (L5, L10, or L20) is a rolling average of the last N games ending at the playhead.
  • Season mode — the two handles on the scrubber define a range. All stats are aggregated over that range, no rolling window.

The playhead drags left/right and shows the state of the team as of that game. There is no future data — everything is what you would have known at the time.

Situational hero (top strip)

Four level-1 signals, always shown for the primary window:

Chance Control

xGΔ/g = avg(xGF − xGA) over window

Positive = out-chancing opponents. States: OUT-CHANCING (≥ +0.3), EVEN, OUT-CHANCED (≤ −0.3).

Finishing

Finishing luck = avg(GF − xGF) per game

Positive = scoring above expected (regression risk). States: RUNNING HOT (≥ +0.4), ON EXPECTED, SNAKEBITTEN (≤ −0.4).

Honesty: "Running hot" is a warning, not a compliment. Shooting % above expected is unsustainable — the goals slow down, not the process.

Goaltending

GSAx = avg(xGA − GA) per game

Positive = goalies saving more than shot quality warranted. States: STEALING GAMES (≥ +0.5), STEADY, LEAKING (≤ −0.5).

Trend

Trend = L5 xGΔ − L20 xGΔ

States: RISING (≥ +0.25), HOLDING, SLIDING (≤ −0.25). Early signal that the underlying play is shifting.

Signal panels

Chance Creation

both leagues

xGΔ/g = avg(xGF − xGA)

Same as the hero metric, with a full read + sparkline. The panel that most predicts future scoreline — if you're winning on chances, points follow.

Finishing

both leagues

Finishing = avg(GF − xGF)

High = regression risk. Low = due positive. Don't confuse hot finishing with skill.

Goaltending

both leagues

GSAx = avg(xGA − GA)

Isolates the goalie's contribution from the defense in front of them. Positive means the crease is helping.

Honesty: Approximate — assumes the shots faced were of average quality for the game. Slightly misranks a goalie who faced unusually easy or hard shot distributions.

Season Points Pace

both leagues

pts/g = total points / games played

Compared to the live playoff cut — the current pts/g of the team occupying the last playoff spot in the league as of the playhead date.

LeaguePlayoff rankHow the cut is computed
NHLTop 16 of 32Team currently at #16 in the standings, using only games played on or before the playhead date. No future data.
LiigaTop 10 of 15Team currently at #10 in the standings, using only games played on or before the playhead date. No future data.

If no standings snapshot is available for the current date (e.g. very early in the season), the historical fallback is used:

  • NHL fallback: 1.11 pts/g (2024-25 UTA at #16)
  • Liiga fallback: 1.35 pts/g (2024-25 Sport at #10)
Honesty: Early in the season, the live cut is noisy — a couple of leaders skew things. It settles toward historical values as more games are played. Teams with under 3 GP are excluded from the ranking to reduce the noise. Read the cut as "the current bar to clear," not as a prediction.

Power Play

LiigaNHL

Liiga: PP% = PPG / opportunities × 100

Baseline: ~20% (Liiga league average). Flag thresholds: above 22% "above avg", below 15% "below avg".

NHL: PP xG/g = avg(PP xG for) over window

Computed from nhl_shots where strength_state ∈ {5v4, 5v3, 4v3} and the shooting team is us. Baseline: 0.4 xG/g (rough NHL norm).

Honesty: NHL PP is chance-based, not efficiency-based — opportunities (PP count) isn't cleanly available. PIM ÷ 2 overcounts because of majors, misconducts, and coincidental penalties, so we don't report PP%. We report the honest question: "is the PP creating chances?"
Small samples: PP% needs ~30+ opportunities before it stabilizes. Same regression discipline as finishing luck — a hot PP will cool.

Penalty Kill

LiigaNHL

Liiga: PK% = (1 − PPGA / PKs) × 100

Baseline: ~80% (Liiga league norm).

NHL: PK xGA/g = avg(PP xG against) over window

Same shot filter as PP but the shooting team is the opponent. Baseline: 0.5 xGA/g.

Honesty: "Bleeding chances" and "leaking goals" are different problems. NHL panel catches the former; Liiga panel catches the latter. Read them for what they are.

Alert feed

Ranked crit → watch → info → ok. Fired only when the metric crosses honest thresholds and the sample supports it. Common triggers:

  • Goaltending down to ≤ −0.5 GSAx/g — the crease is where the points are going
  • Being out-chanced ≤ −0.3 xGΔ/g — the process is losing before puck luck enters
  • Finishing ≥ +0.4 above expected — regression warning, don't over-read the wins
  • Sliding trend — L5 well below L20 xGΔ, early downturn signal
  • PP / PK — league-specific, sample-gated (Liiga: ≥20 opps/kills; NHL: ≥10 GP)
  • Provisional sample — under 10 GP, signals are directional

Roster (left column)

Per-position, per-league metric families. Rate what the data can rate; refuse to rank what it can't.

Goalies

both leagues

GSAx/game = (team_xGA × goalie_seconds/3600) − GA

Impact ranking. Hotness = z-score of last 5 appearances vs season baseline. Gate: ≥5 appearances and ≥150 minutes.

Honesty: GSAx approximates shots faced as average game quality — it isolates the goalie from team defense better than save% or wins, but it's an estimate, not shot-by-shot.

Forwards

NHL

Impact: ixG/game = sum(per-shot xG) / games

Hotness: z-score of (goals − ixG) over L5 vs season baseline

Impact ranking uses individual xG (chance generation). Hotness uses finishing luck — separately, because they're different questions. Gate: ≥20 shot attempts.

Honesty: Ranks chance generation, not two-way play or defense. Without ice-time or on-ice data, a forward's defensive value is invisible here. Finishing luck flags likely regression — ▲▲ "running hot" is a warning, not a compliment.

Forwards

Liiga

Scoring output: pts/game = (G + A) / games

Hotness: z-score of pts over L5 vs season baseline

Label is "scoring output," not "impact" — the data doesn't support impact claims. Gate: ≥10 games.

Honesty: Ranks raw scoring only. No xG or ice-time available — this does not measure chance quality, defense, or two-way play, and undersells defensive forwards. A hot streak here may be finishing luck the data can't separate.

Defencemen

both leagues

Not ranked. Shown for reference only, in a muted row with G/A/±.

Honesty: Their primary job — suppressing chances against — is invisible without on-ice data. Points miss ~80% of what a defenceman does. +/− is team-context noise (a good D on a bad team looks terrible). Ranking on these would mislead. A coach knows their D better than +/− does.

Hotness (z-score) semantics

Hotness ≠ impact. It's how far the player is from their own recent baseline, not from the league. Formula:

z = (mean_of_last_5_appearances − season_mean) / season_stdev

Requires ≥3 recent appearances and a baseline std > 0. Below thresholds → "provisional."

▲▲ +1.5σ strong hot streak ▲ +0.5σ above baseline — steady within ±0.5σ ▼ −0.5σ below baseline ▼▼ −1.5σ cold streak

Colors differ by position:

  • Goalies — green (hot = stealing games) / cold (leaking)
  • NHL forwardsamber for +z (regression risk, not "good") / cold-blue for −z (due positive). The hotness metric is finishing luck.
  • Liiga forwards — neutral hot/cold (metric is points, no finishing-luck separation available)

Scrubber timeline (bottom)

Every game as a bar. Color = combined result and process:

Won & out-chanced opponent Won but got out-chanced Lost & got out-chanced Lost but out-chanced opp

Bar height = |xGΔ| for that game. Above baseline = out-chanced; below = out-chanced against. Amber wins and cold losses are the honesty signal — result diverged from process.

What the data can't see

Cross-cutting caveats that apply to every metric on this dashboard:

  • No shift/on-ice data — we can't compute real on-ice xG differential per player, so a forward's defense and a defenceman's suppression are invisible.
  • No skater time-on-ice — no per-60 rate stats. Volume is compared per-game, which conflates a top-line minute-eater with a fourth-liner.
  • Individual xG is NHL only — Liiga forwards get raw points, honestly labeled as scoring output.
  • PP opportunities are NHL-invisible — we use chance-based PP xG for NHL instead of PP%, avoiding a known-wrong estimate.
  • Sample size gates everything — early-season and small-window signals are directional, not confident. The dashboard shows the sample size next to hotness for exactly this reason.

When you don't see a caveat here, it's not because we've solved the problem — it's because we've chosen a metric that doesn't need one.

detail view