import sys import unittest from decimal import Decimal from pathlib import Path from unittest.mock import MagicMock AI_ENGINE_ROOT = Path(__file__).resolve().parents[1] if str(AI_ENGINE_ROOT) not in sys.path: sys.path.insert(0, str(AI_ENGINE_ROOT)) from core.engines.odds_predictor import OddsPredictorEngine from features.sidelined_analyzer import SidelinedAnalyzer class EngineNullSafetyTests(unittest.TestCase): def test_odds_predictor_accepts_decimal_inputs_without_crashing(self): engine = OddsPredictorEngine() prediction = engine.predict( odds_data={ "ms_h": Decimal("2.10"), "ms_d": Decimal("3.25"), "ms_a": Decimal("3.60"), "ou25_o": Decimal("1.90"), }, ) self.assertGreater(prediction.market_home_prob, 0.0) self.assertGreater(prediction.market_draw_prob, 0.0) self.assertGreater(prediction.market_away_prob, 0.0) def test_sidelined_analyzer_handles_non_numeric_fields(self): analyzer = SidelinedAnalyzer.__new__(SidelinedAnalyzer) analyzer.position_weights = {"K": 0.35, "D": 0.20, "O": 0.25, "F": 0.30} analyzer.max_rating = 10 analyzer.adaptation_threshold = 10 analyzer.adaptation_discount = 0.5 analyzer.goalkeeper_penalty = 0.15 analyzer.confidence_boost = 10 analyzer.max_impact = 0.85 analyzer.key_player_threshold = 3 analyzer.recent_matches_lookback = 15 analyzer._fetch_player_stats = MagicMock(return_value={}) result = analyzer.analyze( { "totalSidelined": 2, "players": [ { "playerId": "p1", "playerName": "Player One", "positionShort": "O", "matchesMissed": "N/A", "average": "?", "type": "injury", }, { "playerId": "p2", "playerName": "Player Two", "positionShort": "K", "matchesMissed": "12", "average": "6.7", "type": "suspension", }, ], }, ) self.assertEqual(result.total_sidelined, 2) self.assertGreaterEqual(result.impact_score, 0.0) self.assertTrue(len(result.player_details) >= 2) if __name__ == "__main__": unittest.main()