Files
vyndr/scripts/validate-grade-fix.js
builtbykev 4bd770480a Grade-fix validation script + finding doc (before-state, blast-radius SQL, plan)
scripts/validate-grade-fix.js checks the live MLB snapshot for the three
degradation signatures (projection=0, edge=100 cluster, grade/conf disagreement)
— run after the next 14:00 UTC regrade to fingerprint the fix. The finding doc
now records root causes, fixes (commits 888d103/9fc4edf), the blast-radius SQL
(box can't reach Supabase directly), and the shared-path note for NBA/WNBA.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 03:18:36 -04:00

29 lines
2.1 KiB
JavaScript

#!/usr/bin/env node
// Post-fix re-grade validation (work-order item #4). Fetches the LIVE MLB
// snapshot and checks the three degradation signatures are gone:
// 1. projection == 0 count -> expect 0 (the nine vanish; those props refuse)
// 2. edge_pct continuous, not the {20,60,100,140} degenerate cluster, no 100
// 3. grade <-> confidence agreement -> expect 25/25 (letter == band(conf))
// Run: node scripts/validate-grade-fix.js (needs outbound to api.vyndr.app)
const https = require('https');
const BANDS = require('../src/services/python/data/grade_thresholds.json').grade_scale;
const four = (g) => { const s = String(g||'').toUpperCase(); return s === 'A+' ? 'A+' : (s[0]||null); };
function bandOf(conf) { const p = conf/100; for (const [k,b] of Object.entries(BANDS)) if (p>=b.low && p<=b.high) return k; return null; }
function get(url) { return new Promise((res,rej) => https.get(url,(r)=>{let d='';r.on('data',c=>d+=c);r.on('end',()=>res(JSON.parse(d)));}).on('error',rej)); }
(async () => {
const d = await get('https://api.vyndr.app/api/snapshot/mlb');
const g = d.grades || [];
const proj0 = g.filter(x => x.projection === 0 || x.projection == null).length;
const edges = g.map(x => x.edge_pct).filter(v => v != null);
const has100 = edges.includes(100);
const distinctEdges = [...new Set(edges)].sort((a,b)=>a-b);
let agree = 0;
for (const x of g) { const b = bandOf(x.confidence); if (b && four(b) === four(x.grade)) agree++; }
console.log('=== AFTER (post-fix) — live MLB snapshot ===');
console.log('updated_at:', d.updated_at, '| grades:', g.length);
console.log('1. projection==0/null:', proj0, proj0 === 0 ? 'PASS' : 'FAIL (expect 0)');
console.log('2. edge_pct distinct:', distinctEdges.length, 'values; contains 100:', has100, (!has100 && distinctEdges.length > 4) ? 'PASS' : 'CHECK');
console.log(' values:', distinctEdges.slice(0, 20));
console.log('3. grade<->confidence agree:', agree + '/' + g.length, agree === g.length ? 'PASS' : 'FAIL');
})().catch(e => { console.error('validation error:', e.message); process.exit(1); });