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
+49
View File
@@ -0,0 +1,49 @@
const { deltaToSignal, directedDelta } = require('../../src/utils/signals');
describe('signals', () => {
describe('deltaToSignal', () => {
test('0.0-0.49 maps to neutral', () => {
expect(deltaToSignal(0)).toBe('neutral');
expect(deltaToSignal(0.3)).toBe('neutral');
expect(deltaToSignal(0.49)).toBe('neutral');
});
test('0.5-1.99 maps to lean', () => {
expect(deltaToSignal(0.5)).toBe('lean');
expect(deltaToSignal(1.5)).toBe('lean');
expect(deltaToSignal(1.99)).toBe('lean');
});
test('2.0-3.99 maps to bullish', () => {
expect(deltaToSignal(2.0)).toBe('bullish');
expect(deltaToSignal(3.5)).toBe('bullish');
});
test('>= 4.0 maps to strong_bullish', () => {
expect(deltaToSignal(4.0)).toBe('strong_bullish');
expect(deltaToSignal(7.0)).toBe('strong_bullish');
});
test('negative deltas map to bearish equivalents', () => {
expect(deltaToSignal(-0.3)).toBe('neutral');
expect(deltaToSignal(-1.0)).toBe('lean_bearish');
expect(deltaToSignal(-2.5)).toBe('bearish');
expect(deltaToSignal(-5.0)).toBe('strong_bearish');
});
});
describe('directedDelta', () => {
test('over: positive when avg > line', () => {
expect(directedDelta(28, 26, 'over')).toBe(2);
});
test('over: negative when avg < line', () => {
expect(directedDelta(24, 26, 'over')).toBe(-2);
});
test('under: inverts delta', () => {
expect(directedDelta(28, 26, 'under')).toBe(-2);
expect(directedDelta(24, 26, 'under')).toBe(2);
});
});
});