CLV instrument repair: fix attachClosingProb read + recoverable market_unavailable
The closing_prob funnel collapsed 100k priced captures -> 59 usable. Root cause (VERIFIED against prod, join key is PERFECT with 0 mismatches): - attachClosingProb read closing_captures with .limit(50000) and NO ORDER BY on a 730k-row table that is 86% refusal rows -> saw ~7% for MLB, missed most priced closes and declared 200+ rows closeless that HAD a capture. - market_unavailable_reason was write-once/terminal, so a row wrongly declared (truncated read / premature declaration before the capture was visible) could never recover even once its genuine capture existed. 298 rows (204 MLB + 94 WNBA) were stuck this way. Fix (CLV computation only — no grade/locked_odds/outcome touched): - Read ONLY priced captures (missed_reason IS NULL, both odds NOT NULL), scoped to the candidate rows' game_dates -> small AND complete, no arbitrary truncation. - Drop the market_unavailable exclusion from candidates; make it a re-checkable absence: a genuine close now UPGRADES the row (writes closing_prob, clears the verdict). closing_prob stays write-once (first true close wins). No capture + past game -> still declared absent (honest). No churn on already-absent rows. - New internal trigger POST /api/internal/ledger/attach-closing[/:sport] for backfill + verification (scheduler already runs attach per tick). Recovers ~312 usable closes (59 -> ~371), MLB included. Capture itself was healthy all along (94.9% MLB / 95.8% WNBA per-prop coverage). Full suite 3835 green (17/17 instrument tests incl. 2 new recovery cases), web build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
This commit is contained in:
@@ -78,6 +78,7 @@ describe('attachClosingProb — the market half', () => {
|
||||
from: (t) => ({
|
||||
select: () => ({
|
||||
is: function () { return this; }, eq: function () { return this; },
|
||||
not: function () { return this; }, in: function () { return this; },
|
||||
limit: async () => ({ data: t === 'ledger_entries' ? rows : caps, error: null }),
|
||||
}),
|
||||
update: (patch) => ({ eq: async (_c, id) => { updates.push({ id, patch }); return { error: null }; } }),
|
||||
@@ -150,6 +151,30 @@ describe('attachClosingProb — the market half', () => {
|
||||
const fn = src.slice(src.indexOf('async function attachClosingProb'));
|
||||
expect(fn).toMatch(/\.is\('closing_prob', null\)/);
|
||||
});
|
||||
|
||||
// CLV instrument repair — the write-once verdict on market_unavailable_reason
|
||||
// was permanent, so a row wrongly declared closeless (truncated read /
|
||||
// premature declaration) could never recover even though its capture existed.
|
||||
it('RECOVERS a row previously declared market_unavailable when a real close now exists', async () => {
|
||||
const sb = makeSb({
|
||||
rows: [{ ...ROW, market_unavailable_reason: 'no_usable_close' }],
|
||||
caps: [{ player_key: 'josh bell', stat: 'hits', side: 'over', game_date: '2026-07-20', over_odds: -210, under_odds: 170, captured_at: '2026-07-20T22:50:00Z' }],
|
||||
});
|
||||
const out = await ledger.attachClosingProb('mlb', { sb, beforeDate: '2026-07-21' });
|
||||
expect(out.updated).toBe(1);
|
||||
expect(out.recovered).toBe(1);
|
||||
expect(sb.updates[0].patch.closing_prob).toBe(0.647);
|
||||
// the buggy verdict is CLEARED, not left stale beside a real close.
|
||||
expect(sb.updates[0].patch.market_unavailable_reason).toBeNull();
|
||||
});
|
||||
|
||||
it('leaves an already-declared row untouched when there is STILL no capture (no churn, no re-write)', async () => {
|
||||
const sb = makeSb({ rows: [{ ...ROW, market_unavailable_reason: 'no_usable_close' }], caps: [] });
|
||||
const out = await ledger.attachClosingProb('mlb', { sb, beforeDate: '2026-07-21' });
|
||||
expect(out.updated).toBe(0);
|
||||
expect(out.absent).toBe(0);
|
||||
expect(sb.updates).toHaveLength(0); // no write at all
|
||||
});
|
||||
});
|
||||
|
||||
describe('what stays measurable when the market is absent', () => {
|
||||
|
||||
Reference in New Issue
Block a user