2026-04-10 08:08:47 +00:00
|
|
|
import math
|
|
|
|
|
|
|
|
|
|
from tags.slayer import compute_slayer_raw
|
|
|
|
|
from tags.sharpshooter import compute_sharpshooter_raw
|
|
|
|
|
from tags.objective_payload import compute_payload_raw
|
|
|
|
|
from tags.objective_domination import compute_dom_raw
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
# LEAGUE METRICS FOR ALL TAGS (EXCEPT CLUTCH)
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def compute_league_metrics(all_players):
|
|
|
|
|
"""
|
|
|
|
|
Computes league-wide averages and raw-score distributions for:
|
|
|
|
|
- slayer
|
|
|
|
|
- sharpshooter
|
|
|
|
|
- payload
|
|
|
|
|
- consistency
|
|
|
|
|
- domination
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------
|
|
|
|
|
# League averages accumulators
|
|
|
|
|
# -----------------------------------------
|
|
|
|
|
acc = {
|
|
|
|
|
"KD": [],
|
|
|
|
|
"accuracy": [],
|
|
|
|
|
"damage": [],
|
|
|
|
|
"push_time": [],
|
|
|
|
|
"headshot_rate": [],
|
|
|
|
|
"pressure_eff": [],
|
2026-04-15 06:15:07 +00:00
|
|
|
"deaths": [],
|
|
|
|
|
"dom_actions": [],
|
2026-04-10 08:08:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------
|
|
|
|
|
# Raw distributions for percentile ranking
|
|
|
|
|
# -----------------------------------------
|
|
|
|
|
dist = {
|
|
|
|
|
"slayer": [],
|
|
|
|
|
"sharpshooter": [],
|
|
|
|
|
"payload": [],
|
|
|
|
|
"consistency": [],
|
|
|
|
|
"domination": [],
|
2026-04-21 08:55:26 +00:00
|
|
|
"anchor": [],
|
|
|
|
|
"breaker": [],
|
2026-04-10 08:08:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------
|
2026-04-15 06:15:07 +00:00
|
|
|
# Build league averages (PER-MAP NORMALIZED)
|
2026-04-10 08:08:47 +00:00
|
|
|
# -----------------------------------------
|
|
|
|
|
for p in all_players:
|
|
|
|
|
c = p.get("career", {})
|
|
|
|
|
maps = c.get("maps", 0)
|
|
|
|
|
if maps <= 0:
|
|
|
|
|
continue
|
|
|
|
|
|
2026-04-15 06:15:07 +00:00
|
|
|
# Per-map normalization
|
|
|
|
|
kills = c.get("kills", 0) / maps
|
|
|
|
|
deaths = c.get("deaths", 0) / maps
|
|
|
|
|
damage = c.get("damage", 0) / maps
|
|
|
|
|
push = c.get("push_time", 0) / maps
|
|
|
|
|
captures = c.get("captures", c.get("DOM_Captures", 0)) / maps
|
|
|
|
|
counters = c.get("counters", c.get("DOM_Counters", 0)) / maps
|
|
|
|
|
|
2026-04-10 08:08:47 +00:00
|
|
|
shots = c.get("shots", 0)
|
|
|
|
|
shots_hit = c.get("shots_hit", 0)
|
|
|
|
|
headshots = c.get("headshots", 0)
|
|
|
|
|
|
|
|
|
|
KD = kills / deaths if deaths > 0 else kills
|
|
|
|
|
accuracy = shots_hit / shots if shots > 0 else 0
|
|
|
|
|
headshot_rate = headshots / shots_hit if shots_hit > 0 else 0
|
|
|
|
|
pressure_eff = damage / (deaths + 1)
|
|
|
|
|
|
|
|
|
|
acc["KD"].append(KD)
|
|
|
|
|
acc["accuracy"].append(accuracy)
|
|
|
|
|
acc["damage"].append(damage)
|
|
|
|
|
acc["push_time"].append(push)
|
|
|
|
|
acc["headshot_rate"].append(headshot_rate)
|
|
|
|
|
acc["pressure_eff"].append(pressure_eff)
|
2026-04-15 06:15:07 +00:00
|
|
|
acc["deaths"].append(deaths)
|
|
|
|
|
acc["dom_actions"].append(captures + counters)
|
2026-04-10 08:08:47 +00:00
|
|
|
|
|
|
|
|
# -----------------------------------------
|
|
|
|
|
# Compute league averages
|
|
|
|
|
# -----------------------------------------
|
|
|
|
|
league_averages = {
|
|
|
|
|
key: (sum(values) / max(1, len(values)))
|
|
|
|
|
for key, values in acc.items()
|
|
|
|
|
}
|
2026-04-21 08:55:26 +00:00
|
|
|
|
|
|
|
|
# Alias for clarity: deaths is already per-map
|
|
|
|
|
league_averages["deaths_per_map"] = league_averages.get("deaths", 0.0)
|
2026-04-10 08:08:47 +00:00
|
|
|
|
|
|
|
|
# Minimum smoothing to avoid divide-by-zero explosions
|
|
|
|
|
league_averages["accuracy"] = max(league_averages["accuracy"], 0.05)
|
|
|
|
|
league_averages["headshot_rate"] = max(league_averages["headshot_rate"], 0.03)
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------
|
|
|
|
|
# Build raw distributions
|
|
|
|
|
# -----------------------------------------
|
|
|
|
|
for p in all_players:
|
|
|
|
|
c = p.get("career", {})
|
|
|
|
|
matches = p.get("matches", [])
|
|
|
|
|
|
|
|
|
|
# Slayer
|
|
|
|
|
dist["slayer"].append(compute_slayer_raw(p, league_averages))
|
|
|
|
|
|
|
|
|
|
# Sharpshooter
|
|
|
|
|
dist["sharpshooter"].append(compute_sharpshooter_raw(c, league_averages))
|
|
|
|
|
|
|
|
|
|
# Payload
|
|
|
|
|
dist["payload"].append(compute_payload_raw(p))
|
|
|
|
|
|
|
|
|
|
# Domination
|
|
|
|
|
dist["domination"].append(compute_dom_raw(p))
|
2026-04-15 06:15:07 +00:00
|
|
|
|
2026-04-24 06:30:50 +00:00
|
|
|
# Collect the raw consistency score we will calculate in the next step
|
|
|
|
|
dist["consistency"].append(p.get("consistency_raw", 0.0))
|
|
|
|
|
|
2026-04-21 08:55:26 +00:00
|
|
|
from tags.anchor import compute_anchor_raw
|
2026-04-15 06:15:07 +00:00
|
|
|
|
2026-04-21 08:55:26 +00:00
|
|
|
# Consistency raw score for distribution building
|
|
|
|
|
# (You already compute consistency later, but for distributions we only need the raw normalized score)
|
|
|
|
|
consistency_norm = p.get("consistency", {}).get("raw", 0.5)
|
|
|
|
|
|
|
|
|
|
dist["anchor"].append(
|
|
|
|
|
compute_anchor_raw(p, league_averages, consistency_norm)
|
2026-04-15 06:15:07 +00:00
|
|
|
)
|
2026-04-21 08:55:26 +00:00
|
|
|
|
2026-04-10 08:08:47 +00:00
|
|
|
|
|
|
|
|
# -----------------------------------------
|
|
|
|
|
# Sort distributions
|
|
|
|
|
# -----------------------------------------
|
|
|
|
|
distributions = {
|
|
|
|
|
key: sorted(values)
|
|
|
|
|
for key, values in dist.items()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return league_averages, distributions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
# CLUTCH METRICS
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def compute_league_clutch_metrics(all_players):
|
|
|
|
|
"""
|
|
|
|
|
Computes league-wide averages and raw-score distribution for Clutch.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
clutch_raw_values = []
|
|
|
|
|
pressure_values = []
|
|
|
|
|
|
|
|
|
|
for p in all_players:
|
|
|
|
|
clutch_data = p.get("clutch", {})
|
|
|
|
|
raw = p.get("clutch_raw", 0)
|
|
|
|
|
|
|
|
|
|
if raw is not None:
|
|
|
|
|
clutch_raw_values.append(raw)
|
|
|
|
|
|
|
|
|
|
c = p.get("career", {})
|
|
|
|
|
dmg = c.get("damage", 0)
|
|
|
|
|
deaths = c.get("deaths", 0)
|
|
|
|
|
pressure_eff = dmg / (deaths + 1)
|
|
|
|
|
pressure_values.append(pressure_eff)
|
|
|
|
|
|
|
|
|
|
league_averages = {
|
|
|
|
|
"pressure_eff": sum(pressure_values) / max(1, len(pressure_values)),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clutch_distribution = sorted(clutch_raw_values)
|
|
|
|
|
|
|
|
|
|
return league_averages, clutch_distribution
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
__all__ = [
|
|
|
|
|
"compute_league_metrics",
|
|
|
|
|
"compute_league_clutch_metrics",
|
|
|
|
|
]
|