добавил две функции: командная статистика и рефери
This commit is contained in:
383
get_data.py
383
get_data.py
@@ -62,6 +62,7 @@ ALLOWED_LEAGUES = {
|
|||||||
"whl", # Высшая лига. Женщины
|
"whl", # Высшая лига. Женщины
|
||||||
"wcup", # Кубок России. Женщины
|
"wcup", # Кубок России. Женщины
|
||||||
"dubl-b", # Дюбл до 19 лет
|
"dubl-b", # Дюбл до 19 лет
|
||||||
|
# "pr-mezhreg-w13", # Межрегиональные соревнования до 14 лет
|
||||||
# добавляй свои…
|
# добавляй свои…
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,7 +453,7 @@ def format_time(seconds: float | int) -> str:
|
|||||||
return "0:00"
|
return "0:00"
|
||||||
|
|
||||||
|
|
||||||
def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | None = None):
|
def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
Единая точка: принимает уже нормализованный merged, делает нужные вычисления (если надо)
|
Единая точка: принимает уже нормализованный merged, делает нужные вычисления (если надо)
|
||||||
и сохраняет в JSON.
|
и сохраняет в JSON.
|
||||||
@@ -1065,7 +1066,380 @@ def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | No
|
|||||||
atomic_write_json(out_path, started_team)
|
atomic_write_json(out_path, started_team)
|
||||||
logging.info("Сохранил payload: {out_path}")
|
logging.info("Сохранил payload: {out_path}")
|
||||||
|
|
||||||
|
def time_outs_func(data_pbp: list[dict]) -> tuple[str, int, str, int]:
|
||||||
|
"""
|
||||||
|
Вычисляет количество оставшихся таймаутов для обеих команд
|
||||||
|
и формирует строку состояния.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_pbp: Список игровых событий (play-by-play).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Кортеж: (строка команды 1, остаток, строка команды 2, остаток)
|
||||||
|
"""
|
||||||
|
timeout1 = []
|
||||||
|
timeout2 = []
|
||||||
|
|
||||||
|
for event in data_pbp:
|
||||||
|
if event.get("play") == 23:
|
||||||
|
if event.get("startNum") == 1:
|
||||||
|
timeout1.append(event)
|
||||||
|
elif event.get("startNum") == 2:
|
||||||
|
timeout2.append(event)
|
||||||
|
|
||||||
|
def timeout_status(timeout_list: list[dict], last_event: dict) -> tuple[str, int]:
|
||||||
|
period = last_event.get("period", 0)
|
||||||
|
sec = last_event.get("sec", 0)
|
||||||
|
|
||||||
|
if period < 3:
|
||||||
|
timeout_max = 2
|
||||||
|
count = sum(1 for t in timeout_list if t.get("period", 0) <= period)
|
||||||
|
quarter = "1st half"
|
||||||
|
elif period < 5:
|
||||||
|
count = sum(1 for t in timeout_list if 3 <= t.get("period", 0) <= period)
|
||||||
|
quarter = "2nd half"
|
||||||
|
if period == 4 and sec >= 4800 and count in (0, 1):
|
||||||
|
timeout_max = 2
|
||||||
|
else:
|
||||||
|
timeout_max = 3
|
||||||
|
else:
|
||||||
|
timeout_max = 1
|
||||||
|
count = sum(1 for t in timeout_list if t.get("period", 0) == period)
|
||||||
|
quarter = f"OverTime {period - 4}"
|
||||||
|
|
||||||
|
left = max(0, timeout_max - count)
|
||||||
|
word = "Time-outs" if left != 1 else "Time-out"
|
||||||
|
text = f"{left if left != 0 else 'No'} {word} left in {quarter}"
|
||||||
|
return text, left
|
||||||
|
|
||||||
|
if not data_pbp:
|
||||||
|
return "", 0, "", 0
|
||||||
|
|
||||||
|
last_event = data_pbp[-1]
|
||||||
|
t1_str, t1_left = timeout_status(timeout1, last_event)
|
||||||
|
t2_str, t2_left = timeout_status(timeout2, last_event)
|
||||||
|
|
||||||
|
return t1_str, t1_left, t2_str, t2_left
|
||||||
|
|
||||||
|
def add_data_for_teams(new_data: list[dict]) -> tuple[float, list, float]:
|
||||||
|
"""
|
||||||
|
Возвращает усреднённые статистики команды:
|
||||||
|
- средний возраст
|
||||||
|
- очки со старта и скамейки + их доли
|
||||||
|
- средний рост
|
||||||
|
|
||||||
|
Args:
|
||||||
|
new_data (list[dict]): Список игроков с полями "startRole", "stats", "age", "height"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (avg_age: float, points: list, avg_height: float)
|
||||||
|
"""
|
||||||
|
players = [item for item in new_data if item.get("startRole") == "Player"]
|
||||||
|
|
||||||
|
points_start = 0
|
||||||
|
points_bench = 0
|
||||||
|
total_age = 0
|
||||||
|
total_height = 0
|
||||||
|
player_count = len(players)
|
||||||
|
|
||||||
|
for player in players:
|
||||||
|
stats = player.get("stats")
|
||||||
|
if stats:
|
||||||
|
is_start = stats.get("isStart")
|
||||||
|
|
||||||
|
# Очки
|
||||||
|
if is_start is True:
|
||||||
|
points_start += stats.get("points", 0)
|
||||||
|
elif is_start is False:
|
||||||
|
points_bench += stats.get("points", 0)
|
||||||
|
|
||||||
|
# Возраст и рост
|
||||||
|
total_age += player.get("age", 0) or 0
|
||||||
|
total_height += player.get("height", 0) or 0
|
||||||
|
|
||||||
|
total_points = points_start + points_bench
|
||||||
|
points_start_pro = (
|
||||||
|
f"{round(points_start * 100 / total_points)}%" if total_points else "0%"
|
||||||
|
)
|
||||||
|
points_bench_pro = (
|
||||||
|
f"{round(points_bench * 100 / total_points)}%" if total_points else "0%"
|
||||||
|
)
|
||||||
|
|
||||||
|
avg_age = round(total_age / player_count, 1) if player_count else 0
|
||||||
|
avg_height = round(total_height / player_count, 1) if player_count else 0
|
||||||
|
|
||||||
|
points = [points_start, points_start_pro, points_bench, points_bench_pro]
|
||||||
|
return avg_age, points, avg_height
|
||||||
|
|
||||||
|
def add_new_team_stat(
|
||||||
|
data: dict,
|
||||||
|
avg_age: float,
|
||||||
|
points: float,
|
||||||
|
avg_height: float,
|
||||||
|
timeout_str: str,
|
||||||
|
timeout_left: str,
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Добавляет в словарь команды форматированную статистику.
|
||||||
|
Все значения приводятся к строкам.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Исходная статистика команды.
|
||||||
|
avg_age: Средний возраст команды (строка).
|
||||||
|
points: Кортеж из 4 строк: ptsStart, ptsStart_pro, ptsBench, ptsBench_pro.
|
||||||
|
avg_height: Средний рост (в см).
|
||||||
|
timeout_str: Строка отображения таймаутов.
|
||||||
|
timeout_left: Остаток таймаутов.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Обновлённый словарь `data` с новыми ключами.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def safe_int(v): # Локальная защита от ValueError/TypeError
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def format_percent(goal, shot):
|
||||||
|
goal, shot = safe_int(goal), safe_int(shot)
|
||||||
|
return f"{round(goal * 100 / shot)}%" if shot else "0%"
|
||||||
|
|
||||||
|
goal1, shot1 = safe_int(data.get("goal1")), safe_int(data.get("shot1"))
|
||||||
|
goal2, shot2 = safe_int(data.get("goal2")), safe_int(data.get("shot2"))
|
||||||
|
goal3, shot3 = safe_int(data.get("goal3")), safe_int(data.get("shot3"))
|
||||||
|
|
||||||
|
def_reb = safe_int(data.get("defReb"))
|
||||||
|
off_reb = safe_int(data.get("offReb"))
|
||||||
|
|
||||||
|
data.update(
|
||||||
|
{
|
||||||
|
"pt-1": f"{goal1}/{shot1}",
|
||||||
|
"pt-2": f"{goal2}/{shot2}",
|
||||||
|
"pt-3": f"{goal3}/{shot3}",
|
||||||
|
"fg": f"{goal2 + goal3}/{shot2 + shot3}",
|
||||||
|
"pt-1_pro": format_percent(goal1, shot1),
|
||||||
|
"pt-2_pro": format_percent(goal2, shot2),
|
||||||
|
"pt-3_pro": format_percent(goal3, shot3),
|
||||||
|
"fg_pro": format_percent(goal2 + goal3, shot2 + shot3),
|
||||||
|
"Reb": str(def_reb + off_reb),
|
||||||
|
"avgAge": str(avg_age),
|
||||||
|
"ptsStart": str(points[0]),
|
||||||
|
"ptsStart_pro": str(points[1]),
|
||||||
|
"ptsBench": str(points[2]),
|
||||||
|
"ptsBench_pro": str(points[3]),
|
||||||
|
"avgHeight": f"{avg_height} cm",
|
||||||
|
"timeout_left": str(timeout_left),
|
||||||
|
"timeout_str": str(timeout_str),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Приводим все значения к строкам, если нужно строго для сериализации
|
||||||
|
for k in data:
|
||||||
|
data[k] = str(data[k])
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
stat_name_list = [
|
||||||
|
("points", "Очки", "points"),
|
||||||
|
("pt-1", "Штрафные", "free throws"),
|
||||||
|
("pt-1_pro", "штрафные, процент", "free throws pro"),
|
||||||
|
("pt-2", "2-очковые", "2-points"),
|
||||||
|
("pt-2_pro", "2-очковые, процент", "2-points pro"),
|
||||||
|
("pt-3", "3-очковые", "3-points"),
|
||||||
|
("pt-3_pro", "3-очковые, процент", "3-points pro"),
|
||||||
|
("fg", "очки с игры", "field goals"),
|
||||||
|
("fg_pro", "Очки с игры, процент", "field goals pro"),
|
||||||
|
("assist", "Передачи", "assists"),
|
||||||
|
("pass", "", ""),
|
||||||
|
("defReb", "подборы в защите", ""),
|
||||||
|
("offReb", "подборы в нападении", ""),
|
||||||
|
("Reb", "Подборы", "rebounds"),
|
||||||
|
("steal", "Перехваты", "steals"),
|
||||||
|
("block", "Блокшоты", "blocks"),
|
||||||
|
("blocked", "", ""),
|
||||||
|
("turnover", "Потери", "turnovers"),
|
||||||
|
("foul", "Фолы", "fouls"),
|
||||||
|
("foulsOn", "", ""),
|
||||||
|
("foulT", "", ""),
|
||||||
|
("foulD", "", ""),
|
||||||
|
("foulC", "", ""),
|
||||||
|
("foulB", "", ""),
|
||||||
|
("second", "секунды", "seconds"),
|
||||||
|
("dunk", "данки", "dunks"),
|
||||||
|
("fastBreak", "", "fast breaks"),
|
||||||
|
("plusMinus", "+/-", "+/-"),
|
||||||
|
("avgAge", "", "avg Age"),
|
||||||
|
("ptsBench", "", "Bench PTS"),
|
||||||
|
("ptsBench_pro", "", "Bench PTS, %"),
|
||||||
|
("ptsStart", "", "Start PTS"),
|
||||||
|
("ptsStart_pro", "", "Start PTS, %"),
|
||||||
|
("avgHeight", "", "avg height"),
|
||||||
|
("timeout_left", "", "timeout left"),
|
||||||
|
("timeout_str", "", "timeout str"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def Team_Both_Stat(merged: dict, *, out_dir: str = "static") -> None:
|
||||||
|
"""
|
||||||
|
Обновляет файл team_stats.json, содержащий сравнение двух команд.
|
||||||
|
|
||||||
|
Аргументы:
|
||||||
|
stop_event (threading.Event): Событие для остановки цикла.
|
||||||
|
"""
|
||||||
|
logger.info("START making json for team statistics")
|
||||||
|
|
||||||
|
try:
|
||||||
|
teams = merged["result"]["teams"]
|
||||||
|
plays = merged["result"].get("plays", [])
|
||||||
|
|
||||||
|
# Разделение команд
|
||||||
|
team_1 = next((t for t in teams if t["teamNumber"] == 1), None)
|
||||||
|
team_2 = next((t for t in teams if t["teamNumber"] == 2), None)
|
||||||
|
|
||||||
|
if not team_1 or not team_2:
|
||||||
|
logger.warning("Не найдены обе команды в данных")
|
||||||
|
# time.sleep()
|
||||||
|
|
||||||
|
# Таймауты
|
||||||
|
timeout_str1, timeout_left1, timeout_str2, timeout_left2 = time_outs_func(
|
||||||
|
plays
|
||||||
|
)
|
||||||
|
|
||||||
|
# Возраст, очки, рост
|
||||||
|
avg_age_1, points_1, avg_height_1 = add_data_for_teams(
|
||||||
|
team_1.get("starts", [])
|
||||||
|
)
|
||||||
|
avg_age_2, points_2, avg_height_2 = add_data_for_teams(
|
||||||
|
team_2.get("starts", [])
|
||||||
|
)
|
||||||
|
|
||||||
|
if not team_1.get("total") or not team_2.get("total"):
|
||||||
|
logger.debug(
|
||||||
|
"Нет total у команд — пропускаю перезапись team_stats.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Форматирование общей статистики (как и было)
|
||||||
|
total_1 = add_new_team_stat(
|
||||||
|
team_1["total"],
|
||||||
|
avg_age_1,
|
||||||
|
points_1,
|
||||||
|
avg_height_1,
|
||||||
|
timeout_str1,
|
||||||
|
timeout_left1,
|
||||||
|
)
|
||||||
|
total_2 = add_new_team_stat(
|
||||||
|
team_2["total"],
|
||||||
|
avg_age_2,
|
||||||
|
points_2,
|
||||||
|
avg_height_2,
|
||||||
|
timeout_str2,
|
||||||
|
timeout_left2,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Финальный JSON
|
||||||
|
result_json = []
|
||||||
|
for key in total_1:
|
||||||
|
val1 = (
|
||||||
|
int(total_1[key])
|
||||||
|
if isinstance(total_1[key], float)
|
||||||
|
else total_1[key]
|
||||||
|
)
|
||||||
|
val2 = (
|
||||||
|
int(total_2[key])
|
||||||
|
if isinstance(total_2[key], float)
|
||||||
|
else total_2[key]
|
||||||
|
)
|
||||||
|
stat_rus, stat_eng = "", ""
|
||||||
|
for s in stat_name_list:
|
||||||
|
if s[0] == key:
|
||||||
|
stat_rus, stat_eng = s[1], s[2]
|
||||||
|
break
|
||||||
|
|
||||||
|
result_json.append(
|
||||||
|
{
|
||||||
|
"name": key,
|
||||||
|
"nameGFX_rus": stat_rus,
|
||||||
|
"nameGFX_eng": stat_eng,
|
||||||
|
"val1": val1,
|
||||||
|
"val2": val2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
out_path = Path(out_dir) / "team_stats.json"
|
||||||
|
atomic_write_json(out_path, result_json)
|
||||||
|
logging.info("Сохранил payload: {out_path}")
|
||||||
|
|
||||||
|
logger.debug("Успешно записаны данные в team_stats.json")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"Ошибка при обработке командной статистики: {e}", exc_info=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def Referee(merged: dict, *, out_dir: str = "static") -> None:
|
||||||
|
"""
|
||||||
|
Поток, создающий JSON-файл с информацией о судьях матча.
|
||||||
|
"""
|
||||||
|
logger.info("START making json for referee")
|
||||||
|
|
||||||
|
desired_order = [
|
||||||
|
"Crew chief",
|
||||||
|
"Referee 1",
|
||||||
|
"Referee 2",
|
||||||
|
"Commissioner",
|
||||||
|
"Ст.судья",
|
||||||
|
"Судья 1",
|
||||||
|
"Судья 2",
|
||||||
|
"Комиссар",
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Найти судей (teamNumber == 0)
|
||||||
|
team_ref = next(
|
||||||
|
(t for t in merged["result"]["teams"] if t["teamNumber"] == 0), None
|
||||||
|
)
|
||||||
|
if not team_ref:
|
||||||
|
logger.warning("Не найдена судейская бригада в данных.")
|
||||||
|
|
||||||
|
referees_raw = team_ref.get("starts", [])
|
||||||
|
# print(referees_raw)
|
||||||
|
referees = []
|
||||||
|
|
||||||
|
for r in referees_raw:
|
||||||
|
flag_code = (
|
||||||
|
r.get("countryId", "").lower() if r.get("countryName") else ""
|
||||||
|
)
|
||||||
|
referees.append(
|
||||||
|
{
|
||||||
|
"displayNumber": r.get("displayNumber", ""),
|
||||||
|
"positionName": r.get("positionName", ""),
|
||||||
|
"lastNameGFX": f"{r.get('firstName', '')} {r.get('lastName', '')}".strip(),
|
||||||
|
"secondName": r.get("secondName", ""),
|
||||||
|
"birthday": r.get("birthday", ""),
|
||||||
|
"age": r.get("age", 0),
|
||||||
|
"flag": f"https://flagicons.lipis.dev/flags/4x3/{flag_code}.svg",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Сортировка по позиции
|
||||||
|
referees = sorted(
|
||||||
|
referees,
|
||||||
|
key=lambda x: (
|
||||||
|
desired_order.index(x["positionName"])
|
||||||
|
if x["positionName"] in desired_order
|
||||||
|
else len(desired_order)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
out_path = Path(out_dir) / "referee.json"
|
||||||
|
atomic_write_json(out_path, referees)
|
||||||
|
logging.info("Сохранил payload: {out_path}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка в Referee потоке: {e}", exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ==========================
|
# ==========================
|
||||||
# ---- ДОМЕННАЯ ЛОГИКА
|
# ---- ДОМЕННАЯ ЛОГИКА
|
||||||
@@ -1164,6 +1538,8 @@ class PostProcessor:
|
|||||||
try:
|
try:
|
||||||
Json_Team_Generation(merged, out_dir="static", who="team1")
|
Json_Team_Generation(merged, out_dir="static", who="team1")
|
||||||
Json_Team_Generation(merged, out_dir="static", who="team2")
|
Json_Team_Generation(merged, out_dir="static", who="team2")
|
||||||
|
Team_Both_Stat(merged, out_dir="static")
|
||||||
|
Referee(merged, out_dir="static")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(f"Postproc failed: {e}")
|
logging.exception(f"Postproc failed: {e}")
|
||||||
|
|
||||||
@@ -1333,7 +1709,6 @@ def daily_rollover_loop(
|
|||||||
while not stop_event.is_set():
|
while not stop_event.is_set():
|
||||||
now = datetime.now(APP_TZ)
|
now = datetime.now(APP_TZ)
|
||||||
wakeup_at = next_midnight_local(now)
|
wakeup_at = next_midnight_local(now)
|
||||||
print(type(wakeup_at))
|
|
||||||
seconds = (wakeup_at - now).total_seconds()
|
seconds = (wakeup_at - now).total_seconds()
|
||||||
logger.info(
|
logger.info(
|
||||||
# f"Ежедневка: проснусь {datetime.fromisoformat(wakeup_at.isoformat())} (через {int(seconds)} сек)."
|
# f"Ежедневка: проснусь {datetime.fromisoformat(wakeup_at.isoformat())} (через {int(seconds)} сек)."
|
||||||
@@ -1467,6 +1842,8 @@ def main():
|
|||||||
)
|
)
|
||||||
Json_Team_Generation(merged, out_dir="static", who="team1")
|
Json_Team_Generation(merged, out_dir="static", who="team1")
|
||||||
Json_Team_Generation(merged, out_dir="static", who="team2")
|
Json_Team_Generation(merged, out_dir="static", who="team2")
|
||||||
|
Team_Both_Stat(merged, out_dir="static")
|
||||||
|
Referee(merged, out_dir="static")
|
||||||
# print(merged)
|
# print(merged)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Сегодня у {team} нет игры.\nПоследняя сыгранная: gameID={game_id}.\nМониторинг не запускаю."
|
f"Сегодня у {team} нет игры.\nПоследняя сыгранная: gameID={game_id}.\nМониторинг не запускаю."
|
||||||
|
|||||||
Reference in New Issue
Block a user