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:
Kev
2026-07-20 05:41:41 -04:00
parent b76e35f575
commit b33612675d
5 changed files with 191 additions and 19 deletions
+25 -5
View File
@@ -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/);
});
});
+3 -3
View File
@@ -77,14 +77,14 @@ describe('resolveOutcome', () => {
// previous ET day). Verified live: Freddie Freeman's 97-game log has no
// 2026-07-18 because he played the 17th and 19th. Voiding on absence
// destroyed real results.
test('player absent on a final game → UNKNOWN, never void (date may be wrong)', async () => {
test('player absent on a FINAL game → VOID (dates proven correct; DNP is real)', async () => {
const r = await src.resolveOutcome({
...base,
getFullLog: async () => [{ date: '2026-07-16', stat: { doubles: 0 } }],
getSchedule: async () => [{ status: 'Final' }],
});
expect(r.state).toBe('unknown');
expect(r.reason).toBe('player_absent_unconfirmed');
expect(r.state).toBe('void');
expect(r.reason).toBe('player_dnp');
});
test('postponed game → VOID with the postponed reason', async () => {