добавил новый 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=(
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user