добавил новый json INFO

вывожу данные по текущей игре
This commit is contained in:
2025-11-03 22:15:23 +03:00
parent 68fc410b3f
commit dbba78eb25

View File

@@ -236,7 +236,7 @@ def start_live_threads(season, game_id):
args=( args=(
"live-status", "live-status",
URLS["live-status"].format(host=HOST, game_id=game_id), URLS["live-status"].format(host=HOST, game_id=game_id),
.5, 0.5,
stop_event_live, stop_event_live,
), ),
daemon=True, daemon=True,
@@ -246,7 +246,7 @@ def start_live_threads(season, game_id):
args=( args=(
"box-score", "box-score",
URLS["box-score"].format(host=HOST, game_id=game_id), URLS["box-score"].format(host=HOST, game_id=game_id),
.5, 0.5,
stop_event_live, stop_event_live,
), ),
daemon=True, daemon=True,
@@ -284,12 +284,16 @@ def stop_live_threads():
for t in threads_live: for t in threads_live:
t.join(timeout=2) t.join(timeout=2)
if t.is_alive(): 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) still_alive.append(t.name)
threads_live = [] threads_live = []
if still_alive: 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: else:
logger.info("[threads] LIVE threads stopped") logger.info("[threads] LIVE threads stopped")
@@ -308,20 +312,28 @@ def stop_offline_threads():
# Функция запускаемая в потоках # Функция запускаемая в потоках
def get_data_from_API( 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 did_first_fetch = False
while not stop_event.is_set(): while not stop_event.is_set():
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") 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: 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 break
start = time.time() start = time.time()
try: try:
value = requests.get(url, timeout=5).json() value = requests.get(url, timeout=5).json()
did_first_fetch = True # помечаем, что один заход сделали did_first_fetch = True # помечаем, что один заход сделали
except json.JSONDecodeError as json_err: 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}"} value = {"error": f"JSON decode error: {json_err}"}
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
logger.warning(f"[{current_time}] [{name}] Таймаут при запросе {url}") logger.warning(f"[{current_time}] [{name}] Таймаут при запросе {url}")
@@ -339,7 +351,9 @@ def get_data_from_API(
"fail", "fail",
"no-status", "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] ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
results_q.put({"source": name, "ts": ts, "data": value}) results_q.put({"source": name, "ts": ts, "data": value})
@@ -351,13 +365,15 @@ def get_data_from_API(
# to_sleep = sleep_time - elapsed # to_sleep = sleep_time - elapsed
# print(to_sleep) # print(to_sleep)
# if to_sleep > 0: # if to_sleep > 0:
# умное ожидание с быстрым выходом при live # умное ожидание с быстрым выходом при live
slept = 0 slept = 0
while slept < sleep_time: while slept < sleep_time:
if stop_event.is_set(): if stop_event.is_set():
break break
if stop_when_live and globals().get("STATUS") == "live" and did_first_fetch: 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 return
time.sleep(1) time.sleep(1)
slept += 1 slept += 1
@@ -397,7 +413,7 @@ def results_consumer():
latest_data[source] = { latest_data[source] = {
"ts": msg["ts"], "ts": msg["ts"],
"data": incoming_status if incoming_status is not None else payload, "data": incoming_status if incoming_status is not None else payload,
} }
# 1) play-by-play # 1) play-by-play
if "play-by-play" in source: if "play-by-play" in source:
game = latest_data.get("game") game = latest_data.get("game")
@@ -530,7 +546,9 @@ def results_consumer():
globals()["OFFLINE_SWITCH_AT"] = None globals()["OFFLINE_SWITCH_AT"] = None
if globals().get("STATUS") != "live": 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" globals()["STATUS"] = "live"
start_live_threads(SEASON, GAME_ID) start_live_threads(SEASON, GAME_ID)
@@ -542,7 +560,9 @@ def results_consumer():
else: else:
if source == "game": if source == "game":
# has_game_already = "teams" in latest_data # 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 = ( is_full = (
@@ -558,14 +578,21 @@ def results_consumer():
"data": payload, "data": payload,
} }
else: else:
if globals().get("STATUS") in ("live", "live_soon") and has_game_already: if (
logger.debug("results_consumer: LIVE & partial game → keep previous one") globals().get("STATUS") in ("live", "live_soon")
and has_game_already
):
logger.debug(
"results_consumer: LIVE & partial game → keep previous one"
)
else: else:
if not has_game_already: if not has_game_already:
# раньше не было вообще — положим хоть что-то # раньше не было вообще — положим хоть что-то
latest_data["game"] = {"ts": msg["ts"], "data": payload} latest_data["game"] = {"ts": msg["ts"], "data": payload}
else: else:
logger.debug("results_consumer: got partial game, keeping previous one") logger.debug(
"results_consumer: got partial game, keeping previous one"
)
# # game неполный # # game неполный
# if not has_game_already: # if not has_game_already:
# # 👉 раньше game вообще не было — лучше положить хоть что-то # # 👉 раньше game вообще не было — лучше положить хоть что-то
@@ -705,7 +732,11 @@ def build_pretty_status_message():
if isinstance(raw, dict): if isinstance(raw, dict):
# ваш нормальный полный ответ по game имеет структуру: {"data": {"result": {...}}} # ваш нормальный полный ответ по game имеет структуру: {"data": {"result": {...}}}
# но на всякий случай поддержим и вариант, где сразу {"result": {...}} или уже {"game": ...} # но на всякий случай поддержим и вариант, где сразу {"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: else:
result = {} 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 "—" # ls_raw.get("status") or ls_raw.get("gameStatus") or ls_raw.get("state") or "—"
# ) # )
# lines.append(f"🟢 LIVE status: <b>{ls_status}</b>") # lines.append(f"🟢 LIVE status: <b>{ls_status}</b>")
ls_wrap = latest_data.get("live-status") ls_wrap = latest_data.get("live-status")
ls_status = "" ls_status = ""
if ls_wrap: if ls_wrap:
@@ -899,7 +930,7 @@ async def lifespan(app: FastAPI):
GAME_TODAY = is_today GAME_TODAY = is_today
logger.info( logger.info(
f"\nЛига: {LEAGUE}\nСезон: {season}\nКоманда: {TEAM}\nGame ID: {game_id}" f"Лига: {LEAGUE}\nСезон: {season}\nКоманда: {TEAM}\nGame ID: {game_id}"
) )
# 4. запускаем "длинные" потоки (они у тебя и так всегда) # 4. запускаем "длинные" потоки (они у тебя и так всегда)
@@ -1929,70 +1960,68 @@ async def regular_standings():
data = latest_data["actual-standings"]["data"]["items"] data = latest_data["actual-standings"]["data"]["items"]
for item in data: for item in data:
# if item["comp"]["name"] == "Regular Season": # if item["comp"]["name"] == "Regular Season":
if item.get("standings"): if item.get("standings"):
standings_rows = item["standings"] standings_rows = item["standings"]
df = pd.json_normalize(standings_rows) df = pd.json_normalize(standings_rows)
if "scores" in df.columns: if "scores" in df.columns:
df = df.drop(columns=["scores"]) df = df.drop(columns=["scores"])
if ( if (
"totalWin" in df.columns "totalWin" in df.columns
and "totalDefeat" in df.columns and "totalDefeat" in df.columns
and "totalGames" in df.columns and "totalGames" in df.columns
and "totalGoalPlus" in df.columns and "totalGoalPlus" in df.columns
and "totalGoalMinus" in df.columns and "totalGoalMinus" in df.columns
): ):
tw = ( tw = (
pd.to_numeric(df["totalWin"], errors="coerce") pd.to_numeric(df["totalWin"], errors="coerce").fillna(0).astype(int)
.fillna(0) )
.astype(int) td = (
) pd.to_numeric(df["totalDefeat"], errors="coerce")
td = ( .fillna(0)
pd.to_numeric(df["totalDefeat"], errors="coerce") .astype(int)
.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): def calc_percent(row):
win = row.get("totalWin", 0) win = row.get("totalWin", 0)
games = row.get("totalGames", 0) games = row.get("totalGames", 0)
# гарантируем числа # гарантируем числа
try: try:
win = int(win) win = int(win)
except (TypeError, ValueError): except (TypeError, ValueError):
win = 0 win = 0
try: try:
games = int(games) games = int(games)
except (TypeError, ValueError): except (TypeError, ValueError):
games = 0 games = 0
if games == 0 or row["w_l"] == "0 / 0": if games == 0 or row["w_l"] == "0 / 0":
return 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 = ( tg_plus = (
pd.to_numeric(df["totalGoalPlus"], errors="coerce") pd.to_numeric(df["totalGoalPlus"], errors="coerce")
.fillna(0) .fillna(0)
.astype(int) .astype(int)
) )
tg_minus = ( tg_minus = (
pd.to_numeric(df["totalGoalMinus"], errors="coerce") pd.to_numeric(df["totalGoalMinus"], errors="coerce")
.fillna(0) .fillna(0)
.astype(int) .astype(int)
) )
df["plus_minus"] = tg_plus - tg_minus df["plus_minus"] = tg_plus - tg_minus
standings_payload = df.to_dict(orient="records") standings_payload = df.to_dict(orient="records")
return standings_payload return standings_payload
@app.get("/live_status") @app.get("/live_status")
@@ -2027,6 +2056,48 @@ async def live_status():
return [{"foulsA": 0, "foulsB": 0}] 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__": if __name__ == "__main__":
uvicorn.run( uvicorn.run(
"get_data:app", host="0.0.0.0", port=8000, reload=True, log_level="debug" "get_data:app", host="0.0.0.0", port=8000, reload=True, log_level="debug"