Flames Data Lab
Methodologies & credits
How every Lab metric is built, which public formulas we use (with sources), and which methods are FlameWire’s own.
The Flames Data Lab builds metrics from public NHL.com data (standings, club stats, schedules, play-by-play, and shift charts). We do not license advanced stats from third-party feeds. For established public metrics such as Corsi, Dom Game Score, and PDO, we calculate the numbers ourselves from NHL play-by-play and shift charts using publicly documented formulas, and we link those sources below. Metrics marked FlameWire (for example DirtyXg, rolling GS L10, and FlameWire Calc) are our own constructions. Public metrics are written out in full below; experimental ones (like FlameWire Calc) are described at a high level while we keep testing and learning.
1. Data sources
Our main data input is the public NHL.com API (schedules, standings, team and player stats, play-by-play, and related endpoints). From that feed we calculate the Lab metrics described below.
- Team Lab — team standings and club stats.
- Player Lab — skater and goalie counting stats, plus advanced metrics we derive (Game Score, Corsi, DirtyXg, and more).
- Prospects — prospect-oriented stats where available.
FlameWire is independent and not affiliated with the NHL or the Calgary Flames. See Legal & disclaimers.
2. Dom Game Score
Public formula · we calculate it ourselves. Game Score is a single-number box-score + on-ice summary from Dom Luszczyszyn. We apply Dom’s published skater and goalie weights to Flames regular-season games using NHL play-by-play and shift charts — we are not pulling Game Score from an external API.
Source: Dom Luszczyszyn — “Measuring Single Game Productivity” (Hockey-Graphs, 2016).
Skater (per game) — formula we implement:
GS = 0.75·G + 0.7·A1 + 0.55·A2 + 0.075·SOG + 0.05·BLK + 0.15·PD − 0.15·PT + 0.01·FOW − 0.01·FOL + 0.05·CF − 0.05·CA + 0.15·GF − 0.15·GA
CF / CA / GF / GA are 5-on-5 on-ice from play-by-play plus shift charts (player must be on the ice for the event). PD / PT are penalties drawn / taken. Season totals sum game GS; GS/GP is the average.
Goalie (per game) — formula we implement:
GS = −0.75·GA + 0.1·SV
Credit: Dom Luszczyszyn / Hockey-Graphs. Lab label: “Game Score (Dom)”.
3. Corsi / CF% / CF·CA per 60
Public formula · we calculate it ourselves. Shot-attempt differentials (Corsi) are a long-standing public possession proxy. Concept work is associated with Jim Corsi; the “Corsi” name and blog-era popularization are widely linked to Tim Barnes and the early hockey analytics community. Fan sites later standardized CF% presentation.
How we compute it: from NHL play-by-play and shift charts for Flames games — not from Natural Stat Trick, Evolving-Hockey, or any other third-party Corsi feed.
Formulas we implement:
CF = 5v5 on-ice shot attempts for (G + SOG + miss + block toward opp. net) CA = 5v5 on-ice shot attempts against (same events toward our net) CF% = CF / (CF + CA) CF/60 = CF × 60 / (5v5 TOI minutes) CA/60 = CA × 60 / (5v5 TOI minutes)
“On-ice” means the skater’s shift chart places them on the ice when the attempt occurs. Rates use 5v5 TOI, not all-situations clock.
Reference reading (public definitions / usage): Natural Stat Trick, Evolving-Hockey, and general hockey-analytics writing on CF%. Concept credit: Jim Corsi · Tim Barnes & public analytics community.
4. GF%, SF%, PDO
Public formulas · we calculate them ourselves from NHL standings / club stats (team) and from 5v5 on-ice play-by-play + shifts (player advanced block).
Formulas we implement:
GF% = GF / (GF + GA) SF% = SF / (SF + SA) Sh% = GF / SF (on-ice or team, as labeled) Sv% = 1 − (GA / SA) PDO = (Sh% + Sv%) × 100
- GF% (team) uses standings-style goals for / against. Player on-ice GF% uses 5v5 on-ice goals while the skater is out.
- SF% uses shots on goal for / against from club-stats (team) or on-ice SOG (player advanced block).
- PDO clusters near 100 league-wide. Elevated PDO often means “running hot”; depressed PDO often means “running cold.” It is a diagnostic, not a pure skill score.
- Leaders through the years (player GF% / CF% / PDO): these crown the best rate among skaters who clear a minutes and games floor. That is not the same as best player. Goal percentages use fewer events than shot attempts, so lower minute players can still spike a season GF% or PDO (the same small sample issue public sites warn about). On the Lab chart you can raise floors with Standard / Regulars / Strict presets, or type your own min GP, 5v5 minutes, and event counts.
- Lines & pairs (5v5): forward line = three F on ice together; pair = two D on ice together. TOI from shift overlap in 5v5; CF/GF from on-ice shot and goal events while that exact unit was out. GF samples are often small for a specific trio.
- With / without teammate: 5v5 only. With = player A and teammate B both on ice; Without = A on ice and B off ice (among skaters dressed that game). Same CF/GF event logic as other on-ice boards.
PDO origin: the sum-of-percentages “luck” metric was popularized in hockey analytics by Brian King (commonly cited in public hockey stats writing). GF% / SF% are standard score- and shot-share presentations used across public analytics sites such as Natural Stat Trick and Hockey-Reference.
5. Rates, L10 form, A1%, Hits / TK / GV
Per-60 rates are a standard public presentation; we compute them from NHL club-stats TOI and (for event rates) play-by-play.
Formulas we implement:
Stat/60 = counting_stat × 60 / TOI_minutes
A1% = A1 / (A1 + A2)
GS L10 = average of Dom Game Score over the skater’s last 10 scored games
in that season ← FlameWire rolling application of Dom’s GS
- P/60, G/60, A/60, SOG/60 — from club-stats ice time (player Lab leaders / trajectory).
- GS L10 — FlameWire: rolling form using Dom’s published Game Score on our scored game list.
- A1% — share of assists that are primary.
- Hits/60, TK/60, GV/60 — hits, takeaways, giveaways per 60 using all-situations TOI and PBP event counts.
6. GSAA (Goals Saved Above Average)
Public formula · we calculate it ourselves. GSAA estimates goals prevented versus a league-average goaltender facing the same shot volume. It appears widely on sites such as Hockey-Reference and in public analytics writing — not a FlameWire invention.
Formula we implement:
GSAA ≈ SA × (Sv% − league_Sv%)
≈ saves − (shots against × league average save percentage)
League average save percentage comes from public NHL goalie stats for that season; we then apply the formula above for Flames goalies.
6b. Goalie form & workload (FlameWire)
On top of season box scores and GSAA, Goalie Lab cards and charts show a few FlameWire form / workload numbers. They only appear when we already have Dom play-by-play (or club TOI) for that season.
-
Avg TOI / Start % — from NHL club goalie stats
(
timeOnIce, starts ÷ games). Workload, not quality. -
Game Score / GS/GP / GS L10 — Dom’s goalie Game Score
from PBP (
−0.75·GA + 0.1·SV), summed per season; L10 is the rolling average of the last 10 appearances. - SV% L10 — saves ÷ shots against over the same last-10 window. Simple form, not “true talent.”
- B2B / B2B SV% — appearances on consecutive calendar days (game date N and N−1 both in that goalie’s log), plus save % only on those second nights. Small samples; treat carefully.
We do not scrape third-party goalie sites for these. If a Dom season file is missing, the form cells show “—”.
7. DirtyXg (experimental) vs public xG
Expected goals (xG) in modern public models (e.g. MoneyPuck, Natural Stat Trick, Evolving-Hockey) is a full shot-quality model. Those systems typically score every unblocked shot attempt with many features — distance, angle, shot type, rebound state, rush, traffic/screen proxies, strength state, score effects, and more — trained on large multi-season samples so the sum of shot probabilities ≈ expected goals.
DirtyXg is FlameWire’s simpler, still-in-testing shot-quality estimate. It’s built only from public NHL play-by-play (location and shot context). It is not a MoneyPuck / Natural Stat Trick feed and is not meant to replace full public xG models — think of it as a lab sketch of chance quality while we see how well it holds up.
In broad terms: we look at unblocked shot attempts and grade how dangerous the look seems (e.g. slot vs perimeter), with a light nod to shot type. We’re not sharing the full formula yet — this is still a test model. We want more data and more confidence that it’s useful before locking anything in (or deciding it isn’t). Treat DirtyXg as a ballpark for chance quality and finishing (goals vs DirtyXg), not a precise xG clone.
| DirtyXg (experimental) | Full public xG (e.g. MoneyPuck) | |
|---|---|---|
| Source | Public NHL play-by-play | Trained multi-feature models |
| Complexity | Simpler location / type estimate | Continuous probability per shot |
| Context | Does not claim full rebound / traffic / rush modeling | Typically richer context (model-dependent) |
| Use in the Lab | Flames shot-quality & finishing charts (work in progress) | Best available public expected goals |
Inspired by the public expected-goals tradition (MoneyPuck and peers). Early FlameWire experiment — not affiliated with or endorsed by those sites. Treat it as a work in progress.
8. Validation — DirtyXg vs MoneyPuck (10 Flames skaters)
We compared season DirtyXg to MoneyPuck individual expected goals
(I_F_xGoals, situation = all) for
ten 2025–26 Calgary skaters. To keep the comparison fair:
- Player must appear as CGY on MoneyPuck for that season (mid-season leavers with a different “season team” row are excluded — e.g. we do not stack Flames-only DirtyXg against a full non-Flames MoneyPuck total).
- GP and SOG must match between our play-by-play and MoneyPuck (within ±2) so we are scoring the same shot volume.
- Sample includes volume forwards plus two defensemen with enough unblocked attempts.
| Player | Pos | GP | G | MoneyPuck xG | DirtyXg | Δ (Dirty − MP) | |rel| |
|---|---|---|---|---|---|---|---|
| Matt Coronato | R | 80 | 18 | 21.93 | 21.78 | −0.15 | 0.7% |
| Blake Coleman | L | 69 | 20 | 19.95 | 22.04 | +2.09 | 10.5% |
| Mikael Backlund | C | 82 | 17 | 19.77 | 18.33 | −1.44 | 7.3% |
| Morgan Frost | C | 82 | 22 | 18.54 | 19.14 | +0.60 | 3.2% |
| Yegor Sharangovich | C | 78 | 15 | 17.90 | 15.22 | −2.68 | 15.0% |
| Joel Farabee | L | 82 | 20 | 16.71 | 16.18 | −0.53 | 3.2% |
| Connor Zary | C | 74 | 12 | 15.14 | 14.02 | −1.12 | 7.4% |
| Jonathan Huberdeau | L | 50 | 10 | 13.51 | 11.15 | −2.36 | 17.5% |
| Zayne Parekh | D | 37 | 4 | 4.36 | 3.28 | −1.08 | 24.8% |
| Yan Kuznetsov | D | 57 | 4 | 3.38 | 2.55 | −0.83 | 24.6% |
How close on this sample?
| Metric | Result (n = 10) |
|---|---|
| Mean absolute error (MAE) | ~1.3 xG |
| RMSE | ~1.5 xG |
| Max |error| | 2.7 xG (Sharangovich) |
| Mean |relative| error | ~11% (lower on high-volume forwards; higher % on low-xG D) |
| Mean signed error (Dirty − MP) | −0.8 (slight low bias on this sample) |
| Within ±1.0 of MP | 4 / 10 |
| Within ±1.5 of MP | 7 / 10 |
| Within ±2.5 of MP | 9 / 10 |
| Within ±3.0 of MP | 10 / 10 |
- Best case: Coronato essentially ties MoneyPuck (21.8 vs 21.9) with identical GP/SOG/G.
- Typical forward: about 0.5–1.5 xG absolute; often under ~10% relative when xG is high teens/20s.
- Larger misses: Sharangovich (−2.7) and Huberdeau (−2.4) — still same ballpark, but DirtyXg under-called them vs full MP features (rebounds, rush, traffic, etc.).
- Defensemen: absolute errors stay small (~1 xG) because totals are small; relative % looks worse — use DirtyXg/60 and ranks carefully for D.
- This is empirical coverage on n = 10 Flames player-seasons, not a formal 95% confidence interval for the whole NHL.
How to read finishing: Coronato’s 18 G on ~22 DirtyXg / ~22 MP xG is mild underperformance on chance quality — not “he only had an 18-goal chance.” Frost and Farabee sit near both models; Coleman’s DirtyXg runs a bit hot vs MoneyPuck (+2.1) while still tracking the same story as a high-volume shooter.
We’re not retuning DirtyXg just to match MoneyPuck on a small sample — a perfect in-sample fit wouldn’t mean the test model is “better.” For now DirtyXg stays a simple ballpark: usually within about 1–2 of MoneyPuck for high-volume Flames forwards, with occasional larger misses. More seasons and confidence checks come next.
MoneyPuck figures are an external reference for this methodology note only. FlameWire does not redistribute MoneyPuck data feeds.
9. NHLe (prospect NHL equivalency)
Public method · we calculate it ourselves for Flames prospects from multi-league seasons via the public NHL player API.
NHLe (NHL equivalency) translates scoring in junior, college, AHL, Europe, and the NHL onto a common NHL-scale rate. We multiply points per game by a league factor, then blend recent seasons. The headline figure is often shown as NHLe / 82 (translated points over an 82-game pace).
season NHLe rate = (points / GP) × league_factor
NHLe / 82 = NHLe rate × 82
multi-year blend ≈ 50% last + 30% prior + 20% prior-2
(qualifying seasons only)
Factors follow the public NHLe tradition (classic community tables), cross-checked on multi-league NHL careers and shrunk where our samples were success-biased. Examples of v1 weights: NHL 1.00, AHL 0.50, OHL ~0.34, WHL ~0.30, QMJHL ~0.29, NCAA ~0.41, SHL ~0.55, KHL ~0.80. Youth / showcase leagues are excluded.
The NHLe Projection Card also shows age window (e.g. prime prospect years vs fringe depth clock), trend, confidence (sample size), and a labeled draft prior (historical only — not destiny). Draft slot does not override production.
Role ladder (FlameWire)
We map NHLe + age + NHL games into a ceiling / most likely / floor role band. The card leads with ceiling (ultimate potential); most likely and floor are context:
- Forwards: Elite (rare) · Top-6 · Middle-6 · Bottom-6 · Bubble · AHL Depth — short flavor only when position is known (e.g. Top-6 · 2C, Middle-6 · 3C). Generic “F” does not invent wing vs center.
- Defense: Top pair D · Top-4 D · 2nd pair D · 3rd pair D · Bubble (7th D) · AHL Depth only — never top-6 / middle-6 / bottom-6.
- The card leads with ceiling (ultimate potential). Elite is a high bar, separate from Top-6.
Position for flavor comes from the NHL player profile (C / L / R / D) when available, not a generic pool “F.” Age past the typical prospect window caps upside. Goalies are not on this ladder yet.
Shown on the Prospect pool and in the Data Lab prospect module.
Credit: public NHL-equivalency research tradition (Hockey Prospectus lineage and later community recalibrations). Role bands and cutoffs are FlameWire’s implementation for Flames prospects — not a licensed third-party feed.
10. FlameWire Calc (experimental)
Simple idea: think of the FW score like a player’s overall (OVR) rating in today’s NHL video games — a single 0–100 potential grade for how good we think they can become, not a projection of NHL points.
FlameWire method · skaters only · still in testing. Sits beside the NHLe Projection Card. NHLe answers “what does scoring translate to?” FlameWire Calc is our experiment at a blended upside view: a single 0–100 OVR-style potential score plus an upside tier.
At a high level it looks at public development signals such as translated production (from NHLe), draft capital, age/development window, path (juniors / college / AHL / Europe / NHL sample), and related context. We’re not sharing the full formula yet — this is still a test model. We want more seasons of data and more confidence that it’s actually useful before locking anything in (or deciding it isn’t).
Upside tier is a label mapped from that score (forwards: Star / Top-6 / Middle-6 / depth / bubble / long shot; defense uses pair language). vs NHLe compares that tier to the Projection Card’s ultimate potential (Higher / Agrees / Lower). Drivers on the card are plain-language reasons for the grade.
The FW card includes a seasonal FW score chart (last four seasons) so you can see how the OVR-style grade has moved over time. Dashed lines mark upside / current / floor tier bands on the 0–100 scale. Gold line distinguishes it from the red NHLe chart next door.
Goalies are not scored yet — we don’t have a multi-league goalie version we’re happy to test in public. Needs a usable skater NHLe before FW Calc runs.
11. Disclaimers
- Metrics are for fan analysis and education. They are not official NHL statistics and should not be treated as contractual or medical truth.
- Sample sizes, score effects, zone starts, and linemates matter — leaders on small samples can be noisy.
- Public metrics (Corsi, Dom Game Score, PDO, GSAA, NHLe, and similar) are calculated by FlameWire from NHL public data using the formulas and sources listed above — not imported as a finished feed from those sites.
- Prospect NHLe is a scoring translation, not a roster prediction. Draft prior on the NHLe Projection Card is historical context only.
- DirtyXg is an experimental FlameWire shot-quality estimate — always labeled DirtyXg, never bare “xG” without context.
- Original creators and sites named above retain credit for their ideas; any errors in our calculation are ours.
Questions or corrections on methodology credits? Suggest a source · Return to Flames Data Lab · Legal