xHockeyAIxHockeyAI
HomeDeskRosterMemoryTakesAskSoon
2026-27
@xhockeyai
⌘K

Stars Lab

Did he earn the deal last night?

Dallas only·2026-27 desk·@xhockeyai

Lab 2.5·Release Notes →

Value Score methodology

Version v1.38 · data dictionary

Spec + field map for the fair-AAV engine. End-to-end story: ARCHITECTURE.md · src/lib/pipeline/. Path: Cap Rink → this dictionary → misses. Also: research hub, thesis note, ex-ante. Storage: DATA.md.

On this page

  • Plain English
  • Pipeline
  • Inputs
  • Outputs
  • Labels & ELC
  • Deal thesis
  • Worth / anchors
  • Contract trust
  • Projections
  • Comps
  • Not in the model

What this is not

Not an official CBA salary-cap sheet. Team payroll is season-roster AAV from curated/import contracts only — no Spotrac scrape, no LTIR / buried / retention / buyout math. Estimated placeholders are gaps, never silent filler in the total.

What we answer

Is this player worth what they're paid — as a share of the cap, at today's ceiling and at the next-deal / FA-summer ceiling as the cap moves up? Contract AAVs are inputs. The product is worth (%cap) vs cost, with tier-matched market anchors as context when a clearing / elite path applies.

Same-season window: 2025-26 MoneyPuck production, NHL season rosters (API /roster/TEAM/20252026, not /current), and 2025-26 contract AAVs. Transparent TypeScript heuristics — not a trained Python model.

Plain English — what drives fair $

Fair AAV is "what would a reasonable mid-market deal look like for this production?" We blend a few seasons of rates, convert that into dollars, then compare to the actual paycheck in % of the salary capso older deals aren't punished for inflation.

Forwards (C / LW / RW)

  • Scoring pace — adjusted individual xG and primary points (goals + primary assists), paced to 82 games. Secondary assists and high-danger xG add a bit.
  • Minutes — TOI/game scales the offense package (top-line ice ≠ depth ice). Centers get a small bump; wings with real faceoff volume can count as centers for FO credit and comps.
  • On-ice impact — 5v5 relative xG% and on-ice xGF/60 (are they tilting the ice?).
  • Special teams & two-way — PP xG + PP/PK minutes; penalty differential, takeaways vs giveaways; faceoffs for centers.
  • xWAR blend (~28%)— same signals packed into a WAR-style proxy so mid-stars don't slam the ceiling.
  • Elite-tier — when prim82 ≥ 90, TOI ≥ 21, and xWAR ≥ 6.5, skip the soft-cap trim, soften age-30 × long-term haircut, raise the forward fair ceiling (~$16.5M tune), floor offense on a prim-led index so mid adjxG cannot veto a franchise package, and skip the comps second pass (peer medians are mid-star heavy). Flagged in notes as elite-tier.
  • Context — aging curve, RFA discount, long-term risk, soft market anchor on live mid-star deals, optional comps pull.

Defense

  • Role base from TOI — ladder from depth (~$1M) through top-four (~$4.2M) / high top-pair (~$5.5M) to true 1D minutes (~$6.4M) before offense/impact add-ons.
  • Offense — adj/HD xG + primary points + a smaller secondary-assist term.
  • Impact — rel xG% and xGF−xGA rate, scaled by even-strength minutes (usage-weighted).
  • Two-way / ST — blocks get extra weight; PP/PK dollars slightly higher than forwards.
  • Same xWAR blend, aging, RFA/term, comps path as forwards (D peer pool).

Goalies

  • GSAX— MoneyPuck expected goals against minus actual GA, blended up to three seasons so one ugly (or hot) year doesn't own the fair AAV.
  • SV% / GAA — still in the mix; fallback when GSAX is missing.
  • Workload — games played vs a ~50-start reference; starter floorsladder by GP × AAV so high-GP / high-AAV creases don't collapse to backup money.
  • Aging + RFA/term; AAV-tier capstemper hot GSAX on backup and mid deals so a spike can't invent elite fair.

How inputs become fair $ / %cap

  1. Load MoneyPuck all / 5on5 / 5on4 / 4on5 (+ playoffs when present) into a season impact bag.
  2. Blend up to three seasons at per-game rates(default 50/30/20; young risers overweight the latest year). Thin GP seasons (<10) are skipped.
  3. Position heuristic → raw fair dollars (F / D / G paths above), including ~72% component model + ~28% xWAR dollars.
  4. Multiply playoff factor (capped), age / RFA / term / clause factors. Soft market anchor toward the signed deal for non-ELC mid-star extensions still live (age ≤30, ≥2 yrs left, AAV ≥ $4M).
  5. Cap-index: treat those dollars as a share of the $95.5M tune ceiling, then mint fair AAV at the live ceiling ($95.5M). Scoring residuals stay on the active deal: live AAV ÷ ceiling in the final year, signing %cap when current enough, or legacy-blended for older fossil starts. Future extensions stay in projection context.
  6. Optional second pass: market comps (median peer %cap × live ceiling) gently blended into fair AAV — see Comps below.

Data dictionary — inputs

Names below are the ones you'll see in notes, code (SeasonImpact), and SQLite. MoneyPuck CSV column → DB column in parentheses.

MoneyPuck → season impact (skaters)

Model fieldSourcePlain English
adjIxgI_F_flurryScoreVenueAdjustedxGoals → adj_ix_goalsPreferred individual xG (flurry + score + venue adjusted)
ixgI_F_xGoals → ix_goalsRaw individual xG fallback
hdIxgI_F_highDangerxGoals → hd_ix_goalsHigh-danger individual xG
primaryPointsI_F_goals + I_F_primaryAssists → primary_pointsGoals + primary assists (offense that isn't secondary luck)
pointsI_F_points → pointsAll points; secondary = pts − prim
icetimeSec / TOIicetime → icetime_secUsage; drives D role ladder + F usage multiplier
relXgPctonIce − offIce xGoals% (prefer 5on5)Relative on-ice share vs team without them
onIceFxg / AxgOnIce_F_xGoals / OnIce_A_xGoalsOn-ice for/against xG → rates /60
o/d ZoneStartsI_F_*ZoneShiftStartsDeployment tilt (OZ bump slightly discounts; DZ boosts)
ppIxg / pp·pk icesituation 5on4 / 4on5 rowsSpecial-teams dollars
two-way bagpenalties±, takeaways, giveaways, dzone giveaways, blocks, FOWSmall dollar adjustments; FOW mainly for C

Derived rates (what notes show)

  • adjxG82 / prim82— 82-game paced rates after GP pace dampening (<40 GP softens projection).
  • TOI— minutes per game from blended icetime (with a points-based fallback if ice is empty so D role doesn't collapse).
  • xWAR — proxy parts: offense + impact + special teams + two-way (xwar.ts). Not Evolving-Hockey GAR/WAR.
  • GSAX — goalie xGA − GA (MoneyPuck), multi-year blended.

Contract inputs

  • aav — actual AAV (curated / import / estimated). Gold set + known maps win conflicts.
  • cap_pct_at_sign — AAV ÷ ceiling in the signing season (preferred actual %cap).
  • expiry_status — RFA → 0.9× fair; UFA unchanged.
  • years remaining / trade clause — long aging deals get a term haircut; NMC/NTC ≈ 0.98×.
  • quality — estimated forces low confidence; import dampens high → medium.

Data dictionary — outputs

Stored on value_scores (fair/actual/surplus/label/confidence/notes). %cap fields are computed at score time (ValueResult) even when not every column is persisted.

  • fairAav— model worth in today's dollars (fair%cap × live ceiling).
  • fairCapPct — fair as share of cap (primary unit).
  • actualCapPct — scoring actual: active-deal live AAV ÷ ceiling in the final year; otherwise signing %cap when known, or live AAV ÷ ceiling when no sign share is known. Pre-2023 starts with sign% ≫ live% blend toward live (notes: legacy-%cap). Signed-future AAVs are shown as projection context, not current label burden. Stored cap_pct_at_sign remains for display.
  • surplus — fairAav − actualAav (+ = team surplus / underpay).
  • surplusCapPct — fair%cap − scoring actual%cap (+ = underpay). Labels use this. Display also shows at sign residual (fair − sign%) and vs live residual (fair − AAV/live ceiling) when both exist.
  • label — overpay | fair | underpay.
  • confidence — low / medium / high from blended GP + seasons used; dampened for estimated AAV, ELC, sparse MP.
  • methodVersion — currently v1.38.
  • notes — human-readable keys: position path, adjxG82/prim82/TOI/xWAR or GSAX/SV%, blend label (3yr 50/30/20, riser 80/15/5, …), mkt-anchor, elite-tier, near-elite, live-%cap, legacy-%cap, ELC/bridge, comps …, season window, %cap actual→fair.

Label bands, ELC / bridge, confidence

  • Default band — |surplusCapPct| ≥ ~2.1 pp (≈ $2M at the $95.5M tune ceiling) → overpay / underpay; inside → fair.
  • Low confidence — wider band (~3.1pp ≈ $3M) so thin samples don't scream.
  • ELC / bridge — cheap young deals (roughly ≤$1M age≤27, ≤$1.75M age≤25, or cheap RFA). Notes flag ELC/bridge. Positive surplus usually stays fairunless talent clears a high bar vs a ~$4–5M bridge. Leaderboards hide AAV < $2.5M ("market board").
  • Market deal anchor— pulls model fair toward the signed %cap on live mid-contract deals so a just-paid market extension doesn't auto-label underpay. Skipped for ELCs and when skipMarketAnchor (ex-ante research).

Deal thesis · why this deal

Player overview and contract pages show a short Why this deal block — makes sense / tough sledding / mixed — built by rules from the same Value Score inputs (label, surplusCapPct, at-sign vs live %cap residual, elite-tier / ELC notes, age × years remaining, sample confidence, prim/adjxG/xWAR). Not an LLM take; optional polish later would be labeled separately. Does not change fair AAV or labels.

Path: Cap Rink → player card (model inputs + thesis) → this section. Dual residuals on the card: at sign = fair% − %cap when signed; vs live = fair% − AAV ÷ today's ceiling. Scoring label uses the blended actual%cap (see Outputs).

Worth · identities

Player pages lead with Worth — not primarily "fair $ vs current AAV." Single source: src/lib/value/worth.ts. Contract tab / UFA board / next-deal bands use the same helpers — no duplicate arithmetic in components. Not a METHOD_VERSION dial.

  • dollars = round$1k(fairCapPct × ceiling(season))
  • burdenPct = AAV ÷ ceiling(season)  (dollars = AAV, exact invert)
  • fairCapPct fixed across seasons; only ceiling(season) changes
  • |dollars / ceiling − fairCapPct| ≤ 1000 / ceiling
  • stored fairAav → fairCapPct = fairAav / liveCeiling
  • Worth now / FA summer — worthFromFairPct at production vs FA-summer ceiling (same fair%).
  • vs market anchors — tiered registry in anchors.ts (young_C / elite_wing / elite_F / top_D / starter_G). Worth cites matching tier only via anchorContextForTier: Carlsson/Bedard for young-C clearing; Kaprizov/Pasta/Rantanen for star-F; McDavid/Draisaitl/Matthews for elite-tier. Soft floors from tier medians / Kaprizov-class band (~14.8%) — not a hard peg. Mid-stars without a tier tag: no cite spam.
  • Star-F floor — %cap space then × tune ceil (fromCapPct); live $ via indexFairToLiveCap.
  • AAV burden path — burdenSchedule /burdenFromAav.
  • Earn-by — when projected fair% catches a fixed AAV over the deal term. burdenPct = AAV ÷ ceiling(season) (deal eases as the cap rises). Paths: rise (age ≤25 under-burden skaters — fair% grows toward a clamped role ceiling before decline; “aging up into deal”), hold (primes — flat fair%, cap dilution), decline (age ≥29 or clear age-curve drop — ageFactor). Growth clamps: ≤35% of the initial gap, ≤+2.5 pp, ≤1.22× current fair — no invented 1D miracles. First season where fair ≥ burden (or within 0.5 pp). Prefer signed_future gold when present. Projection only; ELC-scored fairs (e.g. Bedard) will show a long earn-by by design. See earnBy.ts.

Assert: npm run test:worth.

Contract trust tiers

AAV provenance is separate from Value Score confidence. Loud surplus / overpay theater only runs when the contract is PR-verified or curated. Estimated and unverified AAVs stay visible for books, but badges and headlines mute.

  • PR-verified — gold deal + NHL.com team PR / official news URL.
  • Curated — curated or import AAV, not yet PR-linked on nhl.com.
  • Estimated — placeholder; excluded from surplus totals; UI shows data thin.
  • Unverified — audit gap or stale; treat AAV with caution while coverage continues.

Team pages surface N/M PR-verified from coverage audits. Soft Cap projections never outrank thin trust. Source: src/lib/contracts/trust.ts.

Cap / contract projections

A separate accounting layer — not a second Value Score and not a METHOD_VERSION dial. Team pages show committed AAV for the next two seasons; player contract tabs show “on books through …” for the live deal, any signed future gold extension, and a muted next-deal band only when ≤2 years remain and no extension is on file.

  • Cap path — announced ceilings from capHistory.ts; seasons beyond the last NHL/NHLPA figure step +8%/yr (recent raises ~5–9%). Each row is flagged known or projected.
  • Locked / signed future — curated/import AAV whose start_season / end_season covers the projection year. Future-start gold (Harley, Carlsson, etc.) stays off live 2025-26 AAV at resolve, but overlays into projections as signed_future once the season ≥ start — not as pending UFA/RFA.
  • Next deal (model)— fair %cap from the current Value Score × that FA class's ceiling (explicitly % of e.g. 2026-27 $104M ceil, not the production-year ceil) ±15% band. Suppressed when a signed future gold deal covers that summer. Low confidence; never a loud overpay badge. Soft renewals are listed but not subtracted from team cap space by default.

Still not CBA tooling: no LTIR, retention, burial, buyouts, or scraped rumor boards.

Market comps blend

After the model scores, peers are matched on position group, scoring pace, age (±5), and deal size in %cap at signing (curated AAV ≥ $2M, 40+ GP, not ELC/bridge). Forwards prefer C↔C or wing↔wing when enough same-role peers exist.

  • Comps-implied fair = median peer %cap × today's ceiling (nominal AAV median if %cap missing).
  • That comps fair is mixed 85/15 with the model fair inside the comps helper; then blendWithCompspulls the stored result ~20–36% toward comps when n≥4 (0.20 / 0.28 / 0.36 at n≥4 / n≥6 / n≥8). ELC/bridge and AAV < $2M skip the post-pass.
  • Shown on player contract pages and /research/comps.

How we get better

Feeling "off" is usually stale AAV data, ELC talent vs cost (correct but loud), or the dollar scale drifting vs the real market. Fix with a calibration loop — not mystery features.

  • Market cohort — curated deals AAV ≥ $2M, age ≥ 24, 40+ GP. Primary MAE in %cap percentage points (npm run calibrate).
  • Residual review — biggest |%cap residual|: verify AAV / %cap@sign first, then dial.
  • True GAR/WARis licensed. Our xWAR proxy is open and transparent — not pretending it's EH.

Team AAV payroll

Team pages list the NHL 2025-26 season roster only. Payroll is the sum of curated/import AAVs vs the $95.5M ceiling / $70.6M floor for context, plus surplus. Missing and estimated contracts are counted separately and excluded from the payroll total.

Next-year outlook

Player pages and Ask can answer "how should X look next year?" from the same DB: current vs prior MoneyPuck rates, TOI usage, the Value Score age curve, contract context, and an illustrative GP/points band if the role sticks. /research/outlook freezes those bands on past seasons. This is not a trained projection model — small samples stay low-confidence on purpose.

What is not in the model

  • Licensed Evolving-Hockey / hockey-graphs GAR/WAR (we use an open xWAR proxy instead).
  • Full CBA tooling: LTIR, buried contracts, retention, buyouts, bonuses, escrow, offer sheets.
  • Scraped Puckpedia / Spotrac / HockeyStats HTML — curated AAV only.
  • Line chemistry, coach systems, zone-entry microstats, tracking, scouting grades, injury history beyond GP sample.
  • Draft capital, trade return, intangibles, "culture" narratives.
  • A trained ML regressor — every dial is a readable TypeScript heuristic in score.ts.

Still incomplete

Richer microstats, continuous contract feeds, and deeper ex-ante holdouts. Run npm run calibrate after every model change.

Research hubModel missesHome