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:
@@ -405,6 +405,33 @@ router.post('/ledger/settle', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/internal/ledger/attach-closing[/:sport] — CLV instrument repair.
|
||||
* Attach the de-vigged closing probability from `closing_captures` onto locked
|
||||
* model-record rows. Recovers rows a truncated read / premature verdict wrongly
|
||||
* declared closeless (market_unavailable is a re-checkable absence, not terminal).
|
||||
* Idempotent: `closing_prob` is write-once; a row with a genuine close never
|
||||
* changes, and a row with no capture stays honestly absent.
|
||||
*/
|
||||
async function attachClosingHandler(req, res) {
|
||||
const ledger = require('../services/ledgerService');
|
||||
const ALL = ['mlb', 'wnba', 'nba', 'soccer'];
|
||||
const only = String(req.params.sport || '').toLowerCase();
|
||||
const sports = only ? [only] : ALL;
|
||||
try {
|
||||
const results = {};
|
||||
for (const sp of sports) results[sp] = await ledger.attachClosingProb(sp, { limit: Number(req.query.limit) || undefined });
|
||||
return res.json({ ok: true, results });
|
||||
} catch (err) {
|
||||
const message = err && err.message ? err.message : String(err);
|
||||
console.error('[internal/ledger/attach-closing] failed:', message);
|
||||
return res.status(500).json({ ok: false, error: message });
|
||||
}
|
||||
}
|
||||
// Express 5 (path-to-regexp v8) has no `/:sport?` optional param — register both.
|
||||
router.post('/ledger/attach-closing', attachClosingHandler);
|
||||
router.post('/ledger/attach-closing/:sport', attachClosingHandler);
|
||||
|
||||
/**
|
||||
* POST /api/internal/newsletter/send (Session S7, a1) — assemble today's
|
||||
* VYNDR REPORT from the pipeline (snapshot signals + streak lens + the
|
||||
|
||||
Reference in New Issue
Block a user