Power-derive the LODO threshold: hits restored through the gate, rbi/runs
routed as date-driven PHASE 0 — threshold derived BLIND, before any stat was re-read. A reversal is informative only if that date's Brier delta is distinguishable from zero at its row count. Per-row Brier difference d_i = (pc-y)^2 - (p-y)^2, so SE(n) = SD(d)/sqrt(n) and n* = (SD(d)/|effect|)^2. Pooled across all four stats so no single stat's verdict could shape the threshold deciding it: pooled rows 3,417 | SD(d) 0.09816 | |effect| 0.01175 n* = (0.09816/0.01175)^2 = 69.8 -> 70 The hand-chosen 20 sat at 0.54 SE -- a coin flip. That is the defect this removes, and why the previous verdict moved with the number. Committed as calibrationRegistry.LODO_MIN_HELD_ROWS = 70 with LODO_THRESHOLD_BASIS; a test recomputes (SD/effect)^2 and asserts it equals the constant, so it cannot drift from its own justification. The derivation script prints no stat verdict, no date and no reversal. PHASE 1 — LODO at n*, applied cold: hits 5 informative drops, 0 reversals PASS total_bases 4 informative drops, 0 reversals PASS rbi reverses 2026-08-01 (n=99) FAIL runs reverses 08-01 (n=86), 08-05 (244) FAIL hits held-out deltas -0.0041/-0.0080/-0.0192/-0.0140/-0.0139 across 123-272 row dates, favourite sign holding on every testable drop. THIS IS THE INSTRUMENT FINALLY POWERED, NOT VINDICATION OF A PREDICTION -- the withdrawal at6ae11f1was correct on the instrument available then, which admitted 20- and 25-row dates as evidence. Nothing about hits changed; the threshold stopped being chosen. PHASE 2 — both failures are DATE-DRIVEN, not underpowered. Every reversal sits above n*=70 (99, 86, 244), so no threshold and no further accrual rescues either: isotonic is fitting day-structure. Routed to the low-parameter calibrator queue (Platt/beta), not built here. PHASE 3 — CALIBRATION_DEPLOYED is now ['hits','total_bases'], frozen and tested, both PROVISIONAL with auto-demotion armed and the >=40 date-cluster promotion bar unchanged. hits stackability for chain.chainAcross is RESTORED, and the record shows it returned through the powered gate rather than by fiat. hits bands rebuilt on p_win_calibrated (765 eval rows): every archetype still one band, still base_rate -- calibrated YES, proven-per-archetype NO. PHASE 4 logged: the deploy set is now set by a power-derived, pre-committed, tested constant rather than an operator-chosen number. At6ae11f1that rule moved the live path AGAINST the operator; it has now moved it back on the same evidence because the instrument changed. Both directions are the rule working. And calibrated p_win separates within archetype no better than raw across 13 archetype slots on two deployed stats -- per-archetype separation will come from proven factors or not at all. p_win never mutated; no Bonferroni slot consumed; counter and frozen clusters verified byte-identical file by file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1sivYNqY2TS5ftykmHBU9
This commit is contained in:
@@ -27,6 +27,43 @@
|
||||
const { knownNumber } = require('../../utils/known');
|
||||
|
||||
const STATUS = Object.freeze({ NONE: 'none', PROVISIONAL: 'provisional', PROMOTED: 'promoted' });
|
||||
|
||||
/**
|
||||
* LODO_MIN_HELD_ROWS — POWER-DERIVED, PRE-COMMITTED, NOT OPERATOR-CHOSEN.
|
||||
*
|
||||
* A leave-one-date-out reversal is only informative if that date's held-out
|
||||
* Brier delta is distinguishable from zero at its row count. Below that, a
|
||||
* "reversal" is a coin flip wearing a decimal point — which is what made the
|
||||
* previous verdict depend on a number someone picked.
|
||||
*
|
||||
* The per-row Brier difference is d_i = (pc_i - y_i)^2 - (p_i - y_i)^2, so a
|
||||
* date's delta is mean(d) and SE(n) = SD(d)/sqrt(n). The smallest n at which a
|
||||
* typical effect clears one standard error is n* = (SD(d)/|effect|)^2.
|
||||
*
|
||||
* Measured 2026-08-07, pooled across all four stats so that no single stat's
|
||||
* verdict could shape the threshold that decides it:
|
||||
*
|
||||
* pooled rows 3,417
|
||||
* SD(d) 0.09816
|
||||
* |effect| 0.01175
|
||||
* n* = (0.09816/0.01175)^2 = 69.8 -> 70
|
||||
*
|
||||
* SE-vs-n: n=20 gives effect/SE 0.54, n=50 gives 0.85, n=75 gives 1.04. So
|
||||
* anything under ~70 held rows cannot tell a real reversal from noise.
|
||||
*
|
||||
* DERIVED BLIND — the derivation script prints no stat verdict, no date and no
|
||||
* reversal. It ran, and this constant was committed, before any stat was
|
||||
* re-read. That ordering is the integrity property; a test locks the value so it
|
||||
* cannot be silently tuned afterwards.
|
||||
*/
|
||||
const LODO_MIN_HELD_ROWS = 70;
|
||||
const LODO_THRESHOLD_BASIS = Object.freeze({
|
||||
pooled_rows: 3417,
|
||||
per_row_brier_diff_sd: 0.09816,
|
||||
pooled_effect_abs_mean: 0.01175,
|
||||
rule: 'n* = (SD(d) / |effect|)^2',
|
||||
derived_blind: true,
|
||||
});
|
||||
/** The ORIGINAL floor, correctly scoped: promotion, not deploy. */
|
||||
const PROMOTION_DATE_CLUSTERS = 40;
|
||||
|
||||
@@ -107,4 +144,4 @@ function createRegistry(initial = {}) {
|
||||
return { deploy, reverify, serves, get, all, log: () => log.slice() };
|
||||
}
|
||||
|
||||
module.exports = { createRegistry, STATUS, PROMOTION_DATE_CLUSTERS };
|
||||
module.exports = { createRegistry, STATUS, PROMOTION_DATE_CLUSTERS, LODO_MIN_HELD_ROWS, LODO_THRESHOLD_BASIS };
|
||||
|
||||
@@ -280,10 +280,23 @@ async function loadPitcherArsenals(sport) {
|
||||
* number. PROVISIONAL: auto-demoted the first time the held-out interval stops
|
||||
* excluding zero or the favourite over-prediction flips sign.
|
||||
*
|
||||
* hits / rbi / runs are deliberately ABSENT — each fails LODO. See
|
||||
* specs/lodo-provisional-calibration.md.
|
||||
* Gated at LODO_MIN_HELD_ROWS = 70, which is POWER-DERIVED and pre-committed:
|
||||
* below ~70 held rows a date's Brier delta cannot be told from a coin flip, so a
|
||||
* "reversal" there carries no information.
|
||||
*
|
||||
* hits was WITHDRAWN at 6ae11f1 and is RESTORED here. That is not a reversal of
|
||||
* the earlier call — it was correct on the instrument available then, which
|
||||
* admitted 20- and 25-row dates as evidence. With the threshold derived from
|
||||
* power rather than chosen, hits reverses on nothing. The restoration came
|
||||
* through the gate, not around it.
|
||||
*
|
||||
* rbi and runs remain ABSENT, and their failures are NOT underpowered: each
|
||||
* reverses on a date comfortably above the threshold (rbi 2026-08-01 n=99; runs
|
||||
* 2026-08-01 n=86 and 2026-08-05 n=244). Those are DATE-DRIVEN failures — no
|
||||
* threshold and no further accrual rescues them, and isotonic is fitting
|
||||
* day-structure. Routed to the low-parameter calibrator queue.
|
||||
*/
|
||||
const CALIBRATION_DEPLOYED = Object.freeze(['total_bases']);
|
||||
const CALIBRATION_DEPLOYED = Object.freeze(['hits', 'total_bases']);
|
||||
|
||||
async function runSnapshot(sport, opts = {}) {
|
||||
const sp = String(sport || '').toLowerCase();
|
||||
|
||||
Reference in New Issue
Block a user