обновил статусы матчей, чтобы правильнее ожидалось, когда матч сегодня
This commit is contained in:
282
get_data_new.py
282
get_data_new.py
@@ -450,11 +450,29 @@ def is_game_live(game_obj: dict) -> bool:
|
||||
|
||||
if status in ("resultconfirmed", "finished", "result"):
|
||||
return False
|
||||
if status in ("scheduled", "notstarted", "draft"):
|
||||
if status in ("notstarted", "draft"):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def classify_game_state_from_status(status_raw: str) -> str:
|
||||
"""
|
||||
Делит статус игры на три фазы:
|
||||
- "finished" -> матч точно завершён
|
||||
- "upcoming" -> матч ещё не начался, но он сегодня
|
||||
- "live" -> матч идёт
|
||||
|
||||
Используется в get_data_API(), чтобы решить, что делать дальше.
|
||||
"""
|
||||
status = (status_raw or "").lower()
|
||||
if status in ("resultconfirmed", "finished", "result"):
|
||||
return "finished"
|
||||
if status in ("scheduled", "notstarted", "draft"):
|
||||
return "upcoming"
|
||||
# всё остальное считаем лайвом
|
||||
return "live"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 6. Лайв-петля: опрос API и поток рендера
|
||||
# ============================================================================
|
||||
@@ -738,7 +756,6 @@ def Referee(merged: dict, *, out_dir: str = "static") -> None:
|
||||
logger.warning("Не найдена судейская бригада в данных.")
|
||||
|
||||
referees_raw = team_ref.get("starts", [])
|
||||
# print(referees_raw)
|
||||
referees = []
|
||||
|
||||
for r in referees_raw:
|
||||
@@ -777,7 +794,7 @@ 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
|
||||
|
||||
@@ -811,10 +828,13 @@ def Play_By_Play(data: dict) -> None:
|
||||
return
|
||||
|
||||
# Получение текущего времени игры
|
||||
json_live_status = data["result"]["live_status"] if "result" in data and "live_status" in data["result"] else None
|
||||
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
|
||||
@@ -825,10 +845,6 @@ def Play_By_Play(data: dict) -> None:
|
||||
# Создание 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")
|
||||
@@ -846,8 +862,7 @@ def Play_By_Play(data: dict) -> None:
|
||||
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
|
||||
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"]
|
||||
@@ -859,9 +874,7 @@ def Play_By_Play(data: dict) -> None:
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
lambda x: (f"{x // 60}:{str(x % 60).zfill(2)}" if isinstance(x, int) else x)
|
||||
)
|
||||
|
||||
# Текстовые поля
|
||||
@@ -873,7 +886,7 @@ def Play_By_Play(data: dict) -> None:
|
||||
else team2_name
|
||||
)
|
||||
|
||||
# ✅ Правильный порядок счёта в зависимости от команды
|
||||
# правильный порядок счёта в зависимости от команды
|
||||
if team == team1_name:
|
||||
score = f"{s1}-{s2}"
|
||||
else:
|
||||
@@ -921,9 +934,7 @@ def Play_By_Play(data: dict) -> None:
|
||||
|
||||
# Порядок колонок
|
||||
main_cols = ["text", "text_time"]
|
||||
all_cols = main_cols + [
|
||||
col for col in df_goals.columns if col not in main_cols
|
||||
]
|
||||
all_cols = main_cols + [col for col in df_goals.columns if col not in main_cols]
|
||||
df_goals = df_goals[all_cols]
|
||||
|
||||
# Сохранение JSON
|
||||
@@ -937,7 +948,6 @@ def Play_By_Play(data: dict) -> None:
|
||||
logger.error(f"Ошибка в Play_By_Play: {e}", exc_info=True)
|
||||
|
||||
|
||||
|
||||
def render_once_after_game(
|
||||
session: requests.Session,
|
||||
league: str,
|
||||
@@ -962,7 +972,7 @@ def render_once_after_game(
|
||||
"""
|
||||
try:
|
||||
logger.info(f"[RENDER_ONCE] Fetching final game snapshot for game_id={game_id}")
|
||||
# === 1. один запрос к API (ручка "game") ===
|
||||
# один запрос к API (ручка "game")
|
||||
state = fetch_api_data(
|
||||
session,
|
||||
"game",
|
||||
@@ -971,7 +981,6 @@ def render_once_after_game(
|
||||
lang=lang,
|
||||
)
|
||||
|
||||
# === 3. прогнать вычисления как в render_loop ===
|
||||
Team_Both_Stat(state)
|
||||
Json_Team_Generation(state, who="team1")
|
||||
Json_Team_Generation(state, who="team2")
|
||||
@@ -979,7 +988,6 @@ def render_once_after_game(
|
||||
Referee(state)
|
||||
Play_By_Play(state)
|
||||
|
||||
# === 4. live_status и общий state ===
|
||||
atomic_write_json(state["result"], out_name)
|
||||
|
||||
logger.info("[RENDER_ONCE] финальные json сохранены успешно")
|
||||
@@ -1054,7 +1062,6 @@ def Json_Team_Generation(
|
||||
|
||||
for item in starts:
|
||||
stats = item.get("stats") or {}
|
||||
# маппинг одной строки игрока
|
||||
row = {
|
||||
"id": item.get("personId") or "",
|
||||
"num": item.get("displayNumber"),
|
||||
@@ -1290,7 +1297,6 @@ def time_outs_func(data_pbp: List[dict]) -> Tuple[str, int, str, int]:
|
||||
elif period < 5:
|
||||
count = sum(1 for t in timeout_list if 3 <= t.get("period", 0) <= period)
|
||||
quarter = "2nd half"
|
||||
# в концовке 4-й четверти лимит может ужиматься
|
||||
if period == 4 and sec >= 4800 and count in (0, 1):
|
||||
timeout_max = 2
|
||||
else:
|
||||
@@ -1418,14 +1424,12 @@ def add_new_team_stat(
|
||||
}
|
||||
)
|
||||
|
||||
# всё -> строки (UI не должен думать о типах)
|
||||
for k in data:
|
||||
data[k] = str(data[k])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
# Статическая таблица "как назвать метрику"
|
||||
stat_name_list = [
|
||||
("points", "Очки", "points"),
|
||||
("pt-1", "Штрафные", "free throws"),
|
||||
@@ -1469,13 +1473,6 @@ stat_name_list = [
|
||||
def Team_Both_Stat(merged: dict) -> None:
|
||||
"""
|
||||
Формирует сводку по двум командам и пишет её в static/team_stats.json.
|
||||
|
||||
Делает:
|
||||
- считает таймауты для обеих команд,
|
||||
- считает средний возраст / рост,
|
||||
- считает очки старт / скамейка,
|
||||
- добавляет проценты попаданий, подборы и т.д.,
|
||||
- мапит имена метрик на удобные подписи.
|
||||
"""
|
||||
logger.info("START making json for team statistics")
|
||||
|
||||
@@ -1483,7 +1480,6 @@ def Team_Both_Stat(merged: dict) -> None:
|
||||
teams = merged["result"]["teams"]
|
||||
plays = merged["result"].get("plays", [])
|
||||
|
||||
# Разделяем команды по teamNumber
|
||||
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)
|
||||
|
||||
@@ -1491,17 +1487,14 @@ def Team_Both_Stat(merged: dict) -> None:
|
||||
logger.warning("Не найдены обе команды в данных")
|
||||
return
|
||||
|
||||
# Таймауты
|
||||
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 агрегаты
|
||||
total_1 = add_new_team_stat(
|
||||
team_1["total"],
|
||||
avg_age_1,
|
||||
@@ -1519,7 +1512,6 @@ def Team_Both_Stat(merged: dict) -> None:
|
||||
timeout_left2,
|
||||
)
|
||||
|
||||
# Готовим список пар "метрика -> команда1 vs команда2"
|
||||
result_json = []
|
||||
for key in total_1:
|
||||
val1 = total_1[key]
|
||||
@@ -1552,10 +1544,6 @@ def Team_Both_Stat(merged: dict) -> None:
|
||||
def Scores_Quarter(merged: dict) -> None:
|
||||
"""
|
||||
Пишет счёт по четвертям и овертаймам в static/scores.json.
|
||||
|
||||
Логика:
|
||||
- если есть game.result.game.fullScore -> парсим "XX:YY,AA:BB,..."
|
||||
- иначе используем scoreByPeriods из box-score
|
||||
"""
|
||||
logger.info("START making json for scores quarter")
|
||||
|
||||
@@ -1566,7 +1554,6 @@ def Scores_Quarter(merged: dict) -> None:
|
||||
full_score_str = merged.get("result", {}).get("game", {}).get("fullScore", "")
|
||||
|
||||
if full_score_str:
|
||||
# пример: "19:15,20:22,18:18,25:10"
|
||||
full_score_list = full_score_str.split(",")
|
||||
for i, score_str in enumerate(full_score_list[: len(score_by_quarter)]):
|
||||
parts = score_str.split(":")
|
||||
@@ -1600,41 +1587,25 @@ def Standing_func(
|
||||
) -> None:
|
||||
"""
|
||||
Фоновый поток с турнирной таблицей (standings).
|
||||
|
||||
Что делает:
|
||||
- Периодически (не чаще, чем interval для "standings" в URLS) тянет /standings
|
||||
для лиги+сезона.
|
||||
- Для каждой подтаблицы (regular season, playoffs и т.д.) нормализует данные,
|
||||
досчитывает полезные колонки (W/L, %) и сохраняет в
|
||||
static/standings_<league>_<compName>.json
|
||||
- Останавливается, когда поднят stop_event.
|
||||
|
||||
Почему отдельный поток?
|
||||
- standings нам нужна даже во время лайва игры, но не каждую секунду.
|
||||
- Она не должна блокировать рендер и не должна блокировать poll_game_live.
|
||||
"""
|
||||
logger.info("[STANDINGS_THREAD] start standings loop")
|
||||
|
||||
# когда мы последний раз успешно обновили standings
|
||||
last_call_ts = 0
|
||||
json_seasons = fetch_api_data(
|
||||
session, "seasons", host=HOST, league=league, lang=lang
|
||||
)
|
||||
season = json_seasons[0]["season"]
|
||||
|
||||
# как часто вообще можно дёргать standings
|
||||
interval = get_interval_by_name("standings")
|
||||
|
||||
while not stop_event.is_set():
|
||||
now = time.time()
|
||||
|
||||
# достаточно рано? если нет — просто подожди немного
|
||||
if now - last_call_ts < interval:
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
try:
|
||||
# тянем свежие данные standings тем же способом, что в get_data_API
|
||||
data_standings = fetch_api_data(
|
||||
session,
|
||||
"standings",
|
||||
@@ -1644,17 +1615,11 @@ def Standing_func(
|
||||
lang=lang,
|
||||
)
|
||||
|
||||
# fetch_api_data для standings вернёт либо:
|
||||
# - dict с "items": [...], либо
|
||||
# - сам массив items (если get_items нашёл список)
|
||||
# Мы хотим привести к единому формату, как было в твоём коде.
|
||||
if not data_standings:
|
||||
logger.debug("[STANDINGS_THREAD] standings empty")
|
||||
# не обновляем last_call_ts, чтобы через секунду попытаться снова
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# Если data_standings оказался списком, приведём к виду {"items": [...]}:
|
||||
if isinstance(data_standings, list):
|
||||
items = data_standings
|
||||
else:
|
||||
@@ -1662,26 +1627,21 @@ def Standing_func(
|
||||
|
||||
if not items:
|
||||
logger.debug("[STANDINGS_THREAD] no items in standings")
|
||||
last_call_ts = now # запрос был успешным, но пустым
|
||||
last_call_ts = now
|
||||
continue
|
||||
|
||||
# Обрабатываем каждый "item" внутри standings:
|
||||
for item in items:
|
||||
comp = item.get("comp", {})
|
||||
comp_name = (comp.get("name") or "unknown_comp").replace(" ", "_")
|
||||
comp_name = (comp.get("name") or "unknown_comp").replace(" ", "_").replace("|", "")
|
||||
|
||||
# 1) обычная таблица регулярки
|
||||
if item.get("standings"):
|
||||
standings_rows = item["standings"]
|
||||
|
||||
# pandas нормализация
|
||||
df = pd.json_normalize(standings_rows)
|
||||
|
||||
# убираем поле 'scores', если есть
|
||||
if "scores" in df.columns:
|
||||
df = df.drop(columns=["scores"])
|
||||
|
||||
# добавляем w_l, procent, plus_minus если есть нужные столбцы
|
||||
if (
|
||||
"totalWin" in df.columns
|
||||
and "totalDefeat" in df.columns
|
||||
@@ -1689,43 +1649,43 @@ def Standing_func(
|
||||
and "totalGoalPlus" in df.columns
|
||||
and "totalGoalMinus" in df.columns
|
||||
):
|
||||
# W / L
|
||||
df["w_l"] = (
|
||||
df["totalWin"].fillna(0).astype(int).astype(str)
|
||||
+ " / "
|
||||
+ df["totalDefeat"].fillna(0).astype(int).astype(str)
|
||||
)
|
||||
tw = pd.to_numeric(df["totalWin"], errors="coerce").fillna(0).astype(int)
|
||||
td = pd.to_numeric(df["totalDefeat"], errors="coerce").fillna(0).astype(int)
|
||||
|
||||
df["w_l"] = tw.astype(str) + " / " + td.astype(str)
|
||||
|
||||
# % побед
|
||||
def calc_percent(row):
|
||||
win = row.get("totalWin", 0)
|
||||
games = row.get("totalGames", 0)
|
||||
if (
|
||||
pd.isna(win)
|
||||
or pd.isna(games)
|
||||
or games == 0
|
||||
or (row["w_l"] == "0 / 0")
|
||||
):
|
||||
|
||||
# гарантируем числа
|
||||
try:
|
||||
win = int(win)
|
||||
except (TypeError, ValueError):
|
||||
win = 0
|
||||
try:
|
||||
games = int(games)
|
||||
except (TypeError, ValueError):
|
||||
games = 0
|
||||
|
||||
if games == 0 or row["w_l"] == "0 / 0":
|
||||
return 0
|
||||
|
||||
return round(win * 100 / games + 0.000005)
|
||||
|
||||
df["procent"] = df.apply(calc_percent, axis=1)
|
||||
|
||||
# +/- по очкам
|
||||
df["plus_minus"] = df["totalGoalPlus"].fillna(0).astype(
|
||||
int
|
||||
) - df["totalGoalMinus"].fillna(0).astype(int)
|
||||
tg_plus = pd.to_numeric(df["totalGoalPlus"], errors="coerce").fillna(0).astype(int)
|
||||
tg_minus = pd.to_numeric(df["totalGoalMinus"], errors="coerce").fillna(0).astype(int)
|
||||
|
||||
df["plus_minus"] = tg_plus - tg_minus
|
||||
|
||||
# готовим питоновский список словарей для атомарной записи
|
||||
standings_payload = df.to_dict(orient="records")
|
||||
|
||||
filename = f"standings_{league}_{comp_name}"
|
||||
atomic_write_json(standings_payload, filename, out_dir)
|
||||
logger.info(
|
||||
f"[STANDINGS_THREAD] сохранил {filename}.json"
|
||||
)
|
||||
logger.info(f"[STANDINGS_THREAD] сохранил {filename}.json")
|
||||
|
||||
# 2) плейофф-пары (playoffPairs)
|
||||
elif item.get("playoffPairs"):
|
||||
playoff_rows = item["playoffPairs"]
|
||||
df = pd.json_normalize(playoff_rows)
|
||||
@@ -1738,17 +1698,14 @@ def Standing_func(
|
||||
f"[STANDINGS_THREAD] saved {filename}.json (playoffPairs, {len(standings_payload)} rows)"
|
||||
)
|
||||
|
||||
# если ни standings ни playoffPairs — просто пропускаем этот блок
|
||||
else:
|
||||
continue
|
||||
|
||||
# если всё прошло без исключения — фиксируем время удачного апдейта
|
||||
last_call_ts = now
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[STANDINGS_THREAD] ошибка в турнирном положении: {e}")
|
||||
|
||||
# не жрём CPU впустую
|
||||
time.sleep(1)
|
||||
|
||||
logger.info("[STANDINGS_THREAD] stop standings loop")
|
||||
@@ -1765,22 +1722,15 @@ def get_data_API(
|
||||
team: str,
|
||||
lang: str,
|
||||
stop_event: threading.Event,
|
||||
) -> None:
|
||||
) -> str:
|
||||
"""
|
||||
Один "дневной прогон" логики:
|
||||
1. Узнать текущий сезон
|
||||
2. Обновить standings и calendar
|
||||
3. Найти игру для нашей команды сегодня (today_game) или последнюю законченную (last_played)
|
||||
4. Если есть last_played и нет игры сегодня:
|
||||
- забрать /game по last_played
|
||||
- один раз сгенерировать финальные JSON (render_once_after_game)
|
||||
5. Если есть игра сегодня:
|
||||
- забрать /game по today_game
|
||||
- если статус игры live:
|
||||
· запустить live-петлю (run_live_loop) с рендером в реальном времени
|
||||
иначе (игра уже финальная, не live):
|
||||
· один раз сгенерировать финальные JSON (render_once_after_game)
|
||||
6. Если нет ничего -> просто логируем и выходим
|
||||
Один "дневной прогон" логики.
|
||||
Возвращает day_state:
|
||||
- "upcoming" -> матч сегодня (домашний), но ещё не начался
|
||||
- "live_done" -> был live_loop и завершился
|
||||
- "finished_now" -> матч уже завершён, отрендерили финал
|
||||
- "no_game" -> сегодня игры нет вообще
|
||||
- "error" -> что-то упало по дороге
|
||||
"""
|
||||
# 1. сезоны
|
||||
json_seasons = fetch_api_data(
|
||||
@@ -1788,7 +1738,7 @@ def get_data_API(
|
||||
)
|
||||
if not json_seasons:
|
||||
logger.error("Не удалось получить список сезонов")
|
||||
return
|
||||
return "error"
|
||||
|
||||
season = json_seasons[0]["season"]
|
||||
|
||||
@@ -1811,59 +1761,90 @@ def get_data_API(
|
||||
)
|
||||
if not json_calendar:
|
||||
logger.error("Не удалось получить список матчей")
|
||||
return
|
||||
return "error"
|
||||
|
||||
# 3. какая игра нас интересует?
|
||||
# 3. определяем игру
|
||||
today_game, last_played = get_game_id(json_calendar, team)
|
||||
print(today_game, last_played)
|
||||
|
||||
# 4. уже сыграли матч, а сегодня не играем
|
||||
# Ветка А: есть завершённая игра, но сегодня нет матча
|
||||
if last_played and not today_game:
|
||||
game_id = last_played["game"]["id"]
|
||||
logger.info(f"Последний завершённый матч id={game_id}")
|
||||
render_once_after_game(session, league, season, game_id, lang)
|
||||
return
|
||||
return "finished_now"
|
||||
|
||||
# 5. матч сегодня есть
|
||||
# Ветка Б: матч сегодня есть (для домашней команды)
|
||||
if today_game:
|
||||
game_id = today_game["game"]["id"]
|
||||
logger.info(f"Онлайн матч id={game_id}")
|
||||
|
||||
# Всегда обновляем /game прямо сейчас
|
||||
# Обновляем api_* файлы сразу
|
||||
fetch_api_data(session, "game", host=HOST, game_id=game_id, lang=lang)
|
||||
|
||||
# и эти ручки тоже сразу дергаем, чтобы у нас были свежие 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_status_raw = fetch_api_data(
|
||||
session, "live-status", host=HOST, game_id=game_id
|
||||
)
|
||||
|
||||
# если матч идёт — запускаем live поток, он сам будет рендерить в цикле
|
||||
if is_game_live(today_game["game"]):
|
||||
# Определяем состояние матча
|
||||
status_calendar = today_game["game"].get("gameStatus", "")
|
||||
status_live = ""
|
||||
# print(live_status_raw)
|
||||
if isinstance(live_status_raw, dict):
|
||||
# бывают ответы вида {"status":"404","message":"Not found","result":None}
|
||||
ls_result = live_status_raw.get("result")
|
||||
if isinstance(ls_result, dict):
|
||||
status_live = ls_result.get("gameStatus", "")
|
||||
# если result == None -> просто считаем, что live-статуса нет (ещё не начался)
|
||||
|
||||
effective_status = status_live or status_calendar
|
||||
phase = classify_game_state_from_status(effective_status)
|
||||
|
||||
if phase == "live":
|
||||
# матч идёт → запускаем live_loop блокирующе
|
||||
t = threading.Thread(
|
||||
target=run_live_loop,
|
||||
args=(league, season, game_id, lang, today_game["game"], stop_event),
|
||||
daemon=False,
|
||||
)
|
||||
t.start()
|
||||
logger.info("live thread spawned, waiting for it to finish...")
|
||||
logger.info("[get_data_API] live thread spawned, waiting for it to finish")
|
||||
|
||||
try:
|
||||
t.join()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("KeyboardInterrupt while waiting live thread -> stop_event")
|
||||
logger.info(
|
||||
"[get_data_API] KeyboardInterrupt while waiting live thread -> stop_event"
|
||||
)
|
||||
stop_event.set()
|
||||
t.join()
|
||||
|
||||
logger.info("live thread finished")
|
||||
logger.info("[get_data_API] live thread finished")
|
||||
return "live_done"
|
||||
|
||||
else:
|
||||
# матч сегодня, но он уже финальный (resultconfirmed / finished)
|
||||
# значит просто один раз считаем все json-ы
|
||||
if phase == "upcoming":
|
||||
# матч сегодня, но ещё не начался (Scheduled / NotStarted / Draft)
|
||||
# не генерим финал, не спим до завтра
|
||||
logger.info(
|
||||
f"Матч {game_id} сегодня, но ещё не начался (status={effective_status}). Ждём старт."
|
||||
)
|
||||
return "upcoming"
|
||||
|
||||
if phase == "finished":
|
||||
# матч уже закончен → однократно пререндерили всё и можем спать
|
||||
render_once_after_game(session, league, season, game_id, lang)
|
||||
return "finished_now"
|
||||
|
||||
return
|
||||
# на всякий случай (если API дал что-то новое)
|
||||
logger.info(
|
||||
f"[get_data_API] Неожиданная фаза '{phase}', status={effective_status}. Считаем как 'upcoming'."
|
||||
)
|
||||
return "upcoming"
|
||||
|
||||
# 6. ничего подходящего вообще
|
||||
# Ветка В: нет матча сегодня, нет последнего завершённого
|
||||
logger.info("Для этой команды игр сегодня нет и нет завершённой последней игры.")
|
||||
return "no_game"
|
||||
|
||||
|
||||
def main():
|
||||
@@ -1873,14 +1854,9 @@ def main():
|
||||
parser.add_argument("--lang", default="en")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Один общий stop_event на всё приложение.
|
||||
stop_event = threading.Event()
|
||||
|
||||
# Одна сессия для standings-потока.
|
||||
# Её достаточно, потому что standings не требует суперчастого обновления.
|
||||
standings_session = create_session()
|
||||
|
||||
# Запускаем standings-поток навсегда (пока процесс жив).
|
||||
standings_thread = threading.Thread(
|
||||
target=Standing_func,
|
||||
args=(standings_session, args.league, None, args.lang, stop_event),
|
||||
@@ -1889,20 +1865,37 @@ def main():
|
||||
standings_thread.start()
|
||||
logger.info("[MAIN] standings thread started (global)")
|
||||
|
||||
# Основной дневной цикл.
|
||||
while True:
|
||||
session = create_session()
|
||||
try:
|
||||
get_data_API(session, args.league, args.team, args.lang, stop_event)
|
||||
day_state = get_data_API(
|
||||
session, args.league, args.team, args.lang, stop_event
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
logger.info("KeyboardInterrupt -> останавливаем всё")
|
||||
stop_event.set()
|
||||
break
|
||||
except Exception as e:
|
||||
logger.exception(f"main loop crash: {e}")
|
||||
day_state = "error"
|
||||
|
||||
# спим до завтра 00:05
|
||||
now = datetime.now(APP_TZ)
|
||||
|
||||
if day_state == "upcoming":
|
||||
# матч сегодня, но ещё не начался → НЕ спим до завтра.
|
||||
time.sleep(120) # 2 минуты опроса статуса до старта
|
||||
continue
|
||||
|
||||
if day_state == "error":
|
||||
# что-то пошло не так → подожди минуту и попробуем ещё раз
|
||||
time.sleep(60)
|
||||
continue
|
||||
|
||||
# сюда мы попадаем если:
|
||||
# - live_done (лайв отработался до конца)
|
||||
# - finished_now (матч уже был, всё посчитали)
|
||||
# - no_game (сегодня матчей вообще нет)
|
||||
# -> можно лечь до завтра 00:05
|
||||
tomorrow = (now + timedelta(days=1)).replace(
|
||||
hour=0, minute=5, second=0, microsecond=0
|
||||
)
|
||||
@@ -1913,9 +1906,12 @@ def main():
|
||||
)
|
||||
sleep_seconds = (tomorrow - now).total_seconds()
|
||||
|
||||
hours_left = int(sleep_seconds // 3600)
|
||||
mins_left = int((sleep_seconds % 3600) // 60)
|
||||
|
||||
logger.info(
|
||||
f"Работа за день завершена. Засыпаем до {tomorrow.strftime('%d.%m %H:%M')} "
|
||||
f"(~{round(sleep_seconds/3600, 2)} ч)."
|
||||
f"(~{hours_left}.{mins_left} ч)."
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -1925,14 +1921,10 @@ def main():
|
||||
stop_event.set()
|
||||
break
|
||||
|
||||
# Выход из while True → стопаем standings-поток
|
||||
stop_event.set()
|
||||
standings_thread.join()
|
||||
logger.info("[MAIN] standings thread stopped, shutdown complete")
|
||||
|
||||
# идём на новую итерацию while True
|
||||
# (новая сессия / новый stop_event создаются в начале цикла)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 9. Точка входа
|
||||
|
||||
Reference in New Issue
Block a user