OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      59dff7423e3c
DATE      2025-08-28
SUBJECT   nix: add player/team leaderboard scripts
FILES     3 CHANGED
HASH      59dff7423e3c
DATE      2025-08-28
SUBJECT   nix: add player/team leaderboard
          scripts
FILES     3 CHANGED
 

diff --git a/nix/apps/default.nix b/nix/apps/default.nix
index e4b1e03..2cf3100 100644
--- a/nix/apps/default.nix
+++ b/nix/apps/default.nix
@@ -1,11 +1,6 @@
 {
   lib,
-  classes,
-  encounter,
-  buffs,
-  debuffs,
   inputs,
-  trinket,
   api,
   simulation,
   ...
@@ -15,23 +10,25 @@
     getDB = pkgs.callPackage ./getDB.nix {inherit inputs;};
     getCMLeaders = import ./challenge-mode-leaderboard.nix {inherit api writers p
ython3Packages;};
     parseCMs = import ./challenge-mode-parser.nix {inherit api writers python3Pac
kages;};
+    teamLeaderboards = import ./team-leaderboard-generator.nix {inherit writers p
ython3Packages;};
+    playerLeaderboards = import ./player-leaderboard-generator.nix {inherit write
rs python3Packages;};
     # Convert simulation data to apps
     simulationApps =
-      lib.mapAttrs (name: sim: {
+      lib.mapAttrs (_name: sim: {
         type = "app";
         program = "${sim.script}/bin/${sim.metadata.output}-aggregator";
       })
       (simulation.generateMassSimulations pkgs);
 
     raceComparisonApps =
-      lib.mapAttrs (name: raceComp: {
+      lib.mapAttrs (_name: raceComp: {
         type = "app";
         program = "${raceComp.script}/bin/${raceComp.metadata.output}-aggregator"
;
       })
       (simulation.generateRaceComparisons pkgs);
 
     trinketComparisonApps =
-      lib.mapAttrs (name: trinketComp: {
+      lib.mapAttrs (_name: trinketComp: {
         type = "app";
         program = "${trinketComp.script}/bin/${trinketComp.metadata.output}-aggre
gator";
       })
@@ -58,6 +55,14 @@
           type = "app";
           program = "${parseCMs}/bin/cm-leaderboard-parser";
         };
+        teamLeaderboards = {
+          type = "app";
+          program = "${teamLeaderboards}/bin/team-leaderboard-generator";
+        };
+        playerLeaderboards = {
+          type = "app";
+          program = "${playerLeaderboards}/bin/player-leaderboard-generator";
+        };
         testGroupSim = {
           type = "app";
           program = "${simulation.generateTestGroupSim pkgs}/bin/test-group-sim";

diff --git a/nix/apps/default.nix b/nix/apps
/default.nix
index e4b1e03..2cf3100 100644
--- a/nix/apps/default.nix
+++ b/nix/apps/default.nix
@@ -1,11 +1,6 @@
 {
   lib,
-  classes,
-  encounter,
-  buffs,
-  debuffs,
   inputs,
-  trinket,
   api,
   simulation,
   ...
@@ -15,23 +10,25 @@
     getDB = pkgs.callPackage ./getDB.nix {i
nherit inputs;};
     getCMLeaders = import ./challenge-mode-
leaderboard.nix {inherit api writers python3
Packages;};
     parseCMs = import ./challenge-mode-pars
er.nix {inherit api writers python3Packages;
};
+    teamLeaderboards = import ./team-leader
board-generator.nix {inherit writers python3
Packages;};
+    playerLeaderboards = import ./player-le
aderboard-generator.nix {inherit writers pyt
hon3Packages;};
     # Convert simulation data to apps
     simulationApps =
-      lib.mapAttrs (name: sim: {
+      lib.mapAttrs (_name: sim: {
         type = "app";
         program = "${sim.script}/bin/${sim.
metadata.output}-aggregator";
       })
       (simulation.generateMassSimulations p
kgs);
 
     raceComparisonApps =
-      lib.mapAttrs (name: raceComp: {
+      lib.mapAttrs (_name: raceComp: {
         type = "app";
         program = "${raceComp.script}/bin/$
{raceComp.metadata.output}-aggregator";
       })
       (simulation.generateRaceComparisons p
kgs);
 
     trinketComparisonApps =
-      lib.mapAttrs (name: trinketComp: {
+      lib.mapAttrs (_name: trinketComp: {
         type = "app";
         program = "${trinketComp.script}/bi
n/${trinketComp.metadata.output}-aggregator"
;
       })
@@ -58,6 +55,14 @@
           type = "app";
           program = "${parseCMs}/bin/cm-lea
derboard-parser";
         };
+        teamLeaderboards = {
+          type = "app";
+          program = "${teamLeaderboards}/bi
n/team-leaderboard-generator";
+        };
+        playerLeaderboards = {
+          type = "app";
+          program = "${playerLeaderboards}/
bin/player-leaderboard-generator";
+        };
         testGroupSim = {
           type = "app";
           program = "${simulation.generateT
estGroupSim pkgs}/bin/test-group-sim";
 

diff --git a/nix/apps/player-leaderboard-generator.nix b/nix/apps/player-leaderboa
rd-generator.nix
new file mode 100644
index 0000000..a2f4a52
--- /dev/null
+++ b/nix/apps/player-leaderboard-generator.nix
@@ -0,0 +1,333 @@
+{
+  writers,
+  python3Packages,
+  ...
+}: let
+  playerLeaderboardScript =
+    writers.writePython3Bin "player-leaderboard-generator" {
+      libraries = [python3Packages.requests];
+      doCheck = false;
+    }
+    ''
+      import os
+      import json
+      import glob
+      from pathlib import Path
+      from collections import defaultdict
+      import sys
+
+      INPUT_ROOT = "./web/public/data/challenge-mode"
+      GLOBAL_LEADERBOARD_ROOT = "./web/public/data/leaderboards/global"
+      OUTPUT_ROOT = "./web/public/data/player-leaderboards"
+      TOP_N_PLAYERS = 250
+
+      def get_player_id(member):
+          # extract player ID from member data
+          return member.get("id") or member.get("profile", {}).get("id", 0)
+
+      def get_player_name(member):
+          # extract player name from member data
+          return member.get("name") or member.get("profile", {}).get("name", "Unk
nown")
+
+      def get_player_realm(member):
+          # extract player realm from member data
+          return member.get("realm_slug") or member.get("profile", {}).get("realm
", {}).get("slug", "unknown")
+
+      def load_global_rankings():
+          # load global rankings for all dungeons
+          global_rankings = {}
+
+          for dungeon_slug in ['gate-of-the-setting-sun', 'mogu-shan-palace', 'sc
arlet-halls',
+                               'scarlet-monastery', 'scholomance', 'shado-pan-mon
astery',
+                               'siege-of-niuzao-temple', 'stormstout-brewery', 't
emple-of-the-jade-serpent']:
+              global_file = os.path.join(GLOBAL_LEADERBOARD_ROOT, dungeon_slug, '
leaderboard.json')
+              if os.path.exists(global_file):
+                  try:
+                      with open(global_file, 'r', encoding='utf-8') as f:
+                          data = json.load(f)
+                          global_rankings[dungeon_slug] = {}
+
+                          for run in data.get('leaderboard', []):
+                              # create unique identifier for this run
+                              duration = run['duration']
+                              timestamp = run['completed_timestamp']
+                              member_names = tuple(sorted([m['name'] for m in run
['members']]))
+                              run_key = (duration, timestamp, member_names)
+                              global_rankings[dungeon_slug][run_key] = run.get('r
anking', 0)
+                  except (json.JSONDecodeError, IOError) as e:
+                      print(f"Warning: Could not load global rankings for {dungeo
n_slug}: {e}")
+
+          return global_rankings
+
+      def lookup_global_ranking(run_data, global_rankings):
+          # look up the global ranking for a run
+          dungeon_slug = run_data['dungeon_slug']
+          if dungeon_slug not in global_rankings:
+              return "~"
+
+          duration = run_data['duration']
+          timestamp = run_data['completed_timestamp']
+          member_names = tuple(sorted(run_data['member_names']))
+          run_key = (duration, timestamp, member_names)
+
+          # return global ranking if found otherwise ~
+          return global_rankings[dungeon_slug].get(run_key, "~")
+
+      def analyze_players():
+          # analyze all challenge mode data to identify individual player perform
ance
+          print("Starting player analysis...")
+
+          print("Loading global rankings...")
+          global_rankings = load_global_rankings()
+
+          # track player runs: player_id -> dungeon_slug -> list of runs
+          player_runs = defaultdict(lambda: defaultdict(list))
+          available_dungeons = set()
+          all_players_data = {}
+
+          search_path = os.path.join(INPUT_ROOT, "**", "*.json")
+          leaderboard_files = glob.glob(search_path, recursive=True)
+
+          if not leaderboard_files:
+              print(f"FATAL: No leaderboard JSON files found in {os.path.abspath(
INPUT_ROOT)}", file=sys.stderr)
+              print("Please run the challenge mode parser first.", file=sys.stder
r)
+              sys.exit(1)
+
+          print(f"Found {len(leaderboard_files)} leaderboard files to analyze.")
+
+          # first pass: collect all runs per player
+          for file_path in leaderboard_files:
+              path = Path(file_path)
+              parts = path.parts
+              try:
+                  region = parts[-4]
+                  realm_slug = parts[-3]
+                  dungeon_slug = parts[-2]
+              except IndexError:
+                  print(f"Warning: Could not parse path structure for {file_path}
. Skipping.")
+                  continue
+
+              try:
+                  with open(file_path, 'r', encoding='utf-8') as f:
+                      data = json.load(f)
+              except (json.JSONDecodeError, IOError) as e:
+                  print(f"Warning: Could not read or parse {file_path}. Skipping.
 Error: {e}")
+                  continue
+
+              # extract dungeon name and track available dungeons
+              map_name = data.get("map", {}).get("name", {})
+              dungeon_name = map_name.get("en_US", dungeon_slug) if isinstance(ma
p_name, dict) else map_name
+              available_dungeons.add(dungeon_slug)
+
+              runs = data.get("leading_groups", [])
+
+              for run in runs:
+                  members = run.get("members", [])
+                  if len(members) != 5:
+                      continue
+
+                  # process each player in this run
+                  for member in members:
+                      player_id = get_player_id(member)
+                      if player_id == 0:
+                          continue
+
+                      # store player data
+                      if player_id not in all_players_data:
+                          all_players_data[player_id] = {
+                              "name": get_player_name(member),
+                              "realm_slug": get_player_realm(member),
+                              "specs": []
+                          }
+                      # add spec_id if it exists
+                      spec_id = member.get("spec_id")
+                      if spec_id and spec_id not in all_players_data[player_id]["
specs"]:
+                          all_players_data[player_id]["specs"].append(spec_id)
+
+                      # create run data for this player
+                      run_data = {
+                          "duration": run["duration"],
+                          "completed_timestamp": run["completed_timestamp"],
+                          "ranking": run.get("ranking", 0),
+                          "region": region,
+                          "realm_slug": realm_slug,
+                          "dungeon_name": dungeon_name,
+                          "dungeon_slug": dungeon_slug,
+                          "members": members,
+                          "member_names": [get_player_name(m) for m in members]
+                      }
+
+                      # store this run for the player
+                      player_runs[player_id][dungeon_slug].append(run_data)
+
+          print(f"Identified {len(player_runs)} unique players.")
+          print(f"Available dungeons: {sorted(available_dungeons)}")
+
+          # second pass: for each player find their best time per dungeon
+          print("Analyzing player performance across all dungeons...")
+          qualified_players = []
+          players_analyzed = 0
+          players_with_complete_coverage = 0
+
+          for player_id, dungeon_data in player_runs.items():
+              players_analyzed += 1
+              dungeons_completed = set(dungeon_data.keys())
+
+              # requirement: player must have runs in all available dungeons
+              if dungeons_completed != available_dungeons:
+                  continue
+
+              players_with_complete_coverage += 1
+
+              # find best run for each dungeon
+              best_runs_per_dungeon = {}
+
+              for dungeon_slug, runs in dungeon_data.items():
+                  # deduplicate runs within this dungeon
+                  unique_runs = []
+                  seen_dungeon_runs = set()
+                  for run in runs:
+                      member_names_sorted = tuple(sorted(run["member_names"]))
+                      run_id = (run["duration"], run["completed_timestamp"], memb
er_names_sorted)
+                      if run_id not in seen_dungeon_runs:
+                          seen_dungeon_runs.add(run_id)
+                          unique_runs.append(run)
+
+                  # sort unique runs by duration to get best time for this player
+                  sorted_runs = sorted(unique_runs, key=lambda x: x["duration"])
+                  best_run = sorted_runs[0]
+
+                  # look up global ranking for this run
+                  global_ranking = lookup_global_ranking(best_run, global_ranking
s)
+
+                  # store full member data with spec information
+                  all_members_data = []
+                  for member in best_run["members"]:
+                      member_name = get_player_name(member)
+                      all_members_data.append({
+                          "name": member_name,
+                          "realm_slug": get_player_realm(member),
+                          "spec_id": member.get("spec_id"),
+                          "id": get_player_id(member)
+                      })
+
+                  best_runs_per_dungeon[dungeon_slug] = {
+                      "duration": best_run["duration"],
+                      "dungeon_name": best_run["dungeon_name"],
+                      "ranking": global_ranking,
+                      "completed_timestamp": best_run["completed_timestamp"],
+                      "region": best_run["region"],
+                      "realm_slug": best_run["realm_slug"],
+                      "team_members": [name for name in best_run["member_names"] 
if name != all_players_data[player_id]["name"]],
+                      "all_members": all_members_data
+                  }
+
+              # calculate combined best time across all dungeons
+              combined_best_time = sum(run["duration"] for run in best_runs_per_d
ungeon.values())
+
+              # calculate player statistics
+              regions_played = set()
+              total_runs = 0
+              spec_frequency = {}
+
+              for runs in dungeon_data.values():
+                  for run in runs:
+                      regions_played.add(run["region"])
+                      total_runs += 1
+
+              player_info = all_players_data[player_id]
+
+              # count spec frequency based on best runs per dungeon only
+              for dungeon_slug, best_run_data in best_runs_per_dungeon.items():
+                  # find this players spec in the all_members data for this best 
run
+                  for member in best_run_data.get("all_members", []):
+                      if member["name"] == player_info["name"]:
+                          spec_id = member.get("spec_id")
+                          if spec_id:
+                              spec_frequency[spec_id] = spec_frequency.get(spec_i
d, 0) + 1
+                          break
+
+              # determine most played spec based on best runs only
+              most_played_spec = max(spec_frequency.items(), key=lambda x: x[1])[
0] if spec_frequency else None
+
+              qualified_players.append({
+                  "player_id": player_id,
+                  "name": player_info["name"],
+                  "realm_slug": player_info["realm_slug"],
+                  "main_spec_id": most_played_spec,
+                  "dungeons_completed": len(dungeons_completed),
+                  "total_runs": total_runs,
+                  "combined_best_time": combined_best_time,
+                  "average_best_time": combined_best_time / len(dungeons_complete
d),
+                  "regions_played": list(regions_played),
+                  "best_runs_per_dungeon": best_runs_per_dungeon
+              })
+
+          print(f"Analysis results:")
+          print(f"  Players analyzed: {players_analyzed}")
+          print(f"  Players with complete coverage: {players_with_complete_covera
ge}")
+          print(f"  Final qualifying players: {len(qualified_players)}")
+
+          return qualified_players
+
+      def generate_player_leaderboard(players):
+          # generate player leaderboard file sorted by combined best time
+          if not players:
+              print("No qualifying players found.")
+              return
+
+          print(f"Generating player leaderboard...")
+          os.makedirs(OUTPUT_ROOT, exist_ok=True)
+
+          # sort by combined best times across all dungeons
+          players_by_combined = sorted(players, key=lambda x: x["combined_best_ti
me"])[:TOP_N_PLAYERS]
+
+          # add rankings
+          for i, player in enumerate(players_by_combined):
+              player["ranking"] = i + 1
+
+          output_file = os.path.join(OUTPUT_ROOT, "best-overall.json")
+          with open(output_file, 'w', encoding='utf-8') as f:
+              json.dump({
+                  "title": "Best Players Overall",
+                  "description": "Individual players ranked by their combined bes
t times across all 9 dungeons (complete coverage required)",
+                  "generated_timestamp": int(__import__('time').time() * 1000),
+                  "total_players": len(players),
+                  "leaderboard": players_by_combined
+              }, f, separators=(',', ':'))
+
+          print(f"  Generated best-overall.json with {len(players_by_combined)} p
layers")
+
+          # generate summary statistics
+          summary_file = os.path.join(OUTPUT_ROOT, "summary.json")
+          with open(summary_file, 'w', encoding='utf-8') as f:
+              json.dump({
+                  "total_players_analyzed": len(players),
+                  "players_with_complete_coverage": len(players),
+                  "total_runs_processed": sum(p["total_runs"] for p in players),
+                  "average_runs_per_player": sum(p["total_runs"] for p in players
) / len(players) if players else 0,
+                  "most_active_player_runs": max(p["total_runs"] for p in players
) if players else 0,
+                  "generated_timestamp": int(__import__('time').time() * 1000)
+              }, f, separators=(',', ':'))
+
+      def main():
+          print("=== WoW Challenge Mode Player Leaderboard Generator ===")
+          print(f"Top players per leaderboard: {TOP_N_PLAYERS}")
+          print()
+
+          # analyze players from challenge mode data
+          players = analyze_players()
+
+          # generate leaderboard file
+          generate_player_leaderboard(players)
+
+          print(f"\nPlayer leaderboard generated in: {os.path.abspath(OUTPUT_ROOT
)}")
+          print("Available leaderboard:")
+          print("  - best-overall.json: Players by combined best time across all 
dungeons")
+          print("  - summary.json: Analysis statistics")
+
+      if __name__ == "__main__":
+          main()
+    '';
+in
+  playerLeaderboardScript

diff --git a/nix/apps/player-leaderboard-gen
erator.nix b/nix/apps/player-leaderboard-gen
erator.nix
new file mode 100644
index 0000000..a2f4a52
--- /dev/null
+++ b/nix/apps/player-leaderboard-generator.
nix
@@ -0,0 +1,333 @@
+{
+  writers,
+  python3Packages,
+  ...
+}: let
+  playerLeaderboardScript =
+    writers.writePython3Bin "player-leaderb
oard-generator" {
+      libraries = [python3Packages.requests
];
+      doCheck = false;
+    }
+    ''
+      import os
+      import json
+      import glob
+      from pathlib import Path
+      from collections import defaultdict
+      import sys
+
+      INPUT_ROOT = "./web/public/data/chall
enge-mode"
+      GLOBAL_LEADERBOARD_ROOT = "./web/publ
ic/data/leaderboards/global"
+      OUTPUT_ROOT = "./web/public/data/play
er-leaderboards"
+      TOP_N_PLAYERS = 250
+
+      def get_player_id(member):
+          # extract player ID from member d
ata
+          return member.get("id") or member
.get("profile", {}).get("id", 0)
+
+      def get_player_name(member):
+          # extract player name from member
 data
+          return member.get("name") or memb
er.get("profile", {}).get("name", "Unknown")
+
+      def get_player_realm(member):
+          # extract player realm from membe
r data
+          return member.get("realm_slug") o
r member.get("profile", {}).get("realm", {})
.get("slug", "unknown")
+
+      def load_global_rankings():
+          # load global rankings for all du
ngeons
+          global_rankings = {}
+
+          for dungeon_slug in ['gate-of-the
-setting-sun', 'mogu-shan-palace', 'scarlet-
halls',
+                               'scarlet-mon
astery', 'scholomance', 'shado-pan-monastery
',
+                               'siege-of-ni
uzao-temple', 'stormstout-brewery', 'temple-
of-the-jade-serpent']:
+              global_file = os.path.join(GL
OBAL_LEADERBOARD_ROOT, dungeon_slug, 'leader
board.json')
+              if os.path.exists(global_file
):
+                  try:
+                      with open(global_file
, 'r', encoding='utf-8') as f:
+                          data = json.load(
f)
+                          global_rankings[d
ungeon_slug] = {}
+
+                          for run in data.g
et('leaderboard', []):
+                              # create uniq
ue identifier for this run
+                              duration = ru
n['duration']
+                              timestamp = r
un['completed_timestamp']
+                              member_names 
= tuple(sorted([m['name'] for m in run['memb
ers']]))
+                              run_key = (du
ration, timestamp, member_names)
+                              global_rankin
gs[dungeon_slug][run_key] = run.get('ranking
', 0)
+                  except (json.JSONDecodeEr
ror, IOError) as e:
+                      print(f"Warning: Coul
d not load global rankings for {dungeon_slug
}: {e}")
+
+          return global_rankings
+
+      def lookup_global_ranking(run_data, g
lobal_rankings):
+          # look up the global ranking for 
a run
+          dungeon_slug = run_data['dungeon_
slug']
+          if dungeon_slug not in global_ran
kings:
+              return "~"
+
+          duration = run_data['duration']
+          timestamp = run_data['completed_t
imestamp']
+          member_names = tuple(sorted(run_d
ata['member_names']))
+          run_key = (duration, timestamp, m
ember_names)
+
+          # return global ranking if found 
otherwise ~
+          return global_rankings[dungeon_sl
ug].get(run_key, "~")
+
+      def analyze_players():
+          # analyze all challenge mode data
 to identify individual player performance
+          print("Starting player analysis..
.")
+
+          print("Loading global rankings...
")
+          global_rankings = load_global_ran
kings()
+
+          # track player runs: player_id ->
 dungeon_slug -> list of runs
+          player_runs = defaultdict(lambda:
 defaultdict(list))
+          available_dungeons = set()
+          all_players_data = {}
+
+          search_path = os.path.join(INPUT_
ROOT, "**", "*.json")
+          leaderboard_files = glob.glob(sea
rch_path, recursive=True)
+
+          if not leaderboard_files:
+              print(f"FATAL: No leaderboard
 JSON files found in {os.path.abspath(INPUT_
ROOT)}", file=sys.stderr)
+              print("Please run the challen
ge mode parser first.", file=sys.stderr)
+              sys.exit(1)
+
+          print(f"Found {len(leaderboard_fi
les)} leaderboard files to analyze.")
+
+          # first pass: collect all runs pe
r player
+          for file_path in leaderboard_file
s:
+              path = Path(file_path)
+              parts = path.parts
+              try:
+                  region = parts[-4]
+                  realm_slug = parts[-3]
+                  dungeon_slug = parts[-2]
+              except IndexError:
+                  print(f"Warning: Could no
t parse path structure for {file_path}. Skip
ping.")
+                  continue
+
+              try:
+                  with open(file_path, 'r',
 encoding='utf-8') as f:
+                      data = json.load(f)
+              except (json.JSONDecodeError,
 IOError) as e:
+                  print(f"Warning: Could no
t read or parse {file_path}. Skipping. Error
: {e}")
+                  continue
+
+              # extract dungeon name and tr
ack available dungeons
+              map_name = data.get("map", {}
).get("name", {})
+              dungeon_name = map_name.get("
en_US", dungeon_slug) if isinstance(map_name
, dict) else map_name
+              available_dungeons.add(dungeo
n_slug)
+
+              runs = data.get("leading_grou
ps", [])
+
+              for run in runs:
+                  members = run.get("member
s", [])
+                  if len(members) != 5:
+                      continue
+
+                  # process each player in 
this run
+                  for member in members:
+                      player_id = get_playe
r_id(member)
+                      if player_id == 0:
+                          continue
+
+                      # store player data
+                      if player_id not in a
ll_players_data:
+                          all_players_data[
player_id] = {
+                              "name": get_p
layer_name(member),
+                              "realm_slug":
 get_player_realm(member),
+                              "specs": []
+                          }
+                      # add spec_id if it e
xists
+                      spec_id = member.get(
"spec_id")
+                      if spec_id and spec_i
d not in all_players_data[player_id]["specs"
]:
+                          all_players_data[
player_id]["specs"].append(spec_id)
+
+                      # create run data for
 this player
+                      run_data = {
+                          "duration": run["
duration"],
+                          "completed_timest
amp": run["completed_timestamp"],
+                          "ranking": run.ge
t("ranking", 0),
+                          "region": region,
+                          "realm_slug": rea
lm_slug,
+                          "dungeon_name": d
ungeon_name,
+                          "dungeon_slug": d
ungeon_slug,
+                          "members": member
s,
+                          "member_names": [
get_player_name(m) for m in members]
+                      }
+
+                      # store this run for 
the player
+                      player_runs[player_id
][dungeon_slug].append(run_data)
+
+          print(f"Identified {len(player_ru
ns)} unique players.")
+          print(f"Available dungeons: {sort
ed(available_dungeons)}")
+
+          # second pass: for each player fi
nd their best time per dungeon
+          print("Analyzing player performan
ce across all dungeons...")
+          qualified_players = []
+          players_analyzed = 0
+          players_with_complete_coverage = 
0
+
+          for player_id, dungeon_data in pl
ayer_runs.items():
+              players_analyzed += 1
+              dungeons_completed = set(dung
eon_data.keys())
+
+              # requirement: player must ha
ve runs in all available dungeons
+              if dungeons_completed != avai
lable_dungeons:
+                  continue
+
+              players_with_complete_coverag
e += 1
+
+              # find best run for each dung
eon
+              best_runs_per_dungeon = {}
+
+              for dungeon_slug, runs in dun
geon_data.items():
+                  # deduplicate runs within
 this dungeon
+                  unique_runs = []
+                  seen_dungeon_runs = set()
+                  for run in runs:
+                      member_names_sorted =
 tuple(sorted(run["member_names"]))
+                      run_id = (run["durati
on"], run["completed_timestamp"], member_nam
es_sorted)
+                      if run_id not in seen
_dungeon_runs:
+                          seen_dungeon_runs
.add(run_id)
+                          unique_runs.appen
d(run)
+
+                  # sort unique runs by dur
ation to get best time for this player
+                  sorted_runs = sorted(uniq
ue_runs, key=lambda x: x["duration"])
+                  best_run = sorted_runs[0]
+
+                  # look up global ranking 
for this run
+                  global_ranking = lookup_g
lobal_ranking(best_run, global_rankings)
+
+                  # store full member data 
with spec information
+                  all_members_data = []
+                  for member in best_run["m
embers"]:
+                      member_name = get_pla
yer_name(member)
+                      all_members_data.appe
nd({
+                          "name": member_na
me,
+                          "realm_slug": get
_player_realm(member),
+                          "spec_id": member
.get("spec_id"),
+                          "id": get_player_
id(member)
+                      })
+
+                  best_runs_per_dungeon[dun
geon_slug] = {
+                      "duration": best_run[
"duration"],
+                      "dungeon_name": best_
run["dungeon_name"],
+                      "ranking": global_ran
king,
+                      "completed_timestamp"
: best_run["completed_timestamp"],
+                      "region": best_run["r
egion"],
+                      "realm_slug": best_ru
n["realm_slug"],
+                      "team_members": [name
 for name in best_run["member_names"] if nam
e != all_players_data[player_id]["name"]],
+                      "all_members": all_me
mbers_data
+                  }
+
+              # calculate combined best tim
e across all dungeons
+              combined_best_time = sum(run[
"duration"] for run in best_runs_per_dungeon
.values())
+
+              # calculate player statistics
+              regions_played = set()
+              total_runs = 0
+              spec_frequency = {}
+
+              for runs in dungeon_data.valu
es():
+                  for run in runs:
+                      regions_played.add(ru
n["region"])
+                      total_runs += 1
+
+              player_info = all_players_dat
a[player_id]
+
+              # count spec frequency based 
on best runs per dungeon only
+              for dungeon_slug, best_run_da
ta in best_runs_per_dungeon.items():
+                  # find this players spec 
in the all_members data for this best run
+                  for member in best_run_da
ta.get("all_members", []):
+                      if member["name"] == 
player_info["name"]:
+                          spec_id = member.
get("spec_id")
+                          if spec_id:
+                              spec_frequenc
y[spec_id] = spec_frequency.get(spec_id, 0) 
+ 1
+                          break
+
+              # determine most played spec 
based on best runs only
+              most_played_spec = max(spec_f
requency.items(), key=lambda x: x[1])[0] if 
spec_frequency else None
+
+              qualified_players.append({
+                  "player_id": player_id,
+                  "name": player_info["name
"],
+                  "realm_slug": player_info
["realm_slug"],
+                  "main_spec_id": most_play
ed_spec,
+                  "dungeons_completed": len
(dungeons_completed),
+                  "total_runs": total_runs,
+                  "combined_best_time": com
bined_best_time,
+                  "average_best_time": comb
ined_best_time / len(dungeons_completed),
+                  "regions_played": list(re
gions_played),
+                  "best_runs_per_dungeon": 
best_runs_per_dungeon
+              })
+
+          print(f"Analysis results:")
+          print(f"  Players analyzed: {play
ers_analyzed}")
+          print(f"  Players with complete c
overage: {players_with_complete_coverage}")
+          print(f"  Final qualifying player
s: {len(qualified_players)}")
+
+          return qualified_players
+
+      def generate_player_leaderboard(playe
rs):
+          # generate player leaderboard fil
e sorted by combined best time
+          if not players:
+              print("No qualifying players 
found.")
+              return
+
+          print(f"Generating player leaderb
oard...")
+          os.makedirs(OUTPUT_ROOT, exist_ok
=True)
+
+          # sort by combined best times acr
oss all dungeons
+          players_by_combined = sorted(play
ers, key=lambda x: x["combined_best_time"])[
:TOP_N_PLAYERS]
+
+          # add rankings
+          for i, player in enumerate(player
s_by_combined):
+              player["ranking"] = i + 1
+
+          output_file = os.path.join(OUTPUT
_ROOT, "best-overall.json")
+          with open(output_file, 'w', encod
ing='utf-8') as f:
+              json.dump({
+                  "title": "Best Players Ov
erall",
+                  "description": "Individua
l players ranked by their combined best time
s across all 9 dungeons (complete coverage r
equired)",
+                  "generated_timestamp": in
t(__import__('time').time() * 1000),
+                  "total_players": len(play
ers),
+                  "leaderboard": players_by
_combined
+              }, f, separators=(',', ':'))
+
+          print(f"  Generated best-overall.
json with {len(players_by_combined)} players
")
+
+          # generate summary statistics
+          summary_file = os.path.join(OUTPU
T_ROOT, "summary.json")
+          with open(summary_file, 'w', enco
ding='utf-8') as f:
+              json.dump({
+                  "total_players_analyzed":
 len(players),
+                  "players_with_complete_co
verage": len(players),
+                  "total_runs_processed": s
um(p["total_runs"] for p in players),
+                  "average_runs_per_player"
: sum(p["total_runs"] for p in players) / le
n(players) if players else 0,
+                  "most_active_player_runs"
: max(p["total_runs"] for p in players) if p
layers else 0,
+                  "generated_timestamp": in
t(__import__('time').time() * 1000)
+              }, f, separators=(',', ':'))
+
+      def main():
+          print("=== WoW Challenge Mode Pla
yer Leaderboard Generator ===")
+          print(f"Top players per leaderboa
rd: {TOP_N_PLAYERS}")
+          print()
+
+          # analyze players from challenge 
mode data
+          players = analyze_players()
+
+          # generate leaderboard file
+          generate_player_leaderboard(playe
rs)
+
+          print(f"\nPlayer leaderboard gene
rated in: {os.path.abspath(OUTPUT_ROOT)}")
+          print("Available leaderboard:")
+          print("  - best-overall.json: Pla
yers by combined best time across all dungeo
ns")
+          print("  - summary.json: Analysis
 statistics")
+
+      if __name__ == "__main__":
+          main()
+    '';
+in
+  playerLeaderboardScript
 

diff --git a/nix/apps/team-leaderboard-generator.nix b/nix/apps/team-leaderboard-g
enerator.nix
new file mode 100644
index 0000000..5de1d01
--- /dev/null
+++ b/nix/apps/team-leaderboard-generator.nix
@@ -0,0 +1,549 @@
+{
+  writers,
+  python3Packages,
+  ...
+}: let
+  teamLeaderboardScript =
+    writers.writePython3Bin "team-leaderboard-generator" {
+      libraries = [python3Packages.requests];
+      doCheck = false;
+    }
+    ''
+      import os
+      import json
+      import glob
+      from pathlib import Path
+      from itertools import combinations
+      from collections import defaultdict
+      import sys
+
+      INPUT_ROOT = "./web/public/data/challenge-mode"
+      GLOBAL_LEADERBOARD_ROOT = "./web/public/data/leaderboards/global"
+      OUTPUT_ROOT = "./web/public/data/team-leaderboards"
+      MIN_TEAM_RUNS = 2
+      TOP_N_TEAMS = 50
+
+      def get_player_id(member):
+          # extract player ID from member data
+          return member.get("id") or member.get("profile", {}).get("id", 0)
+
+      def get_player_name(member):
+          # extract player name from member data
+          return member.get("name") or member.get("profile", {}).get("name", "Unk
nown")
+
+      def get_player_realm(member):
+          # extract player realm from member data
+          return member.get("realm_slug") or member.get("profile", {}).get("realm
", {}).get("slug", "unknown")
+
+      def generate_team_cores(members):
+          # generate all possible 3-player core combinations from a 5-player team
+          player_ids = [get_player_id(member) for member in members]
+          core_combinations = list(combinations(sorted(player_ids), 3))
+          return core_combinations
+
+      def create_team_signature(core_ids):
+          # create a unique signature for a 3-player core team
+          return "-".join(map(str, sorted(core_ids)))
+
+      def load_global_rankings():
+          # load global rankings for all dungeons
+          global_rankings = {}
+
+          for dungeon_slug in ['gate-of-the-setting-sun', 'mogu-shan-palace', 'sc
arlet-halls',
+                               'scarlet-monastery', 'scholomance', 'shado-pan-mon
astery',
+                               'siege-of-niuzao-temple', 'stormstout-brewery', 't
emple-of-the-jade-serpent']:
+              global_file = os.path.join(GLOBAL_LEADERBOARD_ROOT, dungeon_slug, '
leaderboard.json')
+              if os.path.exists(global_file):
+                  try:
+                      with open(global_file, 'r', encoding='utf-8') as f:
+                          data = json.load(f)
+                          global_rankings[dungeon_slug] = {}
+
+                          for run in data.get('leaderboard', []):
+                              # create unique identifier for this run
+                              duration = run['duration']
+                              timestamp = run['completed_timestamp']
+                              member_names = tuple(sorted([m['name'] for m in run
['members']]))
+                              run_key = (duration, timestamp, member_names)
+                              global_rankings[dungeon_slug][run_key] = run.get('r
anking', 0)
+                  except (json.JSONDecodeError, IOError) as e:
+                      print(f"Warning: Could not load global rankings for {dungeo
n_slug}: {e}")
+
+          return global_rankings
+
+      def lookup_global_ranking(run_data, global_rankings):
+          # look up the global ranking for a run
+          dungeon_slug = run_data['dungeon_slug']
+          if dungeon_slug not in global_rankings:
+              return "~"
+
+          duration = run_data['duration']
+          timestamp = run_data['completed_timestamp']
+          member_names = tuple(sorted(run_data['member_names']))
+          run_key = (duration, timestamp, member_names)
+
+          # return global ranking if found otherwise ~
+          return global_rankings[dungeon_slug].get(run_key, "~")
+
+      def get_team_info(core_ids, all_members_data):
+          # get team information for a core team
+          team_info = []
+          for player_id in core_ids:
+              if player_id in all_members_data:
+                  member_data = all_members_data[player_id]
+                  team_member = {
+                      "id": player_id,
+                      "name": member_data["name"],
+                      "realm_slug": member_data["realm_slug"]
+                  }
+                  # add spec info if available
+                  if member_data.get("specs"):
+                      team_member["spec_id"] = member_data["specs"][0]
+                  team_info.append(team_member)
+              else:
+                  team_info.append({
+                      "id": player_id,
+                      "name": "Unknown",
+                      "realm_slug": "unknown"
+                  })
+          return team_info
+
+      def deduplicate_overlapping_teams(teams, all_members_data):
+          # merge teams that share most of their best times
+          remaining_teams = teams.copy()
+          deduplicated = []
+
+          while remaining_teams:
+              current_team = remaining_teams.pop(0)
+              current_best_times = current_team["best_runs_per_dungeon"]
+
+              # Find teams that share most best times with current team
+              similar_teams = [current_team]
+              non_similar = []
+
+              for other_team in remaining_teams:
+                  other_best_times = other_team["best_runs_per_dungeon"]
+
+                  # Count how many dungeons have identical best times
+                  matching_dungeons = 0
+                  total_dungeons = min(len(current_best_times), len(other_best_ti
mes))
+
+                  for dungeon_slug in current_best_times:
+                      if dungeon_slug in other_best_times:
+                          if current_best_times[dungeon_slug]["duration"] == othe
r_best_times[dungeon_slug]["duration"]:
+                              matching_dungeons += 1
+
+                  # If they share 6+ identical times out of 9 dungeons, consider 
them same team
+                  match_percentage = matching_dungeons / total_dungeons if total_
dungeons > 0 else 0
+                  if matching_dungeons >= 6 and match_percentage >= 0.6:
+                      similar_teams.append(other_team)
+                  else:
+                      non_similar.append(other_team)
+
+              remaining_teams = non_similar
+
+              if len(similar_teams) == 1:
+                  # No similar teams found
+                  deduplicated.append(current_team)
+              else:
+                  # Merge similar teams (same underlying team with different 3-pl
ayer core perspectives)
+                  # Collect all members and data
+                  all_core_members = set()
+                  all_extended_members = set()
+                  all_runs = []
+                  regions_played = set()
+                  total_runs = 0
+                  best_combined_time = float('inf')
+                  best_team_runs = None
+
+                  for team in similar_teams:
+                      # Collect core members
+                      core_ids = list(map(int, team["team_signature"].split("-"))
)
+                      all_core_members.update(core_ids)
+
+                      # Collect extended roster
+                      for member in team["extended_roster"]:
+                          all_extended_members.add(member["name"] + "@" + member[
"realm_slug"])
+
+                      # Collect runs and other data
+                      all_runs.extend(team["all_runs"])
+                      regions_played.update(team["regions_played"])
+                      total_runs += team["total_runs"]
+
+                      # Use the best performance among similar teams
+                      if team["combined_best_time"] < best_combined_time:
+                          best_combined_time = team["combined_best_time"]
+                          best_team_runs = team["best_runs_per_dungeon"]
+
+                  # Create merged team - limit core to 3 most consistent players 
across best runs
+                  # Count participation in best runs across all similar teams
+                  player_best_participation = defaultdict(int)
+                  for team in similar_teams:
+                      for run_data in team["best_runs_per_dungeon"].values():
+                          # Find actual run to count participation
+                          for dungeon_runs in [team["all_runs"]]:  # Use all_runs
 from team
+                              for run in dungeon_runs:
+                                  if run["duration"] == run_data["duration"] and 
run["completed_timestamp"] == run_data["completed_timestamp"]:
+                                      for member_id in run["member_ids"]:
+                                          player_best_participation[member_id] +=
 1
+                                      break
+
+                  # Select top 3 most consistent players as merged core
+                  top_players = sorted(player_best_participation.items(), key=lam
bda x: x[1], reverse=True)[:3]
+                  merged_core_ids = sorted([player_id for player_id, count in top
_players])
+                  merged_core_info = get_team_info(merged_core_ids, all_members_d
ata)
+
+                  # Create extended roster from players who appear in the merged 
team's best runs
+                  merged_extended_roster_ids = set()
+                  for run_data in best_team_runs.values():
+                      # Find players who participated in this best run across all
 similar teams
+                      for team in similar_teams:
+                          for run in team["all_runs"]:
+                              if run["duration"] == run_data["duration"] and run[
"completed_timestamp"] == run_data["completed_timestamp"]:
+                                  merged_extended_roster_ids.update(run["member_i
ds"])
+                                  break
+
+                  merged_extended_roster = []
+                  for member_id in sorted(merged_extended_roster_ids):
+                      if member_id in all_members_data:
+                          member_data = all_members_data[member_id]
+                          roster_member = {
+                              "name": member_data["name"],
+                              "realm_slug": member_data["realm_slug"]
+                          }
+                          # Add spec info if available (use first spec if multipl
e)
+                          if member_data.get("specs"):
+                              roster_member["spec_id"] = member_data["specs"][0]
+                          merged_extended_roster.append(roster_member)
+
+                  merged_sig = create_team_signature(merged_core_ids)
+
+                  merged_team = {
+                      "team_signature": merged_sig,
+                      "core_members": merged_core_info,
+                      "extended_roster": merged_extended_roster,
+                      "dungeons_completed": similar_teams[0]["dungeons_completed"
],
+                      "total_runs": total_runs // len(similar_teams),
+                      "combined_best_time": best_combined_time,
+                      "average_best_time": best_combined_time / similar_teams[0][
"dungeons_completed"],
+                      "regions_played": list(regions_played),
+                      "best_runs_per_dungeon": best_team_runs,
+                      "all_runs": sorted(all_runs, key=lambda x: x["duration"])
+                  }
+
+                  deduplicated.append(merged_team)
+
+          return deduplicated
+
+      def analyze_teams():
+          # analyze all challenge mode data to identify unique teams and their op
timal 3-player cores
+          print("Starting team analysis...")
+
+          print("Loading global rankings...")
+          global_rankings = load_global_rankings()
+
+          # first pass: collect all runs and group by extended rosters
+          roster_runs = defaultdict(lambda: defaultdict(list))
+          available_dungeons = set()
+          all_members_data = {}
+
+          search_path = os.path.join(INPUT_ROOT, "**", "*.json")
+          leaderboard_files = glob.glob(search_path, recursive=True)
+
+          if not leaderboard_files:
+              print(f"FATAL: No leaderboard JSON files found in {os.path.abspath(
INPUT_ROOT)}", file=sys.stderr)
+              print("Please run the challenge mode parser first.", file=sys.stder
r)
+              sys.exit(1)
+
+          print(f"Found {len(leaderboard_files)} leaderboard files to analyze.")
+
+          # First pass: collect all runs and identify unique extended rosters
+          extended_rosters = {}  # roster_sig -> set of all player_ids who have r
un together
+
+          for file_path in leaderboard_files:
+              path = Path(file_path)
+              parts = path.parts
+              try:
+                  region = parts[-4]
+                  realm_slug = parts[-3]
+                  dungeon_slug = parts[-2]
+              except IndexError:
+                  print(f"Warning: Could not parse path structure for {file_path}
. Skipping.")
+                  continue
+
+              try:
+                  with open(file_path, 'r', encoding='utf-8') as f:
+                      data = json.load(f)
+              except (json.JSONDecodeError, IOError) as e:
+                  print(f"Warning: Could not read or parse {file_path}. Skipping.
 Error: {e}")
+                  continue
+
+              # Extract dungeon name and track available dungeons
+              map_name = data.get("map", {}).get("name", {})
+              dungeon_name = map_name.get("en_US", dungeon_slug) if isinstance(ma
p_name, dict) else map_name
+              available_dungeons.add(dungeon_slug)
+
+              runs = data.get("leading_groups", [])
+
+              for run in runs:
+                  members = run.get("members", [])
+                  if len(members) != 5:
+                      continue
+
+                  # Store member data for later lookup (track all specs they've p
layed)
+                  for member in members:
+                      player_id = get_player_id(member)
+                      if player_id not in all_members_data:
+                          all_members_data[player_id] = {
+                              "name": get_player_name(member),
+                              "realm_slug": get_player_realm(member),
+                              "specs": []
+                          }
+                      # Add spec_id if it exists
+                      spec_id = member.get("spec_id")
+                      if spec_id and spec_id not in all_members_data[player_id]["
specs"]:
+                          all_members_data[player_id]["specs"].append(spec_id)
+
+                  # Create run data
+                  member_ids = [get_player_id(m) for m in members]
+                  run_data = {
+                      "duration": run["duration"],
+                      "completed_timestamp": run["completed_timestamp"],
+                      "ranking": run.get("ranking", 0),
+                      "region": region,
+                      "realm_slug": realm_slug,
+                      "dungeon_name": dungeon_name,
+                      "dungeon_slug": dungeon_slug,
+                      "members": members,
+                      "member_ids": member_ids,
+                      "member_names": [get_player_name(m) for m in members]
+                  }
+
+                  # Track extended rosters - players who run together consistentl
y
+                  # Group runs by overlapping player combinations to identify ext
ended teams
+                  found_roster = None
+                  current_players = set(member_ids)
+
+                  # Check if this run matches any existing extended roster
+                  for roster_sig, roster_players in extended_rosters.items():
+                      # If 3+ players overlap, consider this part of the same ext
ended roster
+                      overlap = len(current_players.intersection(roster_players))
+                      if overlap >= 3:
+                          found_roster = roster_sig
+                          # Add new players to the extended roster
+                          extended_rosters[roster_sig].update(current_players)
+                          break
+
+                  # If no matching roster found, create new one
+                  if found_roster is None:
+                      roster_sig = create_team_signature(sorted(member_ids))
+                      extended_rosters[roster_sig] = current_players.copy()
+                      found_roster = roster_sig
+
+                  # Store run for this extended roster
+                  roster_runs[found_roster][dungeon_slug].append(run_data)
+
+          print(f"Identified {len(extended_rosters)} unique extended rosters.")
+          print(f"Available dungeons: {sorted(available_dungeons)}")
+
+          # Second pass: For each extended roster, identify best 3-player core an
d performance
+          print("Analyzing extended rosters to identify consistent 3-player cores
...")
+          qualified_teams = []
+          rosters_analyzed = 0
+          rosters_with_complete_coverage = 0
+
+          for roster_sig, dungeon_data in roster_runs.items():
+              rosters_analyzed += 1
+              dungeons_completed = set(dungeon_data.keys())
+
+              # REQUIREMENT: Roster must have runs in ALL available dungeons
+              if dungeons_completed != available_dungeons:
+                  continue
+
+              rosters_with_complete_coverage += 1
+
+              # Find best run for each dungeon and track player participation in 
best runs
+              best_runs_per_dungeon = {}
+              player_participation_in_best = defaultdict(int)
+
+              for dungeon_slug, runs in dungeon_data.items():
+                  # Deduplicate runs within this dungeon (same run across differe
nt realms)
+                  unique_runs = []
+                  seen_dungeon_runs = set()
+                  for run in runs:
+                      member_names_sorted = tuple(sorted(run["member_names"]))
+                      run_id = (run["duration"], run["completed_timestamp"], memb
er_names_sorted)
+                      if run_id not in seen_dungeon_runs:
+                          seen_dungeon_runs.add(run_id)
+                          unique_runs.append(run)
+
+                  # Sort unique runs by duration to get best time for this roster
 in this dungeon
+                  sorted_runs = sorted(unique_runs, key=lambda x: x["duration"])
+                  best_run = sorted_runs[0]
+
+                  # Look up global ranking for this run
+                  global_ranking = lookup_global_ranking(best_run, global_ranking
s)
+
+                  best_runs_per_dungeon[dungeon_slug] = {
+                      "duration": best_run["duration"],
+                      "dungeon_name": best_run["dungeon_name"],
+                      "ranking": global_ranking,  # Use global ranking instead of
 realm ranking
+                      "completed_timestamp": best_run["completed_timestamp"],
+                      "region": best_run["region"],
+                      "realm_slug": best_run["realm_slug"],
+                      "members": best_run["member_names"]
+                  }
+
+                  # Track which players appear in the best runs (for core identif
ication)
+                  for player_id in best_run["member_ids"]:
+                      player_participation_in_best[player_id] += 1
+
+              # Identify the 3-player core: players who appear in the most best r
uns
+              total_dungeons = len(dungeons_completed)
+              participation_sorted = sorted(player_participation_in_best.items(),
 key=lambda x: x[1], reverse=True)
+
+              # Take top 3 players who appear in the most best runs as the core
+              if len(participation_sorted) >= 3:
+                  core_ids = sorted([pid for pid, count in participation_sorted[:
3]])
+              else:
+                  continue  # Not enough consistent players
+
+              # Get extended roster (only players who appear in the best runs per
 dungeon)
+              extended_roster_ids = set()
+              for run_data in best_runs_per_dungeon.values():
+                  # Find the actual run to get member_ids
+                  for dungeon_slug, runs in dungeon_data.items():
+                      for run in runs:
+                          if run["duration"] == run_data["duration"] and run["com
pleted_timestamp"] == run_data["completed_timestamp"]:
+                              extended_roster_ids.update(run["member_ids"])
+                              break
+
+              # REQUIREMENT: Roster must have minimum number of total runs
+              total_runs = sum(len(runs) for runs in dungeon_data.values())
+              if total_runs < MIN_TEAM_RUNS:
+                  continue
+
+              # Create core team info
+              core_info = get_team_info(core_ids, all_members_data)
+
+              # Create extended roster info (only players who contributed to best
 times)
+              extended_roster = []
+              for player_id in sorted(extended_roster_ids):
+                  if player_id in all_members_data:
+                      member_data = all_members_data[player_id]
+                      roster_member = {
+                          "name": member_data["name"],
+                          "realm_slug": member_data["realm_slug"]
+                      }
+                      # Add spec info if available (use first spec if multiple)
+                      if member_data.get("specs"):
+                          roster_member["spec_id"] = member_data["specs"][0]
+                      extended_roster.append(roster_member)
+
+              # Calculate combined best time across ALL dungeons
+              combined_best_time = sum(run["duration"] for run in best_runs_per_d
ungeon.values())
+
+              # Calculate team statistics
+              regions_played = set()
+              all_team_runs = []
+              seen_runs = set()  # Track unique runs by (duration, timestamp)
+              for runs in dungeon_data.values():
+                  for run in runs:
+                      regions_played.add(run["region"])
+                      # Create unique identifier for run deduplication (include m
ember names for cross-realm duplicates)
+                      member_names_sorted = tuple(sorted(run["member_names"]))
+                      run_id = (run["duration"], run["completed_timestamp"], memb
er_names_sorted)
+                      if run_id not in seen_runs:
+                          seen_runs.add(run_id)
+                          all_team_runs.append(run)
+
+              # Use core signature for uniqueness
+              core_sig = create_team_signature(core_ids)
+
+              qualified_teams.append({
+                  "team_signature": core_sig,
+                  "core_members": core_info,
+                  "extended_roster": extended_roster,
+                  "dungeons_completed": len(dungeons_completed),
+                  "total_runs": total_runs,
+                  "combined_best_time": combined_best_time,
+                  "average_best_time": combined_best_time / len(dungeons_complete
d),
+                  "regions_played": list(regions_played),
+                  "best_runs_per_dungeon": best_runs_per_dungeon,
+                  "all_runs": sorted(all_team_runs, key=lambda x: x["duration"])
+              })
+
+          print(f"Analysis results:")
+          print(f"  Extended rosters analyzed: {rosters_analyzed}")
+          print(f"  Rosters with complete coverage: {rosters_with_complete_covera
ge}")
+          print(f"  Final qualifying teams: {len(qualified_teams)}")
+
+          return qualified_teams, all_members_data
+
+      def generate_team_leaderboard(teams):
+          """Generate team leaderboard file sorted by combined best time"""
+          if not teams:
+              print("No qualifying teams found.")
+              return
+
+          print(f"Generating team leaderboard...")
+          os.makedirs(OUTPUT_ROOT, exist_ok=True)
+
+          # Sort by combined best times across ALL dungeons (primary ranking)
+          teams_by_combined = sorted(teams, key=lambda x: x["combined_best_time"]
)[:TOP_N_TEAMS]
+
+          # Add rankings
+          for i, team in enumerate(teams_by_combined):
+              team["ranking"] = i + 1
+
+          output_file = os.path.join(OUTPUT_ROOT, "best-overall.json")
+          with open(output_file, 'w', encoding='utf-8') as f:
+              json.dump({
+                  "title": "Best Teams Overall",
+                  "description": "Teams ranked by their combined best times acros
s all 9 dungeons (complete coverage required)",
+                  "generated_timestamp": int(__import__('time').time() * 1000),
+                  "total_teams": len(teams),
+                  "min_runs_required": MIN_TEAM_RUNS,
+                  "leaderboard": teams_by_combined
+              }, f, separators=(',', ':'))
+
+          print(f"  Generated best-overall.json with {len(teams_by_combined)} tea
ms")
+
+          # Generate summary statistics
+          summary_file = os.path.join(OUTPUT_ROOT, "summary.json")
+          with open(summary_file, 'w', encoding='utf-8') as f:
+              json.dump({
+                  "total_teams_analyzed": len(teams),
+                  "teams_with_complete_coverage": len(teams),  # All teams in the
 list have complete coverage
+                  "total_runs_processed": sum(t["total_runs"] for t in teams) // 
10,  # Divide by 10 since each run creates 10 team combinations
+                  "average_runs_per_team": sum(t["total_runs"] for t in teams) / 
len(teams) if teams else 0,
+                  "most_active_team_runs": max(t["total_runs"] for t in teams) if
 teams else 0,
+                  "generated_timestamp": int(__import__('time').time() * 1000)
+              }, f, separators=(',', ':'))
+
+      def main():
+          print("=== WoW Challenge Mode Team Leaderboard Generator ===")
+          print(f"Minimum runs required per team: {MIN_TEAM_RUNS}")
+          print(f"Top teams per leaderboard: {TOP_N_TEAMS}")
+          print()
+
+          # Analyze teams from challenge mode data
+          teams, all_members_data = analyze_teams()
+
+          # Deduplicate overlapping teams (same underlying team with different co
re perspectives)
+          teams = deduplicate_overlapping_teams(teams, all_members_data)
+          print(f"After deduplication: {len(teams)} unique teams")
+
+          # Generate leaderboard file
+          generate_team_leaderboard(teams)
+
+          print(f"\nTeam leaderboard generated in: {os.path.abspath(OUTPUT_ROOT)}
")
+          print("Available files:")
+          print("  - best-overall.json: Teams by combined best time across all du
ngeons")
+          print("  - summary.json: Analysis statistics")
+
+      if __name__ == "__main__":
+          main()
+    '';
+in
+  teamLeaderboardScript

diff --git a/nix/apps/team-leaderboard-gener
ator.nix b/nix/apps/team-leaderboard-generat
or.nix
new file mode 100644
index 0000000..5de1d01
--- /dev/null
+++ b/nix/apps/team-leaderboard-generator.ni
x
@@ -0,0 +1,549 @@
+{
+  writers,
+  python3Packages,
+  ...
+}: let
+  teamLeaderboardScript =
+    writers.writePython3Bin "team-leaderboa
rd-generator" {
+      libraries = [python3Packages.requests
];
+      doCheck = false;
+    }
+    ''
+      import os
+      import json
+      import glob
+      from pathlib import Path
+      from itertools import combinations
+      from collections import defaultdict
+      import sys
+
+      INPUT_ROOT = "./web/public/data/chall
enge-mode"
+      GLOBAL_LEADERBOARD_ROOT = "./web/publ
ic/data/leaderboards/global"
+      OUTPUT_ROOT = "./web/public/data/team
-leaderboards"
+      MIN_TEAM_RUNS = 2
+      TOP_N_TEAMS = 50
+
+      def get_player_id(member):
+          # extract player ID from member d
ata
+          return member.get("id") or member
.get("profile", {}).get("id", 0)
+
+      def get_player_name(member):
+          # extract player name from member
 data
+          return member.get("name") or memb
er.get("profile", {}).get("name", "Unknown")
+
+      def get_player_realm(member):
+          # extract player realm from membe
r data
+          return member.get("realm_slug") o
r member.get("profile", {}).get("realm", {})
.get("slug", "unknown")
+
+      def generate_team_cores(members):
+          # generate all possible 3-player 
core combinations from a 5-player team
+          player_ids = [get_player_id(membe
r) for member in members]
+          core_combinations = list(combinat
ions(sorted(player_ids), 3))
+          return core_combinations
+
+      def create_team_signature(core_ids):
+          # create a unique signature for a
 3-player core team
+          return "-".join(map(str, sorted(c
ore_ids)))
+
+      def load_global_rankings():
+          # load global rankings for all du
ngeons
+          global_rankings = {}
+
+          for dungeon_slug in ['gate-of-the
-setting-sun', 'mogu-shan-palace', 'scarlet-
halls',
+                               'scarlet-mon
astery', 'scholomance', 'shado-pan-monastery
',
+                               'siege-of-ni
uzao-temple', 'stormstout-brewery', 'temple-
of-the-jade-serpent']:
+              global_file = os.path.join(GL
OBAL_LEADERBOARD_ROOT, dungeon_slug, 'leader
board.json')
+              if os.path.exists(global_file
):
+                  try:
+                      with open(global_file
, 'r', encoding='utf-8') as f:
+                          data = json.load(
f)
+                          global_rankings[d
ungeon_slug] = {}
+
+                          for run in data.g
et('leaderboard', []):
+                              # create uniq
ue identifier for this run
+                              duration = ru
n['duration']
+                              timestamp = r
un['completed_timestamp']
+                              member_names 
= tuple(sorted([m['name'] for m in run['memb
ers']]))
+                              run_key = (du
ration, timestamp, member_names)
+                              global_rankin
gs[dungeon_slug][run_key] = run.get('ranking
', 0)
+                  except (json.JSONDecodeEr
ror, IOError) as e:
+                      print(f"Warning: Coul
d not load global rankings for {dungeon_slug
}: {e}")
+
+          return global_rankings
+
+      def lookup_global_ranking(run_data, g
lobal_rankings):
+          # look up the global ranking for 
a run
+          dungeon_slug = run_data['dungeon_
slug']
+          if dungeon_slug not in global_ran
kings:
+              return "~"
+
+          duration = run_data['duration']
+          timestamp = run_data['completed_t
imestamp']
+          member_names = tuple(sorted(run_d
ata['member_names']))
+          run_key = (duration, timestamp, m
ember_names)
+
+          # return global ranking if found 
otherwise ~
+          return global_rankings[dungeon_sl
ug].get(run_key, "~")
+
+      def get_team_info(core_ids, all_membe
rs_data):
+          # get team information for a core
 team
+          team_info = []
+          for player_id in core_ids:
+              if player_id in all_members_d
ata:
+                  member_data = all_members
_data[player_id]
+                  team_member = {
+                      "id": player_id,
+                      "name": member_data["
name"],
+                      "realm_slug": member_
data["realm_slug"]
+                  }
+                  # add spec info if availa
ble
+                  if member_data.get("specs
"):
+                      team_member["spec_id"
] = member_data["specs"][0]
+                  team_info.append(team_mem
ber)
+              else:
+                  team_info.append({
+                      "id": player_id,
+                      "name": "Unknown",
+                      "realm_slug": "unknow
n"
+                  })
+          return team_info
+
+      def deduplicate_overlapping_teams(tea
ms, all_members_data):
+          # merge teams that share most of 
their best times
+          remaining_teams = teams.copy()
+          deduplicated = []
+
+          while remaining_teams:
+              current_team = remaining_team
s.pop(0)
+              current_best_times = current_
team["best_runs_per_dungeon"]
+
+              # Find teams that share most 
best times with current team
+              similar_teams = [current_team
]
+              non_similar = []
+
+              for other_team in remaining_t
eams:
+                  other_best_times = other_
team["best_runs_per_dungeon"]
+
+                  # Count how many dungeons
 have identical best times
+                  matching_dungeons = 0
+                  total_dungeons = min(len(
current_best_times), len(other_best_times))
+
+                  for dungeon_slug in curre
nt_best_times:
+                      if dungeon_slug in ot
her_best_times:
+                          if current_best_t
imes[dungeon_slug]["duration"] == other_best
_times[dungeon_slug]["duration"]:
+                              matching_dung
eons += 1
+
+                  # If they share 6+ identi
cal times out of 9 dungeons, consider them s
ame team
+                  match_percentage = matchi
ng_dungeons / total_dungeons if total_dungeo
ns > 0 else 0
+                  if matching_dungeons >= 6
 and match_percentage >= 0.6:
+                      similar_teams.append(
other_team)
+                  else:
+                      non_similar.append(ot
her_team)
+
+              remaining_teams = non_similar
+
+              if len(similar_teams) == 1:
+                  # No similar teams found
+                  deduplicated.append(curre
nt_team)
+              else:
+                  # Merge similar teams (sa
me underlying team with different 3-player c
ore perspectives)
+                  # Collect all members and
 data
+                  all_core_members = set()
+                  all_extended_members = se
t()
+                  all_runs = []
+                  regions_played = set()
+                  total_runs = 0
+                  best_combined_time = floa
t('inf')
+                  best_team_runs = None
+
+                  for team in similar_teams
:
+                      # Collect core member
s
+                      core_ids = list(map(i
nt, team["team_signature"].split("-")))
+                      all_core_members.upda
te(core_ids)
+
+                      # Collect extended ro
ster
+                      for member in team["e
xtended_roster"]:
+                          all_extended_memb
ers.add(member["name"] + "@" + member["realm
_slug"])
+
+                      # Collect runs and ot
her data
+                      all_runs.extend(team[
"all_runs"])
+                      regions_played.update
(team["regions_played"])
+                      total_runs += team["t
otal_runs"]
+
+                      # Use the best perfor
mance among similar teams
+                      if team["combined_bes
t_time"] < best_combined_time:
+                          best_combined_tim
e = team["combined_best_time"]
+                          best_team_runs = 
team["best_runs_per_dungeon"]
+
+                  # Create merged team - li
mit core to 3 most consistent players across
 best runs
+                  # Count participation in 
best runs across all similar teams
+                  player_best_participation
 = defaultdict(int)
+                  for team in similar_teams
:
+                      for run_data in team[
"best_runs_per_dungeon"].values():
+                          # Find actual run
 to count participation
+                          for dungeon_runs 
in [team["all_runs"]]:  # Use all_runs from 
team
+                              for run in du
ngeon_runs:
+                                  if run["d
uration"] == run_data["duration"] and run["c
ompleted_timestamp"] == run_data["completed_
timestamp"]:
+                                      for m
ember_id in run["member_ids"]:
+                                          p
layer_best_participation[member_id] += 1
+                                      break
+
+                  # Select top 3 most consi
stent players as merged core
+                  top_players = sorted(play
er_best_participation.items(), key=lambda x:
 x[1], reverse=True)[:3]
+                  merged_core_ids = sorted(
[player_id for player_id, count in top_playe
rs])
+                  merged_core_info = get_te
am_info(merged_core_ids, all_members_data)
+
+                  # Create extended roster 
from players who appear in the merged team's
 best runs
+                  merged_extended_roster_id
s = set()
+                  for run_data in best_team
_runs.values():
+                      # Find players who pa
rticipated in this best run across all simil
ar teams
+                      for team in similar_t
eams:
+                          for run in team["
all_runs"]:
+                              if run["durat
ion"] == run_data["duration"] and run["compl
eted_timestamp"] == run_data["completed_time
stamp"]:
+                                  merged_ex
tended_roster_ids.update(run["member_ids"])
+                                  break
+
+                  merged_extended_roster = 
[]
+                  for member_id in sorted(m
erged_extended_roster_ids):
+                      if member_id in all_m
embers_data:
+                          member_data = all
_members_data[member_id]
+                          roster_member = {
+                              "name": membe
r_data["name"],
+                              "realm_slug":
 member_data["realm_slug"]
+                          }
+                          # Add spec info i
f available (use first spec if multiple)
+                          if member_data.ge
t("specs"):
+                              roster_member
["spec_id"] = member_data["specs"][0]
+                          merged_extended_r
oster.append(roster_member)
+
+                  merged_sig = create_team_
signature(merged_core_ids)
+
+                  merged_team = {
+                      "team_signature": mer
ged_sig,
+                      "core_members": merge
d_core_info,
+                      "extended_roster": me
rged_extended_roster,
+                      "dungeons_completed":
 similar_teams[0]["dungeons_completed"],
+                      "total_runs": total_r
uns // len(similar_teams),
+                      "combined_best_time":
 best_combined_time,
+                      "average_best_time": 
best_combined_time / similar_teams[0]["dunge
ons_completed"],
+                      "regions_played": lis
t(regions_played),
+                      "best_runs_per_dungeo
n": best_team_runs,
+                      "all_runs": sorted(al
l_runs, key=lambda x: x["duration"])
+                  }
+
+                  deduplicated.append(merge
d_team)
+
+          return deduplicated
+
+      def analyze_teams():
+          # analyze all challenge mode data
 to identify unique teams and their optimal 
3-player cores
+          print("Starting team analysis..."
)
+
+          print("Loading global rankings...
")
+          global_rankings = load_global_ran
kings()
+
+          # first pass: collect all runs an
d group by extended rosters
+          roster_runs = defaultdict(lambda:
 defaultdict(list))
+          available_dungeons = set()
+          all_members_data = {}
+
+          search_path = os.path.join(INPUT_
ROOT, "**", "*.json")
+          leaderboard_files = glob.glob(sea
rch_path, recursive=True)
+
+          if not leaderboard_files:
+              print(f"FATAL: No leaderboard
 JSON files found in {os.path.abspath(INPUT_
ROOT)}", file=sys.stderr)
+              print("Please run the challen
ge mode parser first.", file=sys.stderr)
+              sys.exit(1)
+
+          print(f"Found {len(leaderboard_fi
les)} leaderboard files to analyze.")
+
+          # First pass: collect all runs an
d identify unique extended rosters
+          extended_rosters = {}  # roster_s
ig -> set of all player_ids who have run tog
ether
+
+          for file_path in leaderboard_file
s:
+              path = Path(file_path)
+              parts = path.parts
+              try:
+                  region = parts[-4]
+                  realm_slug = parts[-3]
+                  dungeon_slug = parts[-2]
+              except IndexError:
+                  print(f"Warning: Could no
t parse path structure for {file_path}. Skip
ping.")
+                  continue
+
+              try:
+                  with open(file_path, 'r',
 encoding='utf-8') as f:
+                      data = json.load(f)
+              except (json.JSONDecodeError,
 IOError) as e:
+                  print(f"Warning: Could no
t read or parse {file_path}. Skipping. Error
: {e}")
+                  continue
+
+              # Extract dungeon name and tr
ack available dungeons
+              map_name = data.get("map", {}
).get("name", {})
+              dungeon_name = map_name.get("
en_US", dungeon_slug) if isinstance(map_name
, dict) else map_name
+              available_dungeons.add(dungeo
n_slug)
+
+              runs = data.get("leading_grou
ps", [])
+
+              for run in runs:
+                  members = run.get("member
s", [])
+                  if len(members) != 5:
+                      continue
+
+                  # Store member data for l
ater lookup (track all specs they've played)
+                  for member in members:
+                      player_id = get_playe
r_id(member)
+                      if player_id not in a
ll_members_data:
+                          all_members_data[
player_id] = {
+                              "name": get_p
layer_name(member),
+                              "realm_slug":
 get_player_realm(member),
+                              "specs": []
+                          }
+                      # Add spec_id if it e
xists
+                      spec_id = member.get(
"spec_id")
+                      if spec_id and spec_i
d not in all_members_data[player_id]["specs"
]:
+                          all_members_data[
player_id]["specs"].append(spec_id)
+
+                  # Create run data
+                  member_ids = [get_player_
id(m) for m in members]
+                  run_data = {
+                      "duration": run["dura
tion"],
+                      "completed_timestamp"
: run["completed_timestamp"],
+                      "ranking": run.get("r
anking", 0),
+                      "region": region,
+                      "realm_slug": realm_s
lug,
+                      "dungeon_name": dunge
on_name,
+                      "dungeon_slug": dunge
on_slug,
+                      "members": members,
+                      "member_ids": member_
ids,
+                      "member_names": [get_
player_name(m) for m in members]
+                  }
+
+                  # Track extended rosters 
- players who run together consistently
+                  # Group runs by overlappi
ng player combinations to identify extended 
teams
+                  found_roster = None
+                  current_players = set(mem
ber_ids)
+
+                  # Check if this run match
es any existing extended roster
+                  for roster_sig, roster_pl
ayers in extended_rosters.items():
+                      # If 3+ players overl
ap, consider this part of the same extended 
roster
+                      overlap = len(current
_players.intersection(roster_players))
+                      if overlap >= 3:
+                          found_roster = ro
ster_sig
+                          # Add new players
 to the extended roster
+                          extended_rosters[
roster_sig].update(current_players)
+                          break
+
+                  # If no matching roster f
ound, create new one
+                  if found_roster is None:
+                      roster_sig = create_t
eam_signature(sorted(member_ids))
+                      extended_rosters[rost
er_sig] = current_players.copy()
+                      found_roster = roster
_sig
+
+                  # Store run for this exte
nded roster
+                  roster_runs[found_roster]
[dungeon_slug].append(run_data)
+
+          print(f"Identified {len(extended_
rosters)} unique extended rosters.")
+          print(f"Available dungeons: {sort
ed(available_dungeons)}")
+
+          # Second pass: For each extended 
roster, identify best 3-player core and perf
ormance
+          print("Analyzing extended rosters
 to identify consistent 3-player cores...")
+          qualified_teams = []
+          rosters_analyzed = 0
+          rosters_with_complete_coverage = 
0
+
+          for roster_sig, dungeon_data in r
oster_runs.items():
+              rosters_analyzed += 1
+              dungeons_completed = set(dung
eon_data.keys())
+
+              # REQUIREMENT: Roster must ha
ve runs in ALL available dungeons
+              if dungeons_completed != avai
lable_dungeons:
+                  continue
+
+              rosters_with_complete_coverag
e += 1
+
+              # Find best run for each dung
eon and track player participation in best r
uns
+              best_runs_per_dungeon = {}
+              player_participation_in_best 
= defaultdict(int)
+
+              for dungeon_slug, runs in dun
geon_data.items():
+                  # Deduplicate runs within
 this dungeon (same run across different rea
lms)
+                  unique_runs = []
+                  seen_dungeon_runs = set()
+                  for run in runs:
+                      member_names_sorted =
 tuple(sorted(run["member_names"]))
+                      run_id = (run["durati
on"], run["completed_timestamp"], member_nam
es_sorted)
+                      if run_id not in seen
_dungeon_runs:
+                          seen_dungeon_runs
.add(run_id)
+                          unique_runs.appen
d(run)
+
+                  # Sort unique runs by dur
ation to get best time for this roster in th
is dungeon
+                  sorted_runs = sorted(uniq
ue_runs, key=lambda x: x["duration"])
+                  best_run = sorted_runs[0]
+
+                  # Look up global ranking 
for this run
+                  global_ranking = lookup_g
lobal_ranking(best_run, global_rankings)
+
+                  best_runs_per_dungeon[dun
geon_slug] = {
+                      "duration": best_run[
"duration"],
+                      "dungeon_name": best_
run["dungeon_name"],
+                      "ranking": global_ran
king,  # Use global ranking instead of realm
 ranking
+                      "completed_timestamp"
: best_run["completed_timestamp"],
+                      "region": best_run["r
egion"],
+                      "realm_slug": best_ru
n["realm_slug"],
+                      "members": best_run["
member_names"]
+                  }
+
+                  # Track which players app
ear in the best runs (for core identificatio
n)
+                  for player_id in best_run
["member_ids"]:
+                      player_participation_
in_best[player_id] += 1
+
+              # Identify the 3-player core:
 players who appear in the most best runs
+              total_dungeons = len(dungeons
_completed)
+              participation_sorted = sorted
(player_participation_in_best.items(), key=l
ambda x: x[1], reverse=True)
+
+              # Take top 3 players who appe
ar in the most best runs as the core
+              if len(participation_sorted) 
>= 3:
+                  core_ids = sorted([pid fo
r pid, count in participation_sorted[:3]])
+              else:
+                  continue  # Not enough co
nsistent players
+
+              # Get extended roster (only p
layers who appear in the best runs per dunge
on)
+              extended_roster_ids = set()
+              for run_data in best_runs_per
_dungeon.values():
+                  # Find the actual run to 
get member_ids
+                  for dungeon_slug, runs in
 dungeon_data.items():
+                      for run in runs:
+                          if run["duration"
] == run_data["duration"] and run["completed
_timestamp"] == run_data["completed_timestam
p"]:
+                              extended_rost
er_ids.update(run["member_ids"])
+                              break
+
+              # REQUIREMENT: Roster must ha
ve minimum number of total runs
+              total_runs = sum(len(runs) fo
r runs in dungeon_data.values())
+              if total_runs < MIN_TEAM_RUNS
:
+                  continue
+
+              # Create core team info
+              core_info = get_team_info(cor
e_ids, all_members_data)
+
+              # Create extended roster info
 (only players who contributed to best times
)
+              extended_roster = []
+              for player_id in sorted(exten
ded_roster_ids):
+                  if player_id in all_membe
rs_data:
+                      member_data = all_mem
bers_data[player_id]
+                      roster_member = {
+                          "name": member_da
ta["name"],
+                          "realm_slug": mem
ber_data["realm_slug"]
+                      }
+                      # Add spec info if av
ailable (use first spec if multiple)
+                      if member_data.get("s
pecs"):
+                          roster_member["sp
ec_id"] = member_data["specs"][0]
+                      extended_roster.appen
d(roster_member)
+
+              # Calculate combined best tim
e across ALL dungeons
+              combined_best_time = sum(run[
"duration"] for run in best_runs_per_dungeon
.values())
+
+              # Calculate team statistics
+              regions_played = set()
+              all_team_runs = []
+              seen_runs = set()  # Track un
ique runs by (duration, timestamp)
+              for runs in dungeon_data.valu
es():
+                  for run in runs:
+                      regions_played.add(ru
n["region"])
+                      # Create unique ident
ifier for run deduplication (include member 
names for cross-realm duplicates)
+                      member_names_sorted =
 tuple(sorted(run["member_names"]))
+                      run_id = (run["durati
on"], run["completed_timestamp"], member_nam
es_sorted)
+                      if run_id not in seen
_runs:
+                          seen_runs.add(run
_id)
+                          all_team_runs.app
end(run)
+
+              # Use core signature for uniq
ueness
+              core_sig = create_team_signat
ure(core_ids)
+
+              qualified_teams.append({
+                  "team_signature": core_si
g,
+                  "core_members": core_info
,
+                  "extended_roster": extend
ed_roster,
+                  "dungeons_completed": len
(dungeons_completed),
+                  "total_runs": total_runs,
+                  "combined_best_time": com
bined_best_time,
+                  "average_best_time": comb
ined_best_time / len(dungeons_completed),
+                  "regions_played": list(re
gions_played),
+                  "best_runs_per_dungeon": 
best_runs_per_dungeon,
+                  "all_runs": sorted(all_te
am_runs, key=lambda x: x["duration"])
+              })
+
+          print(f"Analysis results:")
+          print(f"  Extended rosters analyz
ed: {rosters_analyzed}")
+          print(f"  Rosters with complete c
overage: {rosters_with_complete_coverage}")
+          print(f"  Final qualifying teams:
 {len(qualified_teams)}")
+
+          return qualified_teams, all_membe
rs_data
+
+      def generate_team_leaderboard(teams):
+          """Generate team leaderboard file
 sorted by combined best time"""
+          if not teams:
+              print("No qualifying teams fo
und.")
+              return
+
+          print(f"Generating team leaderboa
rd...")
+          os.makedirs(OUTPUT_ROOT, exist_ok
=True)
+
+          # Sort by combined best times acr
oss ALL dungeons (primary ranking)
+          teams_by_combined = sorted(teams,
 key=lambda x: x["combined_best_time"])[:TOP
_N_TEAMS]
+
+          # Add rankings
+          for i, team in enumerate(teams_by
_combined):
+              team["ranking"] = i + 1
+
+          output_file = os.path.join(OUTPUT
_ROOT, "best-overall.json")
+          with open(output_file, 'w', encod
ing='utf-8') as f:
+              json.dump({
+                  "title": "Best Teams Over
all",
+                  "description": "Teams ran
ked by their combined best times across all 
9 dungeons (complete coverage required)",
+                  "generated_timestamp": in
t(__import__('time').time() * 1000),
+                  "total_teams": len(teams)
,
+                  "min_runs_required": MIN_
TEAM_RUNS,
+                  "leaderboard": teams_by_c
ombined
+              }, f, separators=(',', ':'))
+
+          print(f"  Generated best-overall.
json with {len(teams_by_combined)} teams")
+
+          # Generate summary statistics
+          summary_file = os.path.join(OUTPU
T_ROOT, "summary.json")
+          with open(summary_file, 'w', enco
ding='utf-8') as f:
+              json.dump({
+                  "total_teams_analyzed": l
en(teams),
+                  "teams_with_complete_cove
rage": len(teams),  # All teams in the list 
have complete coverage
+                  "total_runs_processed": s
um(t["total_runs"] for t in teams) // 10,  #
 Divide by 10 since each run creates 10 team
 combinations
+                  "average_runs_per_team": 
sum(t["total_runs"] for t in teams) / len(te
ams) if teams else 0,
+                  "most_active_team_runs": 
max(t["total_runs"] for t in teams) if teams
 else 0,
+                  "generated_timestamp": in
t(__import__('time').time() * 1000)
+              }, f, separators=(',', ':'))
+
+      def main():
+          print("=== WoW Challenge Mode Tea
m Leaderboard Generator ===")
+          print(f"Minimum runs required per
 team: {MIN_TEAM_RUNS}")
+          print(f"Top teams per leaderboard
: {TOP_N_TEAMS}")
+          print()
+
+          # Analyze teams from challenge mo
de data
+          teams, all_members_data = analyze
_teams()
+
+          # Deduplicate overlapping teams (
same underlying team with different core per
spectives)
+          teams = deduplicate_overlapping_t
eams(teams, all_members_data)
+          print(f"After deduplication: {len
(teams)} unique teams")
+
+          # Generate leaderboard file
+          generate_team_leaderboard(teams)
+
+          print(f"\nTeam leaderboard genera
ted in: {os.path.abspath(OUTPUT_ROOT)}")
+          print("Available files:")
+          print("  - best-overall.json: Tea
ms by combined best time across all dungeons
")
+          print("  - summary.json: Analysis
 statistics")
+
+      if __name__ == "__main__":
+          main()
+    '';
+in
+  teamLeaderboardScript
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET