2026-04-10 08:08:47 +00:00
|
|
|
# prediction_engine.py (Modernized)
|
|
|
|
|
|
2026-04-08 08:13:02 +00:00
|
|
|
import math
|
2026-04-10 08:08:47 +00:00
|
|
|
import os
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
from analysis.team_identity_v2 import generate_team_identity_block
|
|
|
|
|
|
|
|
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
CAREER_DB_PATH = os.path.join(BASE_DIR, "final_db.json")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
# Load DB
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def load_career_db():
|
|
|
|
|
if not os.path.exists(CAREER_DB_PATH):
|
|
|
|
|
return None
|
|
|
|
|
with open(CAREER_DB_PATH, "r", encoding="utf-8") as f:
|
|
|
|
|
return json.load(f)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_player_entry(db, pid):
|
|
|
|
|
pid = str(pid)
|
|
|
|
|
return db["players"].get(pid)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
# Logistic Win Chance
|
|
|
|
|
# ---------------------------------------------------------
|
2026-04-08 08:13:02 +00:00
|
|
|
|
|
|
|
|
def logistic_win_chance(teamA_avg, teamB_avg, k=1.1):
|
|
|
|
|
if max(teamA_avg, teamB_avg) == 0:
|
|
|
|
|
return 50, 50
|
|
|
|
|
|
|
|
|
|
edge = (teamB_avg - teamA_avg) / max(teamA_avg, teamB_avg)
|
|
|
|
|
pB = 1 / (1 + math.exp(-k * edge))
|
|
|
|
|
pA = 1 - pB
|
|
|
|
|
|
|
|
|
|
return round(pA * 100), round(pB * 100)
|
|
|
|
|
|
|
|
|
|
|
2026-04-10 08:08:47 +00:00
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
# Compute Tag Averages (Modern Tag System)
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
|
2026-04-08 08:13:02 +00:00
|
|
|
def compute_tag_averages(teamA_players, teamB_players):
|
2026-04-10 08:08:47 +00:00
|
|
|
"""
|
|
|
|
|
Compute team averages using modern tag raw scores.
|
|
|
|
|
Each player dict must contain:
|
|
|
|
|
- id
|
|
|
|
|
- team
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
db = load_career_db()
|
|
|
|
|
if db is None:
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
def avg_tag(players, tag_name):
|
|
|
|
|
vals = []
|
|
|
|
|
for p in players:
|
|
|
|
|
pid = p.get("id") or p.get("PlayerUUID") or p.get("uuid")
|
|
|
|
|
entry = _get_player_entry(db, pid)
|
|
|
|
|
if not entry:
|
|
|
|
|
continue
|
|
|
|
|
tag = entry.get(tag_name, {})
|
|
|
|
|
vals.append(tag.get("raw", 0.0))
|
|
|
|
|
return sum(vals) / max(1, len(vals))
|
2026-04-08 08:13:02 +00:00
|
|
|
|
|
|
|
|
return {
|
2026-04-10 08:08:47 +00:00
|
|
|
"Slayer": (
|
|
|
|
|
avg_tag(teamA_players, "slayer"),
|
|
|
|
|
avg_tag(teamB_players, "slayer"),
|
|
|
|
|
),
|
|
|
|
|
"ObjPL": (
|
|
|
|
|
avg_tag(teamA_players, "objective_payload"),
|
|
|
|
|
avg_tag(teamB_players, "objective_payload"),
|
|
|
|
|
),
|
|
|
|
|
"ObjDOM": (
|
|
|
|
|
avg_tag(teamA_players, "objective_domination"),
|
|
|
|
|
avg_tag(teamB_players, "objective_domination"),
|
|
|
|
|
),
|
|
|
|
|
"Sharpshooter": (
|
|
|
|
|
avg_tag(teamA_players, "sharpshooter"),
|
|
|
|
|
avg_tag(teamB_players, "sharpshooter"),
|
|
|
|
|
),
|
|
|
|
|
"Consistency": (
|
|
|
|
|
avg_tag(teamA_players, "consistency"),
|
|
|
|
|
avg_tag(teamB_players, "consistency"),
|
|
|
|
|
),
|
|
|
|
|
"Clutch": (
|
|
|
|
|
avg_tag(teamA_players, "clutch"),
|
|
|
|
|
avg_tag(teamB_players, "clutch"),
|
|
|
|
|
),
|
2026-04-08 08:13:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-04-10 08:08:47 +00:00
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
# Full Prediction Engine (Option D)
|
|
|
|
|
# ---------------------------------------------------------
|
2026-04-08 08:13:02 +00:00
|
|
|
|
2026-04-10 08:08:47 +00:00
|
|
|
def generate_predictions(teamA_players, teamB_players):
|
2026-04-08 08:13:02 +00:00
|
|
|
team_a = teamA_players[0].get("team", "Team A")
|
|
|
|
|
team_b = teamB_players[0].get("team", "Team B")
|
|
|
|
|
|
|
|
|
|
# --- Compute averages ---
|
|
|
|
|
avgs = compute_tag_averages(teamA_players, teamB_players)
|
|
|
|
|
|
|
|
|
|
# --- Per-tag win chances ---
|
|
|
|
|
per_tag = {}
|
|
|
|
|
for tag, (a_avg, b_avg) in avgs.items():
|
2026-04-09 10:11:39 +00:00
|
|
|
per_tag[tag] = logistic_win_chance(a_avg, b_avg)
|
2026-04-08 08:13:02 +00:00
|
|
|
|
|
|
|
|
# --- Weighted overall prediction ---
|
|
|
|
|
weights = {
|
|
|
|
|
"Slayer": 0.30,
|
|
|
|
|
"ObjPL": 0.25,
|
|
|
|
|
"ObjDOM": 0.25,
|
|
|
|
|
"Sharpshooter": 0.10,
|
|
|
|
|
"Consistency": 0.05,
|
|
|
|
|
"Clutch": 0.05,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
overall_A = sum(per_tag[tag][0] * w for tag, w in weights.items())
|
|
|
|
|
overall_B = sum(per_tag[tag][1] * w for tag, w in weights.items())
|
|
|
|
|
|
|
|
|
|
total = overall_A + overall_B
|
|
|
|
|
if total > 0:
|
|
|
|
|
overall_A = round((overall_A / total) * 100)
|
|
|
|
|
overall_B = 100 - overall_A
|
|
|
|
|
else:
|
|
|
|
|
overall_A = overall_B = 50
|
|
|
|
|
|
|
|
|
|
# --- Narrative summary ---
|
|
|
|
|
narrative = []
|
|
|
|
|
|
|
|
|
|
def add_line(tag, a, b):
|
|
|
|
|
if a > b:
|
|
|
|
|
narrative.append(f"{team_a} hold the edge in {tag}.")
|
|
|
|
|
elif b > a:
|
|
|
|
|
narrative.append(f"{team_b} hold the edge in {tag}.")
|
|
|
|
|
|
|
|
|
|
for tag, (a_avg, b_avg) in avgs.items():
|
|
|
|
|
add_line(tag, a_avg, b_avg)
|
|
|
|
|
|
|
|
|
|
if overall_A > overall_B:
|
|
|
|
|
narrative.append(f"Overall, {team_a} enter this matchup with a slight advantage.")
|
|
|
|
|
elif overall_B > overall_A:
|
|
|
|
|
narrative.append(f"Overall, {team_b} enter this matchup with a slight advantage.")
|
|
|
|
|
else:
|
|
|
|
|
narrative.append("Overall, this matchup looks extremely close.")
|
|
|
|
|
|
|
|
|
|
# --- Build final text output ---
|
|
|
|
|
lines = []
|
|
|
|
|
lines.append(f"MATCH PREDICTIONS — {team_a} vs {team_b}\n")
|
|
|
|
|
|
|
|
|
|
for tag, (pA, pB) in per_tag.items():
|
|
|
|
|
lines.append(f"{tag} Win Chance: {team_a} {pA}% — {team_b} {pB}%")
|
|
|
|
|
|
|
|
|
|
lines.append("\nOVERALL WIN CHANCE:")
|
|
|
|
|
lines.append(f"{team_a} {overall_A}% — {team_b} {overall_B}%")
|
|
|
|
|
|
|
|
|
|
lines.append("\nNARRATIVE SUMMARY:")
|
|
|
|
|
lines.append(" ".join(narrative))
|
|
|
|
|
|
|
|
|
|
return "\n".join(lines)
|