2026-04-10 08:08:47 +00:00
|
|
|
# storylines.py (Modernized & GUI-Compatible)
|
|
|
|
|
|
|
|
|
|
from analysis.team_identity_v2 import generate_team_identity_block
|
|
|
|
|
from analysis.team_identity_v2 import summarize_team_tags
|
|
|
|
|
|
|
|
|
|
__all__ = ["matchup_storyline", "generate_storyline"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
# Team Tag Aggregation
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
|
2026-04-15 06:15:07 +00:00
|
|
|
from utils.safe_tag import safe_pct, safe_tier, safe_avg
|
|
|
|
|
|
2026-04-10 08:08:47 +00:00
|
|
|
def summarize_team_tags(team_players):
|
|
|
|
|
"""
|
|
|
|
|
Aggregates tag tiers + percentiles for a team.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
tags = [
|
|
|
|
|
"slayer",
|
|
|
|
|
"objective_payload",
|
|
|
|
|
"objective_domination",
|
|
|
|
|
"sharpshooter",
|
|
|
|
|
"consistency",
|
|
|
|
|
"clutch",
|
2026-04-21 08:55:26 +00:00
|
|
|
"anchor",
|
|
|
|
|
"breaker",
|
2026-04-10 08:08:47 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
summary = {}
|
|
|
|
|
|
|
|
|
|
for tag in tags:
|
|
|
|
|
pcts = []
|
|
|
|
|
tiers = []
|
|
|
|
|
|
|
|
|
|
for p in team_players:
|
|
|
|
|
tag_data = p.get(tag, {})
|
2026-04-15 06:15:07 +00:00
|
|
|
pcts.append(safe_pct(tag_data.get("pct")))
|
|
|
|
|
tiers.append(safe_tier(tag_data.get("tier")))
|
2026-04-10 08:08:47 +00:00
|
|
|
|
2026-04-23 09:24:48 +00:00
|
|
|
# -----------------------------
|
|
|
|
|
# FIX: safe top-tier calculation
|
|
|
|
|
# -----------------------------
|
|
|
|
|
valid_tiers = [t for t in tiers if t in "SABCD"]
|
|
|
|
|
top_tier = max(valid_tiers, key=lambda t: "SABCD".index(t)) if valid_tiers else "-"
|
|
|
|
|
|
2026-04-10 08:08:47 +00:00
|
|
|
summary[tag] = {
|
2026-04-15 06:15:07 +00:00
|
|
|
"avg_pct": safe_avg(pcts),
|
2026-04-23 09:24:48 +00:00
|
|
|
"top_tier": top_tier,
|
2026-04-10 08:08:47 +00:00
|
|
|
"count_S": tiers.count("S"),
|
|
|
|
|
"count_A": tiers.count("A"),
|
|
|
|
|
"count_B": tiers.count("B"),
|
|
|
|
|
"count_C": tiers.count("C"),
|
|
|
|
|
"count_D": tiers.count("D"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return summary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
# Team Strength Comparison
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def compare_team_strengths(teamA, teamB, team_a_name, team_b_name):
|
|
|
|
|
"""
|
|
|
|
|
Compares two team tag summaries and returns storyline points.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
storyline = []
|
|
|
|
|
|
|
|
|
|
tags = {
|
|
|
|
|
"slayer": "Slayer (Elimination Pressure)",
|
|
|
|
|
"objective_payload": "Payload Objective",
|
|
|
|
|
"objective_domination": "Domination Objective",
|
|
|
|
|
"sharpshooter": "Sharpshooter (Accuracy)",
|
|
|
|
|
"consistency": "Consistency (Stability)",
|
|
|
|
|
"clutch": "Clutch (High-Pressure Performance)",
|
2026-04-21 08:55:26 +00:00
|
|
|
"anchor": "Anchor (Defensive Stability)",
|
|
|
|
|
"breaker": "Breaker (Offensive Disruption)",
|
2026-04-10 08:08:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for tag, label in tags.items():
|
|
|
|
|
a = teamA[tag]["avg_pct"]
|
|
|
|
|
b = teamB[tag]["avg_pct"]
|
|
|
|
|
|
|
|
|
|
diff = a - b
|
2026-04-21 08:55:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# Custom storyline for Anchor
|
|
|
|
|
if tag == "anchor":
|
|
|
|
|
if abs(diff) < 5:
|
|
|
|
|
storyline.append("Both teams are evenly matched in defensive stability.")
|
|
|
|
|
elif diff > 0:
|
|
|
|
|
storyline.append(f"{team_a_name} have stronger defensive stability, surviving longer and preventing collapses.")
|
|
|
|
|
else:
|
|
|
|
|
storyline.append(f"{team_b_name} have stronger defensive stability, giving them an edge in long fights.")
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Generic storyline for all other tags
|
2026-04-10 08:08:47 +00:00
|
|
|
if abs(diff) < 5:
|
|
|
|
|
storyline.append(f"Both teams are evenly matched in {label}.")
|
|
|
|
|
elif diff > 0:
|
|
|
|
|
storyline.append(f"{team_a_name} hold an advantage in {label}.")
|
|
|
|
|
else:
|
|
|
|
|
storyline.append(f"{team_b_name} hold an advantage in {label}.")
|
|
|
|
|
|
|
|
|
|
return storyline
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
# Main Storyline Generator
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def generate_storyline(teamA_players, teamB_players):
|
|
|
|
|
"""
|
|
|
|
|
Generates a full caster-ready storyline block.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Extract real team names
|
|
|
|
|
team_a_name = teamA_players[0].get("team", "Team A")
|
|
|
|
|
team_b_name = teamB_players[0].get("team", "Team B")
|
|
|
|
|
|
|
|
|
|
# 1. Summaries
|
|
|
|
|
A = summarize_team_tags(teamA_players)
|
|
|
|
|
B = summarize_team_tags(teamB_players)
|
|
|
|
|
|
|
|
|
|
# 2. Strength comparison
|
|
|
|
|
matchup_points = compare_team_strengths(A, B, team_a_name, team_b_name)
|
|
|
|
|
|
|
|
|
|
# 3. Identify elite players
|
|
|
|
|
elite_A = [p["name"] for p in teamA_players if p.get("slayer", {}).get("tier") == "S"]
|
|
|
|
|
elite_B = [p["name"] for p in teamB_players if p.get("slayer", {}).get("tier") == "S"]
|
|
|
|
|
|
|
|
|
|
# 4. Build storyline text
|
|
|
|
|
lines = []
|
|
|
|
|
|
|
|
|
|
lines.append(f"MATCH STORYLINE — {team_a_name} vs {team_b_name}\n")
|
|
|
|
|
|
|
|
|
|
# Team A identity
|
|
|
|
|
lines.append(f"{team_a_name} Identity:")
|
|
|
|
|
for tag, data in A.items():
|
|
|
|
|
lines.append(
|
|
|
|
|
f" - {tag.replace('_', ' ').title()}: "
|
|
|
|
|
f"{data['top_tier']} Tier (avg {data['avg_pct']:.1f} percentile)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
lines.append("")
|
|
|
|
|
|
|
|
|
|
# Team B identity
|
|
|
|
|
lines.append(f"{team_b_name} Identity:")
|
|
|
|
|
for tag, data in B.items():
|
|
|
|
|
lines.append(
|
|
|
|
|
f" - {tag.replace('_', ' ').title()}: "
|
|
|
|
|
f"{data['top_tier']} Tier (avg {data['avg_pct']:.1f} percentile)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
lines.append("")
|
|
|
|
|
|
|
|
|
|
# Elite players
|
|
|
|
|
if elite_A:
|
|
|
|
|
lines.append(f"{team_a_name} Elite Players: {', '.join(elite_A)}")
|
|
|
|
|
if elite_B:
|
|
|
|
|
lines.append(f"{team_b_name} Elite Players: {', '.join(elite_B)}")
|
|
|
|
|
|
|
|
|
|
lines.append("")
|
|
|
|
|
|
|
|
|
|
# Matchup points
|
|
|
|
|
lines.append("Matchup Breakdown:")
|
|
|
|
|
for point in matchup_points:
|
|
|
|
|
lines.append(f" - {point}")
|
|
|
|
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
# Legacy Wrapper (GUI expects this exact name)
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def matchup_storyline(teamA_players, teamB_players):
|
|
|
|
|
"""
|
|
|
|
|
Legacy wrapper for GUI compatibility.
|
|
|
|
|
"""
|
|
|
|
|
return generate_storyline(teamA_players, teamB_players)
|