добавил новый 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=(
"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: <b>{ls_status}</b>")
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"