@@ -0,0 +1,40 @@
|
||||
"""
|
||||
MatchData dataclass — core data transfer object used throughout the engine.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class MatchData:
|
||||
match_id: str
|
||||
home_team_id: str
|
||||
away_team_id: str
|
||||
home_team_name: str
|
||||
away_team_name: str
|
||||
match_date_ms: int
|
||||
sport: str
|
||||
league_id: Optional[str]
|
||||
league_name: str
|
||||
referee_name: Optional[str]
|
||||
odds_data: Dict[str, float]
|
||||
home_lineup: Optional[List[str]]
|
||||
away_lineup: Optional[List[str]]
|
||||
sidelined_data: Optional[Dict[str, Any]]
|
||||
home_goals_avg: float
|
||||
home_conceded_avg: float
|
||||
away_goals_avg: float
|
||||
away_conceded_avg: float
|
||||
home_position: int
|
||||
away_position: int
|
||||
lineup_source: str
|
||||
status: str = ""
|
||||
state: Optional[str] = None
|
||||
substate: Optional[str] = None
|
||||
current_score_home: Optional[int] = None
|
||||
current_score_away: Optional[int] = None
|
||||
lineup_confidence: float = 0.0
|
||||
source_table: str = "matches"
|
||||
@@ -0,0 +1,292 @@
|
||||
"""
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user