diff --git a/get_data.py b/get_data.py
index c5d9245..5ebfa32 100644
--- a/get_data.py
+++ b/get_data.py
@@ -17,9 +17,6 @@ from fastapi.responses import Response
import logging
import logging.config
import platform
-import socket
-
-# передадим параметры через аргументы или глобальные переменные
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser()
@@ -29,7 +26,6 @@ parser.add_argument("--lang", default="en")
args = parser.parse_args()
MYHOST = platform.node()
-user_name = socket.gethostname()
if not os.path.exists("logs"):
os.makedirs("logs")
@@ -68,7 +64,7 @@ log_config = {
"formatters": {
"telegram": {
"class": "telegram_handler.HtmlFormatter",
- "format": f"%(levelname)s [{MYHOST.upper()}] [{user_name}]\n%(message)s",
+ "format": f"%(levelname)s [{MYHOST.upper()}]\n%(message)s",
"use_emoji": "True",
},
"simple": {
@@ -136,6 +132,7 @@ URLS = {
"play-by-play": "{host}/api/abc/games/play-by-play?id={game_id}",
}
+
def maybe_clear_for_vmix(payload):
"""
Если включён режим очистки — возвращаем payload,
@@ -158,20 +155,6 @@ def start_offline_threads(season, game_id):
stop_live_threads()
stop_offline_threads()
logger.info("[threads] switching to OFFLINE mode ...")
- # for key in latest_data:
- # latest_data[key] = wipe_json_values(latest_data[key])
- # 🔹 очищаем latest_data безопасно, чтобы не ломать структуру
- # keep_keys = {
- # "game",
- # "pregame",
- # "pregame-full-stats",
- # "actual-standings",
- # "calendar",
- # }
- # for key in list(latest_data.keys()):
- # if key not in keep_keys:
- # del latest_data[key]
-
stop_event_offline.clear()
@@ -619,10 +602,14 @@ def results_consumer():
and GAME_START_DT.date() == datetime.now().date()
):
globals()["STATUS"] = "finished_wait"
- globals()["CLEAR_OUTPUT_FOR_VMIX"] = True # 👈 включаем режим "пустых" данных
+ globals()[
+ "CLEAR_OUTPUT_FOR_VMIX"
+ ] = True # 👈 включаем режим "пустых" данных
else:
globals()["STATUS"] = "finished_wait"
- globals()["CLEAR_OUTPUT_FOR_VMIX"] = True # 👈 включаем режим "пустых" данных
+ globals()[
+ "CLEAR_OUTPUT_FOR_VMIX"
+ ] = True # 👈 включаем режим "пустых" данных
human_time = datetime.fromtimestamp(switch_at).strftime(
"%H:%M:%S"
@@ -642,7 +629,9 @@ def results_consumer():
"online" in raw_ls_status_low or "live" in raw_ls_status_low
):
# если до этого стояла отложка — уберём
- globals()["CLEAR_OUTPUT_FOR_VMIX"] = False # 👈 выключаем очистку
+ globals()[
+ "CLEAR_OUTPUT_FOR_VMIX"
+ ] = False # 👈 выключаем очистку
if globals().get("OFFLINE_SWITCH_AT") is not None:
logger.info(
"[status] match back to LIVE → cancel scheduled OFFLINE"
@@ -699,25 +688,7 @@ def results_consumer():
logger.debug(
"results_consumer: LIVE & partial game → keep previous one"
)
-
- # 2) Когда матч УЖЕ online (STATUS == 'live'):
- # - поток 'game' в live-режиме погаснет сам (stop_when_live=True),
- # но если вдруг что-то долетит, кладём только полный JSON.
continue
- # # game неполный
- # if not has_game_already:
- # # 👉 раньше game вообще не было — лучше положить хоть что-то
- # latest_data["game"] = {
- # "ts": msg["ts"],
- # "data": payload,
- # }
- # else:
- # # 👉 уже есть какой-то game — неполным НЕ затираем
- # logger.debug(
- # "results_consumer: got partial game, keeping previous one"
- # )
-
- # и обязательно continue/return из этого elif/if
else:
latest_data[source] = {
"ts": msg["ts"],
@@ -1012,20 +983,6 @@ def start_offline_prevgame(season, prev_game_id: str):
logger.info("[threads] switching to OFFLINE mode (previous game) ...")
- # оставим только полезные ключи
- keep_keys = {
- "game",
- "pregame",
- "pregame-full-stats",
- "actual-standings",
- "calendar",
- }
- # for key in list(latest_data.keys()):
- # if key not in keep_keys:
- # del latest_data[key]
- # for key in latest_data:
- # latest_data[key] = wipe_json_values(latest_data[key])
-
stop_event_offline.clear()
threads_offline = [
threading.Thread(
@@ -1215,7 +1172,9 @@ def start_prestart_watcher(game_dt: datetime | None):
f"[prestart] {now:%H:%M:%S}, игра в {game_dt:%H:%M}, включаем LIVE threads по правилу T-1:10"
)
STATUS = "live_soon"
- globals()["CLEAR_OUTPUT_FOR_VMIX"] = False # можно оставить пустоту до первых живых данных
+ globals()[
+ "CLEAR_OUTPUT_FOR_VMIX"
+ ] = False # можно оставить пустоту до первых живых данных
stop_offline_threads() # на всякий случай
start_live_threads(SEASON, GAME_ID)
did_live = True
@@ -1431,6 +1390,7 @@ def wipe_json_values(obj):
else:
return ""
+
@app.get("/started_team1")
async def started_team1(sort_by: str = None):
data = await team("team1")
@@ -1769,25 +1729,6 @@ def get_latest_game_safe(name: str):
return game
-def format_time(seconds: float | int) -> str:
- """
- Форматирует время в секундах в строку "M:SS".
-
- Args:
- seconds (float | int): Количество секунд.
-
- Returns:
- str: Время в формате "M:SS".
- """
- try:
- total_seconds = int(float(seconds))
- minutes = total_seconds // 60
- sec = total_seconds % 60
- return f"{minutes}:{sec:02}"
- except (ValueError, TypeError):
- return "0:00"
-
-
def _pick_last_avg_and_sum(stats_list: list) -> tuple[dict, dict]:
"""Возвращает (season_sum, season_avg) из seasonStats. Безопасно при пустых данных."""
if not isinstance(stats_list, list) or len(stats_list) == 0:
@@ -2747,7 +2688,9 @@ async def live_status():
if not ls:
# live-status ещё не прилетел
- return maybe_clear_for_vmix([{"foulsA": 0, "foulsB": 0, "scoreA": 0, "scoreB": 0}])
+ return maybe_clear_for_vmix(
+ [{"foulsA": 0, "foulsB": 0, "scoreA": 0, "scoreB": 0}]
+ )
raw = ls.get("data")
@@ -2765,10 +2708,14 @@ async def live_status():
return maybe_clear_for_vmix([{"status": raw}])
# fallback
- return maybe_clear_for_vmix([{"foulsA": 0, "foulsB": 0, "scoreA": 0, "scoreB": 0}])
+ return maybe_clear_for_vmix(
+ [{"foulsA": 0, "foulsB": 0, "scoreA": 0, "scoreB": 0}]
+ )
else:
# матч не идёт — как у тебя было
- return maybe_clear_for_vmix([{"foulsA": 0, "foulsB": 0, "scoreA": 0, "scoreB": 0}])
+ return maybe_clear_for_vmix(
+ [{"foulsA": 0, "foulsB": 0, "scoreA": 0, "scoreB": 0}]
+ )
@app.get("/info")
@@ -2795,25 +2742,166 @@ async def info():
full_format = date_obj.strftime("%A, %#d %B %Y")
short_format = date_obj.strftime("%A, %#d %b")
- return maybe_clear_for_vmix([
- {
- "team1": team1_name,
- "team2": team2_name,
- "team1_short": team1_name_short,
- "team2_short": team2_name_short,
- "logo1": team1_logo,
- "logo2": team2_logo,
- "arena": arena,
- "short_arena": arena_short,
- "region": region,
- "league": league,
- "league_full": league_full,
- "season": season,
- "stadia": stadia,
- "date1": str(full_format),
- "date2": str(short_format),
- }
- ])
+ return maybe_clear_for_vmix(
+ [
+ {
+ "team1": team1_name,
+ "team2": team2_name,
+ "team1_short": team1_name_short,
+ "team2_short": team2_name_short,
+ "logo1": team1_logo,
+ "logo2": team2_logo,
+ "arena": arena,
+ "short_arena": arena_short,
+ "region": region,
+ "league": league,
+ "league_full": league_full,
+ "season": season,
+ "stadia": stadia,
+ "date1": str(full_format),
+ "date2": str(short_format),
+ }
+ ]
+ )
+
+
+@app.get("/play_by_play")
+async def play_by_play():
+ data = latest_data["game"]["data"]["result"]
+ data_pbp = data["plays"]
+
+ team1_name = data["team1"]["name"]
+ team2_name = data["team2"]["name"]
+
+ json_live_status = latest_data["live-status"]["data"]
+
+ team1_startnum = [
+ i["startNum"]
+ for i in next(
+ (t for t in data["teams"] if t["teamNumber"] == 1),
+ None,
+ )["starts"]
+ if i["startRole"] == "Player"
+ ]
+ team2_startnum = [
+ i["startNum"]
+ for i in next(
+ (t for t in data["teams"] if t["teamNumber"] == 2),
+ None,
+ )["starts"]
+ if i["startRole"] == "Player"
+ ]
+
+ # если вообще нет плей-бай-плея — просто отдаём пустой список
+ if not data_pbp:
+ return maybe_clear_for_vmix([])
+
+ df_data_pbp = pd.DataFrame(data_pbp[::-1])
+ last_event = data_pbp[-1]
+
+ if "play" not in df_data_pbp:
+ return maybe_clear_for_vmix([])
+
+ if json_live_status["status"] != "Not Found":
+ json_quarter = json_live_status["result"]["period"]
+ json_second = json_live_status["result"]["second"]
+ else:
+ json_quarter = last_event["period"]
+ json_second = 0
+
+ if "3x3" in LEAGUE:
+ df_data_pbp["play"].replace({2: 1, 3: 2}, inplace=True)
+
+ df_goals = df_data_pbp.loc[df_data_pbp["play"].isin([1, 2, 3])].copy()
+ if df_goals.empty:
+ return maybe_clear_for_vmix([])
+
+ df_goals.loc[df_goals["startNum"].isin(team1_startnum), "score1"] = df_goals["play"]
+ df_goals.loc[df_goals["startNum"].isin(team2_startnum), "score2"] = 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"] = df_goals["sec"].astype(str).str.slice(0, -1).astype(int)
+ df_goals["time_now"] = (600 if json_quarter < 5 else 300) - json_second
+ df_goals["quar"] = json_quarter - df_goals["period"]
+
+ # без numpy: diff_time через маски pandas
+ same_quarter = df_goals["quar"] == 0
+ other_quarter = ~same_quarter
+
+ df_goals.loc[same_quarter, "diff_time"] = (
+ df_goals.loc[same_quarter, "time_now"] - df_goals.loc[same_quarter, "new_sec"]
+ )
+
+ df_goals.loc[other_quarter, "diff_time"] = (
+ 600 * df_goals.loc[other_quarter, "quar"]
+ - df_goals.loc[other_quarter, "new_sec"]
+ + df_goals.loc[other_quarter, "time_now"]
+ )
+
+ df_goals["diff_time"] = df_goals["diff_time"].astype(int)
+
+ df_goals["diff_time_str"] = df_goals["diff_time"].apply(
+ lambda x: f"{x // 60}:{str(x % 60).zfill(2)}"
+ )
+ df_goals["team"] = df_goals.apply(
+ lambda row: team1_name if not pd.isna(row["score1"]) else team2_name,
+ axis=1,
+ )
+ df_goals["text_rus"] = df_goals.apply(
+ lambda row: (
+ f"рывок {int(row['score_sum1'])}-{int(row['score_sum2'])}"
+ if not pd.isna(row["score1"])
+ else f"рывок {int(row['score_sum2'])}-{int(row['score_sum1'])}"
+ ),
+ axis=1,
+ )
+ df_goals["text_time_rus"] = df_goals.apply(
+ lambda row: (
+ f"рывок {int(row['score_sum1'])}-{int(row['score_sum2'])} за {row['diff_time_str']}"
+ if not pd.isna(row["score1"])
+ else f"рывок {int(row['score_sum2'])}-{int(row['score_sum1'])} за {row['diff_time_str']}"
+ ),
+ axis=1,
+ )
+ df_goals["text"] = df_goals.apply(
+ lambda row: (
+ f"{team1_name} {int(row['score_sum1'])}-{int(row['score_sum2'])} run"
+ if not pd.isna(row["score1"])
+ else f"{team2_name} {int(row['score_sum2'])}-{int(row['score_sum1'])} run"
+ ),
+ axis=1,
+ )
+ df_goals["text_time"] = df_goals.apply(
+ lambda row: (
+ f"{team1_name} {int(row['score_sum1'])}-{int(row['score_sum2'])} run in last {row['diff_time_str']}"
+ if not pd.isna(row["score1"])
+ else f"{team2_name} {int(row['score_sum2'])}-{int(row['score_sum1'])} run in last {row['diff_time_str']}"
+ ),
+ axis=1,
+ )
+
+ new_order = ["text", "text_time"] + [
+ col for col in df_goals.columns if col not in ["text", "text_time"]
+ ]
+ df_goals = df_goals[new_order]
+
+ for _ in ["children", "start", "stop", "hl", "sort", "startNum", "zone", "x", "y"]:
+ if _ in df_goals.columns:
+ del df_goals[_]
+
+ # 👉 здесь избавляемся от NaN: только для score1/score2
+ df_goals["score1"] = df_goals["score1"].fillna("")
+ df_goals["score2"] = df_goals["score2"].fillna("")
+
+ # если хочешь вообще никаких NaN во всём JSON — можно так:
+ # df_goals = df_goals.fillna("")
+
+ payload = df_goals.to_dict(orient="records")
+ return maybe_clear_for_vmix(payload)
+
+
+
if __name__ == "__main__":