This commit is contained in:
Executable
+188
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
Referee Predictor Engine - V20 Ensemble Component
|
||||
Analyzes referee patterns for cards, goals, and home bias.
|
||||
|
||||
Weight: 15% in ensemble
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Dict, Optional
|
||||
from dataclasses import dataclass
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from features.referee_engine import get_referee_engine
|
||||
|
||||
|
||||
@dataclass
|
||||
class RefereePrediction:
|
||||
"""Referee engine prediction output."""
|
||||
referee_name: str = ""
|
||||
matches_officiated: int = 0
|
||||
|
||||
# Card tendencies
|
||||
avg_yellow_cards: float = 4.0
|
||||
avg_red_cards: float = 0.2
|
||||
is_card_heavy: bool = False # Above average cards
|
||||
|
||||
# Goal tendencies
|
||||
avg_goals_per_match: float = 2.5
|
||||
over_25_rate: float = 0.50
|
||||
is_high_scoring: bool = False # Above average goals
|
||||
|
||||
# Home bias
|
||||
home_win_rate: float = 0.45
|
||||
home_bias: float = 0.0 # -1 to +1, positive = favors home
|
||||
|
||||
# Penalty tendency
|
||||
penalty_rate: float = 0.15
|
||||
|
||||
confidence: float = 0.0
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"referee_name": self.referee_name,
|
||||
"matches_officiated": self.matches_officiated,
|
||||
"avg_yellow_cards": round(self.avg_yellow_cards, 1),
|
||||
"avg_red_cards": round(self.avg_red_cards, 2),
|
||||
"is_card_heavy": self.is_card_heavy,
|
||||
"avg_goals_per_match": round(self.avg_goals_per_match, 2),
|
||||
"over_25_rate": round(self.over_25_rate * 100, 1),
|
||||
"is_high_scoring": self.is_high_scoring,
|
||||
"home_win_rate": round(self.home_win_rate * 100, 1),
|
||||
"home_bias": round(self.home_bias, 2),
|
||||
"penalty_rate": round(self.penalty_rate * 100, 1),
|
||||
"confidence": round(self.confidence, 1)
|
||||
}
|
||||
|
||||
|
||||
class RefereePredictorEngine:
|
||||
"""
|
||||
Referee-based prediction engine.
|
||||
|
||||
Analyzes:
|
||||
- Card tendency (sarı/kırmızı kart ortalaması)
|
||||
- Goal tendency (maç başına gol, 2.5 üst oranı)
|
||||
- Home bias (ev sahibi lehine karar oranı)
|
||||
- Penalty tendency (penaltı verme oranı)
|
||||
"""
|
||||
|
||||
# League average benchmarks
|
||||
LEAGUE_AVG_GOALS = 2.65
|
||||
LEAGUE_AVG_YELLOW = 4.0
|
||||
LEAGUE_HOME_WIN_RATE = 0.45
|
||||
|
||||
def __init__(self):
|
||||
self.referee_engine = get_referee_engine()
|
||||
print("✅ RefereePredictorEngine initialized")
|
||||
|
||||
def predict(self,
|
||||
match_id: str = None,
|
||||
referee_name: str = None,
|
||||
league_id: str = None) -> RefereePrediction:
|
||||
"""
|
||||
Generate referee-based prediction.
|
||||
|
||||
Args:
|
||||
match_id: Match ID to find referee
|
||||
referee_name: Or provide referee name directly
|
||||
league_id: League ID to scope stats (prevents name collisions)
|
||||
|
||||
Returns:
|
||||
RefereePrediction with referee analysis
|
||||
"""
|
||||
|
||||
# Get referee features
|
||||
if match_id:
|
||||
features = self.referee_engine.get_features(match_id, league_id=league_id)
|
||||
# Live flows may already have referee_name while match_officials table is sparse.
|
||||
# Prefer the richer profile if direct-name lookup has more history.
|
||||
if referee_name:
|
||||
name_features = self.referee_engine.get_features_by_name(referee_name, league_id=league_id)
|
||||
if (name_features.get("referee_matches", 0) or 0) > (features.get("referee_matches", 0) or 0):
|
||||
features = name_features
|
||||
elif referee_name:
|
||||
features = self.referee_engine.get_features_by_name(referee_name, league_id=league_id)
|
||||
else:
|
||||
# Return default
|
||||
return RefereePrediction(confidence=10.0)
|
||||
|
||||
ref_name = features.get("referee_name", "Unknown")
|
||||
matches = features.get("referee_matches", 0)
|
||||
|
||||
if matches < 5:
|
||||
# Not enough data
|
||||
return RefereePrediction(
|
||||
referee_name=ref_name,
|
||||
matches_officiated=matches,
|
||||
confidence=20.0
|
||||
)
|
||||
|
||||
# Extract features
|
||||
avg_yellow = features.get("referee_avg_yellow", 4.0)
|
||||
avg_red = features.get("referee_avg_red", 0.2)
|
||||
avg_goals = features.get("referee_avg_goals", 2.5)
|
||||
over25_rate = features.get("referee_over25_rate", 0.5)
|
||||
home_win_rate = features.get("referee_home_win_rate", 0.45) if "referee_home_win_rate" in features else 0.45
|
||||
home_bias = features.get("referee_home_bias", 0.0)
|
||||
penalty_rate = features.get("referee_penalty_rate", 0.15)
|
||||
|
||||
# Determine tendencies
|
||||
is_card_heavy = (avg_yellow + avg_red * 4) > (self.LEAGUE_AVG_YELLOW + 1)
|
||||
is_high_scoring = avg_goals > self.LEAGUE_AVG_GOALS
|
||||
|
||||
# Confidence based on matches officiated
|
||||
confidence = min(90.0, 30.0 + matches * 2)
|
||||
|
||||
return RefereePrediction(
|
||||
referee_name=ref_name,
|
||||
matches_officiated=matches,
|
||||
avg_yellow_cards=avg_yellow,
|
||||
avg_red_cards=avg_red,
|
||||
is_card_heavy=is_card_heavy,
|
||||
avg_goals_per_match=avg_goals,
|
||||
over_25_rate=over25_rate,
|
||||
is_high_scoring=is_high_scoring,
|
||||
home_win_rate=home_win_rate,
|
||||
home_bias=home_bias,
|
||||
penalty_rate=penalty_rate,
|
||||
confidence=confidence
|
||||
)
|
||||
|
||||
def get_modifiers(self, prediction: RefereePrediction) -> Dict[str, float]:
|
||||
"""
|
||||
Get modifiers to apply to other predictions based on referee profile.
|
||||
"""
|
||||
return {
|
||||
# Home team gets slight boost if referee has home bias
|
||||
"home_modifier": 1.0 + (prediction.home_bias * 0.05),
|
||||
# O/U modifier
|
||||
"over_25_modifier": 1.0 + (prediction.avg_goals_per_match - self.LEAGUE_AVG_GOALS) * 0.1,
|
||||
# Card modifier for card markets
|
||||
"cards_modifier": 1.0 + (prediction.avg_yellow_cards - self.LEAGUE_AVG_YELLOW) * 0.05
|
||||
}
|
||||
|
||||
|
||||
# Singleton
|
||||
_engine: Optional[RefereePredictorEngine] = None
|
||||
|
||||
|
||||
def get_referee_predictor() -> RefereePredictorEngine:
|
||||
global _engine
|
||||
if _engine is None:
|
||||
_engine = RefereePredictorEngine()
|
||||
return _engine
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
engine = get_referee_predictor()
|
||||
|
||||
print("\n🧪 Referee Predictor Engine Test")
|
||||
print("=" * 50)
|
||||
|
||||
pred = engine.predict(referee_name="Cüneyt Çakır")
|
||||
|
||||
print(f"\n📊 Prediction:")
|
||||
for k, v in pred.to_dict().items():
|
||||
print(f" {k}: {v}")
|
||||
Reference in New Issue
Block a user