""" Shared prediction dataclasses used across the AI engine. These were originally defined in models/v20_ensemble.py and are extracted here so they can be used without importing the full V20 ensemble. """ from dataclasses import dataclass, field from typing import Any, Dict, List, Optional from core.calculators.score_calculator import ScorePrediction @dataclass class MarketPrediction: """Prediction for a single betting market.""" market_type: str pick: str probability: float confidence: float odds: float = 0.0 is_recommended: bool = False is_value_bet: bool = False edge: float = 0.0 # Expected edge over market def to_dict(self) -> dict: return { "market_type": self.market_type, "pick": self.pick, "probability": round(self.probability * 100, 1), "confidence": round(self.confidence, 1), "odds": self.odds, "is_recommended": self.is_recommended, "is_value_bet": self.is_value_bet, "edge": round(self.edge, 1) } @dataclass class FullMatchPrediction: """Complete prediction for a match with ALL markets.""" match_id: str home_team: str away_team: str match_date: str = "" # === MAÇ SONUCU (1X2) === ms_home_prob: float = 0.33 ms_draw_prob: float = 0.33 ms_away_prob: float = 0.33 ms_pick: str = "" ms_confidence: float = 0.0 # === ÇİFTE ŞANS === dc_1x_prob: float = 0.66 dc_x2_prob: float = 0.66 dc_12_prob: float = 0.66 dc_pick: str = "" dc_confidence: float = 0.0 # === ALT/ÜST GOLLER === # 1.5 over_15_prob: float = 0.70 under_15_prob: float = 0.30 ou15_pick: str = "" ou15_confidence: float = 0.0 # 2.5 over_25_prob: float = 0.50 under_25_prob: float = 0.50 ou25_pick: str = "" ou25_confidence: float = 0.0 # 3.5 over_35_prob: float = 0.30 under_35_prob: float = 0.70 ou35_pick: str = "" ou35_confidence: float = 0.0 # === KARŞILIKLI GOL (BTTS) === btts_yes_prob: float = 0.50 btts_no_prob: float = 0.50 btts_pick: str = "" btts_confidence: float = 0.0 # === İLK YARI SONUCU === ht_home_prob: float = 0.30 ht_draw_prob: float = 0.40 ht_away_prob: float = 0.30 ht_pick: str = "" ht_confidence: float = 0.0 # === SKOR TAHMİNLERİ === score: Optional[ScorePrediction] = None predicted_ft_score: str = "1-1" predicted_ht_score: str = "0-0" ft_scores_top5: List[Dict] = field(default_factory=list) # === xG (Expected Goals) === home_xg: float = 1.3 away_xg: float = 1.1 total_xg: float = 2.4 # === RISK DEĞERLENDİRMESİ === risk_level: str = "MEDIUM" # LOW, MEDIUM, HIGH, EXTREME risk_score: float = 0.0 is_surprise_risk: bool = False surprise_type: str = "" risk_warnings: List[str] = field(default_factory=list) ht_ft_probs: Dict[str, float] = field(default_factory=dict) # === GLM-5 SÜRPRİZ SKORU === upset_score: int = 0 # 0-100 arası sürpriz skoru upset_level: str = "LOW" # LOW, MEDIUM, HIGH, EXTREME upset_reasons: List[str] = field(default_factory=list) # === SÜRPRİZ PROFİLİ === surprise_score: float = 0.0 # 0-100 overall surprise risk score surprise_comment: str = "" # Human-readable surprise commentary surprise_reasons: List[str] = field(default_factory=list) # Flagged risk reasons surprise_breakdown: List[Dict[str, Any]] = field(default_factory=list) # Per-factor {code, points, label} # === ENGINE KATKILARI === team_confidence: float = 0.0 player_confidence: float = 0.0 odds_confidence: float = 0.0 referee_confidence: float = 0.0 # === KORNER & KART & DİĞER === total_corners_pred: float = 9.5 corner_pick: str = "9.5 Üst" total_cards_pred: float = 4.5 card_pick: str = "4.5 Alt" cards_over_prob: float = 0.50 cards_under_prob: float = 0.50 cards_confidence: float = 0.0 handicap_pick: str = "" handicap_home_prob: float = 0.33 handicap_draw_prob: float = 0.34 handicap_away_prob: float = 0.33 handicap_confidence: float = 0.0 ht_over_05_prob: float = 0.65 ht_under_05_prob: float = 0.35 ht_over_15_prob: float = 0.30 ht_under_15_prob: float = 0.70 ht_ou_pick: str = "İY 0.5 Üst" ht_ou15_pick: str = "İY 1.5 Alt" odd_even_pick: str = "Çift" odd_prob: float = 0.50 # Tek olasılığı even_prob: float = 0.50 # Çift olasılığı # === TAVSİYELER (RECOMMENDATIONS) === best_bet: Optional[MarketPrediction] = None recommended_bets: List[MarketPrediction] = field(default_factory=list) alternative_bet: Optional[MarketPrediction] = None expert_recommendation: Dict[str, Any] = field(default_factory=dict) # === DETAILED ANALYSIS === analysis_details: Dict[str, Any] = field(default_factory=dict) def to_dict(self) -> dict: return { "match_info": { "match_id": self.match_id, "home_team": self.home_team, "away_team": self.away_team, "match_date": self.match_date }, "predictions": { "match_result": { "1": round(self.ms_home_prob * 100, 1), "X": round(self.ms_draw_prob * 100, 1), "2": round(self.ms_away_prob * 100, 1), "pick": self.ms_pick, "confidence": round(self.ms_confidence, 1) }, "double_chance": { "1X": round(self.dc_1x_prob * 100, 1), "X2": round(self.dc_x2_prob * 100, 1), "12": round(self.dc_12_prob * 100, 1), "pick": self.dc_pick, "confidence": round(self.dc_confidence, 1) }, "over_under": { "1.5": { "over": round(self.over_15_prob * 100, 1), "under": round(self.under_15_prob * 100, 1), "pick": self.ou15_pick, "confidence": round(self.ou15_confidence, 1) }, "2.5": { "over": round(self.over_25_prob * 100, 1), "under": round(self.under_25_prob * 100, 1), "pick": self.ou25_pick, "confidence": round(self.ou25_confidence, 1) }, "3.5": { "over": round(self.over_35_prob * 100, 1), "under": round(self.under_35_prob * 100, 1), "pick": self.ou35_pick, "confidence": round(self.ou35_confidence, 1) } }, "btts": { "yes": round(self.btts_yes_prob * 100, 1), "no": round(self.btts_no_prob * 100, 1), "pick": self.btts_pick, "confidence": round(self.btts_confidence, 1) }, "first_half": { "1": round(self.ht_home_prob * 100, 1), "X": round(self.ht_draw_prob * 100, 1), "2": round(self.ht_away_prob * 100, 1), "pick": self.ht_pick, "confidence": round(self.ht_confidence, 1), "over_under_05": { "over": round(self.ht_over_05_prob * 100, 1), "under": round(self.ht_under_05_prob * 100, 1), "pick": self.ht_ou_pick }, "over_under_15": { "over": round(self.ht_over_15_prob * 100, 1), "under": round(self.ht_under_15_prob * 100, 1), "pick": self.ht_ou15_pick } }, "scores": { "predicted_ft": self.predicted_ft_score, "predicted_ht": self.predicted_ht_score, "top_5_ft_scores": self.ft_scores_top5 }, "others": { "handicap": { "pick": self.handicap_pick, "confidence": round(self.handicap_confidence, 1), "home": round(self.handicap_home_prob * 100, 1), "draw": round(self.handicap_draw_prob * 100, 1), "away": round(self.handicap_away_prob * 100, 1) }, "corners": { "total": round(self.total_corners_pred, 1), "pick": self.corner_pick }, "cards": { "total": round(self.total_cards_pred, 1), "pick": self.card_pick, "confidence": round(self.cards_confidence, 1), "over": round(self.cards_over_prob * 100, 1), "under": round(self.cards_under_prob * 100, 1) }, "odd_even": { "pick": self.odd_even_pick, "tek": round(self.odd_prob * 100, 1), "cift": round(self.even_prob * 100, 1) } }, "xg": { "home": round(self.home_xg, 2), "away": round(self.away_xg, 2), "total": round(self.total_xg, 2) } }, "risk": { "level": self.risk_level, "score": round(self.risk_score, 1), "is_surprise_risk": self.is_surprise_risk, "surprise_type": self.surprise_type, "ht_ft_probs": {k: round(v * 100, 1) for k, v in self.ht_ft_probs.items()} if self.ht_ft_probs else {}, "warnings": self.risk_warnings }, "upset_analysis": { "score": self.upset_score, "level": self.upset_level, "reasons": self.upset_reasons }, "engine_breakdown": { "team_engine": round(self.team_confidence, 1), "player_engine": round(self.player_confidence, 1), "odds_engine": round(self.odds_confidence, 1), "referee_engine": round(self.referee_confidence, 1) }, "recommendations": { "best_bet": self.best_bet.to_dict() if self.best_bet else None, "all_recommended": [b.to_dict() for b in self.recommended_bets] if self.recommended_bets else [], "alternative_bet": self.alternative_bet.to_dict() if self.alternative_bet else None }, "analysis_details": self.analysis_details }