добавил сезонную статистику и карьерную статистику на игроков
This commit is contained in:
432
get_data.py
432
get_data.py
@@ -310,6 +310,18 @@ def stop_offline_threads():
|
||||
logger.info("[threads] OFFLINE threads stopped")
|
||||
|
||||
|
||||
def has_full_game_ready() -> bool:
|
||||
game = latest_data.get("game")
|
||||
if not game:
|
||||
return False
|
||||
payload = game.get("data", game)
|
||||
return (
|
||||
isinstance(payload, dict)
|
||||
and isinstance(payload.get("data"), dict)
|
||||
and isinstance(payload["data"].get("result"), dict)
|
||||
and "teams" in payload["data"]["result"]
|
||||
)
|
||||
|
||||
# Функция запускаемая в потоках
|
||||
def get_data_from_API(
|
||||
name: str,
|
||||
@@ -321,12 +333,10 @@ def get_data_from_API(
|
||||
did_first_fetch = False
|
||||
while not stop_event.is_set():
|
||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
if stop_when_live and globals().get("STATUS") == "live" and did_first_fetch:
|
||||
logger.info(
|
||||
f"{[{current_time}]} [{name}] stopping because STATUS='live' and first fetch done"
|
||||
)
|
||||
if stop_when_live and globals().get("STATUS") == "live" and has_full_game_ready():
|
||||
logger.info(f"{[{current_time}]} [{name}] stopping because STATUS='live' and full game is ready")
|
||||
break
|
||||
start = time.time()
|
||||
|
||||
try:
|
||||
value = requests.get(url, timeout=5).json()
|
||||
did_first_fetch = True # помечаем, что один заход сделали
|
||||
@@ -370,11 +380,10 @@ def get_data_from_API(
|
||||
while slept < sleep_time:
|
||||
if stop_event.is_set():
|
||||
break
|
||||
if stop_when_live and globals().get("STATUS") == "live" and did_first_fetch:
|
||||
logger.info(
|
||||
f"[{name}] stopping during sleep because STATUS='live' and first fetch done"
|
||||
)
|
||||
if stop_when_live and globals().get("STATUS") == "live" and has_full_game_ready():
|
||||
logger.info(f"[{name}] stopping during sleep because STATUS='live' and full game is ready")
|
||||
return
|
||||
|
||||
time.sleep(1)
|
||||
slept += 1
|
||||
# если запрос занял дольше — просто сразу следующую итерацию
|
||||
@@ -575,19 +584,19 @@ def results_consumer():
|
||||
# чтобы /status видел "живость" раз в 5 минут независимо от полноты JSON.
|
||||
if globals().get("STATUS") != "live":
|
||||
latest_data["game"] = {"ts": msg["ts"], "data": payload}
|
||||
# можно залогировать для отладки:
|
||||
logger.debug("results_consumer: pre-live game → updated (full=%s)", is_full)
|
||||
else:
|
||||
# ✅ если игры ещё НЕТ в кэше — примем ПЕРВЫЙ game даже неполный,
|
||||
# чтобы box-score/play-by-play могли его дорастить
|
||||
if is_full or not has_game_already:
|
||||
latest_data["game"] = {"ts": msg["ts"], "data": payload}
|
||||
logger.debug("results_consumer: LIVE → stored (full=%s, had=%s)", is_full, has_game_already)
|
||||
else:
|
||||
logger.debug("results_consumer: LIVE & partial game → keep previous one")
|
||||
|
||||
# 2) Когда матч УЖЕ online (STATUS == 'live'):
|
||||
# - поток 'game' в live-режиме погаснет сам (stop_when_live=True),
|
||||
# но если вдруг что-то долетит, кладём только полный JSON.
|
||||
else:
|
||||
if is_full:
|
||||
latest_data["game"] = {"ts": msg["ts"], "data": payload}
|
||||
logger.debug("results_consumer: LIVE & full game → replaced")
|
||||
else:
|
||||
# Ничего не трогаем, чтобы не затереть полноценные данные пустотой.
|
||||
logger.debug("results_consumer: LIVE & partial game → keep previous one")
|
||||
continue
|
||||
# # game неполный
|
||||
# if not has_game_already:
|
||||
@@ -1010,7 +1019,7 @@ def format_time(seconds: float | int) -> str:
|
||||
|
||||
@app.get("/team1")
|
||||
async def team1():
|
||||
game = get_latest_game_safe()
|
||||
game = get_latest_game_safe("game")
|
||||
if not game:
|
||||
raise HTTPException(status_code=503, detail="game data not ready")
|
||||
return await team("team1")
|
||||
@@ -1018,7 +1027,7 @@ async def team1():
|
||||
|
||||
@app.get("/team2")
|
||||
async def team2():
|
||||
game = get_latest_game_safe()
|
||||
game = get_latest_game_safe("game")
|
||||
if not game:
|
||||
raise HTTPException(status_code=503, detail="game data not ready")
|
||||
return await team("team2")
|
||||
@@ -1247,7 +1256,7 @@ async def status(request: Request):
|
||||
|
||||
@app.get("/scores")
|
||||
async def scores():
|
||||
game = get_latest_game_safe()
|
||||
game = get_latest_game_safe("game")
|
||||
if not game:
|
||||
# игры ещё нет или пришёл только частичный ответ
|
||||
# отдаём пустую структуру, чтобы фронт не падал
|
||||
@@ -1309,13 +1318,13 @@ async def top_sorted_team(data):
|
||||
return top_sorted_team
|
||||
|
||||
|
||||
def get_latest_game_safe():
|
||||
def get_latest_game_safe(name:str):
|
||||
"""
|
||||
Безопасно достаём актуальный game из latest_data.
|
||||
Возвращаем None, если структура ещё не готова или прилетел "плохой" game
|
||||
(например, с {"status": "no-status"} без data/result).
|
||||
"""
|
||||
game = latest_data.get("game")
|
||||
game = latest_data.get(name)
|
||||
if not game:
|
||||
return None
|
||||
|
||||
@@ -1338,33 +1347,118 @@ def get_latest_game_safe():
|
||||
return game
|
||||
|
||||
|
||||
def format_time(seconds: float | int) -> str:
|
||||
"""
|
||||
Форматирует время в секундах в строку "M:SS".
|
||||
|
||||
Args:
|
||||
seconds (float | int): Количество секунд.
|
||||
|
||||
Returns:
|
||||
str: Время в формате "M:SS".
|
||||
"""
|
||||
try:
|
||||
total_seconds = int(float(seconds))
|
||||
minutes = total_seconds // 60
|
||||
sec = total_seconds % 60
|
||||
return f"{minutes}:{sec:02}"
|
||||
except (ValueError, TypeError):
|
||||
return "0:00"
|
||||
|
||||
|
||||
def _pick_last_avg_and_sum(stats_list: list) -> tuple[dict, dict]:
|
||||
"""Возвращает (season_sum, season_avg) из seasonStats. Безопасно при пустых данных."""
|
||||
if not isinstance(stats_list, list) or len(stats_list) == 0:
|
||||
return {}, {}
|
||||
# В JSON конец массива: ... {"class":"Sum"}, {"class":"Avg"}
|
||||
last = stats_list[-1] if stats_list else None
|
||||
prev = stats_list[-2] if len(stats_list) >= 2 else None
|
||||
|
||||
season_avg = last.get("stats", {}) if isinstance(last, dict) and str(last.get("class")).lower() == "avg" else {}
|
||||
season_sum = prev.get("stats", {}) if isinstance(prev, dict) and str(prev.get("class")).lower() == "sum" else {}
|
||||
|
||||
# Бывают инверсии порядка (на всякий случай): попробуем найти явно
|
||||
if not season_avg or not season_sum:
|
||||
for x in reversed(stats_list):
|
||||
if isinstance(x, dict) and str(x.get("class")).lower() == "avg" and not season_avg:
|
||||
season_avg = x.get("stats", {}) or {}
|
||||
if isinstance(x, dict) and str(x.get("class")).lower() == "sum" and not season_sum:
|
||||
season_sum = x.get("stats", {}) or {}
|
||||
if season_avg and season_sum:
|
||||
break
|
||||
return season_sum, season_avg
|
||||
|
||||
|
||||
def _pick_career_sum_and_avg(carrier_list: list) -> tuple[dict, dict]:
|
||||
"""Возвращает (career_sum, career_avg) из carrier. В API встречаются блоки с class: Normal/Sum/Avg."""
|
||||
if not isinstance(carrier_list, list) or len(carrier_list) == 0:
|
||||
return {}, {}
|
||||
career_sum, career_avg = {}, {}
|
||||
# Ищем явные «Sum» и «Avg»
|
||||
for x in reversed(carrier_list):
|
||||
if isinstance(x, dict):
|
||||
cls = str(x.get("class", "")).lower()
|
||||
stats = x.get("stats", {}) or {}
|
||||
if cls == "sum" and not career_sum:
|
||||
career_sum = stats
|
||||
elif cls == "avg" and not career_avg:
|
||||
career_avg = stats
|
||||
if career_sum and career_avg:
|
||||
break
|
||||
# Если «Avg» нет (часто для карьеры бывает только Normal/Sum) — ок, оставим пустым
|
||||
return career_sum, career_avg
|
||||
|
||||
|
||||
def _as_int(v, default=0):
|
||||
try:
|
||||
# в JSON часто строки; пустые строки -> 0
|
||||
if v in ("", None):
|
||||
return default
|
||||
return int(float(v))
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
|
||||
def _safe(d: dict) -> dict:
|
||||
return d if isinstance(d, dict) else {}
|
||||
|
||||
|
||||
async def team(who: str):
|
||||
"""
|
||||
Возвращает данные по команде (team1 / team2) из актуального game.
|
||||
Защищена от ситуации, когда latest_data["game"] ещё не прогрелся
|
||||
или в него прилетел "плохой" ответ от API.
|
||||
"""
|
||||
game = get_latest_game_safe()
|
||||
game = get_latest_game_safe("game")
|
||||
if not game:
|
||||
# игра ещё не подгружена или структура кривоватая
|
||||
raise HTTPException(status_code=503, detail="game data not ready")
|
||||
|
||||
full_stat = get_latest_game_safe("pregame-full-stats")
|
||||
if not full_stat:
|
||||
raise HTTPException(status_code=503, detail="pregame-full-stats data not ready")
|
||||
|
||||
# нормализуем доступ к данным
|
||||
game_data = game["data"] if "data" in game else game
|
||||
full_stat_data = full_stat["data"] if "data" in full_stat else full_stat
|
||||
result = game_data[
|
||||
"result"
|
||||
] # здесь уже безопасно, мы проверили в get_latest_game_safe
|
||||
result_full = full_stat_data["result"]
|
||||
|
||||
# в result ожидаем "teams"
|
||||
teams = result.get("teams")
|
||||
|
||||
if not teams:
|
||||
raise HTTPException(status_code=503, detail="game teams not ready")
|
||||
|
||||
# выбираем команду
|
||||
if who == "team1":
|
||||
payload = next((t for t in teams if t.get("teamNumber") == 1), None)
|
||||
payload_full = result_full.get("team1PlayersStats")
|
||||
else:
|
||||
payload = next((t for t in teams if t.get("teamNumber") == 2), None)
|
||||
payload_full = result_full.get("team2PlayersStats")
|
||||
|
||||
if payload is None:
|
||||
raise HTTPException(status_code=404, detail=f"{who} not found in game data")
|
||||
@@ -1383,10 +1477,126 @@ async def team(who: str):
|
||||
("Forward-Center", "FC"),
|
||||
]
|
||||
starts = payload.get("starts", [])
|
||||
team_rows = []
|
||||
|
||||
team_rows = []
|
||||
for item in starts:
|
||||
stats = item.get("stats") or {}
|
||||
pid = str(item.get("personId"))
|
||||
full_obj = next((p for p in (payload_full or []) if str(p.get("personId")) == pid), None)
|
||||
|
||||
season_sum = season_avg = career_sum = career_avg = {}
|
||||
if full_obj:
|
||||
# сезон
|
||||
season_sum, season_avg = _pick_last_avg_and_sum(full_obj.get("seasonStats") or [])
|
||||
# карьера
|
||||
career_sum, career_avg = _pick_career_sum_and_avg(full_obj.get("carrier") or [])
|
||||
|
||||
season_sum = _safe(season_sum)
|
||||
season_avg = _safe(season_avg)
|
||||
career_sum = _safe(career_sum)
|
||||
career_avg = _safe(career_avg)
|
||||
|
||||
# Полезные числа для Totals+Live
|
||||
# live-поля в box-score называются goal1/2/3, shot1/2/3, defReb/offReb и т.п.
|
||||
g1 = _as_int(stats.get("goal1"))
|
||||
s1 = _as_int(stats.get("shot1"))
|
||||
g2 = _as_int(stats.get("goal2"))
|
||||
s2 = _as_int(stats.get("shot2"))
|
||||
g3 = _as_int(stats.get("goal3"))
|
||||
s3 = _as_int(stats.get("shot3"))
|
||||
|
||||
# Сезонные суммы из pregame-full-stats
|
||||
ss_pts = _as_int(season_sum.get("points"))
|
||||
ss_ast = _as_int(season_sum.get("assist"))
|
||||
ss_blk = _as_int(season_sum.get("blockShot"))
|
||||
ss_dreb = _as_int(season_sum.get("defRebound"))
|
||||
ss_oreb = _as_int(season_sum.get("offRebound"))
|
||||
ss_reb = _as_int(season_sum.get("rebound"))
|
||||
ss_stl = _as_int(season_sum.get("steal"))
|
||||
ss_to = _as_int(season_sum.get("turnover"))
|
||||
ss_foul = _as_int(season_sum.get("foul"))
|
||||
ss_sec = _as_int(season_sum.get("second"))
|
||||
ss_gms = _as_int(season_sum.get("games"))
|
||||
ss_st = _as_int(season_sum.get("isStarts"))
|
||||
ss_g1 = _as_int(season_sum.get("goal1"))
|
||||
ss_s1 = _as_int(season_sum.get("shot1"))
|
||||
ss_g2 = _as_int(season_sum.get("goal2"))
|
||||
ss_s2 = _as_int(season_sum.get("shot2"))
|
||||
ss_g3 = _as_int(season_sum.get("goal3"))
|
||||
ss_s3 = _as_int(season_sum.get("shot3"))
|
||||
|
||||
# Карьерные суммы из pregame-full-stats
|
||||
car_ss_pts = _as_int(career_sum.get("points"))
|
||||
car_ss_ast = _as_int(career_sum.get("assist"))
|
||||
car_ss_blk = _as_int(career_sum.get("blockShot"))
|
||||
car_ss_dreb = _as_int(career_sum.get("defRebound"))
|
||||
car_ss_oreb = _as_int(career_sum.get("offRebound"))
|
||||
car_ss_reb = _as_int(career_sum.get("rebound"))
|
||||
car_ss_stl = _as_int(career_sum.get("steal"))
|
||||
car_ss_to = _as_int(career_sum.get("turnover"))
|
||||
car_ss_foul = _as_int(career_sum.get("foul"))
|
||||
car_ss_sec = _as_int(career_sum.get("second"))
|
||||
car_ss_gms = _as_int(career_sum.get("games"))
|
||||
car_ss_st = _as_int(career_sum.get("isStarts"))
|
||||
car_ss_g1 = _as_int(career_sum.get("goal1"))
|
||||
car_ss_s1 = _as_int(career_sum.get("shot1"))
|
||||
car_ss_g2 = _as_int(career_sum.get("goal2"))
|
||||
car_ss_s2 = _as_int(career_sum.get("shot2"))
|
||||
car_ss_g3 = _as_int(career_sum.get("goal3"))
|
||||
car_ss_s3 = _as_int(career_sum.get("shot3"))
|
||||
|
||||
# Totals по сезону, «с учётом текущего матча»:
|
||||
T_points = ss_pts + _as_int(stats.get("points"))
|
||||
T_assist = ss_ast + _as_int(stats.get("assist"))
|
||||
T_block = ss_blk + _as_int(stats.get("block"))
|
||||
T_dreb = ss_dreb + _as_int(stats.get("defReb"))
|
||||
T_oreb = ss_oreb + _as_int(stats.get("offReb"))
|
||||
T_reb = ss_reb + (_as_int(stats.get("defReb")) + _as_int(stats.get("offReb")))
|
||||
T_steal = ss_stl + _as_int(stats.get("steal"))
|
||||
T_turn = ss_to + _as_int(stats.get("turnover"))
|
||||
T_foul = ss_foul + _as_int(stats.get("foul"))
|
||||
T_sec = ss_sec + _as_int(stats.get("second"))
|
||||
T_gms = ss_gms + (1 if _as_int(stats.get("second")) > 0 else 0)
|
||||
T_starts = ss_st + (1 if bool(stats.get("isStart")) else 0)
|
||||
|
||||
T_g1 = ss_g1 + g1
|
||||
T_s1 = ss_s1 + s1
|
||||
T_g2 = ss_g2 + g2
|
||||
T_s2 = ss_s2 + s2
|
||||
T_g3 = ss_g3 + g3
|
||||
T_s3 = ss_s3 + s3
|
||||
|
||||
# Totals по карьере, «с учётом текущего матча»:
|
||||
car_T_points = car_ss_pts + _as_int(stats.get("points"))
|
||||
car_T_assist = car_ss_ast + _as_int(stats.get("assist"))
|
||||
car_T_block = car_ss_blk + _as_int(stats.get("block"))
|
||||
car_T_dreb = car_ss_dreb + _as_int(stats.get("defReb"))
|
||||
car_T_oreb = car_ss_oreb + _as_int(stats.get("offReb"))
|
||||
car_T_reb = car_ss_reb + (_as_int(stats.get("defReb")) + _as_int(stats.get("offReb")))
|
||||
car_T_steal = car_ss_stl + _as_int(stats.get("steal"))
|
||||
car_T_turn = car_ss_to + _as_int(stats.get("turnover"))
|
||||
car_T_foul = car_ss_foul + _as_int(stats.get("foul"))
|
||||
car_T_sec = car_ss_sec + _as_int(stats.get("second"))
|
||||
car_T_gms = car_ss_gms + (1 if _as_int(stats.get("second")) > 0 else 0)
|
||||
car_T_starts = car_ss_st + (1 if bool(stats.get("isStart")) else 0)
|
||||
|
||||
car_T_g1 = car_ss_g1 + g1
|
||||
car_T_s1 = car_ss_s1 + s1
|
||||
car_T_g2 = car_ss_g2 + g2
|
||||
car_T_s2 = car_ss_s2 + s2
|
||||
car_T_g3 = car_ss_g3 + g3
|
||||
car_T_s3 = car_ss_s3 + s3
|
||||
|
||||
# Проценты (без деления на 0)
|
||||
def _pct(goal, shot):
|
||||
return f"{round(goal*100/shot, 1)}%" if shot else "0.0%"
|
||||
|
||||
# Для «23» используем сумму 2-х и 3-х
|
||||
T_g23 = T_g2 + T_g3
|
||||
T_s23 = T_s2 + T_s3
|
||||
car_T_g23 = car_T_g2 + car_T_g3
|
||||
car_T_s23 = car_T_s2 + car_T_s3
|
||||
# print(avg_season, total_season)
|
||||
row = {
|
||||
"id": item.get("personId") or "",
|
||||
"num": item.get("displayNumber"),
|
||||
@@ -1434,67 +1644,119 @@ async def team(who: str):
|
||||
)
|
||||
+ ".svg"
|
||||
),
|
||||
"pts": stats.get("points", 0),
|
||||
"pt-2": f"{stats.get('goal2',0)}/{stats.get('shot2',0)}" if stats else 0,
|
||||
"pt-3": f"{stats.get('goal3',0)}/{stats.get('shot3',0)}" if stats else 0,
|
||||
"pt-1": f"{stats.get('goal1',0)}/{stats.get('shot1',0)}" if stats else 0,
|
||||
"fg": (
|
||||
f"{stats.get('goal2',0)+stats.get('goal3',0)}/"
|
||||
f"{stats.get('shot2',0)+stats.get('shot3',0)}"
|
||||
if stats
|
||||
else 0
|
||||
),
|
||||
"ast": stats.get("assist", 0),
|
||||
"stl": stats.get("steal", 0),
|
||||
"blk": stats.get("block", 0),
|
||||
"blkVic": stats.get("blocked", 0),
|
||||
"dreb": stats.get("defReb", 0),
|
||||
"oreb": stats.get("offReb", 0),
|
||||
"reb": stats.get("defReb", 0) + stats.get("offReb", 0),
|
||||
"to": stats.get("turnover", 0),
|
||||
"foul": stats.get("foul", 0),
|
||||
"foulT": stats.get("foulT", 0),
|
||||
"foulD": stats.get("foulD", 0),
|
||||
"foulC": stats.get("foulC", 0),
|
||||
"foulB": stats.get("foulB", 0),
|
||||
"fouled": stats.get("foulsOn", 0),
|
||||
"plusMinus": stats.get("plusMinus", 0),
|
||||
"dunk": stats.get("dunk", 0),
|
||||
"kpi": (
|
||||
stats.get("points", 0)
|
||||
+ stats.get("defReb", 0)
|
||||
+ stats.get("offReb", 0)
|
||||
+ stats.get("assist", 0)
|
||||
+ stats.get("steal", 0)
|
||||
+ stats.get("block", 0)
|
||||
+ stats.get("foulsOn", 0)
|
||||
+ (stats.get("goal1", 0) - stats.get("shot1", 0))
|
||||
+ (stats.get("goal2", 0) - stats.get("shot2", 0))
|
||||
+ (stats.get("goal3", 0) - stats.get("shot3", 0))
|
||||
- stats.get("turnover", 0)
|
||||
- stats.get("foul", 0)
|
||||
),
|
||||
"time": format_time(stats.get("second", 0)),
|
||||
"pts1q": 0,
|
||||
"pts2q": 0,
|
||||
"pts3q": 0,
|
||||
"pts4q": 0,
|
||||
"pts1h": 0,
|
||||
"pts2h": 0,
|
||||
"Name1GFX": (item.get("firstName") or "").strip(),
|
||||
"Name2GFX": (item.get("lastName") or "").strip(),
|
||||
"photoGFX": (
|
||||
os.path.join(
|
||||
"D:\\Photos",
|
||||
LEAGUE.lower(),
|
||||
result[who]["name"],
|
||||
f"{item.get('displayNumber')}.png",
|
||||
)
|
||||
if item.get("startRole") == "Player"
|
||||
else ""
|
||||
),
|
||||
"isOnCourt": stats.get("isOnCourt", False),
|
||||
}
|
||||
# live-стата
|
||||
"pts": _as_int(stats.get("points")),
|
||||
"pt-2": f"{g2}/{s2}",
|
||||
"pt-3": f"{g3}/{s3}",
|
||||
"pt-1": f"{g1}/{s1}",
|
||||
"fg": f"{g2+g3}/{s2+s3}",
|
||||
"ast": _as_int(stats.get("assist")),
|
||||
"stl": _as_int(stats.get("steal")),
|
||||
"blk": _as_int(stats.get("block")),
|
||||
"blkVic": _as_int(stats.get("blocked")),
|
||||
"dreb": _as_int(stats.get("defReb")),
|
||||
"oreb": _as_int(stats.get("offReb")),
|
||||
"reb": _as_int(stats.get("defReb")) + _as_int(stats.get("offReb")),
|
||||
"to": _as_int(stats.get("turnover")),
|
||||
"foul": _as_int(stats.get("foul")),
|
||||
"foulT": _as_int(stats.get("foulT")),
|
||||
"foulD": _as_int(stats.get("foulD")),
|
||||
"foulC": _as_int(stats.get("foulC")),
|
||||
"foulB": _as_int(stats.get("foulB")),
|
||||
"fouled": _as_int(stats.get("foulsOn")),
|
||||
"plusMinus": _as_int(stats.get("plusMinus")),
|
||||
"dunk": _as_int(stats.get("dunk")),
|
||||
"kpi": (
|
||||
_as_int(stats.get("points"))
|
||||
+ _as_int(stats.get("defReb")) + _as_int(stats.get("offReb"))
|
||||
+ _as_int(stats.get("assist")) + _as_int(stats.get("steal")) + _as_int(stats.get("block"))
|
||||
+ _as_int(stats.get("foulsOn"))
|
||||
+ (g1 - s1) + (g2 - s2) + (g3 - s3)
|
||||
- _as_int(stats.get("turnover")) - _as_int(stats.get("foul"))
|
||||
),
|
||||
"time": format_time(_as_int(stats.get("second"))),
|
||||
|
||||
# сезон — средние (из последнего Avg)
|
||||
"AvgPoints": season_avg.get("points") or "0.0",
|
||||
"AvgAssist": season_avg.get("assist") or "0.0",
|
||||
"AvgBlocks": season_avg.get("blockShot") or "0.0",
|
||||
"AvgDefRebound": season_avg.get("defRebound") or "0.0",
|
||||
"AvgOffRebound": season_avg.get("offRebound") or "0.0",
|
||||
"AvgRebound": season_avg.get("rebound") or "0.0",
|
||||
"AvgSteal": season_avg.get("steal") or "0.0",
|
||||
"AvgTurnover": season_avg.get("turnover") or "0.0",
|
||||
"AvgFoul": season_avg.get("foul") or "0.0",
|
||||
"AvgOpponentFoul": season_avg.get("foulsOnPlayer") or "0.0",
|
||||
"AvgDunk": season_avg.get("dunk") or "0.0",
|
||||
"AvgPlayedTime": season_avg.get("playedTime") or "0:00",
|
||||
"Shot1Percent": season_avg.get("shot1Percent") or "0.0%",
|
||||
"Shot2Percent": season_avg.get("shot2Percent") or "0.0%",
|
||||
"Shot3Percent": season_avg.get("shot3Percent") or "0.0%",
|
||||
"Shot23Percent": season_avg.get("shot23Percent") or "0.0%",
|
||||
|
||||
# сезон — Totals (суммы из Sum + live)
|
||||
"TPoints": T_points,
|
||||
"TShots1": f"{T_g1}/{T_s1}",
|
||||
"TShots2": f"{T_g2}/{T_s2}",
|
||||
"TShots3": f"{T_g3}/{T_s3}",
|
||||
"TShots23": f"{T_g23}/{T_s23}",
|
||||
"TShot1Percent": _pct(T_g1, T_s1),
|
||||
"TShot2Percent": _pct(T_g2, T_s2),
|
||||
"TShot3Percent": _pct(T_g3, T_s3),
|
||||
"TShot23Percent": _pct(T_g23, T_s23),
|
||||
"TAssist": T_assist,
|
||||
"TBlocks": T_block,
|
||||
"TDefRebound": T_dreb,
|
||||
"TOffRebound": T_oreb,
|
||||
"TRebound": T_reb,
|
||||
"TSteal": T_steal,
|
||||
"TTurnover": T_turn,
|
||||
"TFoul": T_foul,
|
||||
"TPlayedTime": format_time(T_sec),
|
||||
"TGameCount": T_gms,
|
||||
"TStartCount": T_starts,
|
||||
|
||||
# карьера — средние (из последнего Avg)
|
||||
"Career_AvgPoints": career_avg.get("points") or "0.0",
|
||||
"Career_AvgAssist": career_avg.get("assist") or "0.0",
|
||||
"Career_AvgBlocks": career_avg.get("blockShot") or "0.0",
|
||||
"Career_AvgDefRebound": career_avg.get("defRebound") or "0.0",
|
||||
"Career_AvgOffRebound": career_avg.get("offRebound") or "0.0",
|
||||
"Career_AvgRebound": career_avg.get("rebound") or "0.0",
|
||||
"Career_AvgSteal": career_avg.get("steal") or "0.0",
|
||||
"Career_AvgTurnover": career_avg.get("turnover") or "0.0",
|
||||
"Career_AvgFoul": career_avg.get("foul") or "0.0",
|
||||
"Career_AvgOpponentFoul": career_avg.get("foulsOnPlayer") or "0.0",
|
||||
"Career_AvgDunk": career_avg.get("dunk") or "0.0",
|
||||
"Career_AvgPlayedTime": career_avg.get("playedTime") or "0:00",
|
||||
"Career_Shot1Percent": career_avg.get("shot1Percent") or "0.0%",
|
||||
"Career_Shot2Percent": career_avg.get("shot2Percent") or "0.0%",
|
||||
"Career_Shot3Percent": career_avg.get("shot3Percent") or "0.0%",
|
||||
"Career_Shot23Percent": career_avg.get("shot23Percent") or "0.0%",
|
||||
|
||||
# карьера — Totals (суммы из Sum + live)
|
||||
"Career_TPoints": car_T_points,
|
||||
"Career_TShots1": f"{car_T_g1}/{car_T_s1}",
|
||||
"Career_TShots2": f"{car_T_g2}/{car_T_s2}",
|
||||
"Career_TShots3": f"{car_T_g3}/{car_T_s3}",
|
||||
"Career_TShots23": f"{car_T_g23}/{car_T_s23}",
|
||||
"Career_TShot1Percent": _pct(car_T_g1, car_T_s1),
|
||||
"Career_TShot2Percent": _pct(car_T_g2, car_T_s2),
|
||||
"Career_TShot3Percent": _pct(car_T_g3, car_T_s3),
|
||||
"Career_TShot23Percent": _pct(car_T_g23, car_T_s23),
|
||||
"Career_TAssist": car_T_assist,
|
||||
"Career_TBlocks": car_T_block,
|
||||
"Career_TDefRebound": car_T_dreb,
|
||||
"Career_TOffRebound": car_T_oreb,
|
||||
"Career_TRebound": car_T_reb,
|
||||
"Career_TSteal": car_T_steal,
|
||||
"Career_TTurnover": car_T_turn,
|
||||
"Career_TFoul": car_T_foul,
|
||||
"Career_TPlayedTime": format_time(car_T_sec),
|
||||
"Career_TGameCount": car_T_gms,
|
||||
"Career_TStartCount": car_T_starts,
|
||||
|
||||
}
|
||||
team_rows.append(row)
|
||||
|
||||
# добиваем до 12 строк, чтобы UI был ровный
|
||||
|
||||
Reference in New Issue
Block a user