feat: Feature 1.3 — Prop Analysis Engine with 6-step grading pipeline

Core intelligence for BetonBLK prop analysis:
- POST /api/analyze/prop — single prop analysis
- POST /api/analyze/batch — multi-prop analysis for parlay scanner
- 6-step pipeline: season avg → recent form → situational splits →
  cross-book lines → kill conditions → grade (A/B/C/D)
- 6 kill conditions: low_minutes, small_sample, b2b_high_usage,
  blowout_risk, split_conflict, no_opponent_data
- Composite scoring with confidence (30-95), bonuses, penalties
- Added spreads market to Odds API fetch (zero extra credits)
- Full reasoning output with step-by-step breakdown

36 new tests (unit + integration), 128 total across all features

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-03-21 11:41:18 -04:00
parent 3da1b4242c
commit c8c0962e56
16 changed files with 1560 additions and 40 deletions
+7
View File
@@ -87,3 +87,10 @@ Outcome level (nested under market.outcomes[]):
- Clerk — better DX but adds a vendor, costs more at scale.
- Auth-agnostic (own users table, plug in later) — rejected: delays RLS setup, more migration work later.
- Consequences: All table access goes through RLS. Service role key bypasses RLS for admin/backend operations. Anon key used for client-side auth flows.
### DECISION-005: Spreads Market Added to Odds API Fetch (Feature 1.3)
- Date: 2026-03-21
- Context: Feature 1.3 kill condition `blowout_risk` needs game point spread. The Odds API supports a `spreads` market alongside player props.
- Decision: Add `spreads` to the comma-separated markets list in the existing per-event API call. Zero additional API credits — the spreads data rides alongside the player prop data in the same request.
- Alternatives considered: Skip blowout_risk for now — rejected because it's a high-value kill condition that prevents bad bets on blowout games.
- Consequences: `oddsService.js` now returns a `spreads` array alongside `props`. The `extractSpreads()` function in `oddsNormalizer.js` parses game-level spread data separately from player-level props.