fb0010222d
missed_window means 'this capture pass ran after first pitch'. It is a fact about our cron cadence, not the market: once a game starts the same prop emits a fresh refusal every ~20 minutes for the rest of the night, per book, per side. Measured over 7 days of production that is 332,608 rows/day - 83.1% of all closing_captures writes, ~66 MB/day - and since B1 filtered both readers, nothing reads them. The filter lives in persist(), not buildCaptureRows(), and that is the whole trick: the caller computes the capture-rate alarm from the full in-memory array, so filtering at build time would have blinded the ops alarm to the exact condition it exists to catch. captureRateAlarm is pure; a test asserts the caller still passes the full array, and that a 10-priced/90-late pass still fires at 0.10. Narrow by design: one_sided_price still persists (liquidity signal), priced captures unchanged, and the rare fault refusals still persist because each names a pipeline fault worth seeing. A missed_window row carrying a price is kept. Growth drops 400,469 -> 67,862 rows/day. The 4.2M historical rows are now static, so the cleanup is a calm decision rather than a race. Nothing deleted. Grade untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
155 lines
6.2 KiB
JavaScript
155 lines
6.2 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* B2 — stop persisting refusals nothing reads.
|
|
*
|
|
* `missed_window` is a fact about our cron cadence, not the market: the same
|
|
* prop generates a fresh refusal every ~20 minutes for the rest of the night,
|
|
* per book, per side. 4.2M rows, ~465k/day, read by nothing since B1.
|
|
*
|
|
* The cut must be NARROW — priced captures and `one_sided_price` are read, and
|
|
* the capture-rate alarm must keep seeing every refusal even though we no longer
|
|
* store them.
|
|
*/
|
|
|
|
const cap = require('../../src/services/closingCapture');
|
|
|
|
const capture = (calls) => ({
|
|
getClient: () => ({
|
|
from() {
|
|
return { insert: async (chunk) => { calls.push(...chunk); return { error: null }; } };
|
|
},
|
|
}),
|
|
});
|
|
|
|
const priced = (p = {}) => ({
|
|
sport: 'mlb', player_key: 'a', stat: 'hits', side: 'over', game_date: '2026-08-12',
|
|
book: 'draftkings', line: 0.5, over_odds: -120, under_odds: 100, missed_reason: null, ...p,
|
|
});
|
|
const refusal = (reason, p = {}) => ({
|
|
sport: 'mlb', player_key: 'a', stat: 'hits', side: 'over', game_date: '2026-08-12',
|
|
book: 'draftkings', line: null, over_odds: null, under_odds: null, missed_reason: reason, ...p,
|
|
});
|
|
|
|
describe('what persists and what does not', () => {
|
|
it('a PRICED capture persists', async () => {
|
|
const calls = [];
|
|
const out = await cap.persist([priced()], capture(calls));
|
|
expect(out.written).toBe(1);
|
|
expect(calls).toHaveLength(1);
|
|
expect(out.skipped_unread).toBe(0);
|
|
});
|
|
|
|
it('a ONE_SIDED_PRICE refusal persists — it is a real liquidity signal', async () => {
|
|
const calls = [];
|
|
const out = await cap.persist([refusal('one_sided_price')], capture(calls));
|
|
expect(out.written).toBe(1);
|
|
expect(calls[0].missed_reason).toBe('one_sided_price');
|
|
});
|
|
|
|
it('a MISSED_WINDOW refusal is NOT persisted', async () => {
|
|
const calls = [];
|
|
const out = await cap.persist([refusal('missed_window')], capture(calls));
|
|
expect(out.written).toBe(0);
|
|
expect(out.skipped_unread).toBe(1);
|
|
expect(calls).toHaveLength(0);
|
|
});
|
|
|
|
it('a mixed pass writes exactly the readable rows', async () => {
|
|
const calls = [];
|
|
const rows = [
|
|
priced(), priced({ player_key: 'b' }),
|
|
refusal('one_sided_price', { player_key: 'c' }),
|
|
refusal('missed_window', { player_key: 'd' }),
|
|
refusal('missed_window', { player_key: 'e' }),
|
|
];
|
|
const out = await cap.persist(rows, capture(calls));
|
|
expect(out.attempted).toBe(5);
|
|
expect(out.written).toBe(3);
|
|
expect(out.skipped_unread).toBe(2);
|
|
expect(calls.map((r) => r.player_key).sort()).toEqual(['a', 'b', 'c']);
|
|
});
|
|
|
|
it('OTHER refusals still persist — each names a pipeline fault worth seeing', async () => {
|
|
for (const reason of ['unbound_game_time', 'doubleheader_ambiguous', 'fetch_failed']) {
|
|
const calls = [];
|
|
const out = await cap.persist([refusal(reason)], capture(calls));
|
|
expect(out.written).toBe(1);
|
|
}
|
|
});
|
|
|
|
it('a missed_window row that somehow CARRIES a price is kept — do not over-cut', async () => {
|
|
const calls = [];
|
|
const out = await cap.persist([refusal('missed_window', { over_odds: -110, under_odds: -110 })], capture(calls));
|
|
expect(out.written).toBe(1);
|
|
});
|
|
|
|
it('the flag restores the old behaviour', async () => {
|
|
const calls = [];
|
|
const out = await cap.persist([refusal('missed_window')], { ...capture(calls), persistMissedWindow: true });
|
|
expect(out.written).toBe(1);
|
|
});
|
|
|
|
it('a pass that is ALL unread refusals never opens a client', async () => {
|
|
let opened = false;
|
|
const out = await cap.persist([refusal('missed_window'), refusal('missed_window')], {
|
|
getClient: () => { opened = true; return null; },
|
|
});
|
|
expect(opened).toBe(false);
|
|
expect(out.skipped_unread).toBe(2);
|
|
expect(out.written).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('THE OPS SIGNAL SURVIVES — the alarm never read the stored rows', () => {
|
|
it('captureRateAlarm is pure: it takes counts, not a database', () => {
|
|
const late = cap.captureRateAlarm({ eligible: 100, captured: 10 });
|
|
expect(late.alarm).toBe(true);
|
|
expect(late.reason).toMatch(/unrecoverable/);
|
|
const fine = cap.captureRateAlarm({ eligible: 100, captured: 90 });
|
|
expect(fine.alarm).toBe(false);
|
|
});
|
|
|
|
it('still fires on a late pass whose refusals are now dropped', async () => {
|
|
// 100 props, 10 priced, 90 post-first-pitch: nothing but the 10 persists,
|
|
// and the alarm must still see 10/100.
|
|
const rows = [
|
|
...Array.from({ length: 10 }, (_, i) => priced({ player_key: `p${i}` })),
|
|
...Array.from({ length: 90 }, (_, i) => refusal('missed_window', { player_key: `m${i}` })),
|
|
];
|
|
const calls = [];
|
|
const out = await cap.persist(rows, capture(calls));
|
|
expect(out.written).toBe(10);
|
|
expect(out.skipped_unread).toBe(90);
|
|
|
|
// The caller computes the alarm from the FULL array, not from what persisted.
|
|
const captured = rows.filter((r) => !r.missed_reason).length;
|
|
const al = cap.captureRateAlarm({ eligible: rows.length, captured });
|
|
expect(al.alarm).toBe(true);
|
|
expect(al.rate).toBeCloseTo(0.10, 5);
|
|
});
|
|
|
|
it('the caller still passes the FULL array to the alarm', () => {
|
|
const src = require('fs').readFileSync(
|
|
require('path').join(__dirname, '..', '..', 'src/services/intradayRefreshService.js'), 'utf8');
|
|
expect(src).toMatch(/captureRateAlarm\(\{ eligible: rows\.length, captured \}\)/);
|
|
expect(src).toMatch(/const captured = rows\.filter\(\(r\) => !r\.missed_reason\)\.length/);
|
|
});
|
|
});
|
|
|
|
describe('buildCaptureRows is UNCHANGED — the alarm depends on it', () => {
|
|
it('still emits missed_window rows in memory', () => {
|
|
const started = { player: 'A', stat_type: 'hits', game_time: '2026-08-12T00:00:00Z', book: 'dk' };
|
|
const rows = cap.buildCaptureRows('mlb', [started], { now: new Date('2026-08-12T02:00:00Z') });
|
|
expect(rows).toHaveLength(2); // both sides
|
|
expect(rows.every((r) => r.missed_reason === 'missed_window')).toBe(true);
|
|
});
|
|
|
|
it('isUnreadRefusal identifies exactly those rows', () => {
|
|
expect(cap.isUnreadRefusal(refusal('missed_window'))).toBe(true);
|
|
expect(cap.isUnreadRefusal(refusal('one_sided_price'))).toBe(false);
|
|
expect(cap.isUnreadRefusal(priced())).toBe(false);
|
|
expect(cap.isUnreadRefusal(null)).toBe(false);
|
|
});
|
|
});
|