411cb6f196
POST /api/scan/parlay — authenticated parlay analysis: - Supabase JWT auth middleware (auth.getUser verification) - 5 correlation types detected between legs (same_game, same_team, same_player_conflicting, positive_correlation, blowout_cascade) - Overall parlay grading (A/B/C/D) with correlation penalty adjustments - Free tier: 5 scans/month, atomic scan count increment - Scan 5: full analysis + personalized upgrade pitch - Scan 6+: 403 block with upgrade pitch - Pitch personalization from scan history (top stats, grades, tier rec) - DB writes: picks + scan_sessions per scan 30 new tests, 158 total (131 Node.js + 27 Python), all passing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
103 lines
4.0 KiB
JavaScript
103 lines
4.0 KiB
JavaScript
const { detectCorrelations } = require('../../src/services/correlationEngine');
|
|
|
|
function makeLeg(overrides = {}) {
|
|
return {
|
|
player: 'Nikola Jokic',
|
|
stat_type: 'points',
|
|
direction: 'over',
|
|
line: 26.5,
|
|
grade: 'B',
|
|
confidence: 70,
|
|
_team: 'DEN',
|
|
_gameTime: '2026-03-21T19:00:00Z',
|
|
reasoning: { steps: { season_avg: { value: 26.3 } } },
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('correlationEngine', () => {
|
|
test('returns empty array when no correlations exist', () => {
|
|
const legs = [
|
|
makeLeg({ player: 'Nikola Jokic', _team: 'DEN', _gameTime: '2026-03-21T19:00:00Z' }),
|
|
makeLeg({ player: 'Jayson Tatum', _team: 'BOS', _gameTime: '2026-03-21T20:00:00Z' }),
|
|
];
|
|
const flags = detectCorrelations(legs, []);
|
|
expect(flags).toEqual([]);
|
|
});
|
|
|
|
test('detects same_game_opposing_players', () => {
|
|
const legs = [
|
|
makeLeg({ player: 'Nikola Jokic', stat_type: 'points', direction: 'over', _team: 'DEN', _gameTime: 'game1' }),
|
|
makeLeg({ player: 'LeBron James', stat_type: 'points', direction: 'over', _team: 'LAL', _gameTime: 'game1' }),
|
|
];
|
|
const flags = detectCorrelations(legs, []);
|
|
expect(flags).toHaveLength(1);
|
|
expect(flags[0].type).toBe('same_game_opposing_players');
|
|
expect(flags[0].impact).toBe('minor_negative');
|
|
});
|
|
|
|
test('detects same_game_same_team', () => {
|
|
const legs = [
|
|
makeLeg({ player: 'LeBron James', _team: 'LAL', _gameTime: 'game1' }),
|
|
makeLeg({ player: 'Anthony Davis', _team: 'LAL', _gameTime: 'game1' }),
|
|
];
|
|
const flags = detectCorrelations(legs, []);
|
|
expect(flags).toHaveLength(1);
|
|
expect(flags[0].type).toBe('same_game_same_team');
|
|
expect(flags[0].impact).toBe('minor_negative');
|
|
});
|
|
|
|
test('detects same_player_conflicting (opposite directions)', () => {
|
|
const legs = [
|
|
makeLeg({ player: 'Nikola Jokic', stat_type: 'points', direction: 'over' }),
|
|
makeLeg({ player: 'Nikola Jokic', stat_type: 'points', direction: 'under' }),
|
|
];
|
|
const flags = detectCorrelations(legs, []);
|
|
expect(flags).toHaveLength(1);
|
|
expect(flags[0].type).toBe('same_player_conflicting');
|
|
expect(flags[0].impact).toBe('major_negative');
|
|
});
|
|
|
|
test('detects positive_correlation (same player, complementary same direction)', () => {
|
|
const legs = [
|
|
makeLeg({ player: 'Nikola Jokic', stat_type: 'points', direction: 'over' }),
|
|
makeLeg({ player: 'Nikola Jokic', stat_type: 'rebounds', direction: 'over' }),
|
|
];
|
|
const flags = detectCorrelations(legs, []);
|
|
expect(flags).toHaveLength(1);
|
|
expect(flags[0].type).toBe('positive_correlation');
|
|
expect(flags[0].impact).toBe('positive');
|
|
});
|
|
|
|
test('detects blowout_cascade (2+ legs, high spread)', () => {
|
|
const legs = [
|
|
makeLeg({ player: 'Nikola Jokic', _team: 'DEN', _gameTime: 'game1' }),
|
|
makeLeg({ player: 'LeBron James', _team: 'LAL', _gameTime: 'game1', stat_type: 'rebounds' }),
|
|
];
|
|
const spreads = [
|
|
{ home_team: 'DEN', away_team: 'LAL', game_time: 'game1', home_spread: -12, book: 'draftkings' },
|
|
];
|
|
const flags = detectCorrelations(legs, spreads);
|
|
const cascade = flags.find((f) => f.type === 'blowout_cascade');
|
|
expect(cascade).toBeDefined();
|
|
expect(cascade.impact).toBe('major_negative');
|
|
});
|
|
|
|
test('handles single leg (no correlations possible)', () => {
|
|
const legs = [makeLeg()];
|
|
const flags = detectCorrelations(legs, []);
|
|
expect(flags).toEqual([]);
|
|
});
|
|
|
|
test('multiple correlations can fire simultaneously', () => {
|
|
const legs = [
|
|
makeLeg({ player: 'Nikola Jokic', stat_type: 'points', direction: 'over', _team: 'DEN', _gameTime: 'game1' }),
|
|
makeLeg({ player: 'LeBron James', stat_type: 'points', direction: 'over', _team: 'LAL', _gameTime: 'game1' }),
|
|
makeLeg({ player: 'Anthony Davis', stat_type: 'rebounds', direction: 'over', _team: 'LAL', _gameTime: 'game1' }),
|
|
];
|
|
const flags = detectCorrelations(legs, []);
|
|
// Jokic vs LeBron = same_game_opposing_players, LeBron vs AD = same_game_same_team
|
|
expect(flags.length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
});
|