main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 37s

This commit is contained in:
2026-05-17 02:17:22 +03:00
parent 17ace9bd12
commit 94c7a4481a
53 changed files with 29602 additions and 7832 deletions
+40
View File
@@ -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"
+292
View File
@@ -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
}