Heal execute: quarantine markers, re-enable DNP voiding, two exclusion scopes
Order 2 Phases 2 + 4. Pre-heal rollback point secured first: vyndr-20260720-093821.dump (856,890 bytes) VERIFIED ON THE BOX, not just exit 0. MIGRATION 027 — two DISTINCT exclusion scopes, deliberately separate: - quarantine_reason: the row's GRADE is untrustworthy (wrong_opponent_grade). The row REMAINS a real public settled result — the bet happened, the outcome is real — but it must never train or validate, so getModelAggregate now excludes it from the denominator alongside void/unrecoverable. - analysis_flags: the row is VALID for settlement and the record but unattributable for PER-GAME analysis (doubleheader dates). Explicitly NOT filtered from aggregates. Collapsing these would either wrongly drop 166 doubleheader rows from the record or wrongly keep 25 wrong-opponent grades inside model validation. Tests assert both directions, including that analysis_flags is NOT filtered. Also adds re_settled_at + settlement_source to model_snapshots. DNP VOIDING RE-ENABLED — reversing my own Order 1.5 disable, with scrutiny, because its premise was FALSE. Order 1.5 assumed a missing player row meant the row's DATE was wrong. The Phase 0 dry-run disproved it: across every bindable row the stored date matched a real game (MIS-DATED: 0), and the players I had cited as counter-evidence were genuine DNPs on their true dates (Freeman 07-18; Kwan/Hedges/Davis 07-17 — their teams played, they did not). The evidence is positive: games FINAL + no line in a full-season log = no bet existed. I got this wrong twice tonight in opposite directions; the dry-run is what caught it. Recording the reasoning in the code so the next reader sees why the flag flipped back. Suite 282/3386 green, build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
This commit is contained in:
@@ -179,7 +179,7 @@ describe('settleLedger — outcome + CLV vs the real result', () => {
|
||||
|
||||
// Session 64 — BEHAVIOUR CHANGED ON PURPOSE. A missing game-log row used to
|
||||
// mean "pending forever". It now depends on what the day's games actually did.
|
||||
test('no row + game FINAL → NOT voided (date may be wrong); attempts bump', async () => {
|
||||
test('no row + game FINAL → VOID (genuine DNP; dates verified correct)', async () => {
|
||||
const sb = fakeSb();
|
||||
sb._state.selectResults = [
|
||||
[{ id: 'r1' }],
|
||||
@@ -190,10 +190,9 @@ describe('settleLedger — outcome + CLV vs the real result', () => {
|
||||
sb, getPlayerStats, now: () => NOW, beforeDate: '2026-07-10',
|
||||
getSchedule: async () => [{ status: 'Final' }],
|
||||
});
|
||||
expect(res.voided).toBe(0);
|
||||
expect(res.voided).toBe(1);
|
||||
expect(res.settled).toBe(0);
|
||||
expect(res.pending).toBe(1);
|
||||
expect(sb._calls.updates[0].values.outcome).toBeUndefined();
|
||||
expect(sb._calls.updates[0].values).toMatchObject({ outcome: 'void', settlement_source: 'player_dnp' });
|
||||
});
|
||||
|
||||
test('no row + game NOT FINAL → stays pending, never voided', async () => {
|
||||
@@ -223,7 +222,7 @@ describe('settleLedger — outcome + CLV vs the real result', () => {
|
||||
const getPlayerStats = async () => ({ found: true, last10: [] });
|
||||
const res = await ledger.settleLedger('mlb', {
|
||||
sb, getPlayerStats, now: () => NOW, beforeDate: '2026-07-10',
|
||||
getSchedule: async () => [{ status: 'Final' }], // player absent, unconfirmed
|
||||
getSchedule: async () => [], // nothing knowable → unrecoverable at the cap
|
||||
});
|
||||
expect(res.unrecoverable).toBe(1);
|
||||
expect(sb._calls.updates[0].values).toMatchObject({ outcome: 'unrecoverable' });
|
||||
@@ -415,3 +414,24 @@ describe('getModelAggregate — clv_distribution (n>=20 gate lives in the servic
|
||||
expect(clvBucketIndex(null)).toBe(-1); // absent beats wrong
|
||||
});
|
||||
});
|
||||
|
||||
describe('Session 64 Order 2 — quarantine vs analysis_flags (two scopes)', () => {
|
||||
const src = require('fs').readFileSync(require('path').join(__dirname, '..', '..', 'src', 'services', 'ledgerService.js'), 'utf8');
|
||||
|
||||
test('getModelAggregate EXCLUDES quarantined rows from the denominator', () => {
|
||||
expect(src).toMatch(/\.is\('quarantine_reason', null\)/);
|
||||
});
|
||||
|
||||
test('getModelAggregate does NOT filter analysis_flags — those rows settle validly', () => {
|
||||
// Doubleheader rows are unattributable per-GAME but their day-total
|
||||
// settlement is real; excluding them would wrongly shrink the record.
|
||||
expect(src).not.toMatch(/\.is\('analysis_flags', null\)/);
|
||||
expect(src).not.toMatch(/analysis_flags.*denominator/);
|
||||
});
|
||||
|
||||
test('quarantine sits alongside void/unrecoverable, not instead of them', () => {
|
||||
const agg = src.slice(src.indexOf('async function getModelAggregate'));
|
||||
expect(agg).toMatch(/void","unrecoverable/);
|
||||
expect(agg).toMatch(/quarantine_reason/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user