Merge branch 'main' of https://git.tvstart.ru/ychernenko/RFB
This commit is contained in:
567
get_data.py
567
get_data.py
@@ -111,6 +111,38 @@ _GAME_CACHE_LOCK = Lock()
|
|||||||
THROTTLE_OLD_MINUTES = 30
|
THROTTLE_OLD_MINUTES = 30
|
||||||
|
|
||||||
FOLDER_JSON = "JSON" if sys.platform.startswith("win") else "static"
|
FOLDER_JSON = "JSON" if sys.platform.startswith("win") else "static"
|
||||||
|
# --- режим однократного прогона для старого матча ---
|
||||||
|
RUN_ONCE = False # включится, если матч не Online
|
||||||
|
CURRENT_GAME_ID: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def is_game_online(game_id: int) -> bool:
|
||||||
|
"""True, если матч в статусе Online (live-status). Возвращает False при любом нештатном ответе."""
|
||||||
|
global URL
|
||||||
|
try:
|
||||||
|
live = get_json(f"{URL}api/abc/games/live-status?id={game_id}")
|
||||||
|
|
||||||
|
if not isinstance(live, dict):
|
||||||
|
logger.warning(f"live-status: неожиданный тип ответа: {type(live).__name__}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
status = live.get("status")
|
||||||
|
result = live.get("result") or {}
|
||||||
|
if not isinstance(result, dict):
|
||||||
|
logger.warning(f"live-status: 'result' не dict: {type(result).__name__}")
|
||||||
|
return False
|
||||||
|
print(status)
|
||||||
|
if status == "Not Found":
|
||||||
|
return False
|
||||||
|
|
||||||
|
game_status = result.get("gameStatus")
|
||||||
|
logger.debug(f"live-status raw: status={status}, gameStatus={game_status}")
|
||||||
|
|
||||||
|
return (status == "Ok") and (game_status == "Online")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"is_game_online: ошибка при запросе live-status для game_id={game_id}: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_ip_address():
|
def get_ip_address():
|
||||||
@@ -126,6 +158,7 @@ def get_ip_address():
|
|||||||
ip_address = socket.gethostbyname(socket.gethostname())
|
ip_address = socket.gethostbyname(socket.gethostname())
|
||||||
return ip_address
|
return ip_address
|
||||||
|
|
||||||
|
|
||||||
def read_match_id_json(path="match_id.json", attempts=10, delay=0.2):
|
def read_match_id_json(path="match_id.json", attempts=10, delay=0.2):
|
||||||
"""Надёжное чтение match_id.json с ретраями при EBUSY/битом JSON."""
|
"""Надёжное чтение match_id.json с ретраями при EBUSY/битом JSON."""
|
||||||
d = delay
|
d = delay
|
||||||
@@ -137,17 +170,22 @@ def read_match_id_json(path="match_id.json", attempts=10, delay=0.2):
|
|||||||
return json.load(f)
|
return json.load(f)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
# файл переписывают — подождём и попробуем снова
|
# файл переписывают — подождём и попробуем снова
|
||||||
time.sleep(d); d = min(d*1.6, 2.0)
|
time.sleep(d)
|
||||||
|
d = min(d * 1.6, 2.0)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
# EBUSY (errno=16) — подождём и ещё раз
|
# EBUSY (errno=16) — подождём и ещё раз
|
||||||
if getattr(e, "errno", None) == errno.EBUSY:
|
if getattr(e, "errno", None) == errno.EBUSY:
|
||||||
time.sleep(d); d = min(d*1.6, 2.0)
|
time.sleep(d)
|
||||||
|
d = min(d * 1.6, 2.0)
|
||||||
continue
|
continue
|
||||||
# иные ошибки — пробрасываем дальше
|
# иные ошибки — пробрасываем дальше
|
||||||
raise
|
raise
|
||||||
logger.error("Не удалось прочитать match_id.json после нескольких попыток; возвращаю {}")
|
logger.error(
|
||||||
|
"Не удалось прочитать match_id.json после нескольких попыток; возвращаю {}"
|
||||||
|
)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
# === Аргументы командной строки ===
|
# === Аргументы командной строки ===
|
||||||
parser = argparse.ArgumentParser(description="VTB Data Fetcher")
|
parser = argparse.ArgumentParser(description="VTB Data Fetcher")
|
||||||
parser.add_argument("--league", type=str, default="vtb", help="League tag")
|
parser.add_argument("--league", type=str, default="vtb", help="League tag")
|
||||||
@@ -206,7 +244,6 @@ def _ipcheck() -> str:
|
|||||||
ip_str = get_ip_address()
|
ip_str = get_ip_address()
|
||||||
except Exception:
|
except Exception:
|
||||||
ip_str = None
|
ip_str = None
|
||||||
|
|
||||||
ip_map = globals().get("ip_check") or {}
|
ip_map = globals().get("ip_check") or {}
|
||||||
if ip_str and isinstance(ip_map, dict):
|
if ip_str and isinstance(ip_map, dict):
|
||||||
host = (ip_map.get(ip_str) or {}).get("host")
|
host = (ip_map.get(ip_str) or {}).get("host")
|
||||||
@@ -231,7 +268,8 @@ def rewrite_file(filename: str, data: dict, directory: str = "JSON") -> None:
|
|||||||
os.makedirs(directory, exist_ok=True)
|
os.makedirs(directory, exist_ok=True)
|
||||||
|
|
||||||
host_prefix = _ipcheck()
|
host_prefix = _ipcheck()
|
||||||
|
# print(host_prefix)
|
||||||
|
# host_prefix = "spb_"
|
||||||
filepath = os.path.join(directory, f"{host_prefix}{filename}.json")
|
filepath = os.path.join(directory, f"{host_prefix}{filename}.json")
|
||||||
# print(filepath) # оставил как у тебя; можно заменить на logger.debug при желании
|
# print(filepath) # оставил как у тебя; можно заменить на logger.debug при желании
|
||||||
|
|
||||||
@@ -365,21 +403,22 @@ def Game_Online(game_id: int) -> dict | None:
|
|||||||
if (
|
if (
|
||||||
cached
|
cached
|
||||||
and cached.get("mode") == "old"
|
and cached.get("mode") == "old"
|
||||||
and (now - cached.get("ts", now)) < timedelta(minutes=OLD_GAME_THROTTLE_MINUTES)
|
and (now - cached.get("ts", now))
|
||||||
|
< timedelta(minutes=OLD_GAME_THROTTLE_MINUTES)
|
||||||
):
|
):
|
||||||
return cached.get("data")
|
return cached.get("data")
|
||||||
|
|
||||||
def build_url(endpoint: str) -> str:
|
def build_url(endpoint: str) -> str:
|
||||||
return f"{URL}api/abc/games/{endpoint}?Id={game_id}&Lang={LANG}"
|
return f"{URL}api/abc/games/{endpoint}?Id={game_id}&Lang={LANG}"
|
||||||
|
|
||||||
|
|
||||||
box_score = get_json(build_url("box-score"))
|
box_score = get_json(build_url("box-score"))
|
||||||
print(box_score)
|
print(box_score)
|
||||||
if not box_score or box_score.get("status") != "Ok":
|
if not box_score or box_score.get("status") != "Ok":
|
||||||
# Проверим — матч сейчас online?
|
# Проверим — матч сейчас online?
|
||||||
live = get_json(f"{URL}api/abc/games/live-status?id={game_id}")
|
live = get_json(f"{URL}api/abc/games/live-status?id={game_id}")
|
||||||
is_online = bool(
|
is_online = bool(
|
||||||
live and live.get("status") == "Ok"
|
live
|
||||||
|
and live.get("status") == "Ok"
|
||||||
and live.get("result", {}).get("gameStatus") == "Online"
|
and live.get("result", {}).get("gameStatus") == "Online"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -391,7 +430,9 @@ def Game_Online(game_id: int) -> dict | None:
|
|||||||
with _GAME_CACHE_LOCK:
|
with _GAME_CACHE_LOCK:
|
||||||
_GAME_CACHE[cache_key] = {"mode": "old", "ts": now, "data": game}
|
_GAME_CACHE[cache_key] = {"mode": "old", "ts": now, "data": game}
|
||||||
else:
|
else:
|
||||||
logger.warning(f"Не удалось получить данные старого матча: game_id={game_id}")
|
logger.warning(
|
||||||
|
f"Не удалось получить данные старого матча: game_id={game_id}"
|
||||||
|
)
|
||||||
return game
|
return game
|
||||||
|
|
||||||
# 1. Получаем box score
|
# 1. Получаем box score
|
||||||
@@ -477,8 +518,8 @@ def game_online_loop(game_id: int, stop_event: threading.Event) -> None:
|
|||||||
game_id (int): ID игры.
|
game_id (int): ID игры.
|
||||||
stop_event (threading.Event): Событие для остановки цикла.
|
stop_event (threading.Event): Событие для остановки цикла.
|
||||||
"""
|
"""
|
||||||
global game_online_data
|
global game_online_data, RUN_ONCE, CURRENT_GAME_ID
|
||||||
|
single_run = RUN_ONCE
|
||||||
while not stop_event.is_set():
|
while not stop_event.is_set():
|
||||||
try:
|
try:
|
||||||
data = Game_Online(game_id)
|
data = Game_Online(game_id)
|
||||||
@@ -490,7 +531,12 @@ def game_online_loop(game_id: int, stop_event: threading.Event) -> None:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка в game_online_loop: {e}", exc_info=True)
|
logger.error(f"Ошибка в game_online_loop: {e}", exc_info=True)
|
||||||
|
|
||||||
stop_event.wait(TIMEOUT_ONLINE) # Лучше чем time.sleep — можно остановить сразу
|
# --- однократный режим для старого матча ---
|
||||||
|
if single_run:
|
||||||
|
logger.info("game_online_loop: однократный режим — выхожу из потока.")
|
||||||
|
return
|
||||||
|
|
||||||
|
stop_event.wait(TIMEOUT_ONLINE)
|
||||||
|
|
||||||
|
|
||||||
def coach_team_stat(data: list[dict], team_id: int) -> dict:
|
def coach_team_stat(data: list[dict], team_id: int) -> dict:
|
||||||
@@ -620,7 +666,6 @@ def calc_shot_percent_by_type(
|
|||||||
goal_key = f"goal{t}"
|
goal_key = f"goal{t}"
|
||||||
shot_key = f"shot{t}"
|
shot_key = f"shot{t}"
|
||||||
|
|
||||||
|
|
||||||
sum_goal_raw = sum_stat.get(goal_key)
|
sum_goal_raw = sum_stat.get(goal_key)
|
||||||
sum_shot_raw = sum_stat.get(shot_key)
|
sum_shot_raw = sum_stat.get(shot_key)
|
||||||
item_goal_raw = item_stats.get(goal_key)
|
item_goal_raw = item_stats.get(goal_key)
|
||||||
@@ -700,10 +745,56 @@ def sum_stat_with_online(
|
|||||||
return base + online_val
|
return base + online_val
|
||||||
|
|
||||||
|
|
||||||
|
def get_carrer_high(player_id, name_stat):
|
||||||
|
try:
|
||||||
|
directory = FOLDER_JSON # type: ignore[name-defined]
|
||||||
|
except NameError:
|
||||||
|
# иначе используем аргумент по умолчанию/переданный
|
||||||
|
pass
|
||||||
|
|
||||||
|
os.makedirs(directory, exist_ok=True)
|
||||||
|
|
||||||
|
host_prefix = _ipcheck()
|
||||||
|
# print(host_prefix)
|
||||||
|
# host_prefix = "spb_"
|
||||||
|
filepath = os.path.join(directory, f"{host_prefix}{player_id}.json")
|
||||||
|
# print(filepath) # оставил как у тебя; можно заменить на logger.debug при желании
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(filepath, "r", encoding="utf-8") as f:
|
||||||
|
player_data = json.load(f)
|
||||||
|
max_points = 0
|
||||||
|
|
||||||
|
for item in player_data:
|
||||||
|
if item.get("class") == "Normal":
|
||||||
|
points = item["stats"].get(name_stat)
|
||||||
|
if points and points.isdigit(): # Проверяем, что значение числовое
|
||||||
|
points = int(points)
|
||||||
|
if points > max_points:
|
||||||
|
max_points = points
|
||||||
|
return max_points
|
||||||
|
except Exception as ex:
|
||||||
|
logger.warning(f"[{player_id}] {ex}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def Json_Team_Generation(who, data, stop_event):
|
def Json_Team_Generation(who, data, stop_event):
|
||||||
logger.info(f"START making json for {data[who]}, {data[f'{who}_id']}")
|
logger.info(f"START making json for {data[who]}, {data[f'{who}_id']}")
|
||||||
global game_online_data
|
global game_online_data, RUN_ONCE
|
||||||
initialized = False
|
initialized = False
|
||||||
|
did_write = False # 👈 флаг: была ли хотя бы одна успешная запись файлов
|
||||||
|
|
||||||
|
# 👇 В однократном режиме дождёмся, пока OnlineLoop положит данные (до 5 сек)
|
||||||
|
if RUN_ONCE:
|
||||||
|
t0 = time.time()
|
||||||
|
while not stop_event.is_set():
|
||||||
|
with game_online_lock:
|
||||||
|
if game_online_data is not None:
|
||||||
|
break
|
||||||
|
if time.time() - t0 > 5: # таймаут ожидания
|
||||||
|
logger.warning(f"Json_Team_Generation[{who}]: нет game_online_data >5с, попробую всё равно.")
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
while not stop_event.is_set():
|
while not stop_event.is_set():
|
||||||
try:
|
try:
|
||||||
@@ -732,7 +823,8 @@ def Json_Team_Generation(who, data, stop_event):
|
|||||||
json_live_status = get_json(url)
|
json_live_status = get_json(url)
|
||||||
online = (
|
online = (
|
||||||
True
|
True
|
||||||
if json_live_status and "status" in json_live_status
|
if json_live_status
|
||||||
|
and "status" in json_live_status
|
||||||
and json_live_status["status"] == "Ok"
|
and json_live_status["status"] == "Ok"
|
||||||
and json_live_status["result"]["gameStatus"] == "Online"
|
and json_live_status["result"]["gameStatus"] == "Online"
|
||||||
else False
|
else False
|
||||||
@@ -756,38 +848,40 @@ def Json_Team_Generation(who, data, stop_event):
|
|||||||
player_season_stat = []
|
player_season_stat = []
|
||||||
player_career_stat = []
|
player_career_stat = []
|
||||||
coach_stat = []
|
coach_stat = []
|
||||||
# with ThreadPoolExecutor() as pool:
|
with ThreadPoolExecutor() as pool:
|
||||||
# player_season_stat_temp = [
|
player_season_stat_temp = [
|
||||||
# pool.submit(Player_Stat_Season, player_id, data["season"])
|
pool.submit(Player_Stat_Season, player_id, data["season"])
|
||||||
# for player_id in player_ids
|
for player_id in player_ids
|
||||||
# ]
|
]
|
||||||
# player_career_stat_temp = [
|
player_career_stat_temp = [
|
||||||
# pool.submit(Player_Stat_Career, player_id)
|
pool.submit(Player_Stat_Career, player_id)
|
||||||
# for player_id in player_ids
|
for player_id in player_ids
|
||||||
# ]
|
]
|
||||||
# coach_stat_temp = [
|
coach_stat_temp = [
|
||||||
# pool.submit(
|
pool.submit(
|
||||||
# Coach_Stat, coach_id, data["season"], data[f"{who}_id"]
|
Coach_Stat, coach_id, data["season"], data[f"{who}_id"]
|
||||||
# )
|
)
|
||||||
# for coach_id in coach_ids
|
for coach_id in coach_ids
|
||||||
# ]
|
]
|
||||||
# player_futures = [pool.submit(Player_all_game, pid) for pid in player_ids]
|
player_futures = [
|
||||||
# all_players_games = []
|
pool.submit(Player_all_game, pid) for pid in player_ids
|
||||||
# for fut in as_completed(player_futures):
|
]
|
||||||
# try:
|
all_players_games = []
|
||||||
# all_players_games.append(fut.result())
|
for fut in as_completed(player_futures):
|
||||||
# except Exception as e:
|
try:
|
||||||
# logger.exception(f"Ошибка при обработке игрока: {e}")
|
all_players_games.append(fut.result())
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Ошибка при обработке игрока: {e}")
|
||||||
|
|
||||||
# player_season_stat += [
|
player_season_stat += [
|
||||||
# res.result() for res in player_season_stat_temp
|
res.result() for res in player_season_stat_temp
|
||||||
# ]
|
]
|
||||||
# player_career_stat += [
|
player_career_stat += [
|
||||||
# res.result() for res in player_career_stat_temp
|
res.result() for res in player_career_stat_temp
|
||||||
# ]
|
]
|
||||||
# coach_stat += [res.result() for res in coach_stat_temp]
|
coach_stat += [res.result() for res in coach_stat_temp]
|
||||||
|
|
||||||
# initialized = True
|
initialized = True
|
||||||
# print(coach_stat)
|
# print(coach_stat)
|
||||||
# while not stop_event.is_set():
|
# while not stop_event.is_set():
|
||||||
role_list = [
|
role_list = [
|
||||||
@@ -810,7 +904,8 @@ def Json_Team_Generation(who, data, stop_event):
|
|||||||
row_player_season = next(
|
row_player_season = next(
|
||||||
(
|
(
|
||||||
v
|
v
|
||||||
for row in player_season_stat if row
|
for row in player_season_stat
|
||||||
|
if row
|
||||||
for k, v in row.items()
|
for k, v in row.items()
|
||||||
if k == item["personId"]
|
if k == item["personId"]
|
||||||
),
|
),
|
||||||
@@ -819,7 +914,8 @@ def Json_Team_Generation(who, data, stop_event):
|
|||||||
row_player_career = next(
|
row_player_career = next(
|
||||||
(
|
(
|
||||||
v
|
v
|
||||||
for row in player_career_stat if row
|
for row in player_career_stat
|
||||||
|
if row
|
||||||
for k, v in row.items()
|
for k, v in row.items()
|
||||||
if k == item["personId"]
|
if k == item["personId"]
|
||||||
),
|
),
|
||||||
@@ -829,7 +925,8 @@ def Json_Team_Generation(who, data, stop_event):
|
|||||||
row_coach_stat = next(
|
row_coach_stat = next(
|
||||||
(
|
(
|
||||||
v
|
v
|
||||||
for row in coach_stat if row
|
for row in coach_stat
|
||||||
|
if row
|
||||||
for k, v in row.items()
|
for k, v in row.items()
|
||||||
if k == item["personId"]
|
if k == item["personId"]
|
||||||
),
|
),
|
||||||
@@ -1127,7 +1224,12 @@ def Json_Team_Generation(who, data, stop_event):
|
|||||||
item["lastName"].strip() if item["lastName"] else ""
|
item["lastName"].strip() if item["lastName"] else ""
|
||||||
),
|
),
|
||||||
"photoGFX": (
|
"photoGFX": (
|
||||||
os.path.join("D:\\Photos", LEAGUE, data[who], f"{item['displayNumber']}.png")
|
os.path.join(
|
||||||
|
"D:\\Photos",
|
||||||
|
LEAGUE,
|
||||||
|
data[who],
|
||||||
|
f"{item['displayNumber']}.png",
|
||||||
|
)
|
||||||
if item["startRole"] == "Player"
|
if item["startRole"] == "Player"
|
||||||
else ""
|
else ""
|
||||||
),
|
),
|
||||||
@@ -1386,8 +1488,92 @@ def Json_Team_Generation(who, data, stop_event):
|
|||||||
"CareerTStartCount": sum_stat_with_online(
|
"CareerTStartCount": sum_stat_with_online(
|
||||||
"isStarts", row_player_career_sum, item["stats"], online
|
"isStarts", row_player_career_sum, item["stats"], online
|
||||||
), # если нужно, можно +1 при старте
|
), # если нужно, можно +1 при старте
|
||||||
|
"AvgCarPoints": (
|
||||||
|
row_player_career_avg["points"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["points"] != ""
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"AvgCarAssist": (
|
||||||
|
row_player_career_avg["assist"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["assist"] != ""
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"AvgCarBlocks": (
|
||||||
|
row_player_career_avg["blockShot"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["blockShot"] != ""
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"AvgCarDefRebound": (
|
||||||
|
row_player_career_avg["defRebound"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["defRebound"] != ""
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"AvgCarOffRebound": (
|
||||||
|
row_player_career_avg["offRebound"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["offRebound"] != ""
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"AvgCarRebound": (
|
||||||
|
row_player_career_avg["rebound"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["rebound"] != ""
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"AvgCarSteal": (
|
||||||
|
row_player_career_avg["steal"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["steal"] != ""
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"AvgCarTurnover": (
|
||||||
|
row_player_career_avg["turnover"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["turnover"] != ""
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"AvgCarFoul": (
|
||||||
|
row_player_career_avg["foul"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["foul"] != ""
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"AvgCarOpponentFoul": (
|
||||||
|
row_player_career_avg["foulsOnPlayer"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["foulsOnPlayer"] != ""
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"AvgCarPlusMinus": (
|
||||||
|
row_player_career_avg["plusMinus"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["plusMinus"] != ""
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"AvgCarDunk": (
|
||||||
|
row_player_career_avg["dunk"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["dunk"] != ""
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"AvgCarKPI": "0.0",
|
||||||
|
"AvgCarPlayedTime": (
|
||||||
|
row_player_career_avg["playedTime"]
|
||||||
|
if row_player_career_avg
|
||||||
|
and row_player_career_avg["playedTime"] != ""
|
||||||
|
else "0:00"
|
||||||
|
),
|
||||||
"HeadCoachStatsCareer": HeadCoachStatsCareer,
|
"HeadCoachStatsCareer": HeadCoachStatsCareer,
|
||||||
"HeadCoachStatsTeam": HeadCoachStatsTeam,
|
"HeadCoachStatsTeam": HeadCoachStatsTeam,
|
||||||
|
"PTS_Career_High": get_carrer_high(item["personId"], "points"),
|
||||||
|
"AST_Career_High": get_carrer_high(item["personId"], "assist"),
|
||||||
|
"REB_Career_High": get_carrer_high(item["personId"], "rebound"),
|
||||||
|
"STL_Career_High": get_carrer_high(item["personId"], "steal"),
|
||||||
|
"BLK_Career_High": get_carrer_high(item["personId"], "blockShot"),
|
||||||
}
|
}
|
||||||
team.append(player)
|
team.append(player)
|
||||||
count_player = sum(1 for x in team if x["startRole"] == "Player")
|
count_player = sum(1 for x in team if x["startRole"] == "Player")
|
||||||
@@ -1479,16 +1665,30 @@ def Json_Team_Generation(who, data, stop_event):
|
|||||||
reverse=False,
|
reverse=False,
|
||||||
)
|
)
|
||||||
rewrite_file(f"started_{who}", started_team)
|
rewrite_file(f"started_{who}", started_team)
|
||||||
|
# обычный цикл / однократный выход:
|
||||||
|
if RUN_ONCE:
|
||||||
|
logger.info(f"Json_Team_Generation[{who}]: однократный режим — файлы записаны, выхожу.")
|
||||||
|
return
|
||||||
|
did_write = True # 👈 отметили успешную запись
|
||||||
time.sleep(TIMEOUT_ONLINE)
|
time.sleep(TIMEOUT_ONLINE)
|
||||||
else:
|
else:
|
||||||
print(f"{who} НЕ ПОЛУЧАЕТСЯ ПРОЧИТАТЬ")
|
print(f"{who} НЕ ПОЛУЧАЕТСЯ ПРОЧИТАТЬ")
|
||||||
|
# --- однократный режим для старого матча ---
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[{who}] Ошибка: {e}, {e.with_traceback()}")
|
print(f"[{who}] Ошибка: {e}, {e.with_traceback()}")
|
||||||
|
if RUN_ONCE:
|
||||||
|
if did_write:
|
||||||
|
logger.info(f"Json_Team_Generation[{who}]: один успешный проход выполнен — выхожу.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# данных ещё не было — короткая задержка и ещё одна попытка
|
||||||
|
stop_event.wait(0.5)
|
||||||
|
continue
|
||||||
time.sleep(TIMEOUT_ONLINE)
|
time.sleep(TIMEOUT_ONLINE)
|
||||||
|
|
||||||
|
|
||||||
def Player_Stat_Season(player_id: str, season: str) -> dict:
|
def Player_Stat_Season(player_id: str, season: str) -> dict:
|
||||||
url = f"{URL}api/abc/players/stats?teamId=0&Tag={LEAGUE}&season={season}&Id={player_id}"
|
url = f"{URL.replace('pro.russiabasket.org', 'vtb-league.org')}api/abc/players/stats?teamId=0&Tag={LEAGUE}&season={season}&Id={player_id}"
|
||||||
player_stat_season = get_json(url)
|
player_stat_season = get_json(url)
|
||||||
|
|
||||||
if not player_stat_season:
|
if not player_stat_season:
|
||||||
@@ -1538,25 +1738,23 @@ def Player_all_game2(player_id: str) -> dict:
|
|||||||
]
|
]
|
||||||
for i in player_season_stat_temp:
|
for i in player_season_stat_temp:
|
||||||
print(i.result())
|
print(i.result())
|
||||||
player_game += [
|
player_game += [res.result() for res in player_season_stat_temp]
|
||||||
res.result() for res in player_season_stat_temp
|
|
||||||
]
|
|
||||||
rewrite_file(player_id, player_game)
|
rewrite_file(player_id, player_game)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def Player_all_game_in_season(player_id: str, season: str) -> List[Dict[str, Any]]:
|
def Player_all_game_in_season(player_id: str, season: str) -> List[Dict[str, Any]]:
|
||||||
url = f"{URL}api/abc/players/stats?tag={LEAGUE}&season={season}&id={player_id}"
|
url = f"{URL.replace('pro.russiabasket.org', 'vtb-league.org')}api/abc/players/stats?tag={LEAGUE}&season={season}&id={player_id}&Lang={LANG}"
|
||||||
player_games = get_json(url)
|
player_games = get_json(url)
|
||||||
if not player_games:
|
if not player_games:
|
||||||
logger.debug(f"Пустой ответ от API для игрока {player_id}, сезон {season}")
|
logger.debug(f"Пустой ответ от API для игрока {player_id}, сезон {season}")
|
||||||
return [
|
return [] # возвращаем пустой список, чтобы тип был стабилен
|
||||||
] # возвращаем пустой список, чтобы тип был стабилен
|
|
||||||
|
|
||||||
items = player_games.get("items") or []
|
items = player_games.get("items") or []
|
||||||
# гарантируем список словарей
|
# гарантируем список словарей
|
||||||
if not isinstance(items, list):
|
if not isinstance(items, list):
|
||||||
logger.warning(f"Неверный формат 'items' для {player_id}, сезон {season}: {type(items)}")
|
logger.warning(
|
||||||
|
f"Неверный формат 'items' для {player_id}, сезон {season}: {type(items)}"
|
||||||
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
for it in items:
|
for it in items:
|
||||||
@@ -1566,41 +1764,45 @@ def Player_all_game_in_season(player_id: str, season: str) -> List[Dict[str, Any
|
|||||||
|
|
||||||
|
|
||||||
def Player_all_game(player_id: str) -> List[Dict[str, Any]]:
|
def Player_all_game(player_id: str) -> List[Dict[str, Any]]:
|
||||||
# url = f"{URL}api/abc/players/info?tag={LEAGUE}&id={player_id}"
|
url = f"{URL.replace('pro.russiabasket.org', 'vtb-league.org')}api/abc/players/info?tag={LEAGUE}&id={player_id}&Lang={LANG}"
|
||||||
# player_seasons = get_json(url)
|
player_seasons = get_json(url)
|
||||||
|
|
||||||
# if not player_seasons:
|
if not player_seasons:
|
||||||
# logger.debug(f"Пустой ответ от API для игрока {player_id}")
|
logger.debug(f"Пустой ответ от API для игрока {player_id}")
|
||||||
# return [] # последовательный тип
|
return [] # последовательный тип
|
||||||
|
|
||||||
# result = player_seasons.get("result") or {}
|
result = player_seasons.get("result") or {}
|
||||||
# seasons = result.get("seasons") or []
|
seasons = result.get("seasons") or []
|
||||||
seasons = [
|
# seasons = [
|
||||||
{"id": 2026},
|
# {"id": 2026},
|
||||||
{"id": 2025},
|
# {"id": 2025},
|
||||||
{"id": 2024},
|
# {"id": 2024},
|
||||||
{"id": 2023},
|
# {"id": 2023},
|
||||||
{"id": 2022},
|
# {"id": 2022},
|
||||||
{"id": 2021},
|
# {"id": 2021},
|
||||||
{"id": 2020},
|
# {"id": 2020},
|
||||||
{"id": 2019},
|
# {"id": 2019},
|
||||||
{"id": 2018},
|
# {"id": 2018},
|
||||||
{"id": 2017},
|
# {"id": 2017},
|
||||||
{"id": 2016},
|
# {"id": 2016},
|
||||||
{"id": 2015},
|
# {"id": 2015},
|
||||||
{"id": 2014},
|
# {"id": 2014},
|
||||||
{"id": 2013},
|
# {"id": 2013},
|
||||||
{"id": 2012},
|
# {"id": 2012},
|
||||||
{"id": 2011},
|
# {"id": 2011},
|
||||||
{"id": 2010},
|
# {"id": 2010},
|
||||||
]
|
# ]
|
||||||
if not isinstance(seasons, list) or not seasons:
|
if not isinstance(seasons, list) or not seasons:
|
||||||
logger.debug(f"Нет сезонов для игрока {player_id}")
|
logger.debug(f"Нет сезонов для игрока {player_id}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
all_games: List[Dict[str, Any]] = []
|
all_games: List[Dict[str, Any]] = []
|
||||||
with ThreadPoolExecutor() as pool:
|
with ThreadPoolExecutor() as pool:
|
||||||
futures = [pool.submit(Player_all_game_in_season, player_id, s.get("id")) for s in seasons if s.get("id")]
|
futures = [
|
||||||
|
pool.submit(Player_all_game_in_season, player_id, s.get("id"))
|
||||||
|
for s in seasons
|
||||||
|
if s.get("id")
|
||||||
|
]
|
||||||
for fut in as_completed(futures):
|
for fut in as_completed(futures):
|
||||||
try:
|
try:
|
||||||
items = fut.result() # это уже список словарей
|
items = fut.result() # это уже список словарей
|
||||||
@@ -1696,7 +1898,7 @@ def default_player_stats() -> list:
|
|||||||
|
|
||||||
|
|
||||||
def Player_Stat_Career(player_id: str) -> dict:
|
def Player_Stat_Career(player_id: str) -> dict:
|
||||||
url = f"{URL}api/abc/players/career?teamId=0&Tag={LEAGUE}&Id={player_id}"
|
url = f"{URL.replace('pro.russiabasket.org', 'vtb-league.org')}api/abc/players/career?teamId=0&Tag={LEAGUE}&Id={player_id}"
|
||||||
player_stat_career = get_json(url)
|
player_stat_career = get_json(url)
|
||||||
|
|
||||||
if not player_stat_career:
|
if not player_stat_career:
|
||||||
@@ -1954,7 +2156,7 @@ def Team_Both_Stat(stop_event: threading.Event) -> None:
|
|||||||
stop_event (threading.Event): Событие для остановки цикла.
|
stop_event (threading.Event): Событие для остановки цикла.
|
||||||
"""
|
"""
|
||||||
logger.info("START making json for team statistics")
|
logger.info("START making json for team statistics")
|
||||||
global game_online_data
|
global game_online_data, RUN_ONCE
|
||||||
|
|
||||||
while not stop_event.is_set():
|
while not stop_event.is_set():
|
||||||
with game_online_lock:
|
with game_online_lock:
|
||||||
@@ -1991,16 +2193,28 @@ def Team_Both_Stat(stop_event: threading.Event) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not team_1.get("total") or not team_2.get("total"):
|
if not team_1.get("total") or not team_2.get("total"):
|
||||||
logger.debug("Нет total у команд — пропускаю перезапись team_stats.json")
|
logger.debug(
|
||||||
|
"Нет total у команд — пропускаю перезапись team_stats.json"
|
||||||
|
)
|
||||||
stop_event.wait(TIMEOUT_ONLINE)
|
stop_event.wait(TIMEOUT_ONLINE)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Форматирование общей статистики (как и было)
|
# Форматирование общей статистики (как и было)
|
||||||
total_1 = add_new_team_stat(
|
total_1 = add_new_team_stat(
|
||||||
team_1["total"], avg_age_1, points_1, avg_height_1, timeout_str1, timeout_left1,
|
team_1["total"],
|
||||||
|
avg_age_1,
|
||||||
|
points_1,
|
||||||
|
avg_height_1,
|
||||||
|
timeout_str1,
|
||||||
|
timeout_left1,
|
||||||
)
|
)
|
||||||
total_2 = add_new_team_stat(
|
total_2 = add_new_team_stat(
|
||||||
team_2["total"], avg_age_2, points_2, avg_height_2, timeout_str2, timeout_left2,
|
team_2["total"],
|
||||||
|
avg_age_2,
|
||||||
|
points_2,
|
||||||
|
avg_height_2,
|
||||||
|
timeout_str2,
|
||||||
|
timeout_left2,
|
||||||
)
|
)
|
||||||
|
|
||||||
# # Форматирование общей статистики
|
# # Форматирование общей статистики
|
||||||
@@ -2052,7 +2266,9 @@ def Team_Both_Stat(stop_event: threading.Event) -> None:
|
|||||||
|
|
||||||
rewrite_file("team_stats", result_json)
|
rewrite_file("team_stats", result_json)
|
||||||
logger.debug("Успешно записаны данные в team_stats.json")
|
logger.debug("Успешно записаны данные в team_stats.json")
|
||||||
|
if RUN_ONCE:
|
||||||
|
logger.info("<Team_Both_Stat>: однократный режим — выхожу из потока.")
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Ошибка при обработке командной статистики: {e}", exc_info=True
|
f"Ошибка при обработке командной статистики: {e}", exc_info=True
|
||||||
@@ -2066,7 +2282,7 @@ def Referee(stop_event: threading.Event) -> None:
|
|||||||
Поток, создающий JSON-файл с информацией о судьях матча.
|
Поток, создающий JSON-файл с информацией о судьях матча.
|
||||||
"""
|
"""
|
||||||
logger.info("START making json for referee")
|
logger.info("START making json for referee")
|
||||||
global game_online_data
|
global game_online_data, RUN_ONCE
|
||||||
|
|
||||||
desired_order = [
|
desired_order = [
|
||||||
"Crew chief",
|
"Crew chief",
|
||||||
@@ -2128,6 +2344,9 @@ def Referee(stop_event: threading.Event) -> None:
|
|||||||
|
|
||||||
rewrite_file("referee", referees)
|
rewrite_file("referee", referees)
|
||||||
logger.debug("Успешно записаны судьи в файл")
|
logger.debug("Успешно записаны судьи в файл")
|
||||||
|
if RUN_ONCE:
|
||||||
|
logger.info("<Referee>: однократный режим — выхожу из потока.")
|
||||||
|
return
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка в Referee потоке: {e}", exc_info=True)
|
logger.error(f"Ошибка в Referee потоке: {e}", exc_info=True)
|
||||||
@@ -2140,7 +2359,7 @@ def Scores_Quarter(stop_event: threading.Event) -> None:
|
|||||||
Поток, обновляющий JSON со счётом по четвертям.
|
Поток, обновляющий JSON со счётом по четвертям.
|
||||||
"""
|
"""
|
||||||
logger.info("START making json for scores quarter")
|
logger.info("START making json for scores quarter")
|
||||||
global game_online_data
|
global game_online_data, RUN_ONCE
|
||||||
|
|
||||||
quarters = ["Q1", "Q2", "Q3", "Q4", "OT1", "OT2", "OT3", "OT4"]
|
quarters = ["Q1", "Q2", "Q3", "Q4", "OT1", "OT2", "OT3", "OT4"]
|
||||||
|
|
||||||
@@ -2185,7 +2404,10 @@ def Scores_Quarter(stop_event: threading.Event) -> None:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка в Scores_Quarter: {e}", exc_info=True)
|
logger.error(f"Ошибка в Scores_Quarter: {e}", exc_info=True)
|
||||||
|
# --- однократный режим для старого матча ---
|
||||||
|
if RUN_ONCE:
|
||||||
|
logger.info("Scores_Quarter: однократный режим — выхожу из потока.")
|
||||||
|
return
|
||||||
stop_event.wait(TIMEOUT_ONLINE)
|
stop_event.wait(TIMEOUT_ONLINE)
|
||||||
|
|
||||||
|
|
||||||
@@ -2201,13 +2423,15 @@ def status_online_func(data: dict) -> dict | None:
|
|||||||
|
|
||||||
if json_live_status.get("status") != "Ok":
|
if json_live_status.get("status") != "Ok":
|
||||||
logger.warning(f"Live status API вернул не 'Ok': {json_live_status}")
|
logger.warning(f"Live status API вернул не 'Ok': {json_live_status}")
|
||||||
return None
|
return {
|
||||||
|
"foulsA": 0,
|
||||||
|
"foulsB": 0,
|
||||||
|
}
|
||||||
|
|
||||||
status_data = json_live_status["result"]
|
status_data = json_live_status["result"]
|
||||||
path_to_png = (
|
path_to_png = (
|
||||||
r"D:\ГРАФИКА\БАСКЕТБОЛ\ЕДИНАЯ ЛИГА ВТБ 2022-2023\Scorebug Indicators"
|
r"D:\ГРАФИКА\БАСКЕТБОЛ\ЕДИНАЯ ЛИГА ВТБ 2022-2023\Scorebug Indicators"
|
||||||
)
|
)
|
||||||
|
|
||||||
fouls_a = min(status_data.get("foulsA", 0), 5)
|
fouls_a = min(status_data.get("foulsA", 0), 5)
|
||||||
fouls_b = min(status_data.get("foulsB", 0), 5)
|
fouls_b = min(status_data.get("foulsB", 0), 5)
|
||||||
|
|
||||||
@@ -2226,8 +2450,9 @@ def Status_Online(data: dict, stop_event: threading.Event) -> None:
|
|||||||
Поток, обновляющий JSON-файл с онлайн-статусом матча.
|
Поток, обновляющий JSON-файл с онлайн-статусом матча.
|
||||||
"""
|
"""
|
||||||
logger.info("START making json for status online")
|
logger.info("START making json for status online")
|
||||||
global game_status_data
|
global game_status_data, RUN_ONCE
|
||||||
|
# «Снимок» режима на момент старта потока
|
||||||
|
single_run = RUN_ONCE
|
||||||
while not stop_event.is_set():
|
while not stop_event.is_set():
|
||||||
try:
|
try:
|
||||||
result = status_online_func(data)
|
result = status_online_func(data)
|
||||||
@@ -2240,7 +2465,10 @@ def Status_Online(data: dict, stop_event: threading.Event) -> None:
|
|||||||
logger.warning("status_online_func вернула None — пропуск записи.")
|
logger.warning("status_online_func вернула None — пропуск записи.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка в Status_Online: {e}", exc_info=True)
|
logger.error(f"Ошибка в Status_Online: {e}", exc_info=True)
|
||||||
|
# --- однократный режим для старого матча ---
|
||||||
|
if single_run:
|
||||||
|
logger.info("Status_Online: однократный режим — выхожу из потока.")
|
||||||
|
return
|
||||||
stop_event.wait(TIMEOUT_ONLINE)
|
stop_event.wait(TIMEOUT_ONLINE)
|
||||||
|
|
||||||
|
|
||||||
@@ -2294,7 +2522,8 @@ def Play_By_Play(data: dict, stop_event: threading.Event) -> None:
|
|||||||
json_live_status = get_json(url)
|
json_live_status = get_json(url)
|
||||||
last_event = plays[-1]
|
last_event = plays[-1]
|
||||||
|
|
||||||
if not json_live_status or json_live_status.get("message") == "Not Found":
|
# if not json_live_status or json_live_status.get("message") == "Not Found":
|
||||||
|
if not json_live_status or json_live_status.get("status") == "Not Found":
|
||||||
period = last_event.get("period", 1)
|
period = last_event.get("period", 1)
|
||||||
second = 0
|
second = 0
|
||||||
else:
|
else:
|
||||||
@@ -2416,7 +2645,9 @@ def Play_By_Play(data: dict, stop_event: threading.Event) -> None:
|
|||||||
|
|
||||||
df_goals.to_json(filepath, orient="records", force_ascii=False, indent=4)
|
df_goals.to_json(filepath, orient="records", force_ascii=False, indent=4)
|
||||||
logger.debug("Успешно положил данные об play-by-play в файл")
|
logger.debug("Успешно положил данные об play-by-play в файл")
|
||||||
|
if RUN_ONCE:
|
||||||
|
logger.info("<Play_By_Play>: однократный режим — выхожу из потока.")
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка в Play_By_Play: {e}", exc_info=True)
|
logger.error(f"Ошибка в Play_By_Play: {e}", exc_info=True)
|
||||||
|
|
||||||
@@ -2428,16 +2659,22 @@ def schedule_daily_restart():
|
|||||||
if not sys.platform.startswith("win"):
|
if not sys.platform.startswith("win"):
|
||||||
while True:
|
while True:
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
next_run = (now + timedelta(days=1)).replace(hour=0, minute=5, second=0, microsecond=0)
|
next_run = (now + timedelta(days=1)).replace(
|
||||||
|
hour=0, minute=5, second=0, microsecond=0
|
||||||
|
)
|
||||||
sleep_time = (next_run - now).total_seconds()
|
sleep_time = (next_run - now).total_seconds()
|
||||||
logger.info(f"Следующий перезапуск get_season_and_schedule запланирован на {next_run.strftime('%Y-%m-%d %H:%M')}")
|
logger.info(
|
||||||
|
f"Следующий перезапуск get_season_and_schedule запланирован на {next_run.strftime('%Y-%m-%d %H:%M')}"
|
||||||
|
)
|
||||||
time.sleep(sleep_time)
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info("⏰ Автоматический перезапуск get_season_and_schedule()")
|
logger.info("⏰ Автоматический перезапуск get_season_and_schedule()")
|
||||||
get_season_and_schedule()
|
get_season_and_schedule()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при автоматическом перезапуске: {e}", exc_info=True)
|
logger.error(
|
||||||
|
f"Ошибка при автоматическом перезапуске: {e}", exc_info=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def clean_np_ints(obj):
|
def clean_np_ints(obj):
|
||||||
@@ -2462,14 +2699,19 @@ def get_season_and_schedule() -> dict | None:
|
|||||||
"""
|
"""
|
||||||
global URL, LEAGUE, LANG, TEAM
|
global URL, LEAGUE, LANG, TEAM
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Получение активного сезона
|
# Получение активного сезона
|
||||||
season_url = f"{URL}api/abc/comps/seasons?Tag={LEAGUE}&Lang={LANG}"
|
season_url = f"{URL}api/abc/comps/seasons?Tag={LEAGUE}&Lang={LANG}"
|
||||||
season_data = get_json(season_url)
|
season_data = get_json(season_url)
|
||||||
|
|
||||||
season = (
|
season = (
|
||||||
season_data.get("result", [{}])[0].get("season") if season_data else None
|
season_data.get("result", [{}])[0].get("season")
|
||||||
|
if season_data and "vtb" in URL
|
||||||
|
else (
|
||||||
|
season_data.get("items", [{}])[0].get("season")
|
||||||
|
if season_data and "pro.russiabasket" in URL
|
||||||
|
else None
|
||||||
|
)
|
||||||
)
|
)
|
||||||
print(season)
|
print(season)
|
||||||
if not season:
|
if not season:
|
||||||
@@ -2509,7 +2751,8 @@ def get_season_and_schedule() -> dict | None:
|
|||||||
|
|
||||||
last_game = df_filtered.iloc[-1]
|
last_game = df_filtered.iloc[-1]
|
||||||
|
|
||||||
return clean_np_ints({
|
return clean_np_ints(
|
||||||
|
{
|
||||||
"season": season,
|
"season": season,
|
||||||
"game_id": last_game["game.id"],
|
"game_id": last_game["game.id"],
|
||||||
"team1_id": last_game["team1.teamId"],
|
"team1_id": last_game["team1.teamId"],
|
||||||
@@ -2518,7 +2761,8 @@ def get_season_and_schedule() -> dict | None:
|
|||||||
"team2": last_game["team2.name"],
|
"team2": last_game["team2.name"],
|
||||||
"when": last_game["game.DateStr"],
|
"when": last_game["game.DateStr"],
|
||||||
"time": last_game["game.localTime"],
|
"time": last_game["game.localTime"],
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при получении сезона или расписания: {e}", exc_info=True)
|
logger.error(f"Ошибка при получении сезона или расписания: {e}", exc_info=True)
|
||||||
@@ -2527,14 +2771,12 @@ def get_season_and_schedule() -> dict | None:
|
|||||||
|
|
||||||
def Standing_func(data: dict, stop_event: threading.Event) -> None:
|
def Standing_func(data: dict, stop_event: threading.Event) -> None:
|
||||||
logger.info("START making json for standings")
|
logger.info("START making json for standings")
|
||||||
global URL, LEAGUE, LANG
|
global URL, LEAGUE, LANG, RUN_ONCE
|
||||||
|
|
||||||
while not stop_event.is_set():
|
while not stop_event.is_set():
|
||||||
try:
|
try:
|
||||||
season = data["season"]
|
season = data["season"]
|
||||||
url = (
|
url = f"{URL}api/abc/comps/actual-standings?tag={LEAGUE}&season={season}&lang={LANG}"
|
||||||
f"{URL}api/abc/comps/actual-standings?tag={LEAGUE}&season={season}&lang={LANG}"
|
|
||||||
)
|
|
||||||
data_standings = get_json(url)
|
data_standings = get_json(url)
|
||||||
|
|
||||||
if data_standings and "items" in data_standings and data_standings["items"]:
|
if data_standings and "items" in data_standings and data_standings["items"]:
|
||||||
@@ -2599,7 +2841,9 @@ def Standing_func(data: dict, stop_event: threading.Event) -> None:
|
|||||||
indent=4,
|
indent=4,
|
||||||
)
|
)
|
||||||
logger.debug("Standings data saved successfully.")
|
logger.debug("Standings data saved successfully.")
|
||||||
|
if RUN_ONCE:
|
||||||
|
logger.info("<Standing_func>: однократный режим — выхожу из потока.")
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Ошибка в турнирном положении: {e}")
|
logger.warning(f"Ошибка в турнирном положении: {e}")
|
||||||
|
|
||||||
@@ -2765,19 +3009,24 @@ def pregame_data(data: dict) -> None:
|
|||||||
"points_23": round((data_team["goal23"] * 100 / data_team["shot23"]), 1),
|
"points_23": round((data_team["goal23"] * 100 / data_team["shot23"]), 1),
|
||||||
"points_1": round((data_team["goal1"] * 100 / data_team["shot1"]), 1),
|
"points_1": round((data_team["goal1"] * 100 / data_team["shot1"]), 1),
|
||||||
"assists": round((data_team["assist"] / data_team["games"]), 1),
|
"assists": round((data_team["assist"] / data_team["games"]), 1),
|
||||||
"rebounds": round(((data_team["defRebound"] + data_team["offRebound"]) / data_team["games"]), 1),
|
"rebounds": round(
|
||||||
|
(
|
||||||
|
(data_team["defRebound"] + data_team["offRebound"])
|
||||||
|
/ data_team["games"]
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
),
|
||||||
"steals": round((data_team["steal"] / data_team["games"]), 1),
|
"steals": round((data_team["steal"] / data_team["games"]), 1),
|
||||||
"turnovers": round((data_team["turnover"] / data_team["games"]), 1),
|
"turnovers": round((data_team["turnover"] / data_team["games"]), 1),
|
||||||
"blocks": round((data_team["blockShot"] / data_team["games"]), 1),
|
"blocks": round((data_team["blockShot"] / data_team["games"]), 1),
|
||||||
"fouls": round((data_team["foul"] / data_team["games"]), 1),
|
"fouls": round((data_team["foul"] / data_team["games"]), 1),
|
||||||
|
|
||||||
}
|
}
|
||||||
teams.append(temp_team)
|
teams.append(temp_team)
|
||||||
rewrite_file("team_comparison", teams)
|
rewrite_file("team_comparison", teams)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global TEAM, LANG, URL
|
global TEAM, LANG, URL, RUN_ONCE, CURRENT_GAME_ID
|
||||||
|
|
||||||
if TEAM is None:
|
if TEAM is None:
|
||||||
logger.critical(f"{myhost}\nКоманда не указана")
|
logger.critical(f"{myhost}\nКоманда не указана")
|
||||||
@@ -2796,8 +3045,8 @@ def main():
|
|||||||
URL = (
|
URL = (
|
||||||
"https://basket.sportoteka.org/"
|
"https://basket.sportoteka.org/"
|
||||||
if "uba" in LEAGUE.lower()
|
if "uba" in LEAGUE.lower()
|
||||||
# else "https://pro.russiabasket.org/"
|
else "https://pro.russiabasket.org/"
|
||||||
else "https://vtb-league.org/"
|
# else "https://vtb-league.org/"
|
||||||
)
|
)
|
||||||
|
|
||||||
stop_event = Event()
|
stop_event = Event()
|
||||||
@@ -2808,11 +3057,24 @@ def main():
|
|||||||
|
|
||||||
# === Ежедневный перезапуск функции на Linux/Mac ===
|
# === Ежедневный перезапуск функции на Linux/Mac ===
|
||||||
if not sys.platform.startswith("win"):
|
if not sys.platform.startswith("win"):
|
||||||
threading.Thread(target=schedule_daily_restart, daemon=True, name="ScheduleRestart").start()
|
threading.Thread(
|
||||||
|
target=schedule_daily_restart, daemon=True, name="ScheduleRestart"
|
||||||
|
).start()
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"{myhost}\n<b>{data['team1']}</b> VS {data['team2']}\n<i>{data['when']} {data['time']}</i>"
|
f"{myhost}\n<b>{data['team1']}</b> VS {data['team2']}\n<i>{data['when']} {data['time']}</i>"
|
||||||
)
|
)
|
||||||
|
CURRENT_GAME_ID = data["game_id"] # запомним глобально id матча
|
||||||
|
# если матч НЕ онлайн — включаем однократный прогон
|
||||||
|
# RUN_ONCE = is_game_online(CURRENT_GAME_ID)
|
||||||
|
when_str = data.get("when", "").strip()
|
||||||
|
game_dt = datetime.strptime(when_str, "%d.%m.%Y")
|
||||||
|
now_date = datetime.now().date()
|
||||||
|
RUN_ONCE = True if game_dt.date() < now_date else False
|
||||||
|
if RUN_ONCE:
|
||||||
|
logger.info("Матч не Online/уже завершён — запускаю потоки в режиме ОДНОГО прохода.")
|
||||||
|
else:
|
||||||
|
logger.info("Матч в Online — работаем в циклах обновления.")
|
||||||
# logger.debug(data)
|
# logger.debug(data)
|
||||||
# data = {
|
# data = {
|
||||||
# "season": 2026,
|
# "season": 2026,
|
||||||
@@ -2835,27 +3097,27 @@ def main():
|
|||||||
args=("team1", data, stop_event),
|
args=("team1", data, stop_event),
|
||||||
name="Team1JSON",
|
name="Team1JSON",
|
||||||
),
|
),
|
||||||
# threading.Thread(
|
threading.Thread(
|
||||||
# target=Json_Team_Generation,
|
target=Json_Team_Generation,
|
||||||
# args=("team2", data, stop_event),
|
args=("team2", data, stop_event),
|
||||||
# name="Team2JSON",
|
name="Team2JSON",
|
||||||
# ),
|
),
|
||||||
# threading.Thread(
|
threading.Thread(
|
||||||
# target=Team_Both_Stat, args=(stop_event,), name="BothTeamsStat"
|
target=Team_Both_Stat, args=(stop_event,), name="BothTeamsStat"
|
||||||
# ),
|
),
|
||||||
# threading.Thread(target=Referee, args=(stop_event,), name="Referee"),
|
threading.Thread(target=Referee, args=(stop_event,), name="Referee"),
|
||||||
# threading.Thread(
|
threading.Thread(
|
||||||
# target=Scores_Quarter, args=(stop_event,), name="QuarterScore"
|
target=Scores_Quarter, args=(stop_event,), name="QuarterScore"
|
||||||
# ),
|
),
|
||||||
# threading.Thread(
|
threading.Thread(
|
||||||
# target=Status_Online, args=(data, stop_event), name="StatusOnline"
|
target=Status_Online, args=(data, stop_event), name="StatusOnline"
|
||||||
# ),
|
),
|
||||||
# threading.Thread(
|
threading.Thread(
|
||||||
# target=Play_By_Play, args=(data, stop_event), name="PlayByPlay"
|
target=Play_By_Play, args=(data, stop_event), name="PlayByPlay"
|
||||||
# ),
|
),
|
||||||
# threading.Thread(
|
threading.Thread(
|
||||||
# target=Standing_func, args=(data, stop_event), name="Standings"
|
target=Standing_func, args=(data, stop_event), name="Standings"
|
||||||
# ),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Запуск всех потоков
|
# Запуск всех потоков
|
||||||
@@ -2863,12 +3125,33 @@ def main():
|
|||||||
t.start()
|
t.start()
|
||||||
logger.debug(f"Поток {t.name} запущен.")
|
logger.debug(f"Поток {t.name} запущен.")
|
||||||
|
|
||||||
# How_To_Play_Quarter(data)
|
How_To_Play_Quarter(data)
|
||||||
# pregame_data(data)
|
pregame_data(data)
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# while True:
|
||||||
|
# time.sleep(1)
|
||||||
|
# except KeyboardInterrupt:
|
||||||
|
# logger.info("Остановка по Ctrl+C... Завершение потоков.")
|
||||||
|
# stop_event.set()
|
||||||
|
# for t in threads:
|
||||||
|
# t.join()
|
||||||
|
# logger.debug(f"Поток {t.name} завершён.")
|
||||||
|
# logger.info("Все потоки завершены.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if RUN_ONCE:
|
||||||
|
# 🔚 Однократный режим: ждём завершения и выходим
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
logger.debug(f"Поток {t.name} завершён.")
|
||||||
|
logger.info("Однократный режим: всё готово, выхожу.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Онлайн-режим: держим процесс живым
|
||||||
while True:
|
while True:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.info("Остановка по Ctrl+C... Завершение потоков.")
|
logger.info("Остановка по Ctrl+C... Завершение потоков.")
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
|
|||||||
1040620
logs/GFX_YorickMori.log
1040620
logs/GFX_YorickMori.log
File diff suppressed because it is too large
Load Diff
2588952
logs/GFX_YorickMori_.log
2588952
logs/GFX_YorickMori_.log
File diff suppressed because it is too large
Load Diff
13
match_id.json
Normal file
13
match_id.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"10.0.85.2111": {"host": "", "tag": "vtb", "root": 1, "team": ""},
|
||||||
|
"10.10.35.21": {"host": "gfx", "tag": "vtb", "root": 1, "team": "Lokomotiv Kuban"},
|
||||||
|
"10.10.35.22": {"host": "krd", "tag": "vtb", "root": 1, "team": "Lokomotiv Kuban1"},
|
||||||
|
"10.10.35.23": {"host": "ekb", "tag": "vtb", "root": 1, "team": "uralmash1"},
|
||||||
|
"10.10.35.24": {"host": "per", "tag": "vtb", "root": 1, "team": "betcity parma1"},
|
||||||
|
"10.10.35.25": {"host": "sar", "tag": "vtb", "root": 1, "team": "avtodor1"},
|
||||||
|
"10.10.35.26": {"host": "spb", "tag": "vtb", "root": 1, "team": "zenit1"},
|
||||||
|
"10.10.35.27": {"host": "sam", "tag": "vtb", "root": 1, "team": "samara1"},
|
||||||
|
"10.10.35.28": {"host": "msk1", "tag": "vtb", "root": 1, "team": "mba-mai1"},
|
||||||
|
"10.10.35.29": {"host": "msk2", "tag": "vtb", "root": 1, "team": "Pari Nizhny Novgorod1"},
|
||||||
|
"10.10.35.30": {"host": "kaz", "tag": "vtb", "root": 1, "team": "unics1"}
|
||||||
|
}
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
Dim url As String = "https://ekb.tvstart.ru/app/static/ekb_team1.json"
|
|
||||||
Dim json As String = ""
|
|
||||||
|
|
||||||
' Чтение URL
|
|
||||||
Try
|
|
||||||
Dim wc As New System.Net.WebClient
|
|
||||||
wc.Encoding = System.Text.Encoding.UTF8
|
|
||||||
json = wc.DownloadString(url)
|
|
||||||
Catch ex As Exception
|
|
||||||
Console.WriteLine("Ошибка загрузки JSON: " & ex.Message)
|
|
||||||
Return
|
|
||||||
End Try
|
|
||||||
|
|
||||||
' --- Парсинг первых 12 num + NameGFX ---
|
|
||||||
Dim nums(11) As String
|
|
||||||
Dim names(11) As String
|
|
||||||
Dim count As Integer = 0
|
|
||||||
Dim pos As Integer = 0
|
|
||||||
|
|
||||||
While count < 12
|
|
||||||
' ищем "num"
|
|
||||||
Dim k As Integer = json.IndexOf("""num""", pos)
|
|
||||||
If k = -1 Then Exit While
|
|
||||||
|
|
||||||
Dim c As Integer = json.IndexOf(":", k)
|
|
||||||
If c = -1 Then Exit While
|
|
||||||
|
|
||||||
Dim j As Integer = c + 1
|
|
||||||
While j < json.Length AndAlso Char.IsWhiteSpace(json(j))
|
|
||||||
j += 1
|
|
||||||
End While
|
|
||||||
|
|
||||||
Dim numVal As String = ""
|
|
||||||
If j < json.Length AndAlso json(j) = """"c Then
|
|
||||||
j += 1
|
|
||||||
Dim startQ As Integer = j
|
|
||||||
While j < json.Length AndAlso json(j) <> """"c
|
|
||||||
j += 1
|
|
||||||
End While
|
|
||||||
numVal = json.Substring(startQ, j - startQ)
|
|
||||||
j += 1
|
|
||||||
Else
|
|
||||||
Dim startN As Integer = j
|
|
||||||
While j < json.Length AndAlso (Char.IsDigit(json(j)) OrElse json(j) = "-"c OrElse json(j) = "."c)
|
|
||||||
j += 1
|
|
||||||
End While
|
|
||||||
numVal = json.Substring(startN, j - startN)
|
|
||||||
End If
|
|
||||||
|
|
||||||
' ищем "NameGFX"
|
|
||||||
pos = j
|
|
||||||
Dim kn As Integer = json.IndexOf("""NameGFX""", pos)
|
|
||||||
If kn = -1 Then Exit While
|
|
||||||
|
|
||||||
Dim cn As Integer = json.IndexOf(":", kn)
|
|
||||||
If cn = -1 Then Exit While
|
|
||||||
|
|
||||||
Dim jn As Integer = cn + 1
|
|
||||||
While jn < json.Length AndAlso Char.IsWhiteSpace(json(jn))
|
|
||||||
jn += 1
|
|
||||||
End While
|
|
||||||
|
|
||||||
Dim nameVal As String = ""
|
|
||||||
If jn < json.Length AndAlso json(jn) = """"c Then
|
|
||||||
jn += 1
|
|
||||||
Dim startGN As Integer = jn
|
|
||||||
While jn < json.Length AndAlso json(jn) <> """"c
|
|
||||||
jn += 1
|
|
||||||
End While
|
|
||||||
nameVal = json.Substring(startGN, jn - startGN)
|
|
||||||
jn += 1
|
|
||||||
End If
|
|
||||||
|
|
||||||
nums(count) = numVal
|
|
||||||
names(count) = nameVal
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
pos = jn
|
|
||||||
End While
|
|
||||||
|
|
||||||
' --- Выводим результат ---
|
|
||||||
Console.WriteLine("=== Первые " & count.ToString() & " игроков ===")
|
|
||||||
For i As Integer = 0 To count - 1
|
|
||||||
Console.WriteLine(nums(i) & "_" & names(i))
|
|
||||||
Next
|
|
||||||
|
|
||||||
' --- Выводим результат ---
|
|
||||||
Console.WriteLine("=== Первые " & count.ToString() & " игроков ===")
|
|
||||||
For i As Integer = 0 To count - 1
|
|
||||||
Console.WriteLine(nums(i) & "_" & names(i))
|
|
||||||
|
|
||||||
' === Отправляем в титр TeamRoster.gtzip ===
|
|
||||||
' Номер
|
|
||||||
API.Function("SetText", Input:="TeamRoster.gtzip", SelectedName:="PlayerNamber" & (i + 1).ToString() & ".Text", Value:=nums(i))
|
|
||||||
' Имя
|
|
||||||
API.Function("SetText", Input:="TeamRoster.gtzip", SelectedName:="PlayerName" & (i + 1).ToString() & ".Text", Value:=names(i))
|
|
||||||
Next
|
|
||||||
15439
static/game_online.json
15439
static/game_online.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,128 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "Crew chief",
|
|
||||||
"lastNameGFX": "Aleksey Davydov",
|
|
||||||
"secondName": "Mikhailovich",
|
|
||||||
"birthday": "1984-10-05T00:00:00",
|
|
||||||
"age": 41,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "Referee 1",
|
|
||||||
"lastNameGFX": "Sergey Mikhailov",
|
|
||||||
"secondName": "Alexandrovich",
|
|
||||||
"birthday": "1977-05-20T00:00:00",
|
|
||||||
"age": 48,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "Referee 2",
|
|
||||||
"lastNameGFX": "Maksim Zhitlukhin",
|
|
||||||
"secondName": "Sergeevich",
|
|
||||||
"birthday": "1986-12-19T00:00:00",
|
|
||||||
"age": 38,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "Commissioner",
|
|
||||||
"lastNameGFX": "Igor Lebedev",
|
|
||||||
"secondName": "Anatolevich",
|
|
||||||
"birthday": "1965-05-27T00:00:00",
|
|
||||||
"age": 60,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "Scorer",
|
|
||||||
"lastNameGFX": "Viktoriya Isaeva",
|
|
||||||
"secondName": "Dmitrievna",
|
|
||||||
"birthday": "1996-01-09T00:00:00",
|
|
||||||
"age": 29,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "Assistant Scorer",
|
|
||||||
"lastNameGFX": "Dmitriy Kibenko",
|
|
||||||
"secondName": "Andreevich",
|
|
||||||
"birthday": "1983-12-25T00:00:00",
|
|
||||||
"age": 41,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "Timekeeper",
|
|
||||||
"lastNameGFX": "Olga Prosneva",
|
|
||||||
"secondName": "Nikolaevna",
|
|
||||||
"birthday": "1971-06-15T00:00:00",
|
|
||||||
"age": 54,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "Operator 24 sec",
|
|
||||||
"lastNameGFX": "Aleksey Nagibin",
|
|
||||||
"secondName": "Vitalevich",
|
|
||||||
"birthday": "1982-08-21T00:00:00",
|
|
||||||
"age": 43,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "Dictor",
|
|
||||||
"lastNameGFX": "Адель Халимов",
|
|
||||||
"secondName": "Рашидович",
|
|
||||||
"birthday": "1996-07-31T00:00:00",
|
|
||||||
"age": 29,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "Statistic",
|
|
||||||
"lastNameGFX": "Veronika Shuvagina",
|
|
||||||
"secondName": "Vladimirovna",
|
|
||||||
"birthday": "1968-05-08T00:00:00",
|
|
||||||
"age": 57,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "IS Operator",
|
|
||||||
"lastNameGFX": "Rashid Khabibullin",
|
|
||||||
"secondName": "Rinatovich",
|
|
||||||
"birthday": "1987-07-26T00:00:00",
|
|
||||||
"age": 38,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "Statistic",
|
|
||||||
"lastNameGFX": "Mariya Shuvagina",
|
|
||||||
"secondName": "Dmitrievna",
|
|
||||||
"birthday": "2002-08-04T00:00:00",
|
|
||||||
"age": 23,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "Video reviewer",
|
|
||||||
"lastNameGFX": "Kamil Habibullin",
|
|
||||||
"secondName": "Ildarovich",
|
|
||||||
"birthday": "1975-12-08T00:00:00",
|
|
||||||
"age": 49,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/ru.svg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"displayNumber": "",
|
|
||||||
"positionName": "-",
|
|
||||||
"lastNameGFX": "None None",
|
|
||||||
"secondName": null,
|
|
||||||
"birthday": null,
|
|
||||||
"age": null,
|
|
||||||
"flag": "https://flagicons.lipis.dev/flags/4x3/.svg"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
22008
static/schedule.json
22008
static/schedule.json
File diff suppressed because it is too large
Load Diff
@@ -1,42 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"Q": "Q1",
|
|
||||||
"score1": "33",
|
|
||||||
"score2": "20"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Q": "Q2",
|
|
||||||
"score1": "26",
|
|
||||||
"score2": "21"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Q": "Q3",
|
|
||||||
"score1": "26",
|
|
||||||
"score2": "20"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Q": "Q4",
|
|
||||||
"score1": "18",
|
|
||||||
"score2": "22"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Q": "OT1",
|
|
||||||
"score1": "",
|
|
||||||
"score2": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Q": "OT2",
|
|
||||||
"score1": "",
|
|
||||||
"score2": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Q": "OT3",
|
|
||||||
"score1": "",
|
|
||||||
"score2": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Q": "OT4",
|
|
||||||
"score1": "",
|
|
||||||
"score2": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"team": "UNICS",
|
|
||||||
"winQ1": 0,
|
|
||||||
"loseQ1": 0,
|
|
||||||
"drawQ1": 0,
|
|
||||||
"scoreQ1": 0,
|
|
||||||
"score_avgQ1": null,
|
|
||||||
"winQ2": 0,
|
|
||||||
"loseQ2": 0,
|
|
||||||
"drawQ2": 0,
|
|
||||||
"scoreQ2": 0,
|
|
||||||
"score_avgQ2": null,
|
|
||||||
"winQ3": 0,
|
|
||||||
"loseQ3": 0,
|
|
||||||
"drawQ3": 0,
|
|
||||||
"scoreQ3": 0,
|
|
||||||
"score_avgQ3": null,
|
|
||||||
"winQ4": 0,
|
|
||||||
"loseQ4": 0,
|
|
||||||
"drawQ4": 0,
|
|
||||||
"scoreQ4": 0,
|
|
||||||
"score_avgQ4": null,
|
|
||||||
"winOT1": 0,
|
|
||||||
"loseOT1": 0,
|
|
||||||
"drawOT1": 0,
|
|
||||||
"scoreOT1": 0,
|
|
||||||
"score_avgOT1": null,
|
|
||||||
"winOT2": 0,
|
|
||||||
"loseOT2": 0,
|
|
||||||
"drawOT2": 0,
|
|
||||||
"scoreOT2": 0,
|
|
||||||
"score_avgOT2": null,
|
|
||||||
"winOT3": 0,
|
|
||||||
"loseOT3": 0,
|
|
||||||
"drawOT3": 0,
|
|
||||||
"scoreOT3": 0,
|
|
||||||
"score_avgOT3": null,
|
|
||||||
"winOT4": 0,
|
|
||||||
"loseOT4": 0,
|
|
||||||
"drawOT4": 0,
|
|
||||||
"scoreOT4": 0,
|
|
||||||
"score_avgOT4": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"team": "BETCITY PARMA",
|
|
||||||
"winQ1": 0,
|
|
||||||
"loseQ1": 0,
|
|
||||||
"drawQ1": 0,
|
|
||||||
"scoreQ1": 0,
|
|
||||||
"score_avgQ1": null,
|
|
||||||
"winQ2": 0,
|
|
||||||
"loseQ2": 0,
|
|
||||||
"drawQ2": 0,
|
|
||||||
"scoreQ2": 0,
|
|
||||||
"score_avgQ2": null,
|
|
||||||
"winQ3": 0,
|
|
||||||
"loseQ3": 0,
|
|
||||||
"drawQ3": 0,
|
|
||||||
"scoreQ3": 0,
|
|
||||||
"score_avgQ3": null,
|
|
||||||
"winQ4": 0,
|
|
||||||
"loseQ4": 0,
|
|
||||||
"drawQ4": 0,
|
|
||||||
"scoreQ4": 0,
|
|
||||||
"score_avgQ4": null,
|
|
||||||
"winOT1": 0,
|
|
||||||
"loseOT1": 0,
|
|
||||||
"drawOT1": 0,
|
|
||||||
"scoreOT1": 0,
|
|
||||||
"score_avgOT1": null,
|
|
||||||
"winOT2": 0,
|
|
||||||
"loseOT2": 0,
|
|
||||||
"drawOT2": 0,
|
|
||||||
"scoreOT2": 0,
|
|
||||||
"score_avgOT2": null,
|
|
||||||
"winOT3": 0,
|
|
||||||
"loseOT3": 0,
|
|
||||||
"drawOT3": 0,
|
|
||||||
"scoreOT3": 0,
|
|
||||||
"score_avgOT3": null,
|
|
||||||
"winOT4": 0,
|
|
||||||
"loseOT4": 0,
|
|
||||||
"drawOT4": 0,
|
|
||||||
"scoreOT4": 0,
|
|
||||||
"score_avgOT4": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[]
|
|
||||||
1858
static/team1.json
1858
static/team1.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1742
static/team2.json
1742
static/team2.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,296 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"name": "points",
|
|
||||||
"nameGFX_rus": "Очки",
|
|
||||||
"nameGFX_eng": "points",
|
|
||||||
"val1": "103",
|
|
||||||
"val2": "83"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "goal2",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "28",
|
|
||||||
"val2": "25"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "shot2",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "42",
|
|
||||||
"val2": "43"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "goal3",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "9",
|
|
||||||
"val2": "7"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "shot3",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "24",
|
|
||||||
"val2": "17"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "goal1",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "20",
|
|
||||||
"val2": "12"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "shot1",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "21",
|
|
||||||
"val2": "16"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "assist",
|
|
||||||
"nameGFX_rus": "Передачи",
|
|
||||||
"nameGFX_eng": "assists",
|
|
||||||
"val1": "29",
|
|
||||||
"val2": "24"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pass",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "32",
|
|
||||||
"val2": "24"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "steal",
|
|
||||||
"nameGFX_rus": "Перехваты",
|
|
||||||
"nameGFX_eng": "steals",
|
|
||||||
"val1": "10",
|
|
||||||
"val2": "9"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "block",
|
|
||||||
"nameGFX_rus": "Блокшоты",
|
|
||||||
"nameGFX_eng": "blocks",
|
|
||||||
"val1": "3",
|
|
||||||
"val2": "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "blocked",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "0",
|
|
||||||
"val2": "3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "defReb",
|
|
||||||
"nameGFX_rus": "подборы в защите",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "19",
|
|
||||||
"val2": "15"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "offReb",
|
|
||||||
"nameGFX_rus": "подборы в нападении",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "14",
|
|
||||||
"val2": "8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "foulsOn",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "24",
|
|
||||||
"val2": "23"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "turnover",
|
|
||||||
"nameGFX_rus": "Потери",
|
|
||||||
"nameGFX_eng": "turnovers",
|
|
||||||
"val1": "16",
|
|
||||||
"val2": "19"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "foul",
|
|
||||||
"nameGFX_rus": "Фолы",
|
|
||||||
"nameGFX_eng": "fouls",
|
|
||||||
"val1": "23",
|
|
||||||
"val2": "24"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "foulT",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "0",
|
|
||||||
"val2": "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "foulD",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "0",
|
|
||||||
"val2": "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "foulC",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "0",
|
|
||||||
"val2": "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "foulB",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "",
|
|
||||||
"val1": "0",
|
|
||||||
"val2": "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "second",
|
|
||||||
"nameGFX_rus": "секунды",
|
|
||||||
"nameGFX_eng": "seconds",
|
|
||||||
"val1": "12000",
|
|
||||||
"val2": "12000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dunk",
|
|
||||||
"nameGFX_rus": "данки",
|
|
||||||
"nameGFX_eng": "dunks",
|
|
||||||
"val1": "7",
|
|
||||||
"val2": "4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fastBreak",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "fast breaks",
|
|
||||||
"val1": "6",
|
|
||||||
"val2": "4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "plusMinus",
|
|
||||||
"nameGFX_rus": "+/-",
|
|
||||||
"nameGFX_eng": "+/-",
|
|
||||||
"val1": "100",
|
|
||||||
"val2": "-100"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pt-1",
|
|
||||||
"nameGFX_rus": "Штрафные",
|
|
||||||
"nameGFX_eng": "free throws",
|
|
||||||
"val1": "20/21",
|
|
||||||
"val2": "12/16"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pt-2",
|
|
||||||
"nameGFX_rus": "2-очковые",
|
|
||||||
"nameGFX_eng": "2-points",
|
|
||||||
"val1": "28/42",
|
|
||||||
"val2": "25/43"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pt-3",
|
|
||||||
"nameGFX_rus": "3-очковые",
|
|
||||||
"nameGFX_eng": "3-points",
|
|
||||||
"val1": "9/24",
|
|
||||||
"val2": "7/17"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fg",
|
|
||||||
"nameGFX_rus": "очки с игры",
|
|
||||||
"nameGFX_eng": "field goals",
|
|
||||||
"val1": "37/66",
|
|
||||||
"val2": "32/60"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pt-1_pro",
|
|
||||||
"nameGFX_rus": "штрафные, процент",
|
|
||||||
"nameGFX_eng": "free throws pro",
|
|
||||||
"val1": "95%",
|
|
||||||
"val2": "75%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pt-2_pro",
|
|
||||||
"nameGFX_rus": "2-очковые, процент",
|
|
||||||
"nameGFX_eng": "2-points pro",
|
|
||||||
"val1": "67%",
|
|
||||||
"val2": "58%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pt-3_pro",
|
|
||||||
"nameGFX_rus": "3-очковые, процент",
|
|
||||||
"nameGFX_eng": "3-points pro",
|
|
||||||
"val1": "38%",
|
|
||||||
"val2": "41%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fg_pro",
|
|
||||||
"nameGFX_rus": "Очки с игры, процент",
|
|
||||||
"nameGFX_eng": "field goals pro",
|
|
||||||
"val1": "56%",
|
|
||||||
"val2": "53%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Reb",
|
|
||||||
"nameGFX_rus": "Подборы",
|
|
||||||
"nameGFX_eng": "rebounds",
|
|
||||||
"val1": "33",
|
|
||||||
"val2": "23"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "avgAge",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "avg Age",
|
|
||||||
"val1": "26.8",
|
|
||||||
"val2": "25.4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ptsStart",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "Start PTS",
|
|
||||||
"val1": "74",
|
|
||||||
"val2": "51"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ptsStart_pro",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "Start PTS, %",
|
|
||||||
"val1": "72%",
|
|
||||||
"val2": "61%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ptsBench",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "Bench PTS",
|
|
||||||
"val1": "29",
|
|
||||||
"val2": "32"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ptsBench_pro",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "Bench PTS, %",
|
|
||||||
"val1": "28%",
|
|
||||||
"val2": "39%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "avgHeight",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "avg height",
|
|
||||||
"val1": "198.8 cm",
|
|
||||||
"val2": "201.1 cm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "timeout_left",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "timeout left",
|
|
||||||
"val1": "2",
|
|
||||||
"val2": "1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "timeout_str",
|
|
||||||
"nameGFX_rus": "",
|
|
||||||
"nameGFX_eng": "timeout str",
|
|
||||||
"val1": "2 Time-outs left in 2nd half",
|
|
||||||
"val2": "1 Time-out left in 2nd half"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
1394
static/topTeam1.json
1394
static/topTeam1.json
File diff suppressed because it is too large
Load Diff
1394
static/topTeam2.json
1394
static/topTeam2.json
File diff suppressed because it is too large
Load Diff
16
stats.py
16
stats.py
@@ -1,16 +0,0 @@
|
|||||||
import requests
|
|
||||||
import json
|
|
||||||
|
|
||||||
url = "https://org.infobasket.su/Widget/GetOnline/921412?format=json&lang=ru"
|
|
||||||
|
|
||||||
response = requests.get(url)
|
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json()
|
|
||||||
|
|
||||||
# print(data)
|
|
||||||
|
|
||||||
team1 = data["GameTeams"][0]
|
|
||||||
team2 = data["GameTeams"][1]
|
|
||||||
|
|
||||||
print(team1)
|
|
||||||
|
|
||||||
314
visual.py
314
visual.py
@@ -181,7 +181,31 @@ def process_player_data(team_json, player_index):
|
|||||||
"time": str(player_data["CareerTPlayedTime"]),
|
"time": str(player_data["CareerTPlayedTime"]),
|
||||||
}
|
}
|
||||||
|
|
||||||
return [season_total, season_avg, career_total], player_data
|
career_avg = {
|
||||||
|
"name": "Career Average",
|
||||||
|
"game_count": "",
|
||||||
|
"start_count": "",
|
||||||
|
"pts": str(player_data["AvgCarPoints"]),
|
||||||
|
"pt-2": str(player_data["CareerTShot2Percent"]),
|
||||||
|
"pt-3": str(player_data["CareerTShot3Percent"]),
|
||||||
|
"pt-1": str(player_data["CareerTShot1Percent"]),
|
||||||
|
"fg": str(player_data["CareerTShot23Percent"]),
|
||||||
|
"ast": str(player_data["AvgCarAssist"]),
|
||||||
|
"stl": str(player_data["AvgCarSteal"]),
|
||||||
|
"blk": str(player_data["AvgCarBlocks"]),
|
||||||
|
"dreb": str(player_data["AvgCarDefRebound"]),
|
||||||
|
"oreb": str(player_data["AvgCarOffRebound"]),
|
||||||
|
"reb": str(player_data["AvgCarRebound"]),
|
||||||
|
# "to": str(player_data["AvgTurnover"]),
|
||||||
|
# "foul": str(player_data["AvgFoul"]),
|
||||||
|
"fouled": str(player_data["AvgCarOpponentFoul"]),
|
||||||
|
"dunk": str(player_data["AvgCarDunk"]),
|
||||||
|
"time": str(player_data["AvgCarPlayedTime"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return [season_total, season_avg, career_total, career_avg], player_data
|
||||||
|
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
@@ -190,24 +214,24 @@ config = {
|
|||||||
"num": st.column_config.TextColumn("#", width=27),
|
"num": st.column_config.TextColumn("#", width=27),
|
||||||
"NameGFX": st.column_config.TextColumn(width=170),
|
"NameGFX": st.column_config.TextColumn(width=170),
|
||||||
"isOn": st.column_config.TextColumn("🏀", width=27),
|
"isOn": st.column_config.TextColumn("🏀", width=27),
|
||||||
"pts": st.column_config.NumberColumn("PTS", width=27),
|
"pts": st.column_config.TextColumn("PTS", width="content", help="⭐ = Career High"),
|
||||||
"pt-2": st.column_config.TextColumn("2-PT", width=45),
|
"pt-2": st.column_config.TextColumn("2-PT", width="content"),
|
||||||
"pt-3": st.column_config.TextColumn("3-PT", width=45),
|
"pt-3": st.column_config.TextColumn("3-PT", width="content"),
|
||||||
"pt-1": st.column_config.TextColumn("FT", width=45),
|
"pt-1": st.column_config.TextColumn("FT", width="content"),
|
||||||
"fg": st.column_config.TextColumn("FG", width=45),
|
"fg": st.column_config.TextColumn("FG", width="content"),
|
||||||
"ast": st.column_config.NumberColumn("AS", width=27),
|
"ast": st.column_config.TextColumn("AS", width="content"),
|
||||||
"stl": st.column_config.NumberColumn("ST", width=27),
|
"stl": st.column_config.TextColumn("ST", width="content"),
|
||||||
"blk": st.column_config.NumberColumn("BL", width=27),
|
"blk": st.column_config.TextColumn("BL", width="content"),
|
||||||
"blkVic": st.column_config.NumberColumn("BV", width=27),
|
"blkVic": st.column_config.TextColumn("BV", width="content"),
|
||||||
"dreb": st.column_config.NumberColumn("DR", width=27),
|
"dreb": st.column_config.TextColumn("DR", width="content"),
|
||||||
"oreb": st.column_config.NumberColumn("OR", width=27),
|
"oreb": st.column_config.TextColumn("OR", width="content"),
|
||||||
"reb": st.column_config.NumberColumn("R", width=27),
|
"reb": st.column_config.TextColumn("R", width="content"),
|
||||||
"to": st.column_config.NumberColumn("TO", width=27),
|
"to": st.column_config.TextColumn("TO", width="content"),
|
||||||
"foul": st.column_config.NumberColumn("F", width=27),
|
"foul": st.column_config.TextColumn("F", width="content"),
|
||||||
"fouled": st.column_config.NumberColumn("Fed", width=27),
|
"fouled": st.column_config.TextColumn("Fed", width="content"),
|
||||||
"plusMinus": st.column_config.NumberColumn("+/-", width=27),
|
"plusMinus": st.column_config.TextColumn("+/-", width="content"),
|
||||||
"dunk": st.column_config.NumberColumn("DUNK", width=27),
|
"dunk": st.column_config.TextColumn("DUNK", width="content"),
|
||||||
"kpi": st.column_config.NumberColumn("KPI", width=27),
|
"kpi": st.column_config.TextColumn("KPI", width="content"),
|
||||||
"time": st.column_config.TextColumn("TIME"),
|
"time": st.column_config.TextColumn("TIME"),
|
||||||
"game_count": st.column_config.TextColumn("G", width=27),
|
"game_count": st.column_config.TextColumn("G", width=27),
|
||||||
"start_count": st.column_config.TextColumn("S", width=27),
|
"start_count": st.column_config.TextColumn("S", width=27),
|
||||||
@@ -813,19 +837,127 @@ columns_game = [
|
|||||||
if cached_team1 and cached_team2:
|
if cached_team1 and cached_team2:
|
||||||
team1_data = process_team_data(cached_team1, columns_game)
|
team1_data = process_team_data(cached_team1, columns_game)
|
||||||
team2_data = process_team_data(cached_team2, columns_game)
|
team2_data = process_team_data(cached_team2, columns_game)
|
||||||
|
# Добавляем звездочку, если pts > PTS_Career_High
|
||||||
|
def _get_first_number(x):
|
||||||
|
"""Безопасно вытащить число из строки/значения (например '12 (60%)' -> 12)."""
|
||||||
|
try:
|
||||||
|
if x is None:
|
||||||
|
return None
|
||||||
|
s = str(x)
|
||||||
|
# заберём ведущие число/знак (поддержим +/-)
|
||||||
|
import re
|
||||||
|
m = re.search(r"[-+]?\d+(\.\d+)?", s)
|
||||||
|
return float(m.group(0)) if m else None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
CAREER_HIGH_KEYS = {
|
||||||
|
"pts": ["PTS_Career_High", "CareerHighPoints", "career_high_pts"],
|
||||||
|
"ast": ["AST_Career_High", "CareerHighAssist", "career_high_ast"],
|
||||||
|
"stl": ["STL_Career_High", "CareerHighSteal", "career_high_stl"],
|
||||||
|
"blk": ["BLK_Career_High", "CareerHighBlocks", "career_high_blk"],
|
||||||
|
"reb": ["REB_Career_High", "CareerHighRebound", "career_high_reb"],
|
||||||
|
# если нужно — добавь ещё пары "df_column": ["possible_key1","possible_key2"...]
|
||||||
|
}
|
||||||
|
def _build_career_high_map(cached_team_list):
|
||||||
|
"""Вернёт словарь: player_id -> {stat_key: value} для всех доступных максимумов."""
|
||||||
|
out = {}
|
||||||
|
if not isinstance(cached_team_list, list):
|
||||||
|
return out
|
||||||
|
for p in cached_team_list:
|
||||||
|
if not isinstance(p, dict):
|
||||||
|
continue
|
||||||
|
pid = p.get("id")
|
||||||
|
if pid is None:
|
||||||
|
continue
|
||||||
|
out[pid] = {}
|
||||||
|
for stat_col, aliases in CAREER_HIGH_KEYS.items():
|
||||||
|
for k in aliases:
|
||||||
|
if k in p and p[k] not in (None, ""):
|
||||||
|
out[pid][stat_col] = _get_first_number(p[k])
|
||||||
|
break
|
||||||
|
return out
|
||||||
|
|
||||||
|
def _ensure_id_column(df, cached_team_list):
|
||||||
|
"""Присвоить игрокам id в том же порядке, что и в списке cached_team."""
|
||||||
|
try:
|
||||||
|
ids = [p.get("id") if isinstance(p, dict) else None for p in cached_team_list][:len(df)]
|
||||||
|
if "id" not in df.columns:
|
||||||
|
df["id"] = ids
|
||||||
|
else:
|
||||||
|
# не затираем, только заполняем пустые
|
||||||
|
df["id"] = df["id"].fillna(pd.Series(ids, index=df.index))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _mark_star_for_columns(df, cached_team_list, columns):
|
||||||
|
"""
|
||||||
|
Для каждого col в columns: если текущее значение > career high — добавляем ' ⭐️'.
|
||||||
|
Преобразуем колонки в текст (для отображения эмодзи).
|
||||||
|
"""
|
||||||
|
_ensure_id_column(df, cached_team_list)
|
||||||
|
ch_map = _build_career_high_map(cached_team_list)
|
||||||
|
|
||||||
|
def format_with_star(val, career_max):
|
||||||
|
v = _get_first_number(val)
|
||||||
|
cm = _get_first_number(career_max)
|
||||||
|
if v is not None and cm is not None and v >= cm and val > 0:
|
||||||
|
# сохраняем исходное текстовое представление + ⭐️
|
||||||
|
return f"{val} ⭐️"
|
||||||
|
return f"{val}" if val is not None else ""
|
||||||
|
|
||||||
|
for col in columns:
|
||||||
|
if col not in df.columns:
|
||||||
|
continue
|
||||||
|
new_vals = []
|
||||||
|
for idx, row in df.iterrows():
|
||||||
|
pid = row.get("id")
|
||||||
|
career_max = (ch_map.get(pid, {}) or {}).get(col)
|
||||||
|
new_vals.append(format_with_star(row[col], career_max))
|
||||||
|
df[col] = new_vals # теперь это текст для отображения
|
||||||
|
|
||||||
|
STAR_COLUMNS = [
|
||||||
|
"pts", "ast", "stl", "blk", "reb",
|
||||||
|
]
|
||||||
|
team1_data["_pts_num"] = pd.to_numeric(team1_data["pts"], errors="coerce")
|
||||||
|
team1_data["_kpi_num"] = pd.to_numeric(team1_data["kpi"], errors="coerce")
|
||||||
|
team2_data["_pts_num"] = pd.to_numeric(team2_data["pts"], errors="coerce")
|
||||||
|
team2_data["_kpi_num"] = pd.to_numeric(team2_data["kpi"], errors="coerce")
|
||||||
|
def highlight_max_by_refcol(df, view_col, ref_col):
|
||||||
|
ref = pd.to_numeric(df[ref_col], errors="coerce")
|
||||||
|
mx = ref.max()
|
||||||
|
return [("background-color: green" if (pd.notna(v) and v == mx and v > 0) else "")
|
||||||
|
for v in ref]
|
||||||
|
|
||||||
|
_mark_star_for_columns(team1_data, cached_team1, STAR_COLUMNS)
|
||||||
|
_mark_star_for_columns(team2_data, cached_team2, STAR_COLUMNS)
|
||||||
|
|
||||||
# Стилизация данных
|
# Стилизация данных
|
||||||
|
# team1_styled = (
|
||||||
|
# team1_data.style.apply(highlight_grey, axis=1)
|
||||||
|
# .apply(highlight_foul, subset="foul")
|
||||||
|
# .apply(highlight_max, subset="pts")
|
||||||
|
# .apply(highlight_max, subset="kpi")
|
||||||
|
# )
|
||||||
|
# team2_styled = (
|
||||||
|
# team2_data.style.apply(highlight_grey, axis=1)
|
||||||
|
# .apply(highlight_foul, subset="foul")
|
||||||
|
# .apply(highlight_max, subset="pts")
|
||||||
|
# .apply(highlight_max, subset="kpi")
|
||||||
|
# )
|
||||||
team1_styled = (
|
team1_styled = (
|
||||||
team1_data.style.apply(highlight_grey, axis=1)
|
team1_data[columns_game].style
|
||||||
|
.apply(highlight_grey, axis=1)
|
||||||
.apply(highlight_foul, subset="foul")
|
.apply(highlight_foul, subset="foul")
|
||||||
.apply(highlight_max, subset="pts")
|
.apply(lambda _: highlight_max_by_refcol(team1_data, "pts", "_pts_num"), axis=0, subset=["pts"])
|
||||||
.apply(highlight_max, subset="kpi")
|
.apply(lambda _: highlight_max_by_refcol(team1_data, "kpi", "_kpi_num"), axis=0, subset=["kpi"])
|
||||||
)
|
)
|
||||||
team2_styled = (
|
team2_styled = (
|
||||||
team2_data.style.apply(highlight_grey, axis=1)
|
team2_data[columns_game].style
|
||||||
|
.apply(highlight_grey, axis=1)
|
||||||
.apply(highlight_foul, subset="foul")
|
.apply(highlight_foul, subset="foul")
|
||||||
.apply(highlight_max, subset="pts")
|
.apply(lambda _: highlight_max_by_refcol(team2_data, "pts", "_pts_num"), axis=0, subset=["pts"])
|
||||||
.apply(highlight_max, subset="kpi")
|
.apply(lambda _: highlight_max_by_refcol(team2_data, "kpi", "_kpi_num"), axis=0, subset=["kpi"])
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_player_all_game(player_data_1):
|
def get_player_all_game(player_data_1):
|
||||||
@@ -863,29 +995,28 @@ if cached_team1 and cached_team2:
|
|||||||
|
|
||||||
# Сортировка от последнего матча к первому
|
# Сортировка от последнего матча к первому
|
||||||
df_filtered = df_filtered.sort_values(by="game.gameDate", ascending=False)
|
df_filtered = df_filtered.sort_values(by="game.gameDate", ascending=False)
|
||||||
|
|
||||||
# Указать нужные колонки для вывода
|
# Указать нужные колонки для вывода
|
||||||
columns_to_show = [
|
columns_to_show = [
|
||||||
"season",
|
"Сезон",
|
||||||
"game.gameDate",
|
"Дата",
|
||||||
"game.team1Name",
|
"Команда 1",
|
||||||
"game.team2Name",
|
"Команда 2",
|
||||||
"game.score",
|
"Счёт",
|
||||||
"stats.points",
|
"PTS",
|
||||||
"stats.shot2Percent",
|
"2-PTS%",
|
||||||
"stats.shot3Percent",
|
"3-PTS%",
|
||||||
"stats.shot23Percent",
|
"FG%",
|
||||||
"stats.shot1Percent",
|
"FT%",
|
||||||
"stats.assist",
|
"AST",
|
||||||
"stats.steal",
|
"STL",
|
||||||
"stats.blockShot",
|
"BLK",
|
||||||
"stats.defRebound",
|
"DR",
|
||||||
"stats.offRebound",
|
"OR",
|
||||||
"stats.rebound",
|
"REB",
|
||||||
"stats.turnover",
|
"TO",
|
||||||
"stats.foul",
|
"F",
|
||||||
"stats.playedTime",
|
"TIME",
|
||||||
"stats.plusMinus",
|
"+/-",
|
||||||
]
|
]
|
||||||
numeric_cols = [
|
numeric_cols = [
|
||||||
"stats.points",
|
"stats.points",
|
||||||
@@ -897,24 +1028,63 @@ if cached_team1 and cached_team2:
|
|||||||
# df_filtered[numeric_cols] = df_filtered[numeric_cols].apply(
|
# df_filtered[numeric_cols] = df_filtered[numeric_cols].apply(
|
||||||
# pd.to_numeric, errors="coerce"
|
# pd.to_numeric, errors="coerce"
|
||||||
# )
|
# )
|
||||||
df_filtered[numeric_cols] = df_filtered[numeric_cols].apply(pd.to_numeric, errors="coerce")
|
|
||||||
|
# 🟢 Переименовываем колонки для отображения в Streamlit
|
||||||
|
rename_map = {
|
||||||
|
"season": "Сезон",
|
||||||
|
"game.gameDate": "Дата",
|
||||||
|
"game.team1Name": "Команда 1",
|
||||||
|
"game.team2Name": "Команда 2",
|
||||||
|
"game.score": "Счёт",
|
||||||
|
"stats.points": "PTS",
|
||||||
|
"stats.shot2Percent": "2-PTS%",
|
||||||
|
"stats.shot3Percent": "3-PTS%",
|
||||||
|
"stats.shot23Percent": "FG%",
|
||||||
|
"stats.shot1Percent": "FT%",
|
||||||
|
"stats.assist": "AST",
|
||||||
|
"stats.steal": "STL",
|
||||||
|
"stats.blockShot": "BLK",
|
||||||
|
"stats.defRebound": "DR",
|
||||||
|
"stats.offRebound": "OR",
|
||||||
|
"stats.rebound": "REB",
|
||||||
|
"stats.turnover": "TO",
|
||||||
|
"stats.foul": "F",
|
||||||
|
"stats.playedTime": "TIME",
|
||||||
|
"stats.plusMinus": "+/-",
|
||||||
|
}
|
||||||
|
df_filtered[numeric_cols] = df_filtered[numeric_cols].apply(
|
||||||
|
pd.to_numeric, errors="coerce"
|
||||||
|
)
|
||||||
df_filtered[numeric_cols] = df_filtered[numeric_cols].round(0).astype("Int64")
|
df_filtered[numeric_cols] = df_filtered[numeric_cols].round(0).astype("Int64")
|
||||||
|
df_filtered = df_filtered.rename(columns=rename_map)
|
||||||
|
df_filtered["Дата"] = df_filtered["Дата"].dt.strftime("%d.%m.%Y")
|
||||||
styled = (
|
styled = (
|
||||||
df_filtered[columns_to_show]
|
df_filtered[columns_to_show]
|
||||||
.style
|
.style.apply(highlight_max, subset=["PTS"])
|
||||||
.apply(highlight_max, subset=["stats.points"])
|
.apply(highlight_max, subset=["AST"])
|
||||||
.apply(highlight_max, subset=["stats.assist"])
|
.apply(highlight_max, subset=["STL"])
|
||||||
.apply(highlight_max, subset=["stats.steal"])
|
.apply(highlight_max, subset=["BLK"])
|
||||||
.apply(highlight_max, subset=["stats.blockShot"])
|
.apply(highlight_max, subset=["REB"])
|
||||||
.apply(highlight_max, subset=["stats.rebound"])
|
.format(
|
||||||
|
{
|
||||||
|
"Дата": lambda x: x, # уже строка, просто оставляем как есть
|
||||||
|
"PTS": "{:,.0f}".format,
|
||||||
|
"AST": "{:,.0f}".format,
|
||||||
|
"STL": "{:,.0f}".format,
|
||||||
|
"BLK": "{:,.0f}".format,
|
||||||
|
"REB": "{:,.0f}".format,
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return styled
|
return styled
|
||||||
|
|
||||||
# Вывод данных
|
# Вывод данных
|
||||||
col_player1, col_player2 = tab_temp_1.columns((5, 5))
|
col_player1, col_player2 = tab_temp_1.columns((5, 5))
|
||||||
|
config_copy = config.copy()
|
||||||
|
|
||||||
event1 = col_player1.dataframe(
|
event1 = col_player1.dataframe(
|
||||||
team1_styled,
|
team1_styled,
|
||||||
column_config=config,
|
column_config=config_copy,
|
||||||
hide_index=True,
|
hide_index=True,
|
||||||
height=460,
|
height=460,
|
||||||
on_select="rerun",
|
on_select="rerun",
|
||||||
@@ -960,7 +1130,8 @@ if cached_team1 and cached_team2:
|
|||||||
hide_index=True,
|
hide_index=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
col_player1.dataframe(get_player_all_game(player_data_1))
|
col_player1.title("Статистика каждой игры")
|
||||||
|
col_player1.dataframe(get_player_all_game(player_data_1), hide_index=True)
|
||||||
|
|
||||||
if event2.selection and event2.selection.get("rows"):
|
if event2.selection and event2.selection.get("rows"):
|
||||||
selected_index2 = event2.selection["rows"][0]
|
selected_index2 = event2.selection["rows"][0]
|
||||||
@@ -988,7 +1159,8 @@ if cached_team1 and cached_team2:
|
|||||||
column_config=config_season,
|
column_config=config_season,
|
||||||
hide_index=True,
|
hide_index=True,
|
||||||
)
|
)
|
||||||
col_player2.dataframe(get_player_all_game(player_data_2))
|
col_player2.title("Статистика каждой игры")
|
||||||
|
col_player2.dataframe(get_player_all_game(player_data_2), hide_index=True)
|
||||||
|
|
||||||
team_col1, team_col2 = tab_temp_2.columns((5, 5))
|
team_col1, team_col2 = tab_temp_2.columns((5, 5))
|
||||||
if isinstance(cached_team_stats, list) and len(cached_team_stats) >= 34:
|
if isinstance(cached_team_stats, list) and len(cached_team_stats) >= 34:
|
||||||
@@ -1085,12 +1257,31 @@ if cached_standings:
|
|||||||
pass
|
pass
|
||||||
return [""] * len(s)
|
return [""] * len(s)
|
||||||
|
|
||||||
styled = df_st.style.apply(highlight_teams, axis=1)
|
styled = df_st[
|
||||||
|
[
|
||||||
|
"teamId",
|
||||||
|
"start",
|
||||||
|
"place",
|
||||||
|
"name",
|
||||||
|
"regionName",
|
||||||
|
"totalGames",
|
||||||
|
"totalWin",
|
||||||
|
"totalDefeat",
|
||||||
|
"totalPoints",
|
||||||
|
"totalGoalPlus",
|
||||||
|
"totalGoalMinus",
|
||||||
|
"logo",
|
||||||
|
"w_l",
|
||||||
|
"procent",
|
||||||
|
"plus_minus",
|
||||||
|
]
|
||||||
|
].style.apply(highlight_teams, axis=1)
|
||||||
tab_temp_4.dataframe(
|
tab_temp_4.dataframe(
|
||||||
styled,
|
styled,
|
||||||
column_config={"logo": st.column_config.ImageColumn("logo")},
|
column_config={"logo": st.column_config.ImageColumn("logo")},
|
||||||
hide_index=True,
|
hide_index=True,
|
||||||
height=610,
|
height=610,
|
||||||
|
width="content"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1534,20 +1725,19 @@ with tab_online:
|
|||||||
live_data_map = {}
|
live_data_map = {}
|
||||||
|
|
||||||
# Собираем live-данные по каждому game.id
|
# Собираем live-данные по каждому game.id
|
||||||
|
|
||||||
for _, row in df_filtered.iterrows():
|
for _, row in df_filtered.iterrows():
|
||||||
game_id = row["game.id"]
|
game_id = row["game.id"]
|
||||||
try:
|
try:
|
||||||
json_data = requests.get(
|
json_data = requests.get(
|
||||||
f"https://pro.russiabasket.org/api/abc/games/live-status?Id={game_id}&Lang=en",
|
f"https://vtb-league.org/api/abc/games/live-status?Id={game_id}&Lang=en",
|
||||||
).json()
|
).json()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
# json_data = {
|
|
||||||
# "period": None,
|
|
||||||
# "timeToGo": 0.0,
|
|
||||||
# }
|
|
||||||
print(ex)
|
print(ex)
|
||||||
|
|
||||||
# Берём содержимое result (словарь с gameId и данными)
|
# Берём содержимое result (словарь с gameId и данными)
|
||||||
|
if json_data:
|
||||||
result = json_data.get("result", {})
|
result = json_data.get("result", {})
|
||||||
if result and "gameId" in result:
|
if result and "gameId" in result:
|
||||||
live_data_map[result["gameId"]] = result
|
live_data_map[result["gameId"]] = result
|
||||||
|
|||||||
@@ -1,421 +0,0 @@
|
|||||||
*** visual.py 2025-01-22 00:00:00.000000000 +0000
|
|
||||||
--- visual.py 2025-10-07 00:00:00.000000000 +0000
|
|
||||||
***************
|
|
||||||
*** 1,10 ****
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import socket
|
|
||||||
import platform
|
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
|
||||||
import streamlit as st
|
|
||||||
import sys
|
|
||||||
from streamlit_autorefresh import st_autorefresh
|
|
||||||
-
|
|
||||||
st.set_page_config(
|
|
||||||
page_title="Баскетбол",
|
|
||||||
page_icon="🏀",
|
|
||||||
layout="wide",
|
|
||||||
--- 1,10 ----
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import socket
|
|
||||||
import platform
|
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
|
||||||
import streamlit as st
|
|
||||||
import sys
|
|
||||||
from streamlit_autorefresh import st_autorefresh
|
|
||||||
+
|
|
||||||
st.set_page_config(
|
|
||||||
page_title="Баскетбол",
|
|
||||||
page_icon="🏀",
|
|
||||||
layout="wide",
|
|
||||||
***************
|
|
||||||
*** 164,169 ****
|
|
||||||
--- 164,216 ----
|
|
||||||
def ensure_state(key: str, default=None):
|
|
||||||
# Инициализирует ключ один раз и возвращает значение
|
|
||||||
return st.session_state.setdefault(key, default)
|
|
||||||
|
|
||||||
+
|
|
||||||
+ # ======== UNIVERSAL SAFE RENDER WRAPPER ========
|
|
||||||
+ def _is_empty_like(x) -> bool:
|
|
||||||
+ if x is None:
|
|
||||||
+ return True
|
|
||||||
+ # DataFrame
|
|
||||||
+ if isinstance(x, pd.DataFrame):
|
|
||||||
+ return x.empty
|
|
||||||
+ # Pandas Styler
|
|
||||||
+ try:
|
|
||||||
+ # импортируем лениво, чтобы не ломаться, если нет pandas.io.formats.style в рантайме
|
|
||||||
+ from pandas.io.formats.style import Styler # type: ignore
|
|
||||||
+ if isinstance(x, Styler):
|
|
||||||
+ # у Styler нет __len__, но есть .data
|
|
||||||
+ return getattr(x, "data", pd.DataFrame()).empty
|
|
||||||
+ except Exception:
|
|
||||||
+ pass
|
|
||||||
+ # пустые коллекции
|
|
||||||
+ if isinstance(x, (list, tuple, dict, set)):
|
|
||||||
+ return len(x) == 0
|
|
||||||
+ return False
|
|
||||||
+
|
|
||||||
+ def safe_show(func, *args, **kwargs):
|
|
||||||
+ """
|
|
||||||
+ Безопасно вызывает функции отображения Streamlit (dataframe, table, metric, image, markdown, ...).
|
|
||||||
+ - Ничего не рендерит, если основной аргумент данных пуст/None.
|
|
||||||
+ - Нормализует height (убирает None, <0; приводит к int).
|
|
||||||
+ - Возвращает результат вызова func (нужно для dataframe-selection).
|
|
||||||
+ - Перехватывает исключения и показывает предупреждение.
|
|
||||||
+ """
|
|
||||||
+ # Если среди позиционных/именованных аргументов есть пустые/None-данные — не показываем
|
|
||||||
+ for a in args:
|
|
||||||
+ if _is_empty_like(a):
|
|
||||||
+ return None
|
|
||||||
+ for k, v in kwargs.items():
|
|
||||||
+ # пропускаем не-данные параметры (типа width/unsafe_allow_html)
|
|
||||||
+ if k.lower() in ("height", "width", "use_container_width", "unsafe_allow_html", "on_select", "selection_mode", "column_config", "hide_index", "border", "delta_color", "key"):
|
|
||||||
+ continue
|
|
||||||
+ if _is_empty_like(v):
|
|
||||||
+ return None
|
|
||||||
+
|
|
||||||
+ # height -> валидный int, иначе уберём
|
|
||||||
+ if "height" in kwargs:
|
|
||||||
+ h = kwargs.get("height")
|
|
||||||
+ if h is None:
|
|
||||||
+ kwargs.pop("height")
|
|
||||||
+ else:
|
|
||||||
+ try:
|
|
||||||
+ h = int(h)
|
|
||||||
+ if h < 0:
|
|
||||||
+ kwargs.pop("height")
|
|
||||||
+ else:
|
|
||||||
+ kwargs["height"] = h
|
|
||||||
+ except Exception:
|
|
||||||
+ kwargs.pop("height")
|
|
||||||
+
|
|
||||||
+ try:
|
|
||||||
+ return func(*args, **kwargs)
|
|
||||||
+ except Exception as e:
|
|
||||||
+ st.warning(f"⚠️ Ошибка при отображении: {e}")
|
|
||||||
+ return None
|
|
||||||
+ # ======== /SAFE WRAPPER ========
|
|
||||||
+
|
|
||||||
***************
|
|
||||||
*** 221,231 ****
|
|
||||||
col1, col4, col2, col5, col3 = st.columns([1, 5, 3, 5, 1])
|
|
||||||
|
|
||||||
t1 = (result.get("team1") or {})
|
|
||||||
t2 = (result.get("team2") or {})
|
|
||||||
- if t1.get("logo"):
|
|
||||||
- col1.image(t1["logo"], width=100)
|
|
||||||
team1_name = t1.get("name") or ""
|
|
||||||
team2_name = t2.get("name") or ""
|
|
||||||
- if team1_name or team2_name:
|
|
||||||
- col2.markdown(
|
|
||||||
- f"<h2 style='text-align: center'>{team1_name} — {team2_name}</h2>",
|
|
||||||
- unsafe_allow_html=True,
|
|
||||||
- )
|
|
||||||
- if t2.get("logo"):
|
|
||||||
- col3.image(t2["logo"], width=100)
|
|
||||||
+ safe_show(col1.image, t1.get("logo"), width=100)
|
|
||||||
+ if team1_name or team2_name:
|
|
||||||
+ safe_show(
|
|
||||||
+ col2.markdown,
|
|
||||||
+ f"<h2 style='text-align: center'>{team1_name} — {team2_name}</h2>",
|
|
||||||
+ unsafe_allow_html=True,
|
|
||||||
+ )
|
|
||||||
+ safe_show(col3.image, t2.get("logo"), width=100)
|
|
||||||
|
|
||||||
col4_1, col4_2, col4_3 = col4.columns((1, 1, 1))
|
|
||||||
col5_1, col5_2, col5_3 = col5.columns((1, 1, 1))
|
|
||||||
|
|
||||||
***************
|
|
||||||
*** 237,253 ****
|
|
||||||
if isinstance(cached_team_stats, list) and len(cached_team_stats) > 0:
|
|
||||||
v1 = cached_team_stats[0].get("val1")
|
|
||||||
v2 = cached_team_stats[0].get("val2")
|
|
||||||
if v1 is not None and v2 is not None:
|
|
||||||
val1, val2 = int(v1), int(v2)
|
|
||||||
delta_color_1 = "off" if val1 == val2 else "normal"
|
|
||||||
- col4_1.metric("Points", v1, val1 - val2, delta_color_1)
|
|
||||||
- col5_3.metric("Points", v2, val2 - val1, delta_color_1)
|
|
||||||
+ safe_show(col4_1.metric, "Points", v1, val1 - val2, delta_color_1)
|
|
||||||
+ safe_show(col5_3.metric, "Points", v2, val2 - val1, delta_color_1)
|
|
||||||
|
|
||||||
- col4_3.metric("TimeOuts", len(timeout1))
|
|
||||||
- col5_1.metric("TimeOuts", len(timeout2))
|
|
||||||
+ safe_show(col4_3.metric, "TimeOuts", len(timeout1))
|
|
||||||
+ safe_show(col5_1.metric, "TimeOuts", len(timeout2))
|
|
||||||
|
|
||||||
if isinstance(cached_live_status, list) and cached_live_status:
|
|
||||||
foulsA = (cached_live_status[0] or {}).get("foulsA")
|
|
||||||
foulsB = (cached_live_status[0] or {}).get("foulsB")
|
|
||||||
if foulsA is not None:
|
|
||||||
- col4_2.metric("Fouls", foulsA)
|
|
||||||
+ safe_show(col4_2.metric, "Fouls", foulsA)
|
|
||||||
if foulsB is not None:
|
|
||||||
- col5_2.metric("Fouls", foulsB)
|
|
||||||
+ safe_show(col5_2.metric, "Fouls", foulsB)
|
|
||||||
|
|
||||||
***************
|
|
||||||
*** 270,280 ****
|
|
||||||
for q1, q2, col1_i, col2_i in zip(score_by_quarter_1, score_by_quarter_2, col_1_col, col_2_col):
|
|
||||||
count_q += 1
|
|
||||||
name_q = f"OT{count_q-4}" if count_q > 4 else f"Q{count_q}"
|
|
||||||
try:
|
|
||||||
delta_color = "off" if int(q1) == int(q2) else "normal"
|
|
||||||
- col1_i.metric(name_q, q1, int(q1) - int(q2), delta_color, border=True)
|
|
||||||
- col2_i.metric(name_q, q2, int(q2) - int(q1), delta_color, border=True)
|
|
||||||
+ safe_show(col1_i.metric, name_q, q1, int(q1) - int(q2), delta_color, border=True)
|
|
||||||
+ safe_show(col2_i.metric, name_q, q2, int(q2) - int(q1), delta_color, border=True)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
# если кривые данные в JSON, просто пропустим
|
|
||||||
pass
|
|
||||||
|
|
||||||
***************
|
|
||||||
*** 403,424 ****
|
|
||||||
team1_styled = (
|
|
||||||
team1_data.style.apply(highlight_grey, axis=1)
|
|
||||||
.apply(highlight_foul, subset="foul")
|
|
||||||
.apply(highlight_max, subset="pts")
|
|
||||||
)
|
|
||||||
team2_styled = (
|
|
||||||
team2_data.style.apply(highlight_grey, axis=1)
|
|
||||||
.apply(highlight_foul, subset="foul")
|
|
||||||
.apply(highlight_max, subset="pts")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Вывод данных
|
|
||||||
col_player1, col_player2 = tab_temp_1.columns((5, 5))
|
|
||||||
- event1 = col_player1.dataframe(
|
|
||||||
- team1_styled,
|
|
||||||
- column_config=config,
|
|
||||||
- hide_index=True,
|
|
||||||
- height=460,
|
|
||||||
- on_select="rerun",
|
|
||||||
- selection_mode=[
|
|
||||||
- "single-row",
|
|
||||||
- ],
|
|
||||||
- )
|
|
||||||
- event2 = col_player2.dataframe(
|
|
||||||
- team2_styled,
|
|
||||||
- column_config=config,
|
|
||||||
- hide_index=True,
|
|
||||||
- height=460,
|
|
||||||
- on_select="rerun",
|
|
||||||
- selection_mode=[
|
|
||||||
- "single-row",
|
|
||||||
- ],
|
|
||||||
- )
|
|
||||||
+ event1 = safe_show(
|
|
||||||
+ col_player1.dataframe,
|
|
||||||
+ team1_styled,
|
|
||||||
+ column_config=config,
|
|
||||||
+ hide_index=True,
|
|
||||||
+ height=460,
|
|
||||||
+ on_select="rerun",
|
|
||||||
+ selection_mode=["single-row"],
|
|
||||||
+ )
|
|
||||||
+ event2 = safe_show(
|
|
||||||
+ col_player2.dataframe,
|
|
||||||
+ team2_styled,
|
|
||||||
+ column_config=config,
|
|
||||||
+ hide_index=True,
|
|
||||||
+ height=460,
|
|
||||||
+ on_select="rerun",
|
|
||||||
+ selection_mode=["single-row"],
|
|
||||||
+ )
|
|
||||||
|
|
||||||
if event1 and getattr(event1, "selection", None) and event1.selection.get("rows"):
|
|
||||||
selected_index1 = event1.selection["rows"][0]
|
|
||||||
st.session_state["player1"] = (
|
|
||||||
selected_index1 # Сохранение состояния в session_state
|
|
||||||
***************
|
|
||||||
*** 433,441 ****
|
|
||||||
if player_data_1["num"]:
|
|
||||||
z, a, b, c, d, e = col_player1.columns((1, 6, 1, 1, 1, 1))
|
|
||||||
- z.metric("Номер", player_data_1["num"], border=False)
|
|
||||||
- a.metric("Игрок", player_data_1["NameGFX"], border=False)
|
|
||||||
- b.metric("Амплуа", player_data_1["roleShort"], border=False)
|
|
||||||
- c.metric("Возраст", player_data_1["age"], border=False)
|
|
||||||
- d.metric("Рост", player_data_1["height"].split()[0], border=False)
|
|
||||||
- e.metric("Вес", player_data_1["weight"].split()[0], border=False)
|
|
||||||
-
|
|
||||||
- col_player1.dataframe(
|
|
||||||
- selected_player_1,
|
|
||||||
- column_config=config_season,
|
|
||||||
- hide_index=True,
|
|
||||||
- )
|
|
||||||
+ safe_show(z.metric, "Номер", player_data_1["num"], border=False)
|
|
||||||
+ safe_show(a.metric, "Игрок", player_data_1["NameGFX"], border=False)
|
|
||||||
+ safe_show(b.metric, "Амплуа", player_data_1["roleShort"], border=False)
|
|
||||||
+ safe_show(c.metric, "Возраст", player_data_1["age"], border=False)
|
|
||||||
+ safe_show(d.metric, "Рост", player_data_1["height"].split()[0], border=False)
|
|
||||||
+ safe_show(e.metric, "Вес", player_data_1["weight"].split()[0], border=False)
|
|
||||||
+
|
|
||||||
+ safe_show(
|
|
||||||
+ col_player1.dataframe,
|
|
||||||
+ selected_player_1,
|
|
||||||
+ column_config=config_season,
|
|
||||||
+ hide_index=True,
|
|
||||||
+ )
|
|
||||||
***************
|
|
||||||
*** 446,454 ****
|
|
||||||
if player_data_2["num"]:
|
|
||||||
z, a, b, c, d, e = col_player2.columns((1, 6, 1, 1, 1, 1))
|
|
||||||
- z.metric("Номер", player_data_2["num"], border=False)
|
|
||||||
- a.metric("Игрок", player_data_2["NameGFX"], border=False)
|
|
||||||
- b.metric("Амплуа", player_data_2["roleShort"], border=False)
|
|
||||||
- c.metric("Возраст", player_data_2["age"], border=False)
|
|
||||||
- d.metric("Рост", player_data_2["height"].split()[0], border=False)
|
|
||||||
- e.metric("Вес", player_data_2["weight"].split()[0], border=False)
|
|
||||||
-
|
|
||||||
- col_player2.dataframe(
|
|
||||||
- selected_player_2,
|
|
||||||
- column_config=config_season,
|
|
||||||
- hide_index=True,
|
|
||||||
- )
|
|
||||||
+ safe_show(z.metric, "Номер", player_data_2["num"], border=False)
|
|
||||||
+ safe_show(a.metric, "Игрок", player_data_2["NameGFX"], border=False)
|
|
||||||
+ safe_show(b.metric, "Амплуа", player_data_2["roleShort"], border=False)
|
|
||||||
+ safe_show(c.metric, "Возраст", player_data_2["age"], border=False)
|
|
||||||
+ safe_show(d.metric, "Рост", player_data_2["height"].split()[0], border=False)
|
|
||||||
+ safe_show(e.metric, "Вес", player_data_2["weight"].split()[0], border=False)
|
|
||||||
+
|
|
||||||
+ safe_show(
|
|
||||||
+ col_player2.dataframe,
|
|
||||||
+ selected_player_2,
|
|
||||||
+ column_config=config_season,
|
|
||||||
+ hide_index=True,
|
|
||||||
+ )
|
|
||||||
***************
|
|
||||||
*** 459,468 ****
|
|
||||||
if isinstance(cached_team_stats, list) and len(cached_team_stats) >= 34:
|
|
||||||
cached_team_stats_new = [
|
|
||||||
cached_team_stats[0],
|
|
||||||
*cached_team_stats[25:29],
|
|
||||||
cached_team_stats[7],
|
|
||||||
cached_team_stats[33],
|
|
||||||
*cached_team_stats[9:11],
|
|
||||||
*cached_team_stats[15:17],
|
|
||||||
]
|
|
||||||
- tab_temp_2.table(cached_team_stats_new)
|
|
||||||
+ safe_show(tab_temp_2.table, cached_team_stats_new)
|
|
||||||
|
|
||||||
***************
|
|
||||||
*** 470,484 ****
|
|
||||||
- if isinstance(cached_referee, (list, pd.DataFrame)):
|
|
||||||
- tab_temp_3.dataframe(cached_referee, height=600, column_config={"flag": st.column_config.ImageColumn("flag")})
|
|
||||||
-
|
|
||||||
-
|
|
||||||
column_config_ref = {
|
|
||||||
"flag": st.column_config.ImageColumn(
|
|
||||||
"flag",
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
if cached_referee:
|
|
||||||
- tab_temp_3.dataframe(cached_referee, height=600, column_config=column_config_ref)
|
|
||||||
+ safe_show(tab_temp_3.dataframe, cached_referee, height=600, column_config=column_config_ref)
|
|
||||||
|
|
||||||
***************
|
|
||||||
*** 503,511 ****
|
|
||||||
styled = df_st.style.apply(highlight_teams, axis=1)
|
|
||||||
- tab_temp_4.dataframe(
|
|
||||||
- styled,
|
|
||||||
- column_config={"logo": st.column_config.ImageColumn("logo")},
|
|
||||||
- hide_index=True,
|
|
||||||
- height=610,
|
|
||||||
- )
|
|
||||||
+ safe_show(
|
|
||||||
+ tab_temp_4.dataframe,
|
|
||||||
+ styled,
|
|
||||||
+ column_config={"logo": st.column_config.ImageColumn("logo")},
|
|
||||||
+ hide_index=True,
|
|
||||||
+ height=610,
|
|
||||||
+ )
|
|
||||||
|
|
||||||
***************
|
|
||||||
*** 552,558 ****
|
|
||||||
]
|
|
||||||
col.write(q)
|
|
||||||
- col.dataframe(df_col)
|
|
||||||
+ safe_show(col.dataframe, df_col)
|
|
||||||
|
|
||||||
# Овертаймы
|
|
||||||
for index, col in enumerate(columns_quarters):
|
|
||||||
q = columns_quarters_name_ot[index]
|
|
||||||
df_col = [
|
|
||||||
--- 564,570 ----
|
|
||||||
***************
|
|
||||||
*** 572,578 ****
|
|
||||||
]
|
|
||||||
col.write(q)
|
|
||||||
- col.dataframe(df_col)
|
|
||||||
+ safe_show(col.dataframe, df_col)
|
|
||||||
|
|
||||||
***************
|
|
||||||
*** 582,586 ****
|
|
||||||
if isinstance(cached_play_by_play, list) and isinstance(cached_game_online, dict):
|
|
||||||
plays = (cached_game_online.get("result") or {}).get("plays") or []
|
|
||||||
if plays:
|
|
||||||
- tab_temp_6.table(cached_play_by_play)
|
|
||||||
+ safe_show(tab_temp_6.table, cached_play_by_play)
|
|
||||||
|
|
||||||
***************
|
|
||||||
*** 703,724 ****
|
|
||||||
height1 = 38 * max(count_game_1, 10)
|
|
||||||
height2 = 38 * max(count_game_2, 10)
|
|
||||||
- col1_schedule.dataframe(
|
|
||||||
- team1_data,
|
|
||||||
- hide_index=True,
|
|
||||||
- height=int(min(height1, 1200)),
|
|
||||||
- column_config=column_config,
|
|
||||||
- )
|
|
||||||
- col2_schedule.dataframe(
|
|
||||||
- team2_data,
|
|
||||||
- hide_index=True,
|
|
||||||
- height=int(min(height2, 1200)),
|
|
||||||
- column_config=column_config,
|
|
||||||
- )
|
|
||||||
+ safe_show(
|
|
||||||
+ col1_schedule.dataframe,
|
|
||||||
+ team1_data,
|
|
||||||
+ hide_index=True,
|
|
||||||
+ height=int(min(height1, 1200)),
|
|
||||||
+ column_config=column_config,
|
|
||||||
+ )
|
|
||||||
+ safe_show(
|
|
||||||
+ col2_schedule.dataframe,
|
|
||||||
+ team2_data,
|
|
||||||
+ hide_index=True,
|
|
||||||
+ height=int(min(height2, 1200)),
|
|
||||||
+ column_config=column_config,
|
|
||||||
+ )
|
|
||||||
|
|
||||||
***************
|
|
||||||
*** 836,845 ****
|
|
||||||
filtered_data_pbp = temp_data_pbp[mask1]
|
|
||||||
count_pbp = len(filtered_data_pbp)
|
|
||||||
|
|
||||||
column_pbp = ["num", "info", "who", "period", "time"]
|
|
||||||
column_config_pbp = {
|
|
||||||
"info": st.column_config.TextColumn(width="medium"),
|
|
||||||
"who": st.column_config.TextColumn(width="large"),
|
|
||||||
}
|
|
||||||
- col2_pbp.dataframe(
|
|
||||||
- filtered_data_pbp[column_pbp],
|
|
||||||
- column_config=column_config_pbp,
|
|
||||||
- hide_index=True,
|
|
||||||
- height=(38 * count_pbp if count_pbp > 10 else None),
|
|
||||||
- )
|
|
||||||
+ safe_show(
|
|
||||||
+ col2_pbp.dataframe,
|
|
||||||
+ filtered_data_pbp[column_pbp],
|
|
||||||
+ column_config=column_config_pbp,
|
|
||||||
+ hide_index=True,
|
|
||||||
+ height=(38 * count_pbp if count_pbp > 10 else None),
|
|
||||||
+ )
|
|
||||||
else:
|
|
||||||
st.info("Данных play-by-play нет.")
|
|
||||||
Reference in New Issue
Block a user