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
class PlayerPrediction:
"""Player engine prediction output."""
home_squad_quality: float = 50.0 # 0-100
away_squad_quality: float = 50.0
squad_diff: float = 0.0 # -100 to +100
"""Player engine prediction output.
IMPORTANT: squad_quality uses the SAME composite formula as
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
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
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
lineup_available: bool = False
confidence: float = 0.0
@@ -100,10 +105,12 @@ class PlayerPredictorEngine:
"home_goals_last_5": home_analysis.total_goals_last_5,
"home_assists_last_5": home_analysis.total_assists_last_5,
"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_goals_last_5": away_analysis.total_goals_last_5,
"away_assists_last_5": away_analysis.total_assists_last_5,
"away_key_players": away_analysis.key_players_count,
"away_forwards": away_analysis.forward_count or 2,
}
elif match_id:
# Try to get from database
@@ -131,13 +138,31 @@ class PlayerPredictorEngine:
away_goals = features.get("away_goals_last_5", 0)
home_key = features.get("home_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)
# Based on: goals scored, key players, assists
home_quality = min(100, 50 + (home_goals * 3) + (home_key * 5) +
features.get("home_assists_last_5", 0) * 2)
away_quality = min(100, 50 + (away_goals * 3) + (away_key * 5) +
features.get("away_assists_last_5", 0) * 2)
# Calculate squad quality — MUST match extract_training_data.py formula
# Formula: starting_count * 0.3 + goals * 2.0 + assists * 1.0
# + key_players * 3.0 + fwd_count * 1.5
# Typical range: ~3 36 (model trained on this distribution)
home_quality = (
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_diff = home_quality - away_quality
@@ -186,8 +211,10 @@ class PlayerPredictorEngine:
Calculate 1X2 probability modifiers based on squad analysis.
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 {
"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).
"""
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_missing_impact': 0.0, 'away_missing_impact': 0.0,
'home_goals_form': 1.3, 'away_goals_form': 1.3,
@@ -612,7 +612,7 @@ class SingleMatchOrchestrator:
away_lineup=data.away_lineup,
sidelined_data=data.sidelined_data,
)
return {
result = {
'home_squad_quality': float(pred.home_squad_quality),
'away_squad_quality': float(pred.away_squad_quality),
'squad_diff': float(pred.squad_diff),
@@ -623,6 +623,13 @@ class SingleMatchOrchestrator:
'home_goals_form': float(pred.home_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:
print(f"⚠️ Squad features failed: {e}")
return defaults