gg
Deploy Iddaai Backend / build-and-deploy (push) Successful in 6s

This commit is contained in:
2026-05-05 21:27:06 +03:00
parent 56d560af08
commit bfddcaca7d
2 changed files with 49 additions and 15 deletions
+40 -13
View File
@@ -18,15 +18,20 @@ from features.sidelined_analyzer import get_sidelined_analyzer
@dataclass @dataclass
class PlayerPrediction: class PlayerPrediction:
"""Player engine prediction output.""" """Player engine prediction output.
home_squad_quality: float = 50.0 # 0-100
away_squad_quality: float = 50.0 IMPORTANT: squad_quality uses the SAME composite formula as
squad_diff: float = 0.0 # -100 to +100 extract_training_data.py so that inference values match the
distribution the model was trained on (~3-36 range).
"""
home_squad_quality: float = 12.0 # training-scale composite (~3-36)
away_squad_quality: float = 12.0
squad_diff: float = 0.0 # home - away (training scale)
home_key_players: int = 0 home_key_players: int = 0
away_key_players: int = 0 away_key_players: int = 0
home_missing_impact: float = 0.0 # 0-1, how much weaker due to missing players home_missing_impact: float = 0.0 # 0-1, how much weaker due to missing players
away_missing_impact: float = 0.0 away_missing_impact: float = 0.0
home_goals_form: int = 0 # Goals in last 5 matches home_goals_form: int = 0 # Goals in last 5 matches
away_goals_form: int = 0 away_goals_form: int = 0
lineup_available: bool = False lineup_available: bool = False
confidence: float = 0.0 confidence: float = 0.0
@@ -100,10 +105,12 @@ class PlayerPredictorEngine:
"home_goals_last_5": home_analysis.total_goals_last_5, "home_goals_last_5": home_analysis.total_goals_last_5,
"home_assists_last_5": home_analysis.total_assists_last_5, "home_assists_last_5": home_analysis.total_assists_last_5,
"home_key_players": home_analysis.key_players_count, "home_key_players": home_analysis.key_players_count,
"home_forwards": home_analysis.forward_count or 2,
"away_starting_11": away_analysis.starting_count or 11, "away_starting_11": away_analysis.starting_count or 11,
"away_goals_last_5": away_analysis.total_goals_last_5, "away_goals_last_5": away_analysis.total_goals_last_5,
"away_assists_last_5": away_analysis.total_assists_last_5, "away_assists_last_5": away_analysis.total_assists_last_5,
"away_key_players": away_analysis.key_players_count, "away_key_players": away_analysis.key_players_count,
"away_forwards": away_analysis.forward_count or 2,
} }
elif match_id: elif match_id:
# Try to get from database # Try to get from database
@@ -131,13 +138,31 @@ class PlayerPredictorEngine:
away_goals = features.get("away_goals_last_5", 0) away_goals = features.get("away_goals_last_5", 0)
home_key = features.get("home_key_players", 0) home_key = features.get("home_key_players", 0)
away_key = features.get("away_key_players", 0) away_key = features.get("away_key_players", 0)
home_assists = features.get("home_assists_last_5", 0)
away_assists = features.get("away_assists_last_5", 0)
home_starting = features.get("home_starting_11", 11)
away_starting = features.get("away_starting_11", 11)
home_fwd = features.get("home_forwards", 2)
away_fwd = features.get("away_forwards", 2)
# Calculate squad quality (0-100) # Calculate squad quality — MUST match extract_training_data.py formula
# Based on: goals scored, key players, assists # Formula: starting_count * 0.3 + goals * 2.0 + assists * 1.0
home_quality = min(100, 50 + (home_goals * 3) + (home_key * 5) + # + key_players * 3.0 + fwd_count * 1.5
features.get("home_assists_last_5", 0) * 2) # Typical range: ~3 36 (model trained on this distribution)
away_quality = min(100, 50 + (away_goals * 3) + (away_key * 5) + home_quality = (
features.get("away_assists_last_5", 0) * 2) home_starting * 0.3 +
home_goals * 2.0 +
home_assists * 1.0 +
home_key * 3.0 +
home_fwd * 1.5
)
away_quality = (
away_starting * 0.3 +
away_goals * 2.0 +
away_assists * 1.0 +
away_key * 3.0 +
away_fwd * 1.5
)
# Squad difference # Squad difference
squad_diff = home_quality - away_quality squad_diff = home_quality - away_quality
@@ -186,8 +211,10 @@ class PlayerPredictorEngine:
Calculate 1X2 probability modifiers based on squad analysis. Calculate 1X2 probability modifiers based on squad analysis.
Returns modifiers to apply to base probabilities. Returns modifiers to apply to base probabilities.
squad_diff is in training scale (~-33 to +33), normalize to -1..+1.
""" """
diff = prediction.squad_diff / 100 # -1 to +1 diff = prediction.squad_diff / 33.0 # training-scale normalisation
diff = max(-1.0, min(1.0, diff)) # clamp
return { return {
"home_modifier": 1.0 + (diff * 0.3), # Up to +/-30% "home_modifier": 1.0 + (diff * 0.3), # Up to +/-30%
@@ -597,7 +597,7 @@ class SingleMatchOrchestrator:
the model fall back on stronger signals (odds, ELO, form, H2H). the model fall back on stronger signals (odds, ELO, form, H2H).
""" """
defaults = { defaults = {
'home_squad_quality': 0.50, 'away_squad_quality': 0.50, 'squad_diff': 0.0, 'home_squad_quality': 12.0, 'away_squad_quality': 12.0, 'squad_diff': 0.0,
'home_key_players': 3.0, 'away_key_players': 3.0, 'home_key_players': 3.0, 'away_key_players': 3.0,
'home_missing_impact': 0.0, 'away_missing_impact': 0.0, 'home_missing_impact': 0.0, 'away_missing_impact': 0.0,
'home_goals_form': 1.3, 'away_goals_form': 1.3, 'home_goals_form': 1.3, 'away_goals_form': 1.3,
@@ -612,7 +612,7 @@ class SingleMatchOrchestrator:
away_lineup=data.away_lineup, away_lineup=data.away_lineup,
sidelined_data=data.sidelined_data, sidelined_data=data.sidelined_data,
) )
return { result = {
'home_squad_quality': float(pred.home_squad_quality), 'home_squad_quality': float(pred.home_squad_quality),
'away_squad_quality': float(pred.away_squad_quality), 'away_squad_quality': float(pred.away_squad_quality),
'squad_diff': float(pred.squad_diff), 'squad_diff': float(pred.squad_diff),
@@ -623,6 +623,13 @@ class SingleMatchOrchestrator:
'home_goals_form': float(pred.home_goals_form), 'home_goals_form': float(pred.home_goals_form),
'away_goals_form': float(pred.away_goals_form), 'away_goals_form': float(pred.away_goals_form),
} }
# Sanity check: squad_quality must be in training range (~3-36)
for side in ('home', 'away'):
sq = result[f'{side}_squad_quality']
if sq > 50 or sq < 0:
print(f"🚨 SCALE MISMATCH: {side}_squad_quality={sq:.1f} "
f"(expected 3-36). Check player_predictor formula!")
return result
except Exception as e: except Exception as e:
print(f"⚠️ Squad features failed: {e}") print(f"⚠️ Squad features failed: {e}")
return defaults return defaults