добавил функцию по прогону данных один раз, если матч старый
This commit is contained in:
149
get_data_new.py
149
get_data_new.py
@@ -143,6 +143,7 @@ logger.handlers[2].formatter.use_emoji = True
|
||||
# 3. I/O вспомогательные функции
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def atomic_write_json(data: Any, name: str, out_dir: str = "static") -> None:
|
||||
"""
|
||||
Потокобезопасная запись JSON в static/<name>.json.
|
||||
@@ -200,6 +201,7 @@ def _now_iso() -> str:
|
||||
# 4. Работа с HTTP / API
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def create_session() -> requests.Session:
|
||||
"""
|
||||
Создаёт requests.Session с ретраями и дефолтными заголовками.
|
||||
@@ -264,7 +266,9 @@ def get_items(data: dict) -> Optional[list]:
|
||||
return None
|
||||
|
||||
|
||||
def fetch_api_data(session: requests.Session, name: str, name_save: str = None, **kwargs) -> Any:
|
||||
def fetch_api_data(
|
||||
session: requests.Session, name: str, name_save: str = None, **kwargs
|
||||
) -> Any:
|
||||
"""
|
||||
Универсальный обёртчик над API:
|
||||
- строит URL по имени ручки,
|
||||
@@ -312,9 +316,7 @@ def poll_one_endpoint(
|
||||
return endpoint_name, data
|
||||
|
||||
if endpoint_name == "game":
|
||||
data = fetch_api_data(
|
||||
session, "game", host=HOST, game_id=game_id, lang=lang
|
||||
)
|
||||
data = fetch_api_data(session, "game", host=HOST, game_id=game_id, lang=lang)
|
||||
return endpoint_name, data
|
||||
|
||||
if endpoint_name == "pregame-fullstats":
|
||||
@@ -349,6 +351,7 @@ def get_interval_by_name(name: str) -> int:
|
||||
# 5. Работа с расписанием / статусом матча
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def parse_game_start_dt(item: dict) -> datetime:
|
||||
"""
|
||||
Достаёт дату/время начала матча из объекта календаря и нормализует в APP_TZ.
|
||||
@@ -455,6 +458,7 @@ def is_game_live(game_obj: dict) -> bool:
|
||||
# 6. Лайв-петля: опрос API и поток рендера
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def poll_game_live(
|
||||
session: requests.Session,
|
||||
league: str,
|
||||
@@ -487,7 +491,9 @@ def poll_game_live(
|
||||
while True:
|
||||
# внешний стоп: операторская остановка или завершение run_live_loop
|
||||
if stop_event.is_set():
|
||||
logger.info(f"[POLL] stop_event set -> break live poll for game {game_id}")
|
||||
logger.info(
|
||||
f"[POLL] stop_event set -> break live poll for game {game_id}"
|
||||
)
|
||||
break
|
||||
|
||||
now = time.time()
|
||||
@@ -525,10 +531,7 @@ def poll_game_live(
|
||||
# проверяем статус лайва
|
||||
if ep_name == "live-status":
|
||||
if isinstance(data, dict):
|
||||
st = (
|
||||
data.get("result").get("gameStatus")
|
||||
or ""
|
||||
).lower()
|
||||
st = (data.get("result").get("gameStatus") or "").lower()
|
||||
if st in ("resultconfirmed", "finished", "result"):
|
||||
logger.info(
|
||||
f"[POLL] Game {game_id} finished by live-status"
|
||||
@@ -550,7 +553,9 @@ def poll_game_live(
|
||||
|
||||
# вторая точка выхода по stop_event после sleep
|
||||
if stop_event.is_set():
|
||||
logger.info(f"[POLL] stop_event set after sleep -> break live poll for game {game_id}")
|
||||
logger.info(
|
||||
f"[POLL] stop_event set after sleep -> break live poll for game {game_id}"
|
||||
)
|
||||
break
|
||||
|
||||
|
||||
@@ -603,9 +608,7 @@ def build_render_state() -> dict:
|
||||
player["stats"] = stat
|
||||
team["total"] = box_team.get("total", {})
|
||||
|
||||
game_data["scoreByPeriods"] = box_score_data["result"].get(
|
||||
"scoreByPeriods", []
|
||||
)
|
||||
game_data["scoreByPeriods"] = box_score_data["result"].get("scoreByPeriods", [])
|
||||
game_data["fullScore"] = box_score_data["result"].get("fullScore", {})
|
||||
|
||||
# плей-бай-плей и live_status
|
||||
@@ -693,7 +696,6 @@ def run_live_loop(
|
||||
)
|
||||
standings_thread.start()
|
||||
logger.info("[LIVE_THREAD] standings thread spawned")
|
||||
|
||||
|
||||
try:
|
||||
poll_game_live(
|
||||
@@ -715,7 +717,8 @@ def run_live_loop(
|
||||
standings_thread.join()
|
||||
|
||||
logger.info(f"[LIVE_THREAD] stop live loop for game_id={game_id}")
|
||||
|
||||
|
||||
|
||||
def Referee(merged: dict, *, out_dir: str = "static") -> None:
|
||||
"""
|
||||
Поток, создающий JSON-файл с информацией о судьях матча.
|
||||
@@ -776,10 +779,40 @@ def Referee(merged: dict, *, out_dir: str = "static") -> None:
|
||||
logger.error(f"Ошибка в Referee потоке: {e}", exc_info=True)
|
||||
|
||||
|
||||
def render_once_after_game(out_name: str = "game") -> None:
|
||||
"""
|
||||
Одноразовая генерация всех выходных json-файлов (team_stats.json,
|
||||
team1.json, team2.json, scores.json, live_status.json, game.json и т.д.)
|
||||
без запуска вечного render_loop.
|
||||
|
||||
Используется, когда:
|
||||
- матч уже завершён (resultconfirmed/finished),
|
||||
- или матч не идёт (нет лайва), но мы хотим иметь конечные данные по нему.
|
||||
"""
|
||||
try:
|
||||
state = build_render_state()
|
||||
|
||||
# основные сводки по матчу
|
||||
Team_Both_Stat(state)
|
||||
Json_Team_Generation(state, who="team1")
|
||||
Json_Team_Generation(state, who="team2")
|
||||
Scores_Quarter(state)
|
||||
Referee(state)
|
||||
|
||||
# live_status отдельно, + сам матч
|
||||
atomic_write_json([state["result"]["live_status"]], "live_status")
|
||||
atomic_write_json(state["result"], out_name)
|
||||
|
||||
logger.info("[RENDER_ONCE] финальные json сохранены после матча")
|
||||
except Exception as ex:
|
||||
logger.exception(f"[RENDER_ONCE] error while building final state: {ex}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 7. Постобработка статистики для вывода
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def format_time(seconds: float | int) -> str:
|
||||
"""
|
||||
Удобный формат времени для игроков:
|
||||
@@ -878,10 +911,13 @@ def Json_Team_Generation(
|
||||
if item.get("countryId") is None
|
||||
and item.get("countryName") == "Russia"
|
||||
else (
|
||||
"" if item.get("countryId") is None
|
||||
else (item.get("countryId") or "").lower()
|
||||
if item.get("countryName") is not None
|
||||
else ""
|
||||
""
|
||||
if item.get("countryId") is None
|
||||
else (
|
||||
(item.get("countryId") or "").lower()
|
||||
if item.get("countryName") is not None
|
||||
else ""
|
||||
)
|
||||
)
|
||||
)
|
||||
+ ".svg"
|
||||
@@ -1162,6 +1198,7 @@ def add_new_team_stat(
|
||||
|
||||
Возвращает обновлённый словарь.
|
||||
"""
|
||||
|
||||
def safe_int(v):
|
||||
try:
|
||||
return int(v)
|
||||
@@ -1185,21 +1222,17 @@ def add_new_team_stat(
|
||||
"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),
|
||||
}
|
||||
@@ -1495,10 +1528,9 @@ def Standing_func(
|
||||
df["procent"] = df.apply(calc_percent, axis=1)
|
||||
|
||||
# +/- по очкам
|
||||
df["plus_minus"] = (
|
||||
df["totalGoalPlus"].fillna(0).astype(int)
|
||||
- df["totalGoalMinus"].fillna(0).astype(int)
|
||||
)
|
||||
df["plus_minus"] = df["totalGoalPlus"].fillna(0).astype(
|
||||
int
|
||||
) - df["totalGoalMinus"].fillna(0).astype(int)
|
||||
|
||||
# готовим питоновский список словарей для атомарной записи
|
||||
standings_payload = df.to_dict(orient="records")
|
||||
@@ -1542,6 +1574,7 @@ def Standing_func(
|
||||
# 8. Суточный цикл: находим игру, следим в лайве, потом уходим спать
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def get_data_API(
|
||||
session: requests.Session,
|
||||
league: str,
|
||||
@@ -1554,14 +1587,16 @@ def get_data_API(
|
||||
1. Узнать текущий сезон
|
||||
2. Обновить standings и calendar
|
||||
3. Найти игру для нашей команды сегодня (today_game) или последнюю законченную (last_played)
|
||||
4. Если есть last_played и нет игры сегодня -> просто забираем /game и пишем api_game.json
|
||||
4. Если есть last_played и нет игры сегодня:
|
||||
- забрать /game по last_played
|
||||
- один раз сгенерировать финальные JSON (render_once_after_game)
|
||||
5. Если есть игра сегодня:
|
||||
- пишем /game сессии
|
||||
- если статус игры live -> запускаем run_live_loop() в отдельном потоке
|
||||
и ждём его завершения (до конца матча)
|
||||
- забрать /game по today_game
|
||||
- если статус игры live:
|
||||
· запустить live-петлю (run_live_loop) с рендером в реальном времени
|
||||
иначе (игра уже финальная, не live):
|
||||
· один раз сгенерировать финальные JSON (render_once_after_game)
|
||||
6. Если нет ничего -> просто логируем и выходим
|
||||
|
||||
Эта функция НЕ усыпляет процесс — цикл сна делает main().
|
||||
"""
|
||||
# 1. сезоны
|
||||
json_seasons = fetch_api_data(
|
||||
@@ -1574,9 +1609,21 @@ def get_data_API(
|
||||
season = json_seasons[0]["season"]
|
||||
|
||||
# 2. standings и calendar
|
||||
fetch_api_data(session, "standings", host=HOST, league=league, season=season, lang=lang)
|
||||
fetch_api_data(
|
||||
session,
|
||||
"standings",
|
||||
host=HOST,
|
||||
league=league,
|
||||
season=season,
|
||||
lang=lang,
|
||||
)
|
||||
json_calendar = fetch_api_data(
|
||||
session, "calendar", host=HOST, league=league, season=season, lang=lang
|
||||
session,
|
||||
"calendar",
|
||||
host=HOST,
|
||||
league=league,
|
||||
season=season,
|
||||
lang=lang,
|
||||
)
|
||||
if not json_calendar:
|
||||
logger.error("Не удалось получить список матчей")
|
||||
@@ -1585,22 +1632,39 @@ def get_data_API(
|
||||
# 3. какая игра нас интересует?
|
||||
today_game, last_played = get_game_id(json_calendar, team)
|
||||
|
||||
# 4. уже сыграли, но сегодня не играем -> просто пишем /game последнего матча
|
||||
# 4. уже сыграли матч, а сегодня не играем
|
||||
if last_played and not today_game:
|
||||
game_id = last_played["game"]["id"]
|
||||
logger.info(f"Последний завершённый матч id={game_id}")
|
||||
|
||||
# забираем состояние игры
|
||||
fetch_api_data(session, "game", host=HOST, game_id=game_id, lang=lang)
|
||||
|
||||
# плюс box-score / play-by-play / live-status,
|
||||
# чтобы render_once_after_game мог всё посчитать
|
||||
fetch_api_data(session, "box-score", host=HOST, game_id=game_id)
|
||||
fetch_api_data(session, "play-by-play", host=HOST, game_id=game_id)
|
||||
fetch_api_data(session, "live-status", host=HOST, game_id=game_id)
|
||||
|
||||
# одноразовый рендер финальных стейтов
|
||||
render_once_after_game()
|
||||
|
||||
return
|
||||
|
||||
# 5. матч сегодня
|
||||
# 5. матч сегодня есть
|
||||
if today_game:
|
||||
game_id = today_game["game"]["id"]
|
||||
logger.info(f"Онлайн матч id={game_id}")
|
||||
|
||||
# всегда пишем /game прямо сейчас (обновим api_game.json)
|
||||
# Всегда обновляем /game прямо сейчас
|
||||
fetch_api_data(session, "game", host=HOST, game_id=game_id, lang=lang)
|
||||
|
||||
# если матч идёт — стартуем live
|
||||
# и эти ручки тоже сразу дергаем, чтобы у нас были свежие api_*.json
|
||||
fetch_api_data(session, "box-score", host=HOST, game_id=game_id)
|
||||
fetch_api_data(session, "play-by-play", host=HOST, game_id=game_id)
|
||||
fetch_api_data(session, "live-status", host=HOST, game_id=game_id)
|
||||
|
||||
# если матч идёт — запускаем live поток, он сам будет рендерить в цикле
|
||||
if is_game_live(today_game["game"]):
|
||||
t = threading.Thread(
|
||||
target=run_live_loop,
|
||||
@@ -1619,9 +1683,14 @@ def get_data_API(
|
||||
|
||||
logger.info("live thread finished")
|
||||
|
||||
else:
|
||||
# матч сегодня, но он уже финальный (resultconfirmed / finished)
|
||||
# значит просто один раз считаем все json-ы
|
||||
render_once_after_game()
|
||||
|
||||
return
|
||||
|
||||
# 6. ничего подходящего
|
||||
# 6. ничего подходящего вообще
|
||||
logger.info("Для этой команды игр сегодня нет и нет завершённой последней игры.")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user