A probability is served because evidence supports it, not because nothing else answered
The band gate was blocked for its `else` branch. It read:
candidate = F(raw)
served = inCertifiedBand(candidate) ? candidate : RAW
and above raw 0.60 the model is measured overconfident — holdout raw 0.80-0.90
predicts 0.843 and realizes 0.639. So "the calibrator is not supported here" was
being answered with a number already proven wrong. Unsupported calibration does
not make raw true.
Four candidates were adjudicated on ONE split — fit on the earliest 60% of
train, decide support on the last 40%, evaluate on a holdout that saw neither:
A low-param 80.2% coverage 0.24374 REFUTED — its extra region
(raw 0.80-0.90) certified on cert (err +0.040, n=55) and
refuted on holdout (served 0.754 vs observed 0.639), and it
leaves a hole at 0.70-0.80 while serving the island above it
B isotonic 91.3% coverage 0.24337 CERTIFIED, contiguous raw [0.50,0.80)
C empirical band 91.3% coverage 0.24335 REFUTED — refitted point-in-time on
current-model hits the realized rates INVERT in grade order
(B+ 0.593 < B 0.614 < C+ 0.623), so the served function steps
down at raw 0.78. Its shipped constants come from 3,417 props
pooled across four batter stats and do not reproduce here
D raw identity 43.1% coverage 0.24866 certifies raw 0.50-0.60 and only there
Raw is candidate D, not a fallback. It earns exactly one region (holdout error
+0.010 on n=1,316), which is why the law is "raw must earn its region" rather
than "raw is never true". B already covers that region, so no hybrid is built.
Above raw 0.80 nothing is certified and nothing is served. That is the region
where raw is most wrong, isotonic over-corrects (cert err -0.093) and its LODO
mapping at 0.95 has spread 0.180. 8.7% of holdout rows land there.
The registry did not need changing. `serves(stat, p)` already tested certified
bands against the RAW p_win — support in the input domain, the correct question —
and returned {serve:false, reason}. It never said "serve raw". The output-space
gate and the raw fallback were both invented downstream in calibrationService.
ACTIVATION IS OFF. PROBABILITY_CONTRACT_SHADOW defaults to 0, CALIBRATION_DEPLOYED
stays frozen empty, and every served field is byte-identical. This releases the
support first, which is the required order. The shadow records raw belief, the
candidate served value, the state, the estimator identity, and what EV/Kelly/VALUE
would be under the actionability law — into its own column, read by nothing.
Migration 051 was applied to production BEFORE retentionService named the column.
PostgREST builds a bulk insert from the first row's shape, so a key whose column
does not exist 400s the whole batch silently — that is how migration 038 took
retention down for three days.
The user-facing contradiction is NOT fixed here. A B+ still says "realized about
66%" beside a confidence of 84. Fixing that is activation, and activation costs
32% of VALUE flags and 46% of Kelly recommendations on the holdout.
Suite 401/401, 5,580 passed, 4 skipped, deterministic across three runs.
Teeth 23/23, each independently injected and restored byte-identically.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQJeAG8vcDoL5zkiaJyVb8
This commit is contained in:
@@ -193,6 +193,12 @@ function rowsFromSides(base, sides, ctx = {}) {
|
||||
// rows is silently dropped for the whole batch. Filled by
|
||||
// `mergeChainShadow` after enrichment; never read by anything served.
|
||||
chain_shadow: null,
|
||||
|
||||
// The PROBABILITY CONTRACT shadow — what the certified serving contract
|
||||
// WOULD serve, beside what was actually served. Declared always, for the
|
||||
// same first-row-shape reason as chain_shadow. Filled by
|
||||
// `mergeProbabilityContract`; SHADOW ONLY, read by nothing served.
|
||||
probability_contract: null,
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
@@ -825,6 +831,61 @@ function mergeEnrichment(rows, enrichedGrades) {
|
||||
* keeping, and a fabricated third of a triple would poison the adjudication this
|
||||
* column exists to enable.
|
||||
*/
|
||||
/**
|
||||
* PROBABILITY CONTRACT SHADOW.
|
||||
*
|
||||
* Records, per row: the raw model belief, what the certified contract would
|
||||
* serve, the state, the estimator identity, and what EV/Kelly/VALUE would be
|
||||
* under the actionability law. It writes to its own column and mutates nothing
|
||||
* a user sees — `p_win`, `confidence`, `grade`, `ev_pct`, `value` and
|
||||
* `takeable` on the row are untouched.
|
||||
*
|
||||
* The raw probability is ALWAYS carried, including on refusals: it is model
|
||||
* evidence, and a row that records only the refusal cannot be re-adjudicated.
|
||||
*/
|
||||
function mergeProbabilityContract(rows, contract) {
|
||||
if (!Array.isArray(rows) || !rows.length) return rows || [];
|
||||
if (!contract || typeof contract.resolve !== 'function') return rows;
|
||||
const pc = require('./model/probabilityContract');
|
||||
|
||||
return rows.map((r) => {
|
||||
let res;
|
||||
try {
|
||||
res = contract.resolve({ model_version: r.model_version, p_win: numOrNull(r.p_win) });
|
||||
} catch { return r; }
|
||||
if (!res) return r;
|
||||
let derived;
|
||||
// The SIDE'S price. A retention row carries book_odds plus both sides'
|
||||
// prices; using the wrong side would price the opposite bet.
|
||||
const sideOdds = r.book_odds != null ? r.book_odds
|
||||
: (String(r.side) === 'under' ? r.under_odds : r.over_odds);
|
||||
try { derived = pc.derivedClaims(res, sideOdds); } catch { derived = null; }
|
||||
return {
|
||||
...r,
|
||||
probability_contract: {
|
||||
raw_model_probability: res.raw_model_probability,
|
||||
served_probability: res.served_probability,
|
||||
probability_state: res.probability_state,
|
||||
estimator_type: res.estimator_type,
|
||||
estimator_version: res.estimator_version,
|
||||
certification_version: res.certification_version,
|
||||
model_version: res.model_version,
|
||||
reason: res.reason,
|
||||
fitted_through: contract.fitted_through || null,
|
||||
fit_n: contract.fit_n || null,
|
||||
derived: derived ? {
|
||||
available: derived.available,
|
||||
ev_pct: derived.ev_pct,
|
||||
kelly_pct: derived.kelly ? derived.kelly.pct : null,
|
||||
value: derived.value,
|
||||
} : null,
|
||||
// SHADOW. Nothing here has been served to anyone.
|
||||
servable: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function mergeChainShadow(rows, shadow) {
|
||||
if (!Array.isArray(rows) || !rows.length) return rows || [];
|
||||
const byKey = shadow && shadow.byKey;
|
||||
@@ -1183,6 +1244,7 @@ module.exports = {
|
||||
createCollector,
|
||||
mergeEnrichment,
|
||||
mergeChainShadow,
|
||||
mergeProbabilityContract,
|
||||
persist,
|
||||
attachLineage,
|
||||
commitPublication,
|
||||
|
||||
Reference in New Issue
Block a user