""" Upset Engine v2 - GLM-5 Tespitleri ile Geliştirilmiş Sürpriz Tespiti ==================================================================== Yeni Eklenen Faktörler (GLM-5 Analizinden): 1. MARGIN_ANALIZI - Bookmaker margin > %18 = sürpriz riski 2. FAVORI_ORAN_TUZAGI - 1.40-1.60 arası en yüksek sürpriz oranı 3. HAKEM_SURPRIZ_ORANI - Hakemin geçmiş maçlarında ev kayıp oranı 4. FORM_FARKI_TUZAGI - Form farkı > 40 = "çok iyi görünen" favori tuzak Orijinal Faktörler: - Atmosfer (Avrupa gecesi, taraftar baskısı) - Motivasyon asimetrisi (küme düşme vs şampiyon) - Yorgunluk (maç yoğunluğu, seyahat) - Tarihsel upset pattern """ import os import sys from typing import Dict, Any, Optional, Tuple, List from dataclasses import dataclass, field sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) try: import psycopg2 from psycopg2.extras import RealDictCursor except ImportError: psycopg2 = None @dataclass class UpsetFactorsV2: """Upset potansiyelini etkileyen faktörler - v2""" # Orijinal faktörler atmosphere_score: float = 0.0 motivation_score: float = 0.0 fatigue_score: float = 0.0 historical_upset_rate: float = 0.0 # YENİ FAKTÖRLER (GLM-5) margin_score: float = 0.0 # Bookmaker margin analizi favorite_odds_trap: float = 0.0 # Favori oran tuzağı referee_upset_score: float = 0.0 # Hakem sürpriz oranı form_trap_score: float = 0.0 # Form farkı tuzağı # Toplam total_upset_potential: float = 0.0 reasoning: List[str] = field(default_factory=list) # YENİ: Sürpriz skoru (0-100) upset_score: int = 0 upset_level: str = "LOW" # LOW, MEDIUM, HIGH, EXTREME class UpsetEngineV2: """ Favori takımın kaybedeceği maçları tespit eder. v2: GLM-5 analizlerinden elde edilen yeni faktörler eklendi. """ # Yüksek atmosferli stadyumlar HIGH_ATMOSPHERE_TEAMS = { "galatasaray", "fenerbahce", "besiktas", "trabzonspor", "liverpool", "newcastle", "leeds", "dortmund", "union berlin", "olympiacos", "panathinaikos", "aek athens", "boca juniors", "river plate", "celtic", "rangers", "red star belgrade" } EUROPEAN_COMPETITIONS = { "şampiyonlar ligi", "champions league", "uefa champions league", "avrupa ligi", "europa league", "uefa europa league", "konferans ligi", "conference league", "uefa conference league" } # YENİ: Sürpriz oranları (veritabanı analizinden) # Favori oran aralığına göre sürpriz oranları FAVORITE_ODDS_UPSET_RATES = { (1.10, 1.20): 0.111, # %11.1 sürpriz (1.20, 1.30): 0.150, # %15.0 sürpriz (1.30, 1.40): 0.235, # %23.5 sürpriz (1.40, 1.50): 0.333, # %33.3 sürpriz ← DİKKAT! (1.50, 1.60): 0.350, # %35.0 sürpriz ← EN YÜKSEK! } def __init__(self): self.conn = None self._connect_db() def _connect_db(self): if psycopg2 is None: return try: from data.db import get_clean_dsn self.conn = psycopg2.connect(get_clean_dsn()) except Exception as e: print(f"[UpsetEngineV2] DB connection failed: {e}") self.conn = None def _get_conn(self): if self.conn is None or self.conn.closed: self._connect_db() return self.conn # ═════════════════════════════════════════════════════════════════ # YENİ FAKTÖRLER (GLM-5 Analizinden) # ═════════════════════════════════════════════════════════════════ def calculate_margin_score( self, odds_data: Dict[str, float] ) -> Tuple[float, List[str]]: """ GLM-5 Tespiti: Bookmaker margin analizi Margin > %18 → Bookmaker kendini koruyor, favori riskli Margin > %20 → Yüksek risk, sürpriz bekleniyor """ score = 0.0 reasons = [] ms_h = odds_data.get("ms_h", 0) ms_d = odds_data.get("ms_d", 0) ms_a = odds_data.get("ms_a", 0) if ms_h > 0 and ms_d > 0 and ms_a > 0: margin = (1/ms_h + 1/ms_d + 1/ms_a) - 1 if margin > 0.20: score = 0.25 reasons.append(f"⚠️ Margin çok yüksek (%{margin*100:.1f}) - Bookmaker risk görüyor!") elif margin > 0.18: score = 0.15 reasons.append(f"⚠️ Margin yüksek (%{margin*100:.1f}) - Dikkat!") return score, reasons def calculate_favorite_odds_trap( self, favorite_odds: float, favorite_side: str # 'home' or 'away' ) -> Tuple[float, List[str]]: """ GLM-5 Tespiti: Favori oran tuzağı Veritabanı analizine göre: - 1.40-1.50 arası: %33.3 sürpriz - 1.50-1.60 arası: %35.0 sürpriz (EN YÜKSEK!) - < 1.20: Tuzak oranı şüphesi """ score = 0.0 reasons = [] if favorite_odds <= 0: return score, reasons for (low, high), upset_rate in self.FAVORITE_ODDS_UPSET_RATES.items(): if low <= favorite_odds < high: score = upset_rate # Doğrudan sürpriz olasılığı if upset_rate >= 0.30: reasons.append(f"🔴 Favori oran {favorite_odds:.2f} - %{upset_rate*100:.0f} sürpriz oranı!") elif upset_rate >= 0.20: reasons.append(f"⚠️ Favori oran {favorite_odds:.2f} - %{upset_rate*100:.0f} sürpriz riski") break # Çok düşük oran tuzağı if favorite_odds < 1.20: score = max(score, 0.20) reasons.append(f"⚠️ Favori oran çok düşük ({favorite_odds:.2f}) - Tuzak oranı şüphesi") return score, reasons def calculate_referee_upset_score( self, referee_name: str ) -> Tuple[float, List[str]]: """ GLM-5 Tespiti: Hakem sürpriz oranı Hakemin yönettiği maçlarda ev sahibi kayıp oranı > %25 → Yüksek sürpriz riski """ score = 0.0 reasons = [] if not referee_name or not self._get_conn(): return score, reasons try: cur = self._get_conn().cursor() # Hakemin yönettiği maçlarda sonuçlar cur.execute(""" SELECT COUNT(*) as total, SUM(CASE WHEN m.score_home < m.score_away THEN 1 ELSE 0 END) as away_wins, SUM(CASE WHEN m.score_home = m.score_away THEN 1 ELSE 0 END) as draws FROM match_officials mo JOIN matches m ON m.id = mo.match_id WHERE mo.name = %s AND mo.role_id = 1 AND m.score_home IS NOT NULL """, (referee_name,)) row = cur.fetchone() cur.close() if row and row[0] and row[0] >= 3: total = row[0] away_wins = row[1] or 0 draws = row[2] or 0 upset_rate = (away_wins + draws * 0.5) / total if upset_rate > 0.40: score = 0.25 reasons.append(f"👨‍⚖️ {referee_name}: %{upset_rate*100:.0f} sürpriz oranı (YÜKSEK!)") elif upset_rate > 0.30: score = 0.15 reasons.append(f"👨‍⚖️ {referee_name}: %{upset_rate*100:.0f} sürpriz oranı") except Exception as e: pass return score, reasons def calculate_form_trap_score( self, home_form_score: float, away_form_score: float, favorite_side: str ) -> Tuple[float, List[str]]: """ GLM-5 Tespiti: Form farkı tuzağı Form farkı > 40 → "Çok iyi görünen" favori tuzak Favori formu kötü ama oran düşük → Sürpriz bekleniyor """ score = 0.0 reasons = [] form_diff = home_form_score - away_form_score # Form farkı çok büyük if abs(form_diff) > 40: score = 0.20 if form_diff > 0 and favorite_side == 'away': reasons.append(f"🔴 Form tuzağı! Ev sahibi formda ({home_form_score:.0f}) ama deplasman favori") elif form_diff < 0 and favorite_side == 'home': reasons.append(f"🔴 Form tuzağı! Deplasman formda ({away_form_score:.0f}) ama ev sahibi favori") # Favori formu kötü if favorite_side == 'home' and home_form_score < 50: score = max(score, 0.15) reasons.append(f"⚠️ Favori ev sahibi formu düşük ({home_form_score:.0f})") elif favorite_side == 'away' and away_form_score < 50: score = max(score, 0.15) reasons.append(f"⚠️ Favori deplasman formu düşük ({away_form_score:.0f})") return score, reasons # ═════════════════════════════════════════════════════════════════ # ORİJİNAL FAKTÖRLER # ═════════════════════════════════════════════════════════════════ def calculate_atmosphere_score( self, home_team_name: str, league_name: str, is_cup_match: bool = False ) -> Tuple[float, List[str]]: """Orijinal: Atmosfer skoru""" score = 0.0 reasons = [] home_lower = home_team_name.lower() for team in self.HIGH_ATMOSPHERE_TEAMS: if team in home_lower: score += 0.25 reasons.append(f"🔥 {home_team_name} yüksek atmosferli stadyum") break league_lower = league_name.lower() for comp in self.EUROPEAN_COMPETITIONS: if comp in league_lower: score += 0.20 reasons.append("🌟 Avrupa gecesi - ekstra motivasyon") break if is_cup_match: score += 0.10 reasons.append("🏆 Kupa maçı - her şey olabilir") return min(score, 1.0), reasons def calculate_motivation_score( self, home_position: int, away_position: int, total_teams: int = 20 ) -> Tuple[float, List[str]]: """Orijinal: Motivasyon asimetrisi""" score = 0.0 reasons = [] if home_position is not None and away_position is not None: position_diff = away_position - home_position relegation_zone = total_teams - 3 if home_position >= relegation_zone and away_position <= 3: score += 0.30 reasons.append("⚔️ Hayatta kalma savaşı vs şampiyonluk adayı") elif home_position >= relegation_zone: score += 0.15 reasons.append("🔥 Ev sahibi küme düşme hattında") if position_diff < -10: score += 0.15 reasons.append(f"📊 {abs(position_diff)} sıra fark") return min(score, 1.0), reasons # ═════════════════════════════════════════════════════════════════ # ANA FONKSİYON # ═════════════════════════════════════════════════════════════════ def calculate_upset_potential( self, home_team_name: str, home_team_id: str, away_team_name: str, league_name: str, home_position: int = None, away_position: int = None, match_date_ms: int = None, odds_data: Dict[str, float] = None, referee_name: str = None, home_form_score: float = 50.0, away_form_score: float = 50.0, favorite_side: str = None, # 'home', 'away', or 'draw' favorite_odds: float = None ) -> UpsetFactorsV2: """ Tam upset analizi - v2 (GLM-5 geliştirmeleri ile) """ factors = UpsetFactorsV2() all_reasons = [] # 1. Margin analizi (YENİ) if odds_data: factors.margin_score, reasons = self.calculate_margin_score(odds_data) all_reasons.extend(reasons) # 2. Favori oran tuzağı (YENİ) if favorite_odds and favorite_side: factors.favorite_odds_trap, reasons = self.calculate_favorite_odds_trap( favorite_odds, favorite_side ) all_reasons.extend(reasons) # 3. Hakem sürpriz oranı (YENİ) if referee_name: factors.referee_upset_score, reasons = self.calculate_referee_upset_score( referee_name ) all_reasons.extend(reasons) # 4. Form tuzağı (YENİ) factors.form_trap_score, reasons = self.calculate_form_trap_score( home_form_score, away_form_score, favorite_side or 'home' ) all_reasons.extend(reasons) # 5. Atmosfer (orijinal) factors.atmosphere_score, reasons = self.calculate_atmosphere_score( home_team_name, league_name ) all_reasons.extend(reasons) # 6. Motivasyon (orijinal) if home_position is not None and away_position is not None: factors.motivation_score, reasons = self.calculate_motivation_score( home_position, away_position ) all_reasons.extend(reasons) # ═══════════════════════════════════════════════════════════ # SÜRPRİZ SKORU HESAPLAMA (0-100) - GÜÇLENDİRİLMİŞ v2.1 # ═══════════════════════════════════════════════════════════ upset_score = 0 # Margin (> %18 = +20, > %20 = +30) - GÜÇLENDİRİLDİ if factors.margin_score >= 0.25: upset_score += 30 # Artırıldı: 20 -> 30 all_reasons.append("🔴 Margin > %20: Bookmaker büyük risk görüyor!") elif factors.margin_score >= 0.15: upset_score += 20 # Artırıldı: 15 -> 20 all_reasons.append("⚠️ Margin > %18: Dikkatli ol!") # Favori oran tuzağı - GÜÇLENDİRİLDİ if factors.favorite_odds_trap >= 0.30: upset_score += 30 # Artırıldı: 25 -> 30 elif factors.favorite_odds_trap >= 0.20: upset_score += 25 # Artırıldı: 20 -> 25 elif factors.favorite_odds_trap >= 0.15: upset_score += 20 # Artırıldı: 15 -> 20 # Hakem if factors.referee_upset_score >= 0.25: upset_score += 20 elif factors.referee_upset_score >= 0.15: upset_score += 10 # Form tuzağı - GÜÇLENDİRİLDİ if factors.form_trap_score >= 0.20: upset_score += 20 # Artırıldı: 15 -> 20 elif factors.form_trap_score >= 0.15: upset_score += 15 # Artırıldı: 10 -> 15 # Atmosfer - GÜÇLENDİRİLDİ if factors.atmosphere_score >= 0.40: upset_score += 20 # Artırıldı: 15 -> 20 elif factors.atmosphere_score >= 0.25: upset_score += 15 # Artırıldı: 10 -> 15 # Motivasyon if factors.motivation_score >= 0.30: upset_score += 15 elif factors.motivation_score >= 0.15: upset_score += 10 # ═══════════════════════════════════════════════════════════ # YENİ: EKSTRA RİSK FAKTÖRLERİ # ═══════════════════════════════════════════════════════════ # Deplasman favorisi ekstra risk (+10) if favorite_side == 'away': upset_score += 10 all_reasons.append("📍 Deplasman favorisi - ekstra risk!") # Favori formu çok düşük (< 40) = +15 if favorite_side == 'home' and home_form_score < 40: upset_score += 15 all_reasons.append(f"🔴 Favori ev sahibi formu ÇOK DÜŞÜK ({home_form_score:.0f})") elif favorite_side == 'away' and away_form_score < 40: upset_score += 15 all_reasons.append(f"🔴 Favori deplasman formu ÇOK DÜŞÜK ({away_form_score:.0f})") # Çok düşük favori oranı (< 1.30) ama margin yüksek = tuzak şüphesi if favorite_odds and favorite_odds < 1.30 and factors.margin_score >= 0.15: upset_score += 10 all_reasons.append(f"⚠️ Düşük oran ({favorite_odds:.2f}) + yüksek margin = TUZAK ŞÜPHESİ!") factors.upset_score = min(upset_score, 100) # Seviye belirle if factors.upset_score >= 60: factors.upset_level = "EXTREME" elif factors.upset_score >= 45: factors.upset_level = "HIGH" elif factors.upset_score >= 30: factors.upset_level = "MEDIUM" else: factors.upset_level = "LOW" # Toplam upset potansiyeli factors.total_upset_potential = min( (factors.margin_score + factors.favorite_odds_trap + factors.referee_upset_score + factors.form_trap_score + factors.atmosphere_score * 0.5 + factors.motivation_score * 0.5) / 1.5, 1.0 ) factors.reasoning = all_reasons return factors def get_upset_engine_v2(): """Singleton pattern""" return UpsetEngineV2() if __name__ == "__main__": # Test engine = get_upset_engine_v2() # Real Madrid vs Getafe test result = engine.calculate_upset_potential( home_team_name="Real Madrid", home_team_id="test", away_team_name="Getafe", league_name="LaLiga", odds_data={"ms_h": 1.25, "ms_d": 3.92, "ms_a": 6.86}, referee_name="A. Muniz Ruiz", home_form_score=80.0, away_form_score=56.7, favorite_side="home", favorite_odds=1.25 ) print(f"\n{'='*60}") print(f"Real Madrid vs Getafe - Sürpriz Analizi") print(f"{'='*60}") print(f"Sürpriz Skoru: {result.upset_score}/100") print(f"Seviye: {result.upset_level}") print(f"\nNedenler:") for reason in result.reasoning: print(f" {reason}")