# RUNG 1 — opposing-lineup K-rate, derived from data we already hold **2026-08-03.** Challenger-only. Counter, batter cluster and pitcher engine all byte-identical (verified by diff). > **VERDICT: Rung 1 is DERIVED and needs ZERO new sourcing — but it does not > clear, and neither does anything else, because every pitcher result is still > n=57 against a 500 bar.** Rung 2 and Rung 3 are NOT triggered: a rung only > fails when it has been fairly tested, and Rung 1 has not been. > > **The real result is that the cap fix landed: 907 grades per snapshot, up from > 334, with strikeouts 6 → 17.** That is the constraint that decides when any of > this becomes answerable. --- ## 0. Two premise corrections **"whiff / put-away / stuff prove SOLO through the gate."** They do not. Every pitcher feature was **refused on `insufficient_data`** (n=57 < 500). Four *exceeded the effect-size bar* (arm_angle −0.250, whiff +0.213, k_pct +0.206, chase +0.195) — which is why they are worth pursuing — but exceeding one of three thresholds is not passing the gate. **"The carrier was blocked ONLY on the lineup input."** The lineup input was built and measured last session (roster join, 94.7% coverage). It was not missing: it was tested and did not add. What blocks it is **n**, and n was being throttled by the grading cap — which is what this session actually fixed. ## 1. The derivation (Rung 1) — free, from batter data we already ingest Opposing-team K-rate is built by joining the opposing team's roster to the **batter `k_pct` values already in `statcast_aggregates`**. No new feed, no new cost. Coverage **94.7%** of settled strikeout rows. **Improved this session: PA-WEIGHTED, not a flat roster average.** An unweighted mean counts a 12-PA September call-up the same as an everyday starter, which is not the lineup a pitcher faces. Weighting each batter's K-rate by his own `sample_pa` is the closest honest approximation of *who actually bats* from data we already hold. **That change alone reversed the term's contribution:** | head-to-head on strikeouts | resolution | delta vs counter | |---|---|---| | pitcher skill alone | 0.1738 | +0.2377, CI [−0.020, +0.528] | | **+ PA-weighted lineup K-rate** | **0.1953** | **+0.2592, CI [−0.0167, +0.5645]** | | *(last session's unweighted version)* | *0.1285* | *+0.192* | The unweighted proxy **hurt** (0.174 → 0.129). The PA-weighted one **helps** (0.174 → 0.195). Same hypothesis, same data — the derivation was the problem, not the signal. That is the whole point of "derive the best honest version before sourcing anything." The CI is now **[−0.0167, +0.5645]** — very nearly excluding zero, at n=57. ## 2. The gate (STEP 2) Bonferroni across 9 tests. **Everything is refused on sample size**; r is shown because "not enough data yet" and "nothing here" require opposite decisions. **Lineup K-rate, solo:** r vs residual = **+0.004** (n=54). On its own the team K-rate says nothing — which is expected and is *not* an argument against it: it is hypothesised as a **conditioner**, not a standalone predictor. **The carrier, pooled:** raw r 0.175, best component 0.198, **incremental +0.077**. Still explained largely by whiff, but positive now (it was −0.102 unweighted). ### Within pitcher archetype — the order's sharper hypothesis | stratum | n | lineup solo r | best component | **incremental** | |---|---|---|---|---| | FLAME (power) | 20 | +0.117 | 0.117 | **−0.152** | | non-FLAME (finesse/contact) | 24 | −0.161 | 0.183 | **+0.145** | **The two strata have opposite signs** — the shape a genuinely conditional effect makes, and invisible when pooled (the pooled incremental, +0.077, sits between them). That is exactly why the order asked for stratification. **But it is not evidence yet.** At n=20 and n=24 the standard error on a correlation is roughly 0.22, so both estimates are inside noise, and the *direction* does not match the theory: the order predicted the interaction should matter **more** for finesse arms, and the magnitudes here are near-identical with flipped signs. **Recorded as a structure to re-test, not a finding.** ## 3. Head-to-head, and the number that keeps standing out ``` n 57 · base rate 0.597 resolution pitch-v1 (with lineup) 0.1953 counter −0.0639 brier pitch-v1 0.2412 counter 0.2703 delta +0.2592 CI95 [−0.0167, +0.5645] → INCONCLUSIVE ``` **The counter's resolution on strikeouts is NEGATIVE.** Counting a pitcher's recent strikeouts is *anti-predictive* of whether he clears his line, because recent K totals track which lineups he drew and how long he was left in rather than his skill. It remains the one stat where the incumbent has no defensible edge, and the skill model leads it by 0.26 with a CI that now barely spans zero. ## 4. THE CAP FIX LANDED — which is what decides the timeline Verified in production this session: ``` before 334 grades/snapshot · strikeouts 6 after 907 grades/snapshot · strikeouts 17 · outs 16 · ER 16 · hits_allowed 16 ``` **2.7× across the board, 2.8× on strikeouts.** Operational note: the manual `POST /api/internal/snapshot/:sport` now **524s at the Cloudflare gateway** — grading the full board exceeds the 100s edge timeout. **The run still completes server-side** (this snapshot was written by a 524'd request), and the cron is in-process so it is unaffected. Do not read a 524 there as a failure; check `/api/internal/snapshot/status`. ## 5. Rung verdict | rung | status | |---|---| | **Rung 1 — team-season K-rate** | **DERIVED, zero new sourcing, 94.7% coverage, PA-weighted.** Contribution is positive but n=57 — **not fairly tested yet** | | Rung 2 — projected lineup | **NOT TRIGGERED.** Rung 1 has not failed; it has not been tested | | Rung 3 — confirmed-lineup sourcing | **NOT TRIGGERED.** Do not source | **Ship now? No.** Nothing passed the gate, so nothing is calibrated and nothing ships. **Source confirmed lineups? No** — that would be paying for precision on top of a proxy we have not yet measured properly. ## 6. Next 1. **Wait for n.** At 17 strikeout props/snapshot × 5 snapshots/day, strikeouts should reach n≥500 in roughly **a week**, not the three months it was before the cap fix. 2. **Re-run `scripts/pitcher-prove-k.js`** at n≥500 with a point-in-time window from `statcast_history`. Both arrive on a similar timescale. 3. **Re-test the within-archetype strata then** — with ~200 per stratum instead of 20, the opposite-sign structure either survives or dissolves. 4. **Give `arm_angle` a registry entry with a stated mechanism** before it is tested for real. It is the largest |r| in the programme and currently has no declared hypothesis, which is how a fishing result gets promoted. **Not recommended:** sourcing confirmed lineups, moving to Rung 2, calibrating, or reading the opposite-sign strata as a conditional effect. All four would be acting on n=20.