Layer 3 Step 3: Tier-1 mappings live; Tier-2 nomination harness

PHASE 0 GATE — historical out-of-sample testing is NOT available, and the reason
matters. statcast_aggregates is overwritten nightly by design (Layer 1 is a full
re-pull upsert), so it holds season-TO-DATE numbers with no point-in-time
history. Classifying a player for a 15 July game using today's aggregate would
feed the model games from 15-21 July — look-ahead leakage, and the resulting
"out-of-sample" verdict would be worthless. The harness therefore reads the
archetype vector RETAINED at grade time (Session 70's instrument) and runs
FORWARD-ACCRUAL, not historical. Reported rather than worked around.

CANONICAL NAMES ASSERTED. Every mapping references the axis keys the classifier
actually emits, and a test walks both maps against BATTER_AXES / PITCHER_AXES.
A key that does not exist would look active and never fire — a mapping that
appears wired while silently doing nothing is the exact failure this guards.

TIER 1 IS LIVE, tautological and directional: PUNCHOUT/WHIFF raises strikeouts;
SINKER/SEAM lowers home runs allowed and FLY BALL/ELEVATOR raises them (a ball
on the ground cannot leave the park); SURGEON ARM/PINPOINT lowers walks allowed;
SLUGGER/BOMBER raises total bases and home runs; TECHNICIAN/SURGEON raises hits
and lowers strikeouts; GRINDER/SNIPER raises walks. Each adjusts only its named
stat, mirrors exactly on the under side, and leaves an average player untouched.

SPEED IS HONESTLY ABSENT. BURNER/stolen-bases has no axis to key on — SB is a
statsapi field that never reached the aggregate store, so Layer 2 shelved it.
The mapping is an empty object rather than an invented one.

THE TIER-2 HARNESS tests MARGINAL CONTRIBUTION, not correlation. A ground-ball
arm obviously correlates with fewer home runs; the question is whether the
archetype explains the PROJECTION'S RESIDUAL (outcome minus p_win). If the
projection already knows it, the residual carries no signal and the mapping is
rejected as redundant — that hurdle is what catches double-counting. The split
is by DATE, never random, because rows from one game share a pitcher, a park and
a lineup and would leak across a random split. Direction is validated from the
held-out data and a contradicted sign is REJECTED, never silently flipped to
whatever the data says, which would be fitting noise.

LIFECYCLE ENCODED — nominated, live, claimed. A mapping that survives runs live
and is measured; only the quantified public claim waits for the ledger. Nothing
sits dark.

One fixture bug worth recording: my first synthetic generator aliased the
carrier selector against the outcome draw and manufactured a 0.038 effect where
the generator had put zero. The harness rejected it correctly — it just gave the
sign reason instead of the redundancy reason, which is how I found it. The draw
now uses a coprime modulus.

Real candidate run end to end, GROUND-BALL to hits-allowed: INSUFFICIENT, 0 of
200 settled rows, because no settled row carries p_win yet (Session 70's
instrument starts recording at the next new lock). That is the correct verdict
and the expected one.

Tests 3654 passed / 296 suites, web build exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
This commit is contained in:
Kev
2026-07-21 00:30:16 -04:00
parent 56fee267c9
commit 927e867a23
4 changed files with 374 additions and 1 deletions
+13
View File
@@ -59,10 +59,23 @@ const BATTER_MAP = Object.freeze({
stolen_bases: {},
});
/**
* TIER 1 — TAUTOLOGICAL. The archetype IS the stat tendency, so these are safe
* in DIRECTION without an empirical test and run live from day one. They are
* still MEASURED like everything else; only a quantified accuracy claim waits.
* Keys below are the CANONICAL axis keys the classifier emits — a key that does
* not exist would be a silent no-op, so `challengerMappings.test.js` asserts
* every one against BATTER_AXES / PITCHER_AXES.
*/
const PITCHER_MAP = Object.freeze({
strikeouts: { strikeout: +1, chase: +1, velocity: +1, contact_allowed: -1 },
pitcher_strikeouts: { strikeout: +1, chase: +1, velocity: +1, contact_allowed: -1 },
hits_allowed: { strikeout: -1, contact_allowed: +1, ground_ball: -1 },
// Tier 1: a ground-ball arm allows fewer home runs; a fly-ball arm more.
// The most tautological pair in the set — a ball on the ground cannot leave
// the park.
home_runs_allowed: { ground_ball: -1, fly_ball: +1, contact_allowed: +1 },
home_runs: { ground_ball: -1, fly_ball: +1, contact_allowed: +1 },
earned_runs: { contact_allowed: +1, wild: +1, strikeout: -1 },
outs_recorded: { control: +1, ground_ball: +1, wild: -1 },
innings_pitched: { control: +1, ground_ball: +1, wild: -1 },