This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
Unit Test for NEW Skip Logic in BetRecommender
|
||||
==============================================
|
||||
Run with: python ai-engine/tests/test_skip_logic.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
# Add paths
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
|
||||
|
||||
from core.calculators.bet_recommender import BetRecommender, RecommendationResult, MarketPredictionDTO
|
||||
from core.calculators.risk_assessor import RiskAnalysis
|
||||
from core.calculators.match_result_calculator import MatchResultPrediction
|
||||
from core.calculators.over_under_calculator import OverUnderPrediction
|
||||
from config.config_loader import get_config
|
||||
|
||||
@dataclass
|
||||
class DummyContext:
|
||||
"""Minimal mock for CalculationContext"""
|
||||
odds_data: dict
|
||||
|
||||
class TestSkipLogic(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# Mock config to pass into BetRecommender
|
||||
self.mock_config = {
|
||||
"recommendations.market_weights": {"MS": 1.0, "ÇŞ": 0.9, "BTTS": 0.9, "2.5 Üst/Alt": 0.9},
|
||||
"recommendations.safe_markets": ["ÇŞ", "1.5 Üst/Alt"],
|
||||
"recommendations.market_accuracy": {"MS": 65, "ÇŞ": 75, "BTTS": 60, "2.5 Üst/Alt": 65},
|
||||
"recommendations.baseline_accuracy": 65.0,
|
||||
"recommendations.confidence_threshold": 60,
|
||||
"recommendations.value_confidence_min": 45,
|
||||
"recommendations.value_confidence_max": 60,
|
||||
"recommendations.value_edge_margin": 0.03,
|
||||
"recommendations.value_upgrade_edge": 5.0,
|
||||
"recommendations.risk_safe_boost": 1.2,
|
||||
"recommendations.risk_ms_penalty_high": 0.5,
|
||||
"recommendations.risk_other_penalty": 0.7,
|
||||
"recommendations.risk_ms_penalty_medium": 0.8,
|
||||
}
|
||||
self.recommender = BetRecommender(self.mock_config)
|
||||
|
||||
def _make_risk(self, level="MEDIUM", is_surprise=False):
|
||||
return RiskAnalysis(risk_level=level, is_surprise_risk=is_surprise, risk_score=0.5)
|
||||
|
||||
def _make_ms_pred(self, pick, conf):
|
||||
# pick: "1", "X", "2"
|
||||
probs = {"1": {"ms_home_prob": 0.5, "ms_draw_prob": 0.3, "ms_away_prob": 0.2},
|
||||
"X": {"ms_home_prob": 0.2, "ms_draw_prob": 0.5, "ms_away_prob": 0.3},
|
||||
"2": {"ms_home_prob": 0.2, "ms_draw_prob": 0.3, "ms_away_prob": 0.5}}
|
||||
p = probs.get(pick, probs["1"])
|
||||
return MatchResultPrediction(
|
||||
ms_pick=pick, ms_confidence=conf,
|
||||
dc_pick="1X", dc_confidence=0,
|
||||
dc_1x_prob=0.7, dc_x2_prob=0.7, dc_12_prob=0.7,
|
||||
**p
|
||||
)
|
||||
|
||||
def _make_ou_pred(self):
|
||||
return OverUnderPrediction(
|
||||
ou25_pick="2.5 Üst", ou25_confidence=50.0,
|
||||
over_25_prob=0.55, under_25_prob=0.45,
|
||||
|
||||
btts_pick="Var", btts_confidence=50.0,
|
||||
btts_yes_prob=0.55, btts_no_prob=0.45,
|
||||
|
||||
ou15_pick="1.5 Üst", ou15_confidence=60.0, over_15_prob=0.7, under_15_prob=0.3,
|
||||
ou35_pick="3.5 Alt", ou35_confidence=50.0, over_35_prob=0.3, under_35_prob=0.7
|
||||
)
|
||||
|
||||
def test_low_confidence_should_skip(self):
|
||||
"""Confidence < 45% should be SKIPPED"""
|
||||
ms_pred = self._make_ms_pred(pick="2", conf=40.0)
|
||||
ou_pred = self._make_ou_pred()
|
||||
risk = self._make_risk("MEDIUM")
|
||||
ctx = DummyContext(odds_data={"ms_2": 2.5})
|
||||
|
||||
res = self.recommender.calculate(ctx, ms_pred, ou_pred, risk)
|
||||
|
||||
# Check if MS bet is skipped
|
||||
ms_bet = next((b for b in res.skipped_bets if b.market_type == "MS"), None)
|
||||
self.assertIsNotNone(ms_bet, "MS bet with 40% conf should be skipped!")
|
||||
self.assertTrue(ms_bet.is_skip)
|
||||
|
||||
def test_good_confidence_should_recommend(self):
|
||||
"""Confidence > 60% and Good Odds should be RECOMMENDED"""
|
||||
ms_pred = self._make_ms_pred(pick="1", conf=70.0)
|
||||
ou_pred = self._make_ou_pred()
|
||||
risk = self._make_risk("MEDIUM")
|
||||
# Odds 1.80 for 70% prob = Good Value (Need real odds for MS to pass)
|
||||
ctx = DummyContext(odds_data={"ms_1": 1.80, "ou15_o": 1.50}) # Added ou15 odds
|
||||
|
||||
res = self.recommender.calculate(ctx, ms_pred, ou_pred, risk)
|
||||
|
||||
# Check if ANY bet is recommended (doesn't have to be MS, but usually is)
|
||||
self.assertGreater(len(res.recommended_bets), 0, "At least one bet should be recommended!")
|
||||
# Check that MS bet is NOT skipped
|
||||
ms_bet = next((b for b in res.recommended_bets if b.market_type == "MS"), None)
|
||||
if ms_bet:
|
||||
self.assertFalse(ms_bet.is_skip)
|
||||
|
||||
def test_negative_edge_should_skip(self):
|
||||
"""Even with high confidence, if Odds are too low (Bad Value), SKIP"""
|
||||
ms_pred = self._make_ms_pred(pick="1", conf=70.0) # 70% prob
|
||||
ou_pred = self._make_ou_pred()
|
||||
risk = self._make_risk("MEDIUM")
|
||||
# Odds 1.10 -> Implied 90%. Our prob is 70%. Edge is -20% -> SKIP
|
||||
ctx = DummyContext(odds_data={"ms_1": 1.10})
|
||||
|
||||
res = self.recommender.calculate(ctx, ms_pred, ou_pred, risk)
|
||||
|
||||
ms_bet = next((b for b in res.skipped_bets if b.market_type == "MS"), None)
|
||||
self.assertIsNotNone(ms_bet, "MS bet with terrible odds (Negative Edge) should be skipped!")
|
||||
self.assertTrue(ms_bet.is_skip)
|
||||
|
||||
def test_no_bets_recommendation(self):
|
||||
"""If all bets are low confidence, best_bet should be None"""
|
||||
ms_pred = self._make_ms_pred(pick="1", conf=30.0) # Very low conf
|
||||
ou_pred = self._make_ou_pred()
|
||||
# Reset ALL OU confs to low
|
||||
ou_pred.ou25_confidence = 30.0
|
||||
ou_pred.btts_confidence = 30.0
|
||||
ou_pred.ou15_confidence = 30.0 # This was 60 in setUp, causing the fail!
|
||||
ou_pred.ou35_confidence = 30.0
|
||||
|
||||
risk = self._make_risk("MEDIUM")
|
||||
ctx = DummyContext(odds_data={"ms_1": 2.0})
|
||||
|
||||
res = self.recommender.calculate(ctx, ms_pred, ou_pred, risk)
|
||||
|
||||
self.assertIsNone(res.best_bet, "If everything is skipped, there should be no best_bet.")
|
||||
self.assertEqual(len(res.recommended_bets), 0, "No bets should be recommended!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("🧪 Running Skip Logic Unit Tests...")
|
||||
print("="*50)
|
||||
unittest.main(verbosity=2)
|
||||
Reference in New Issue
Block a user