293 lines
11 KiB
Python
293 lines
11 KiB
Python
"""
|
||
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
|
||
}
|