build-val.mjs — VAL (Value Above League) engine (S189)
VAL is a per-season, single-number player-value metric — our answer to PFR's AV, but position-aware, era-adjusted, availability-aware, and transparent. Said out loud "val." Works across ALL sports we host (NFL, NBA, MLB, NHL, MLS): the engine is generic, only the per-sport SPORT_CONFIGS (value stats, weights, playing-time basis) differ.
How it improves on AV: • Era-adjusted BY CONSTRUCTION — z-scored against the OTHER players at the same position IN THAT SAME SEASON, so different eras are automatically comparable. • Position/role-fair — every group has its own value-stat weights and season baseline. • Availability-aware — a per-appearance rate is scaled by playing time, so a full season outvalues a half one without over-rewarding a hot cameo. • Transparent — the weights are right here in the open.
Method (per player-season): weighted composite z of the group's value stats (per- appearance for counting stats, raw for rates) → friendly scale → × availability → × honors nudge. Career VAL = sum; weighted VAL = recency-weighted (wAV-style). Output: data/{sport}/val.json (index only — never mutates profiles).
VAL 3.0 (S320) — MLB, NBA, NHL and MLS regular seasons are measured in their sport's own currency (runs, points, goals) above a REPLACEMENT level the corpus itself observes (what each season's fringe players produced), with every conversion fitted from this corpus's team-seasons (run values, goals per win, the share of team defence a goalie explains) and checked against team results by a capped calibration (a component may not claim more team wins than standings support). Team outcomes (pitcher wins, saves, goalie wins) are not inputs, and neither is any third party's composite. See scripts/lib/val3/*-core.mjs; the measurements every change is graded on are in scripts/check-val-harness.mjs. Season VAL = max(0, wins) × the sport's VAL-points-per-win; the signed wins and their components go to data/{sport}/val-impact.json. NFL regular seasons and every sport's playoff VAL still use the 2.0 method below.
Run: node scripts/build-val.mjs --sport nfl (or nba|mlb|nhl|mls|all)