/** * Session 64 — ESPN team-statistics shape regression. * * `results.stats` is an OBJECT carrying `categories[]`, not an array. The old * parser did `for...of` on it and threw "buckets is not iterable" for EVERY * team (verified live: captured 0, errored 15/15). It was invisible because * refreshTeamStats had no production callers until S63 wired it in. */ const { __internals } = require('../../src/services/intelligence/teamStatsCache'); const { normalize } = __internals; // The real ESPN shape, trimmed. const CURRENT_SHAPE = { results: { stats: { categories: [ { name: 'general', stats: [{ name: 'avgRebounds', value: 33.8 }] }, { name: 'offensive', stats: [{ name: 'fieldGoalPct', value: 47.35 }, { name: 'avgPoints', value: 84.2 }] }, { name: 'defensive', stats: [{ name: 'avgBlocks', value: 4.1 }] }, ], }, }, }; // The legacy array shape must keep working. const LEGACY_SHAPE = { results: { stats: [{ stats: [{ name: 'fieldGoalPct', value: 44.0 }] }] }, }; describe('ESPN team-statistics parsing', () => { test('does not throw on the CURRENT object+categories shape', () => { expect(() => normalize('wnba', CURRENT_SHAPE)).not.toThrow(); }); test('extracts stats that exist in the current shape', () => { expect(normalize('wnba', CURRENT_SHAPE).team_fg_pct).toBeCloseTo(47.35, 1); }); test('still parses the legacy array shape', () => { expect(normalize('wnba', LEGACY_SHAPE).team_fg_pct).toBeCloseTo(44.0, 1); }); test('missing/empty payloads degrade to nulls, never throw', () => { expect(() => normalize('wnba', {})).not.toThrow(); expect(() => normalize('wnba', null)).not.toThrow(); }); test('DOCUMENTS THE GAP: the endpoint carries no defensive-strength metric, so opponent rank stays underivable', () => { const n = normalize('wnba', CURRENT_SHAPE); expect(n.defensive_rating).toBeNull(); expect(n.opponent_ppg).toBeNull(); // If this test ever fails because these populate, the opponent-rank factor // can be revived — see specs/audit-data/grade-collapse.md. }); });