-- p_win RECALIBRATION — per-sport, time-forward holdout. Measure-only. -- specs/pwin-recalibration-holdout.md. NOTE: reliability is measured on BUCKETS -- (predicted vs actual rate, n-weighted). mean|p-outcome| on 0/1 rows is NOT -- calibration — it is noise-dominated individual error. with r as ( select sport, game_date, p_win::numeric p, (outcome='hit')::int won, ntile(2) over (partition by sport order by game_date) half from ledger_entries where user_id is null and outcome in ('hit','miss') and p_win is not null ), tagged as ( select *, case when half=1 then 'train' else 'holdout' end split, width_bucket(p,0.1,1.0,5) bkt, ln(p/(1-p)) lg from r ), tb as ( -- TRAIN buckets = the fit select sport, bkt, count(*) n, avg(lg) mean_lg, avg(won::numeric) rate from tagged where split='train' group by sport, bkt having count(*) >= 8 ), platt as ( select sport, regr_slope(ln(greatest(least(rate,.98),.02)/(1-greatest(least(rate,.98),.02))), mean_lg) b, regr_intercept(ln(greatest(least(rate,.98),.02)/(1-greatest(least(rate,.98),.02))), mean_lg) a from tb group by sport ), hb as ( select h.sport, h.bkt, count(*) n, avg(h.p) pred_raw, avg(1/(1+exp(-(pl.a+pl.b*h.lg)))) pred_platt, avg(coalesce(tb.rate,h.p)) pred_iso, avg(h.won::numeric) actual from tagged h join platt pl on pl.sport=h.sport left join tb on tb.sport=h.sport and tb.bkt=h.bkt where h.split='holdout' group by h.sport,h.bkt having count(*) >= 8 ) select sport, sum(n) holdout_n, count(*) buckets, sum(n*abs(pred_raw-actual))/sum(n) raw_reliability_dev, sum(n*abs(pred_platt-actual))/sum(n) platt_reliability_dev, sum(n*abs(pred_iso-actual))/sum(n) isotonic_reliability_dev from hb group by sport order by sport; -- Resolution (ordering survives?) is the corr(pred, won) variant of the same CTEs.