добавлены Судьи, поправлены Ход игры и События игры на офлайн матч

This commit is contained in:
2025-10-28 12:52:50 +03:00
parent 049e2493b5
commit 94d487fe88
3 changed files with 520 additions and 12 deletions

View File

@@ -10,6 +10,7 @@ from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
from typing import Any, Dict, List, Tuple, Optional
import pandas as pd
import numpy as np
import requests
from requests.adapters import HTTPAdapter
@@ -549,7 +550,7 @@ def poll_game_live(
if game_finished:
break
time.sleep(0.2)
time.sleep(1)
# вторая точка выхода по stop_event после sleep
if stop_event.is_set():
@@ -652,6 +653,8 @@ def render_loop(stop_event: threading.Event, out_name: str = "game") -> None:
Json_Team_Generation(state, who="team1")
Json_Team_Generation(state, who="team2")
Scores_Quarter(state)
Referee(state)
Play_By_Play(state)
# live_status отдельно, + общий state в <out_name>.json
atomic_write_json([state["result"]["live_status"]], "live_status")
@@ -761,7 +764,7 @@ def Referee(merged: dict, *, out_dir: str = "static") -> None:
else len(desired_order)
),
)
out_path = "referee.json"
out_path = "referee"
atomic_write_json(referees, out_path)
logging.info("Сохранил payload: {out_path}")
@@ -769,6 +772,172 @@ def Referee(merged: dict, *, out_dir: str = "static") -> None:
logger.error(f"Ошибка в Referee потоке: {e}", exc_info=True)
def Play_By_Play(data: dict) -> None:
"""
Поток, обновляющий JSON-файл с последовательностью бросков в матче.
"""
logger.info("START making json for play-by-play")
try:
game_data = data["result"] if "result" in data else data
if not game_data:
logger.debug("game_online_data отсутствует")
return
teams = game_data["teams"]
team1_data = next((i for i in teams if i.get("teamNumber") == 1), None)
team2_data = next((i for i in teams if i.get("teamNumber") == 2), None)
if not team1_data or not team2_data:
logger.warning("Не удалось получить команды из game_online_data")
return
team1_name = game_data["team1"]["name"]
team2_name = game_data["team2"]["name"]
team1_startnum = [
p["startNum"]
for p in team1_data.get("starts", [])
if p.get("startRole") == "Player"
]
team2_startnum = [
p["startNum"]
for p in team2_data.get("starts", [])
if p.get("startRole") == "Player"
]
plays = game_data.get("plays", [])
if not plays:
logger.debug("нет данных в play-by-play")
return
# Получение текущего времени игры
json_live_status = data["result"]["live_status"] if "result" in data and "live_status" in data["result"] else None
last_event = plays[-1]
# if not json_live_status or json_live_status.get("message") == "Not Found":
if json_live_status is None:
period = last_event.get("period", 1)
second = 0
else:
period = (json_live_status or {}).get("result", {}).get("period", 1)
second = (json_live_status or {}).get("result", {}).get("second", 0)
# Создание DataFrame из событий
df = pd.DataFrame(plays[::-1])
# Преобразование для лиги 3x3
# if "3x3" in LEAGUE:
# df["play"].replace({2: 1, 3: 2}, inplace=True)
df_goals = df[df["play"].isin([1, 2, 3])].copy()
if df_goals.empty:
logger.debug("нет данных о голах в play-by-play")
return
# Расчёты по очкам и времени
df_goals["score1"] = (
df_goals["startNum"].isin(team1_startnum) * df_goals["play"]
)
df_goals["score2"] = (
df_goals["startNum"].isin(team2_startnum) * df_goals["play"]
)
df_goals["score_sum1"] = df_goals["score1"].fillna(0).cumsum()
df_goals["score_sum2"] = df_goals["score2"].fillna(0).cumsum()
df_goals["new_sec"] = (
pd.to_numeric(df_goals["sec"], errors="coerce").fillna(0).astype(int)
// 10
)
df_goals["time_now"] = (600 if period < 5 else 300) - second
df_goals["quar"] = period - df_goals["period"]
df_goals["diff_time"] = np.where(
df_goals["quar"] == 0,
df_goals["time_now"] - df_goals["new_sec"],
(600 * df_goals["quar"] - df_goals["new_sec"]) + df_goals["time_now"],
)
df_goals["diff_time_str"] = df_goals["diff_time"].apply(
lambda x: (
f"{x // 60}:{str(x % 60).zfill(2)}" if isinstance(x, int) else x
)
)
# Текстовые поля
def generate_text(row, with_time=False, is_rus=False):
s1, s2 = int(row["score_sum1"]), int(row["score_sum2"])
team = (
team1_name
if not pd.isna(row["score1"]) and row["score1"] != 0
else team2_name
)
# ✅ Правильный порядок счёта в зависимости от команды
if team == team1_name:
score = f"{s1}-{s2}"
else:
score = f"{s2}-{s1}"
time_str = (
f" за {row['diff_time_str']}"
if is_rus
else f" in last {row['diff_time_str']}"
)
prefix = "рывок" if is_rus else "run"
return f"{team} {score} {prefix}{time_str if with_time else ''}"
df_goals["text_rus"] = df_goals.apply(
lambda r: generate_text(r, is_rus=True, with_time=False), axis=1
)
df_goals["text_time_rus"] = df_goals.apply(
lambda r: generate_text(r, is_rus=True, with_time=True), axis=1
)
df_goals["text"] = df_goals.apply(
lambda r: generate_text(r, is_rus=False, with_time=False), axis=1
)
df_goals["text_time"] = df_goals.apply(
lambda r: generate_text(r, is_rus=False, with_time=True), axis=1
)
df_goals["team"] = df_goals["score1"].apply(
lambda x: team1_name if not pd.isna(x) and x != 0 else team2_name
)
# Удаление лишнего
drop_cols = [
"children",
"start",
"stop",
"hl",
"sort",
"startNum",
"zone",
"x",
"y",
]
df_goals.drop(columns=drop_cols, inplace=True, errors="ignore")
# Порядок колонок
main_cols = ["text", "text_time"]
all_cols = main_cols + [
col for col in df_goals.columns if col not in main_cols
]
df_goals = df_goals[all_cols]
# Сохранение JSON
directory = "static"
os.makedirs(directory, exist_ok=True)
filepath = os.path.join(directory, "play_by_play.json")
df_goals.to_json(filepath, orient="records", force_ascii=False, indent=4)
logger.debug("Успешно положил данные об play-by-play в файл")
except Exception as e:
logger.error(f"Ошибка в Play_By_Play: {e}", exc_info=True)
def render_once_after_game(
session: requests.Session,
league: str,
@@ -808,6 +977,7 @@ def render_once_after_game(
Json_Team_Generation(state, who="team2")
Scores_Quarter(state)
Referee(state)
Play_By_Play(state)
# === 4. live_status и общий state ===
atomic_write_json(state["result"], out_name)
@@ -817,6 +987,7 @@ def render_once_after_game(
except Exception as ex:
logger.exception(f"[RENDER_ONCE] error while building final state: {ex}")
# ============================================================================
# 7. Постобработка статистики для вывода
# ============================================================================
@@ -1446,7 +1617,9 @@ def Standing_func(
# когда мы последний раз успешно обновили standings
last_call_ts = 0
json_seasons = fetch_api_data(session, "seasons", host=HOST, league=league, lang=lang)
json_seasons = fetch_api_data(
session, "seasons", host=HOST, league=league, lang=lang
)
season = json_seasons[0]["season"]
# как часто вообще можно дёргать standings
@@ -1549,7 +1722,7 @@ def Standing_func(
filename = f"standings_{league}_{comp_name}"
atomic_write_json(standings_payload, filename, out_dir)
logger.info(
f"[STANDINGS_THREAD] saved {filename}.json ({len(standings_payload)} rows)"
f"[STANDINGS_THREAD] сохранил {filename}.json"
)
# 2) плейофф-пары (playoffPairs)
@@ -1757,8 +1930,8 @@ def main():
standings_thread.join()
logger.info("[MAIN] standings thread stopped, shutdown complete")
# идём на новую итерацию while True
# (новая сессия / новый stop_event создаются в начале цикла)
# идём на новую итерацию while True
# (новая сессия / новый stop_event создаются в начале цикла)
# ============================================================================