From dbba78eb25f9ae520b5d4299d5e88c25c1549261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AE=D1=80=D0=B8=D0=B9=20=D0=A7=D0=B5=D1=80=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE?= Date: Mon, 3 Nov 2025 22:15:23 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20json=20INFO=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B2=D0=BE=D0=B6=D1=83=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5?= =?UTF-8?q?=20=D0=BF=D0=BE=20=D1=82=D0=B5=D0=BA=D1=83=D1=89=D0=B5=D0=B9=20?= =?UTF-8?q?=D0=B8=D0=B3=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- get_data.py | 213 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 142 insertions(+), 71 deletions(-) diff --git a/get_data.py b/get_data.py index a1b15d8..f6ef698 100644 --- a/get_data.py +++ b/get_data.py @@ -236,7 +236,7 @@ def start_live_threads(season, game_id): args=( "live-status", URLS["live-status"].format(host=HOST, game_id=game_id), - .5, + 0.5, stop_event_live, ), daemon=True, @@ -246,7 +246,7 @@ def start_live_threads(season, game_id): args=( "box-score", URLS["box-score"].format(host=HOST, game_id=game_id), - .5, + 0.5, stop_event_live, ), daemon=True, @@ -284,12 +284,16 @@ def stop_live_threads(): for t in threads_live: t.join(timeout=2) if t.is_alive(): - logger.warning(f"[{current_time}] [threads] LIVE thread is still alive: {t.name}") + logger.warning( + f"[{current_time}] [threads] LIVE thread is still alive: {t.name}" + ) still_alive.append(t.name) threads_live = [] if still_alive: - logger.warning(f"[{current_time}] [threads] Some LIVE threads did not stop: {still_alive}") + logger.warning( + f"[{current_time}] [threads] Some LIVE threads did not stop: {still_alive}" + ) else: logger.info("[threads] LIVE threads stopped") @@ -308,20 +312,28 @@ def stop_offline_threads(): # Функция запускаемая в потоках def get_data_from_API( - name: str, url: str, sleep_time: float, stop_event: threading.Event, stop_when_live=False + name: str, + url: str, + sleep_time: float, + stop_event: threading.Event, + stop_when_live=False, ): did_first_fetch = False while not stop_event.is_set(): current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") if stop_when_live and globals().get("STATUS") == "live" and did_first_fetch: - logger.info(f"{[{current_time}]} [{name}] stopping because STATUS='live' and first fetch done") + logger.info( + f"{[{current_time}]} [{name}] stopping because STATUS='live' and first fetch done" + ) break start = time.time() try: value = requests.get(url, timeout=5).json() did_first_fetch = True # помечаем, что один заход сделали except json.JSONDecodeError as json_err: - logger.warning(f"[{current_time}] [{name}] Ошибка парсинга JSON: {json_err}") + logger.warning( + f"[{current_time}] [{name}] Ошибка парсинга JSON: {json_err}" + ) value = {"error": f"JSON decode error: {json_err}"} except requests.exceptions.Timeout: logger.warning(f"[{current_time}] [{name}] Таймаут при запросе {url}") @@ -339,7 +351,9 @@ def get_data_from_API( "fail", "no-status", ): - logger.warning(f"[{current_time}] [{name}] API вернул статус '{value.get('status')}'") + logger.warning( + f"[{current_time}] [{name}] API вернул статус '{value.get('status')}'" + ) ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] results_q.put({"source": name, "ts": ts, "data": value}) @@ -351,13 +365,15 @@ def get_data_from_API( # to_sleep = sleep_time - elapsed # print(to_sleep) # if to_sleep > 0: - # умное ожидание с быстрым выходом при live + # умное ожидание с быстрым выходом при live slept = 0 while slept < sleep_time: if stop_event.is_set(): break if stop_when_live and globals().get("STATUS") == "live" and did_first_fetch: - logger.info(f"[{name}] stopping during sleep because STATUS='live' and first fetch done") + logger.info( + f"[{name}] stopping during sleep because STATUS='live' and first fetch done" + ) return time.sleep(1) slept += 1 @@ -397,7 +413,7 @@ def results_consumer(): latest_data[source] = { "ts": msg["ts"], "data": incoming_status if incoming_status is not None else payload, - } + } # 1) play-by-play if "play-by-play" in source: game = latest_data.get("game") @@ -530,7 +546,9 @@ def results_consumer(): globals()["OFFLINE_SWITCH_AT"] = None if globals().get("STATUS") != "live": - logger.info("[status] match became LIVE → switch to LIVE threads") + logger.info( + "[status] match became LIVE → switch to LIVE threads" + ) globals()["STATUS"] = "live" start_live_threads(SEASON, GAME_ID) @@ -542,7 +560,9 @@ def results_consumer(): else: if source == "game": # has_game_already = "teams" in latest_data - has_game_already = "game" in latest_data and isinstance(latest_data.get("game"), dict) + has_game_already = "game" in latest_data and isinstance( + latest_data.get("game"), dict + ) # есть ли в ответе ПОЛНАЯ структура is_full = ( @@ -558,14 +578,21 @@ def results_consumer(): "data": payload, } else: - if globals().get("STATUS") in ("live", "live_soon") and has_game_already: - logger.debug("results_consumer: LIVE & partial game → keep previous one") + if ( + globals().get("STATUS") in ("live", "live_soon") + and has_game_already + ): + logger.debug( + "results_consumer: LIVE & partial game → keep previous one" + ) else: if not has_game_already: # раньше не было вообще — положим хоть что-то latest_data["game"] = {"ts": msg["ts"], "data": payload} else: - logger.debug("results_consumer: got partial game, keeping previous one") + logger.debug( + "results_consumer: got partial game, keeping previous one" + ) # # game неполный # if not has_game_already: # # 👉 раньше game вообще не было — лучше положить хоть что-то @@ -705,7 +732,11 @@ def build_pretty_status_message(): if isinstance(raw, dict): # ваш нормальный полный ответ по game имеет структуру: {"data": {"result": {...}}} # но на всякий случай поддержим и вариант, где сразу {"result": {...}} или уже {"game": ...} - result = raw.get("data", {}).get("result", {}) if "data" in raw else (raw.get("result") or raw) + result = ( + raw.get("data", {}).get("result", {}) + if "data" in raw + else (raw.get("result") or raw) + ) else: result = {} @@ -736,7 +767,7 @@ def build_pretty_status_message(): # ls_raw.get("status") or ls_raw.get("gameStatus") or ls_raw.get("state") or "—" # ) # lines.append(f"🟢 LIVE status: {ls_status}") - + ls_wrap = latest_data.get("live-status") ls_status = "—" if ls_wrap: @@ -899,7 +930,7 @@ async def lifespan(app: FastAPI): GAME_TODAY = is_today logger.info( - f"\nЛига: {LEAGUE}\nСезон: {season}\nКоманда: {TEAM}\nGame ID: {game_id}" + f"Лига: {LEAGUE}\nСезон: {season}\nКоманда: {TEAM}\nGame ID: {game_id}" ) # 4. запускаем "длинные" потоки (они у тебя и так всегда) @@ -1929,70 +1960,68 @@ async def regular_standings(): data = latest_data["actual-standings"]["data"]["items"] for item in data: # if item["comp"]["name"] == "Regular Season": - if item.get("standings"): - standings_rows = item["standings"] + if item.get("standings"): + standings_rows = item["standings"] - df = pd.json_normalize(standings_rows) + df = pd.json_normalize(standings_rows) - if "scores" in df.columns: - df = df.drop(columns=["scores"]) + if "scores" in df.columns: + df = df.drop(columns=["scores"]) - if ( - "totalWin" in df.columns - and "totalDefeat" in df.columns - and "totalGames" in df.columns - and "totalGoalPlus" in df.columns - and "totalGoalMinus" in df.columns - ): - tw = ( - pd.to_numeric(df["totalWin"], errors="coerce") - .fillna(0) - .astype(int) - ) - td = ( - pd.to_numeric(df["totalDefeat"], errors="coerce") - .fillna(0) - .astype(int) - ) + if ( + "totalWin" in df.columns + and "totalDefeat" in df.columns + and "totalGames" in df.columns + and "totalGoalPlus" in df.columns + and "totalGoalMinus" in df.columns + ): + 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) + df["w_l"] = tw.astype(str) + " / " + td.astype(str) - def calc_percent(row): - win = row.get("totalWin", 0) - games = row.get("totalGames", 0) + def calc_percent(row): + win = row.get("totalWin", 0) + games = row.get("totalGames", 0) - # гарантируем числа - try: - win = int(win) - except (TypeError, ValueError): - win = 0 - try: - games = int(games) - except (TypeError, ValueError): - games = 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 + if games == 0 or row["w_l"] == "0 / 0": + return 0 - return round(win * 100 / games + 0.000005) + return round(win * 100 / games + 0.000005) - df["procent"] = df.apply(calc_percent, axis=1) + df["procent"] = df.apply(calc_percent, axis=1) - 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) - ) + 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 + df["plus_minus"] = tg_plus - tg_minus - standings_payload = df.to_dict(orient="records") - return standings_payload + standings_payload = df.to_dict(orient="records") + return standings_payload @app.get("/live_status") @@ -2027,6 +2056,48 @@ async def live_status(): return [{"foulsA": 0, "foulsB": 0}] +@app.get("/info") +async def info(): + data = latest_data["game"]["data"]["result"] + team1_name = data["team1"]["name"] + team2_name = data["team2"]["name"] + team1_logo = data["team1"]["logo"] + team2_logo = data["team2"]["logo"] + arena = data["arena"]["name"] + arena_short = data["arena"]["shortName"] + region = data["region"]["name"] + date_obj = datetime.strptime(data["game"]["localDate"], "%d.%m.%Y") + league = data["league"]["abcName"] + league_full = data["league"]["name"] + season = f'{str(data["league"]["season"]-1)}/{str(data["league"]["season"])[2:]}' + stadia = data["comp"]["name"] + try: + full_format = date_obj.strftime("%A, %-d %B %Y") + short_format = date_obj.strftime("%A, %-d %b") + except ValueError: + full_format = date_obj.strftime("%A, %#d %B %Y") + short_format = date_obj.strftime("%A, %#d %b") + + + return [ + { + "team1": team1_name, + "team2": team2_name, + "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), + } + ] + + if __name__ == "__main__": uvicorn.run( "get_data:app", host="0.0.0.0", port=8000, reload=True, log_level="debug"