f1956dc953
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>
28 lines
1.3 KiB
JavaScript
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);
|
|
});
|
|
});
|