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

258 lines
No EOL
8.5 KiB
Python

import os
import json
from collections import defaultdict
import requests
CACHE_DIR = os.path.join(os.path.dirname(__file__), "cache")
SEASON_CACHE_DIR = os.path.join(CACHE_DIR, "seasons")
os.makedirs(SEASON_CACHE_DIR, exist_ok=True)
from tags.slayer import compute_slayer_for_career_player
from tags.objective_payload import compute_payload_objective_for_career_player
from tags.sharpshooter import compute_sharpshooter_for_career_player
from tags.consistency import compute_consistency
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
API_BASE = "https://dashleague.games/api/v1"
def fetch_json(url):
resp = requests.get(url, timeout=10)
resp.raise_for_status()
return resp.json()
def detect_valid_seasons(max_season=50):
valid = []
for season in range(0, max_season + 1):
try:
url = f"{API_BASE}/stats?season={season}"
resp = requests.get(url, timeout=10)
if resp.status_code != 200:
continue
data = resp.json()
season_stats = data.get("data", [])
if isinstance(season_stats, list) and len(season_stats) > 0:
valid.append(season)
except Exception:
continue
return valid
def fetch_season_stats(season):
url = f"{API_BASE}/stats?season={season}"
data = fetch_json(url)
return data.get("data", [])
def build_career_database(output_path):
seasons = detect_valid_seasons()
if not seasons:
print("No valid seasons found. Aborting career DB build.")
return False
players = {}
league_accumulator = defaultdict(float)
league_counts = defaultdict(int)
for season in seasons:
season_stats = fetch_season_stats(season)
if not isinstance(season_stats, list):
continue
for entry in season_stats:
if not isinstance(entry, dict):
continue
# Normalize inconsistent keys
if "loss" in entry and "losses" not in entry:
entry["losses"] = entry["loss"]
if "shots" not in entry:
entry["shots"] = 0
if "shots_hit" not in entry:
entry["shots_hit"] = 0
if "headshots" not in entry:
entry["headshots"] = 0
if "damage" not in entry:
entry["damage"] = 0
if "maps" not in entry or entry["maps"] in (None, "Coming Soon!"):
entry["maps"] = 0
if "accuracy" not in entry or isinstance(entry["accuracy"], str):
entry["accuracy"] = 0.0
pid = entry.get("id")
if not pid:
continue
if pid not in players:
players[pid] = {
"name": entry.get("name", "Unknown"),
"team_history": set(),
"seasons_played": set(),
"per_season": {},
"career_raw": defaultdict(float)
}
p = players[pid]
team = entry.get("team") or "No Team"
p["team_history"].add(team)
p["seasons_played"].add(season)
p["per_season"][str(season)] = entry
for key in [
"kills",
"deaths",
"headshots",
"accuracy",
"PAY_PushTime",
"DOM_captures",
"DOM_counters",
"maps",
"wins",
"losses",
"damage",
"shots",
"shots_hit",
]:
if key in entry:
p["career_raw"][key] += entry.get(key, 0)
if "KD" in entry:
league_accumulator["KD"] += entry["KD"]
league_counts["KD"] += 1
if "accuracy" in entry:
league_accumulator["accuracy"] += entry["accuracy"]
league_counts["accuracy"] += 1
if "kills" in entry and "maps" in entry and entry["maps"] > 0:
league_accumulator["kills_per_map"] += entry["kills"] / entry["maps"]
league_counts["kills_per_map"] += 1
if "damage" in entry and "maps" in entry and entry["maps"] > 0:
league_accumulator["damage_per_map"] += entry["damage"] / entry["maps"]
league_counts["damage_per_map"] += 1
if "PAY_PushTime" in entry:
league_accumulator["push_time_per_season"] += entry["PAY_PushTime"]
league_counts["push_time_per_season"] += 1
league_averages = {}
for key in league_accumulator:
league_averages[key] = league_accumulator[key] / max(1, league_counts[key])
final_db = {"players": {}, "league_averages": league_averages}
for pid, pdata in players.items():
# 🔥 Skip players with no valid stats
if "career_raw" not in pdata or not pdata["career_raw"]:
continue
# print("DEBUG PLAYER:", pid, pdata["career_raw"])
raw = pdata["career_raw"]
# 🔥 Ensure maps always exists
if "maps" not in raw:
raw["maps"] = 0
maps = raw["maps"] if raw["maps"] > 0 else 1
seasons_played = len(pdata["seasons_played"])
kills_per_map = raw["kills"] / maps
deaths_per_map = raw["deaths"] / maps
push_time_per_season = raw["PAY_PushTime"] / max(1, seasons_played)
KD = raw["kills"] / raw["deaths"] if raw["deaths"] > 0 else raw["kills"]
accuracy = raw["accuracy"] / max(1, seasons_played)
derived = {
"kills_per_map": kills_per_map,
"deaths_per_map": deaths_per_map,
"push_time_per_season": push_time_per_season,
}
final_db["players"][pid] = {
"name": pdata["name"],
"team_history": list(pdata["team_history"]),
"seasons_played": list(pdata["seasons_played"]),
"per_season": pdata["per_season"],
"career": {
"kills": raw["kills"],
"deaths": raw["deaths"],
"KD": KD,
"accuracy": accuracy,
"push_time": raw["PAY_PushTime"],
"captures": raw["DOM_captures"],
"counters": raw["DOM_counters"],
"maps": raw["maps"],
"wins": raw["wins"],
"losses": raw["losses"],
"damage": raw["damage"],
"shots": raw.get("shots", 0),
"shots_hit": raw.get("shots_hit", 0),
"headshots": raw.get("headshots", 0),
"damage_dealt": raw.get("damage", 0),
},
"derived": derived,
}
from data.player_history import get_player_match_history
final_db["players"][pid]["matches"] = get_player_match_history(pid)
# Compute all tag scores per player
for pid, pdata in final_db["players"].items():
# 🔥 Skip players missing a career block
if "career" not in pdata:
print("SKIPPING PLAYER WITH NO CAREER:", pid)
continue
# Slayer
slayer_info = compute_slayer_for_career_player(pdata, league_averages)
pdata["slayer_score_raw"] = slayer_info["score_raw"]
pdata["slayer_score_display"] = slayer_info["score_display"]
pdata["slayer_strength"] = slayer_info["strength"]
# Sharpshooter
sharp = compute_sharpshooter_for_career_player(pdata["career"])
pdata["sharpshooter"] = sharp
# Payload Objective Specialist
obj_components = compute_payload_objective_for_career_player(pdata, league_averages)
pdata["objective_payload_components"] = obj_components
# --- Consistency Tag ---
from tags.consistency import compute_consistency
matches = pdata.get("matches", [])
consistency_raw, components = compute_consistency(matches)
pdata["consistency_components"] = {
**components,
"consistency_raw": consistency_raw,
"consistency_norm": components.get("consistency_norm", consistency_raw)
}
# --- Clutch Tag ---
from tags.clutch import compute_clutch
pdata["clutch_components"] = {
"clutch_norm": compute_clutch(pdata, league_averages)
}
with open(output_path, "w", encoding="utf-8") as f:
json.dump(final_db, f, indent=2)
return True