добавил новый json INFO
вывожу данные по текущей игре
This commit is contained in:
213
get_data.py
213
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: <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"
|
||||
|
||||
Reference in New Issue
Block a user