""" 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)