Files
builtbykev f1956dc953 Session 50: Complete Parlay Lab (2215 tests)
Correlation-aware combined parlay grading — the Desk-tier differentiator.

- Correlation model (parlayService.js, added to S28 funcs): correlationScore
  (game-aware 0.7/0.4/0.2/0.0), combinedGrade (avg penalized by avgCorr*0.5),
  estimatedPayout (fair-odds product * (1-avgCorr) discount), correlationWarning,
  gradeParlay.
- POST /api/parlay/grade (public, 2-6 legs) -> {combined,correlation,payout,legs}.
  Fixed the Next proxy (was forwarding to /api/scan/parlay).
- ParlayContext: legs gained team/game/archetype; tier-aware maxLegs; auto-grades
  the slip (debounced) when legs>=2 -> live combined/correlation/payout; hasLeg/
  legKey/atCap.
- "+" button on every graded prop: StatStrip onAddLeg/isLegActive, wired by
  vyndr/GameCard via useParlay (builds leg w/ team + game). GradeResultCard feeds
  the same context from the scan page.
- ParlayPanel (replaces legacy ParlayTray): bottom slide-up w/ legs, combined
  grade, correlation warning, est payout, CLEAR ALL + floating leg-count badge.
  Tier-gated: free 2 legs (payout blurred -> Desk upsell), Analyst 4, Desk 6.

Backend 2185 -> 2215 tests (+30), 187 suites. Web build clean (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:25:14 -04:00

28 lines
1.3 KiB
JavaScript

// Session 50 — POST /api/parlay/grade (the Parlay Lab endpoint).
const request = require('supertest');
const app = require('../../src/app');
const legs = (n) => Array.from({ length: n }, (_, i) => ({ player: `P${i}`, team: i % 2 ? 'BOS' : 'NYY', game: 'NYY@BOS', stat: 'hits', line: 1.5, grade: i % 2 ? 'B+' : 'A' }));
describe('POST /api/parlay/grade', () => {
it('returns combined grade + correlation + payout for a valid parlay', async () => {
const res = await request(app).post('/api/parlay/grade').send({ legs: legs(3), betAmount: 10 });
expect(res.status).toBe(200);
expect(res.body.combined.grade).toBeTruthy();
expect(typeof res.body.correlation.avg).toBe('number');
expect(res.body.payout.amount).toBeGreaterThan(0);
expect(res.body.legs).toHaveLength(3);
});
it('handles 2 and 6 legs', async () => {
expect((await request(app).post('/api/parlay/grade').send({ legs: legs(2) })).status).toBe(200);
expect((await request(app).post('/api/parlay/grade').send({ legs: legs(6) })).status).toBe(200);
});
it('rejects < 2 or > 6 legs (400)', async () => {
expect((await request(app).post('/api/parlay/grade').send({ legs: legs(1) })).status).toBe(400);
expect((await request(app).post('/api/parlay/grade').send({ legs: legs(7) })).status).toBe(400);
});
});