diff --git a/.gitignore b/.gitignore
index 6175d7a..150cfac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@
/logs/*
*.venv
*.env
-/shotmaps/*
\ No newline at end of file
+/shotmaps/*
+get_data copy.py
\ No newline at end of file
diff --git a/get_data.py b/get_data.py
index 9eefa49..1e661e2 100644
--- a/get_data.py
+++ b/get_data.py
@@ -1731,7 +1731,8 @@ async def status(request: Request):
else latest_data[item]["data"]
),
}
- for item in sorted_keys if item not in ["league_stats", "season_stats"]
+ for item in sorted_keys
+ if item not in ["league_stats", "season_stats"]
],
}
@@ -5000,1260 +5001,6 @@ async def game_history():
)
return history
-
-# Глобальное состояние выбранной сортировки
-CURRENT_SORT = {
- "sort_by": None,
- "direction": "desc",
-}
-
-
-def get_nested_value(data, path: str, default=0):
- if not path:
- return default
-
- keys = path.split(".")
- current = data
-
- for key in keys:
- if isinstance(current, dict):
- if key not in current:
- return default
- current = current[key]
- elif isinstance(current, list):
- try:
- idx = int(key)
- if idx < 0 or idx >= len(current):
- return default
- current = current[idx]
- except:
- return default
- else:
- return default
-
- return current if current not in (None, "", []) else default
-
-
-def collect_paths(prefix, obj, out: set):
- """
- Рекурсивно собирает все пути ключей:
- lastName
- season.points
- carrier.points
- season.0.points
- """
- if isinstance(obj, dict):
- for k, v in obj.items():
- new_prefix = f"{prefix}.{k}" if prefix else k
- out.add(new_prefix)
- collect_paths(new_prefix, v, out)
- elif isinstance(obj, list):
- for i, v in enumerate(obj):
- new_prefix = f"{prefix}.{i}" if prefix else str(i)
- out.add(new_prefix)
- collect_paths(new_prefix, v, out)
-
-
-def extract_all_sort_paths(items: list[dict]) -> list[str]:
- """
- Собирает уникальные пути для сортировки из всех элементов milestones,
- исключая нежелательные ключи.
- """
- paths: set[str] = set()
- for item in items:
- collect_paths("", item, paths)
-
- # ключи, которые нужно исключить
- EXCLUDE = {
- "fastBreak",
- "isStart",
- "foulB",
- "foulC",
- "foulD",
- "foulT",
- "isDoubleDouble",
- "isTripleDouble",
- "pass",
- "second",
- "blockedOwnShot",
- "playedTime",
- }
-
- filtered = []
- for p in paths:
- last_key = p.split(".")[-1]
- if last_key not in EXCLUDE:
- filtered.append(p)
-
- return sorted(filtered)
-
-def _build_milestones() -> list[dict]:
- """
- Собирает milestones по всем игрокам обеих команд из pregame-full-stats.
- Возвращает список словарей с полями:
- lastName, firstName, team, season, career
- где season / career — это dict со статистикой (обычно Sum).
- """
- players_stats_league = (latest_data.get("league_stats") or {}).get("data", {}).get(
- "items"
- ) or []
- if not players_stats_league:
- return []
- players_stats_season = (latest_data.get("season_stats") or {}).get("data", {}).get(
- "items"
- ) or []
- if not players_stats_season:
- return []
-
- milestones: list[dict] = []
- excel_wrap = latest_data.get("excel_TEAMS_LEGEND").get("data") or []
-
- for player in players_stats_league:
- # все записи по этому игроку (обычно одна)
- season_items = [
- item
- for item in players_stats_season
- if item.get("personId") == player.get("personId")
- ]
- team = ""
- display_number = 0
- team_id = None
-
- if season_items:
- season_item = season_items[0]
- base_season_stats = season_item.get("stats") or {}
- season_games = season_item.get("games", 0)
-
- season_stats = dict(base_season_stats)
- season_stats["games"] = season_games
-
- team_id = season_item.get("team", {}).get("id")
- display_number = season_item.get("displayNumber", 0)
- else:
- season_stats = {}
-
- # 🔍 Ищем название команды в excel_wrap
- team_name = ""
- if team_id:
- for row in excel_wrap:
- if int(row.get("Id")) == int(team_id):
- team_name = row.get("Team", "")
- break
-
- # карьера из league_stats
- base_career_stats = player.get("stats") or {}
- career_games = player.get("games", 0)
- career_stats = dict(base_career_stats)
- career_stats["games"] = career_games
-
- season_sum = season_stats # dict
- career_sum = career_stats # dict
- season_avg = compute_avg(season_sum)
- career_avg = compute_avg(career_sum)
-
- milestones.append(
- {
- "lastName": (player.get("person") or {}).get("lastName", ""),
- "firstName": (player.get("person") or {}).get("firstName", ""),
- "team": (
- team_name if team_name != "" else player.get("team").get("name")
- ),
- "displayNumber": display_number,
- "season": season_stats,
- "career": career_stats,
- "season_avg": season_avg,
- "career_avg": career_avg,
- }
- )
- return milestones
-
-
-def format_float(value):
- """
- Возвращает строку float всегда с дробной частью:
- 12 -> "12.0"
- 12.0 -> "12.0"
- 12.5 -> "12.5"
- None -> "0.0"
- """
- # пустые значения -> 0.0
- if value in (None, "", [], {}):
- return "0.0"
-
- # если уже float или int
- if isinstance(value, (int, float)):
- val = float(value)
- # всегда форматируем с дробной частью
- return f"{val:.1f}"
-
- # если строка
- if isinstance(value, str):
- try:
- val = float(value.replace(",", "."))
- return f"{val:.1f}"
- except:
- return "0.0"
-
- # fallback
- try:
- val = float(value)
- return f"{val:.1f}"
- except:
- return "0.0"
-
-
-def compress_milestone_for_vmix(m: dict) -> dict:
- """
- Лёгкая версия игрока для vMix:
- только имя, фамилия, команда, выбранная статистика, её значение и игры.
- """
- data = latest_data["game"]["data"]["result"]
- season_api = (
- f'{str(data["league"]["season"]-1)}-{str(data["league"]["season"])[2:]}'
- )
-
- return {
- "firstName": m.get("firstName", ""),
- "lastName": m.get("lastName", ""),
- "displayNumber": m.get("displayNumber", ""),
- "photo": m.get("photo", ""),
- "team": m.get("team", ""),
- "logo_xls": m.get("logo_xls", ""),
- "statName1": (
- f"Regular season {season_api}"
- if "season" in m.get("statName", "").split(".")[0]
- else "VTB History"
- ),
- "statName2": (
- f'{m.get("statName", "").split(".")[1].replace("points", "point")}s'
- if "shot" not in m.get("statName", "").split(".")[1]
- else m.get("statName", "").split(".")[1]
- ),
- "statValue": m.get("statValue", ""),
- "games": m.get("games", ""),
- }
-
-
-@app.get("/milestones")
-async def milestones(
- sort_by: str | None = None,
- direction: str | None = None,
- vmix: bool = False, # 👈 добавили флаг
-):
- global CURRENT_SORT
-
- if sort_by is not None:
- CURRENT_SORT["sort_by"] = sort_by
-
- if direction is not None:
- CURRENT_SORT["direction"] = direction
-
- sort_by = CURRENT_SORT.get("sort_by")
- direction = CURRENT_SORT.get("direction") or "desc"
- milestones = _build_milestones()
- if not milestones:
- return [{"message": "Данных об истории команд нет!"}]
-
- if sort_by:
- reverse = direction != "asc"
-
- def sort_key(x):
- raw = get_nested_value(x, sort_by, 0)
- return normalize_number(raw)
-
- milestones = sorted(milestones, key=sort_key, reverse=reverse)
-
- for m in milestones:
- row_team = get_excel_row_for_team(m["team"])
- team_logo_exl = row_team.get("TeamLogo", "")
- m["statName"] = sort_by
-
- # значение для сортировки
- raw_val = sort_key(m)
-
- # 🔹 Если сортировка по AVG — оставляем float с дробной частью
- if sort_by.startswith("season_avg.") or sort_by.startswith("career_avg."):
- m["statValue"] = format_float(raw_val)
- else:
- # 🔹 Не AVG — всегда целые числа, если это возможно
- val = normalize_number(raw_val)
- if isinstance(val, float) and val.is_integer():
- val = int(val)
- m["statValue"] = val
-
- # 🆕 GAMES:
- games_val = 0
-
- if sort_by.startswith("season_avg."):
- # сначала пробуем games из season_avg, если там нет — из season (Sum)
- games_raw = get_nested_value(m, "season_avg.games", None)
- if games_raw is None:
- games_raw = get_nested_value(m, "season.games", 0)
- games_val = normalize_number(games_raw)
-
- elif sort_by.startswith("season."):
- # обычный сезонный Sum
- games_raw = get_nested_value(m, "season.games", 0)
- games_val = normalize_number(games_raw)
-
- elif sort_by.startswith("career_avg."):
- # сначала career_avg.games, если пусто — career.games
- games_raw = get_nested_value(m, "career_avg.games", None)
- if games_raw is None:
- games_raw = get_nested_value(m, "career.games", 0)
- games_val = normalize_number(games_raw)
-
- elif sort_by.startswith("carrier.") or sort_by.startswith("career."):
- # старый путь carrier/career (Sum)
- games_raw = get_nested_value(
- m, "carrier.games", None
- ) or get_nested_value(m, "career.games", 0)
- games_val = normalize_number(games_raw)
-
- # на всякий случай тоже дожмём до int, если вдруг float
- if isinstance(games_val, float) and games_val.is_integer():
- games_val = int(games_val)
-
- m["games"] = games_val
- m["logo_xls"] = team_logo_exl
- m["photo"] = os.path.join(
- "D:\\Photos",
- "vtb",
- m["team"],
- f"{m.get('displayNumber')}.png",
- )
-
- else:
- for m in milestones:
- m["statName"] = None
- m["statValue"] = None
- m["games"] = None
- m["logo_xls"] = None
- m["photo"] = r"D:\Графика\БАСКЕТБОЛ\VTB League\ASSETS\EMPTY.png"
- if vmix:
- return [compress_milestone_for_vmix(m) for m in milestones[:20]]
-
- return milestones
-
-
-def compute_avg(stats: dict) -> dict:
- """
- Принимает суммарную статистику игрока вида:
- { "points": 120, "rebounds": 50, "games": 6, ... }
-
- Возвращает словарь со средними значениями:
- { "points": 20.0, "rebounds": 8.3, ... }
-
- Формат всегда float с .0
- """
- if not isinstance(stats, dict):
- return {}
-
- games = stats.get("games", 0)
- if not isinstance(games, (int, float)) or games == 0:
- return {}
-
- avg = {}
- for key, value in stats.items():
- if key == "games":
- continue
- if not isinstance(value, (int, float)):
- continue
-
- avg_val = value / games
-
- # всегда форматируем как float с .0
- if isinstance(avg_val, float) and avg_val.is_integer():
- avg_val = float(f"{int(avg_val)}.0")
- else:
- avg_val = float(f"{avg_val:.1f}")
-
- avg[key] = avg_val
-
- return avg
-
-
-def normalize_number(value):
- """
- Превращает значение в корректное число:
- - '12' -> 12
- - '12.0' -> 12
- - '12.5' -> 12.5
- - None/'' -> 0
- """
- if value in (None, "", [], {}):
- return 0
-
- # если уже int
- if isinstance(value, int):
- return value
-
- # если float
- if isinstance(value, float):
- # если выглядит как целое — сделать int
- return int(value) if value.is_integer() else value
-
- # если строка
- if isinstance(value, str):
- try:
- v = float(value.replace(",", "."))
- return int(v) if v.is_integer() else v
- except:
- return 0
-
- # если что-то другое — неиспользуемый тип
- return 0
-
-
-@app.get("/milestones/ui", response_class=HTMLResponse)
-async def milestones_ui():
- milestones = _build_milestones()
- keys = extract_all_sort_paths(milestones)
- keys_js = json.dumps(keys, ensure_ascii=False)
-
- html = """
-
-
-
-
- Milestones
-
-
-
-
-
-
-
-
- Milestones игроков
- live
-
-
- Слева — список игроков, справа — подробная статистика по 4 блокам (Season / Season Avg / Career / Career Avg).
- Сортировку меняем выбором блока и показателя.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Выбери блок + показатель, чтобы отсортировать
-
-
-
-
-
-
-
-
-
-
- Игрок
- ↕
-
-
- Команда
- ↕
-
-
-
- Статистика
-
-
-
- Значение
-
-
-
- Games
-
-
-
- Сезон (очки)
- ↕
-
-
- Карьера (очки)
- ↕
-
-
- Сырые данные
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Выберите игрока в таблице
-
-
-
-
-
-
-
-
-
- Сырые данные по игроку будут показаны здесь
- в виде таблицы: строки — показатели, столбцы — Season / Season Avg / Career / Career Avg.
-