DL-Broadcast-Tool/match_engine.py
2026-04-08 18:13:02 +10:00

220 lines
No EOL
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# match_engine.py
import os
import json
import math
from tags.objective_payload import compute_payload_objective_team_scores
from tags.objective_domination import compute_dom_objective
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CAREER_DB_PATH = os.path.join(BASE_DIR, "career_stats.json")
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):
return db["players"].get(str(pid)) or db["players"].get(pid)
def rank_match_players_slayer(match_players):
"""
match_players: list of dicts from stats API (current season),
each with at least: id, name, team
Returns: list of ranked players with slayer info
"""
db = load_career_db()
if db is None:
return []
ranked = []
for p in match_players:
pid = p.get("id")
entry = _get_player_entry(db, pid)
if not entry:
continue
score = entry.get("slayer_score_raw", 0.0)
if score <= 0:
continue
ranked.append({
"id": pid,
"name": entry["name"],
"team": entry["team_history"][-1] if entry["team_history"] else p.get("team", "Unknown"),
"score_raw": score,
"score_display": entry.get("slayer_score_display", f"{score:.2f}"),
})
ranked.sort(key=lambda x: x["score_raw"], reverse=True)
for i, r in enumerate(ranked, 1):
r["rank"] = i
return ranked
def rank_match_players_objdom(players):
"""
Rank players by Domination Objective Specialist score.
players = list of player_entry dicts for the current match.
"""
ranked = []
for p in players:
score = compute_dom_objective(p)
ranked.append({
"name": p["name"],
"team": p["team"],
"score_raw": score,
"score_display": f"{score:.2f}",
})
# Sort high → low
ranked.sort(key=lambda x: x["score_raw"], reverse=True)
# Assign global rank
for i, entry in enumerate(ranked, start=1):
entry["rank"] = i
return ranked
def rank_match_players_objpl(match_players):
"""
match_players: list of dicts from stats API (current season),
each with at least: id, name, team
Returns: list of ranked players with ObjPL score
"""
db = load_career_db()
if db is None:
return []
# Build team entries for ObjPL engine
team_entries = []
for p in match_players:
pid = p.get("id")
entry = _get_player_entry(db, pid)
if not entry:
continue
if "objective_payload_components" not in entry:
continue
team_entries.append((pid, entry))
if not team_entries:
return []
obj_scores = compute_payload_objective_team_scores(team_entries)
ranked = []
for pid, entry in team_entries:
score = obj_scores.get(pid, 0.0)
if score <= 0:
continue
ranked.append({
"id": pid,
"name": entry["name"],
"team": entry["team_history"][-1] if entry["team_history"] else "Unknown",
"score_raw": score,
"score_display": f"{score:.2f}",
})
ranked.sort(key=lambda x: x["score_raw"], reverse=True)
for i, r in enumerate(ranked, 1):
r["rank"] = i
return ranked
def split_two_columns(ranked_players, team_a_name, team_b_name):
"""
Keeps global rank 1–N, but splits into left/right columns by team.
"""
left = []
right = []
for p in ranked_players:
if p["team"] == team_a_name:
left.append(p)
elif p["team"] == team_b_name:
right.append(p)
else:
# If team name mismatch, leave them out of columns
pass
return left, right
def compute_slayer_prediction(team_a_players, team_b_players):
"""
team_a_players / team_b_players: lists of ranked player dicts with slayer_score_raw
Returns dict with team averages and win chances.
"""
A = [p["score_raw"] for p in team_a_players]
B = [p["score_raw"] for p in team_b_players]
teamA_avg = sum(A) / max(1, len(A))
teamB_avg = sum(B) / max(1, len(B))
if max(teamA_avg, teamB_avg) == 0:
slayer_edge = 0
else:
slayer_edge = (teamB_avg - teamA_avg) / max(teamA_avg, teamB_avg)
k = 1.1
pB = 1 / (1 + math.exp(-k * slayer_edge))
pA = 1 - pB
return {
"teamA_avg": teamA_avg,
"teamB_avg": teamB_avg,
"teamA_win": round(pA * 100),
"teamB_win": round(pB * 100),
}
def rank_match_players_sharpshooter(match_players):
db = load_career_db()
if db is None:
return []
ranked = []
for p in match_players:
pid = p.get("id")
entry = _get_player_entry(db, pid)
if not entry:
continue
sharp = entry.get("sharpshooter", {})
score = sharp.get("score_raw", 0.0)
if score <= 0:
continue
ranked.append({
"id": pid,
"name": entry["name"],
"team": entry["team_history"][-1] if entry["team_history"] else p.get("team", "Unknown"),
"score_raw": score,
"score_display": sharp.get("score_display", f"{score:.2f}"),
})
ranked.sort(key=lambda x: x["score_raw"], reverse=True)
for i, r in enumerate(ranked, 1):
r["rank"] = i
return ranked
def rank_match_players_clutch(players):
ranked = []
for p in players:
score = p.get("clutch_components", {}).get("clutch_norm", 0.0)
ranked.append({
"name": p.get("name", "Unknown"),
"team": p.get("team", ""),
"score_raw": score,
"score_display": f"{score:.2f}",
})
ranked.sort(key=lambda x: x["score_raw"], reverse=True)
for i, r in enumerate(ranked, start=1):
r["rank"] = i
return ranked