Compare commits

..

1 Commits

Author SHA1 Message Date
6feb69ce7c Merge pull request 'main to test' (#6) from main into Barabanov_TEST
Reviewed-on: #6
2025-10-22 17:06:36 +00:00
8 changed files with 3023 additions and 7666 deletions

4
.gitignore vendored
View File

@@ -2,7 +2,3 @@
/JSON/*
/logs/*
/static/*
get_data_new copy 2.py
get_data_new copy.py
temp.json
get_data_new copy 3.py

View File

@@ -1,302 +0,0 @@
[
{
"PlayTypeID": 0,
"PlayInfoSite": "-",
"PlayInfo": "Пустое событие"
},
{
"PlayTypeID": 1,
"PlayInfoSite": "1 очко",
"PlayInfo": "Штрафной бросок - попадание"
},
{
"PlayTypeID": 2,
"PlayInfoSite": "2 очка",
"PlayInfo": "2х очковый бросок - попадание"
},
{
"PlayTypeID": 3,
"PlayInfoSite": "3 очка",
"PlayInfo": "3х очковый бросок - попадание"
},
{
"PlayTypeID": 4,
"PlayInfoSite": "Мимо 1 очко",
"PlayInfo": "Штрафной бросок - промах"
},
{
"PlayTypeID": 5,
"PlayInfoSite": "Мимо 2 очка",
"PlayInfo": "2х очковый бросок - промах"
},
{
"PlayTypeID": 6,
"PlayInfoSite": "Мимо 3 очка",
"PlayInfo": "3х очковый бросок - промах"
},
{
"PlayTypeID": 7,
"PlayInfoSite": "Замена",
"PlayInfo": "Замена игроков"
},
{
"PlayTypeID": 8,
"PlayInfoSite": "Выход",
"PlayInfo": "Выход на площадку"
},
{
"PlayTypeID": 9,
"PlayInfoSite": "Уход",
"PlayInfo": "Ушел с площадки"
},
{
"PlayTypeID": 10,
"PlayInfoSite": "Потеря",
"PlayInfo": "Потеря"
},
{
"PlayTypeID": 11,
"PlayInfoSite": "Пас-потеря",
"PlayInfo": "Потеря мяча при передаче"
},
{
"PlayTypeID": 12,
"PlayInfoSite": "Пробежка",
"PlayInfo": "Пробежка"
},
{
"PlayTypeID": 13,
"PlayInfoSite": "Заступ в аут",
"PlayInfo": "Заступ в аут"
},
{
"PlayTypeID": 14,
"PlayInfoSite": "Зона",
"PlayInfo": "Зона"
},
{
"PlayTypeID": 15,
"PlayInfoSite": "Двойное ведение",
"PlayInfo": "Двойное ведение"
},
{
"PlayTypeID": 16,
"PlayInfoSite": "3 секунды",
"PlayInfo": "3 секунды"
},
{
"PlayTypeID": 17,
"PlayInfoSite": "5 секунд",
"PlayInfo": "5 секунд"
},
{
"PlayTypeID": 18,
"PlayInfoSite": "8 секунд",
"PlayInfo": "8 секунд"
},
{
"PlayTypeID": 19,
"PlayInfoSite": "24 секунды",
"PlayInfo": "24 секунды"
},
{
"PlayTypeID": 20,
"PlayInfoSite": "Потеря мяча",
"PlayInfo": "Потеря мяча"
},
{
"PlayTypeID": 21,
"PlayInfoSite": "Начало четверти",
"PlayInfo": "Начало четверти?"
},
{
"PlayTypeID": 22,
"PlayInfoSite": "",
"PlayInfo": "Окончание четверти?"
},
{
"PlayTypeID": 23,
"PlayInfoSite": "Тайм-аут",
"PlayInfo": "Тайм-аут"
},
{
"PlayTypeID": 24,
"PlayInfoSite": "",
"PlayInfo": "неизвестно"
},
{
"PlayTypeID": 25,
"PlayInfoSite": "Пас",
"PlayInfo": "Передача"
},
{
"PlayTypeID": 26,
"PlayInfoSite": "Перехват",
"PlayInfo": "Перехват"
},
{
"PlayTypeID": 27,
"PlayInfoSite": "Блокшот",
"PlayInfo": "Блокшот"
},
{
"PlayTypeID": 28,
"PlayInfoSite": "Подбор",
"PlayInfo": "Подбор"
},
{
"PlayTypeID": 29,
"PlayInfoSite": "Быстрый отрыв",
"PlayInfo": "Быстрый отрыв"
},
{
"PlayTypeID": 30,
"PlayInfoSite": "Бросок сверху",
"PlayInfo": "Тип броска"
},
{
"PlayTypeID": 31,
"PlayInfoSite": "В прыжке",
"PlayInfo": "Тип броска"
},
{
"PlayTypeID": 32,
"PlayInfoSite": "В проходе",
"PlayInfo": "Тип броска"
},
{
"PlayTypeID": 33,
"PlayInfoSite": "Добивание",
"PlayInfo": "Тип броска"
},
{
"PlayTypeID": 34,
"PlayInfoSite": "Навес",
"PlayInfo": "Тип броска"
},
{
"PlayTypeID": 35,
"PlayInfoSite": "Бросок крюком",
"PlayInfo": "Тип броска"
},
{
"PlayTypeID": 40,
"PlayInfoSite": "Фол P",
"PlayInfo": "Фол персональный"
},
{
"PlayTypeID": 41,
"PlayInfoSite": "Фол U",
"PlayInfo": "Фол неспортивный"
},
{
"PlayTypeID": 42,
"PlayInfoSite": "Фол T",
"PlayInfo": "Фол технический"
},
{
"PlayTypeID": 43,
"PlayInfoSite": "Фол D",
"PlayInfo": "Фол дисквалифицирующий"
},
{
"PlayTypeID": 44,
"PlayInfoSite": "Фол C",
"PlayInfo": "Фол технический главному тренеру за его личное неспортивное поведение"
},
{
"PlayTypeID": 45,
"PlayInfoSite": "Фол В",
"PlayInfo": "Фол технический главному тренеру по любой другой причине"
},
{
"PlayTypeID": 47,
"PlayInfoSite": "В нападении",
"PlayInfo": "Тип фола"
},
{
"PlayTypeID": 50,
"PlayInfoSite": "Вбрасывание",
"PlayInfo": "Тип фола"
},
{
"PlayTypeID": 51,
"PlayInfoSite": "1 бросок",
"PlayInfo": "Назначение 1го штрафного броска"
},
{
"PlayTypeID": 52,
"PlayInfoSite": "2 броска",
"PlayInfo": "Назначение 2х штрафных бросков"
},
{
"PlayTypeID": 53,
"PlayInfoSite": "3 броска",
"PlayInfo": "Назначение 3х штрафных бросков"
},
{
"PlayTypeID": 54,
"PlayInfoSite": "Компенсация",
"PlayInfo": "Обоюдное пробивание штрафных"
},
{
"PlayTypeID": 59,
"PlayInfoSite": "",
"PlayInfo": "пробел для сайта"
},
{
"PlayTypeID": 60,
"PlayInfoSite": "Разминка команд",
"PlayInfo": "Разминка команд"
},
{
"PlayTypeID": 61,
"PlayInfoSite": "Представление команд",
"PlayInfo": "Представление команд"
},
{
"PlayTypeID": 62,
"PlayInfoSite": "Менее 3 минут до начала игры",
"PlayInfo": "Менее 3 минут до начала игры"
},
{
"PlayTypeID": 63,
"PlayInfoSite": "Игра сейчас начнется",
"PlayInfo": "Игра сейчас начнется"
},
{
"PlayTypeID": 69,
"PlayInfoSite": "Видеопросмотр",
"PlayInfo": "Видеопросмотр?"
},
{
"PlayTypeID": 70,
"PlayInfoSite": "",
"PlayInfo": "Возможно остановка игры?"
},
{
"PlayTypeID": 71,
"PlayInfoSite": "",
"PlayInfo": "Возможно возобновление игры?"
},
{
"PlayTypeID": 72,
"PlayInfoSite": "Видеопросмотр",
"PlayInfo": "Видеопросмотр?"
},
{
"PlayTypeID": 73,
"PlayInfoSite": "Старт видеотрансляции",
"PlayInfo": "Старт видеотрансляции"
},
{
"PlayTypeID": 74,
"PlayInfoSite": "Конец видеотрансляции",
"PlayInfo": "Конец видеотрансляции"
},
{
"PlayTypeID": 100,
"PlayInfoSite": "Матч завершен",
"PlayInfo": "Матч завершен"
}
]

122
README.md
View File

@@ -1,5 +1,3 @@
![VTB logo](https://vtb-league.com/docs/brand/2019/VTB%20League%20logo%20RGB.png)
RFB Stat - это система для автоматического сбора баскетбольной статистики.
Система состоит из двух основных компонентов:
@@ -9,18 +7,21 @@ RFB Stat - это система для автоматического сбор
Требования
* Ubuntu Linux
* Доступ к репозиторию `https://git.tvstart.ru/`
* *Права root для установки!!*
Ubuntu Linux
# Установка
Доступ к репозиторию https://git.tvstart.ru/ychernenko/RFB.git
Права root для установки!!
Установка
Автоматическая установка
Скрипт установки выполняет полную настройку системы:
bash
```shell
chmod +x deploy.sh
./deploy.sh -t <команда> -r <релиз>
```
Параметры командной строки
-t - Название команды (опционально, определяется по IP если не указано)
@@ -29,77 +30,100 @@ chmod +x deploy.sh
-h - Справка по использованию
# Примеры использования
Примеры использования
bash
Установка с автоопределением команды по IP
```shell
# Установка с автоопределением команды по IP
./deploy.sh -r main
```
Установка для конкретной команды
```shell
# Установка для конкретной команды
./deploy.sh -t cska -r main
```
Установка тестовой версии
```shell
# Установка тестовой версии
./deploy.sh -t zenit -r Barabanov_TEST
```
# Пакеты и прочее что будет установленно:
Системные пакеты:
* Python3
* pip3
* virtualenv
* Git
* Net-tools
Python3, pip, virtualenv
Git
Systemd
Net-tools
Виртуальное окружение Python с зависимостями:
* streamlit
* requests
* pandas
* numpy
* plotly
* watchdog
* pillow
* streamlit_autorefresh
streamlit
# Systemd сервисы:
requests
`rfb-data.service - сбор данных`
pandas
`rfb-visual.service - веб-интерфейс`
numpy
plotly
watchdog
pillow
streamlit_autorefresh
Systemd сервисы:
rfb-data.service - сбор данных
rfb-visual.service - веб-интерфейс
Настройка firewall для порта 8501
# Управление сервисами
Просмотр статуса обоих сервисов одновременно
```shell
Просмотр статуса
systemctl status rfb-data.service rfb-visual.service
```
# Просмотр логов
Логи сбора данных
```shell
journalctl -u rfb-data.service -f
```
Логи веб-интерфейса
```shell
journalctl -u rfb-visual.service -f
```
'journalctl -u rfb-visual.service -f'
#Управление сервисами
Перезапуск всех сервисов
```shell
systemctl restart rfb-data.service rfb-visual.service
```
Остановка всех сервисов
```shell
systemctl stop rfb-data.service rfb-visual.service
```
Запуск всех сервисов
```shell
systemctl start rfb-data.service rfb-visual.service
```
# Доступ к приложению
После установки приложение доступно по адресу:
text
`http://ВАШ_IP_АДРЕС:8501`
http://ВАШ_IP_АДРЕС:8501
#Структура проекта
/root/RFB/
├── .venv/ # Виртуальное окружение Python
├── visual.py # Веб-интерфейс Streamlit
├── get_data.py # Скрипт сбора данных
├── requirements.txt # Зависимости Python
└── start_rfb.sh # Скрипт запуска (устарел)
Проверьте настройки firewall:
bash
ufw status
Контакты
Для получения технической поддержки обращайтесь к разработчикам системы.

View File

@@ -7,15 +7,13 @@ YELLOW='\033[1;33m'
NC='\033[0m' # No Color
show_help() {
echo "Использование: $0 -t <домашняя команда> -r <релиз> [-l <лига>]"
echo "Использование: $0 -t <домашняя команда> -r <релиз>"
echo " -t Домашняя команда"
echo " -r Релиз (тег или ветка в git)"
echo " -l Лига (опционально)"
echo " -h Показать эту справку"
echo ""
echo "Пример: $0 -t cska -r main"
echo "Пример: $0 -t zenit -r Barabanov_TEST -l vtb"
echo "Пример: $0 -t avtodor -r main"
echo "Пример: $0 -t zenit -r Barabanov_TEST"
echo ""
exit 0
}
@@ -179,12 +177,8 @@ print('✓ Все основные пакеты успешно импортир
# Функция создания systemd сервисов
create_systemd_services() {
local team="$1"
local league="$2"
log_info "Создание отдельных systemd сервисов для команды: $team"
if [ -n "$league" ]; then
log_info "Лига: $league"
fi
# Останавливаем и отключаем старые сервисы если они есть
for service in rfb-data.service rfb-visual.service rfb-stat.service; do
@@ -198,12 +192,6 @@ create_systemd_services() {
fi
done
# Формируем команду для data сервиса
local data_command="/root/RFB/.venv/bin/python3 /root/RFB/get_data.py --team \"$team\""
if [ -n "$league" ]; then
data_command="$data_command --league \"$league\""
fi
# Сервис для data
local data_service_file="/etc/systemd/system/rfb-data.service"
@@ -217,7 +205,7 @@ Type=simple
User=root
WorkingDirectory=/root/RFB
Environment=PATH=/root/RFB/.venv/bin
ExecStart=$data_command
ExecStart=/root/RFB/.venv/bin/python3 /root/RFB/get_data.py --team $team
Restart=always
RestartSec=10
StandardOutput=journal
@@ -405,14 +393,12 @@ check_services_status() {
main() {
local team=""
local release="main" # значение по умолчанию
local league="" # новая переменная для лиги
# Обработка аргументов командной строки
while getopts "t:r:l:h" opt; do
while getopts "t:r:h" opt; do
case $opt in
t) team="$OPTARG" ;;
r) release="$OPTARG" ;;
l) league="$OPTARG" ;;
h) show_help ;;
*) log_error "Неверный аргумент"; exit 1 ;;
esac
@@ -420,11 +406,6 @@ main() {
log_info "Начало установки RFB Stat..."
log_info "Команда: $team, Релиз: $release"
if [ -n "$league" ]; then
log_info "Лига: $league"
else
log_info "Лига: не указана (будет использовано значение по умолчанию)"
fi
# Проверка прав root
if [[ $EUID -ne 0 ]]; then
@@ -458,7 +439,7 @@ main() {
check_port
# Создание systemd сервисов
create_systemd_services "$final_team" "$league"
create_systemd_services "$final_team"
log_info "Настройка завершена!"
@@ -475,9 +456,6 @@ main() {
log_info "Установка завершена успешно!"
log_info "Приложение должно быть доступно по адресу: http://${ip_address}:8501"
log_info "Команда: $final_team"
if [ -n "$league" ]; then
log_info "Лига: $league"
fi
log_info "Режим: $release"
log_info ""
log_info "Для просмотра логов:"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

184
visual.py
View File

@@ -19,7 +19,7 @@ st.set_page_config(
page_icon="🏀",
layout="wide",
initial_sidebar_state="expanded",
menu_items={"About": "версия 3.2 от 23.10.2025"},
menu_items={"About": "версия 2.0 от 08.10.2025"},
)
REMOVE_PADDING_FROM_SIDES = """
<style>
@@ -203,6 +203,8 @@ def process_player_data(team_json, player_index):
"time": str(player_data["AvgCarPlayedTime"]),
}
return [season_total, season_avg, career_total, career_avg], player_data
@@ -283,6 +285,10 @@ if "player2" not in st.session_state:
st.session_state.player2 = None
myhost = platform.node()
if sys.platform.startswith("win"): # было: if platform == "win32":
FOLDER_JSON = "JSON"
else:
FOLDER_JSON = "static"
@@ -408,7 +414,9 @@ def rewrite_file(filename: str, data: dict, directory: str = "JSON") -> None:
os.makedirs(directory, exist_ok=True)
filepath = os.path.join(directory, f"{filename}.json")
host_prefix = _ipcheck()
filepath = os.path.join(directory, f"{host_prefix}{filename}.json")
# print(filepath) # оставил как у тебя; можно заменить на logger.debug при желании
try:
@@ -423,51 +431,53 @@ ip_check = read_match_id_json("match_id.json") or {}
prefix = _ipcheck()
load_data_from_json("game")
cached_game_online = st.session_state.get("game")
load_data_from_json(f"{prefix}game_online")
cached_game_online = st.session_state.get("game_online")
load_data_from_json("api_game")
cached_api_game = st.session_state.get("api_game")
load_data_from_json("team1")
load_data_from_json(f"{prefix}team1")
cached_team1 = st.session_state.get("team1")
load_data_from_json("team2")
load_data_from_json(f"{prefix}team2")
cached_team2 = st.session_state.get("team2")
load_data_from_json("referee")
load_data_from_json(f"{prefix}referee")
cached_referee = st.session_state.get("referee")
# standings — может не быть тега/файла
league_tag = None
if isinstance(cached_api_game, dict):
league_tag = (cached_api_game["result"].get("league") or {}).get("tag")
comp_name = (cached_api_game["result"].get("comp") or {}).get("name").replace(" ", "_").replace("|", "")
if isinstance(cached_game_online, dict):
league_tag = ((cached_game_online.get("result") or {}).get("league") or {}).get(
"tag"
)
comp_name = (
((cached_game_online.get("result") or {}).get("comp") or {})
.get("name")
.replace(" ", "_")
)
if league_tag:
load_data_from_json(f"standings_{league_tag}_{comp_name}")
load_data_from_json(f"{prefix}standings_{league_tag}_{comp_name}")
cached_standings = (
st.session_state.get(f"standings_{league_tag}_{comp_name}") if league_tag else None
)
load_data_from_json("scores_quarter")
load_data_from_json(f"{prefix}scores_quarter")
cached_scores_quarter = st.session_state.get("scores_quarter")
load_data_from_json("play_by_play")
load_data_from_json(f"{prefix}play_by_play")
cached_play_by_play = st.session_state.get("play_by_play")
load_data_from_json("team_stats")
load_data_from_json(f"{prefix}team_stats")
cached_team_stats = st.session_state.get("team_stats")
load_data_from_json("scores")
load_data_from_json(f"{prefix}scores")
cached_scores = st.session_state.get("scores") or [] # важно!
load_data_from_json("api_live-status")
cached_live_status = st.session_state.get("api_live-status")
load_data_from_json(f"{prefix}live_status")
cached_live_status = st.session_state.get("live_status")
load_data_from_json("schedule")
load_data_from_json(f"{prefix}schedule")
cached_schedule = st.session_state.get("schedule")
load_data_from_json("team_comparison")
load_data_from_json(f"{prefix}team_comparison")
cached_team_comparison = st.session_state.get("team_comparison")
@@ -559,8 +569,8 @@ if isinstance(cached_play_by_play, list) and isinstance(cached_game_online, dict
timeout1 = []
timeout2 = []
if isinstance(cached_api_game, dict):
result = cached_api_game["result"]
if isinstance(cached_game_online, dict):
result = cached_game_online.get("result") or {}
plays = result.get("plays") or []
timeout1, timeout2 = [], []
@@ -575,7 +585,6 @@ if isinstance(cached_api_game, dict):
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 ""
@@ -609,16 +618,18 @@ if isinstance(cached_api_game, dict):
col4_3.metric("TimeOuts", len(timeout1))
col5_1.metric("TimeOuts", len(timeout2))
if isinstance([cached_live_status], list) and cached_live_status:
foulsA = (cached_live_status["result"] or {}).get("foulsA")
foulsB = (cached_live_status["result"] or {}).get("foulsB")
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)
if foulsB is not None:
col5_2.metric("Fouls", foulsB)
if isinstance(cached_game_online, dict) and (cached_game_online.get("plays") or []):
if isinstance(cached_game_online, dict) and (
(cached_game_online.get("result") or {}).get("plays") or []
):
col_1_col = [f"col_1_{i}" for i in range(1, period_max + 1)]
col_2_col = [f"col_2_{i}" for i in range(1, period_max + 1)]
count_q = 0
@@ -827,7 +838,6 @@ columns_game = [
if cached_team1 and cached_team2:
team1_data = process_team_data(cached_team1, columns_game)
team2_data = process_team_data(cached_team2, columns_game)
# Добавляем звездочку, если pts > PTS_Career_High
def _get_first_number(x):
"""Безопасно вытащить число из строки/значения (например '12 (60%)' -> 12)."""
@@ -837,7 +847,6 @@ if cached_team1 and cached_team2:
s = str(x)
# заберём ведущие число/знак (поддержим +/-)
import re
m = re.search(r"[-+]?\d+(\.\d+)?", s)
return float(m.group(0)) if m else None
except Exception:
@@ -851,7 +860,6 @@ if cached_team1 and cached_team2:
"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 = {}
@@ -874,9 +882,7 @@ if cached_team1 and cached_team2:
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)]
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:
@@ -912,24 +918,17 @@ if cached_team1 and cached_team2:
df[col] = new_vals # теперь это текст для отображения
STAR_COLUMNS = [
"pts",
"ast",
"stl",
"blk",
"reb",
"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
]
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)
@@ -948,34 +947,18 @@ if cached_team1 and cached_team2:
# .apply(highlight_max, subset="kpi")
# )
team1_styled = (
team1_data[columns_game]
.style.apply(highlight_grey, axis=1)
team1_data[columns_game].style
.apply(highlight_grey, axis=1)
.apply(highlight_foul, subset="foul")
.apply(
lambda _: highlight_max_by_refcol(team1_data, "pts", "_pts_num"),
axis=0,
subset=["pts"],
)
.apply(
lambda _: highlight_max_by_refcol(team1_data, "kpi", "_kpi_num"),
axis=0,
subset=["kpi"],
)
.apply(lambda _: highlight_max_by_refcol(team1_data, "pts", "_pts_num"), axis=0, subset=["pts"])
.apply(lambda _: highlight_max_by_refcol(team1_data, "kpi", "_kpi_num"), axis=0, subset=["kpi"])
)
team2_styled = (
team2_data[columns_game]
.style.apply(highlight_grey, axis=1)
team2_data[columns_game].style
.apply(highlight_grey, axis=1)
.apply(highlight_foul, subset="foul")
.apply(
lambda _: highlight_max_by_refcol(team2_data, "pts", "_pts_num"),
axis=0,
subset=["pts"],
)
.apply(
lambda _: highlight_max_by_refcol(team2_data, "kpi", "_kpi_num"),
axis=0,
subset=["kpi"],
)
.apply(lambda _: highlight_max_by_refcol(team2_data, "pts", "_pts_num"), axis=0, subset=["pts"])
.apply(lambda _: highlight_max_by_refcol(team2_data, "kpi", "_kpi_num"), axis=0, subset=["kpi"])
)
def get_player_all_game(player_data_1):
@@ -1244,8 +1227,8 @@ column_config_ref = {
def highlight_teams(s):
try:
if s.iloc[0] in (
cached_api_game["result"]["team1"]["teamId"],
cached_api_game["result"]["team2"]["teamId"],
cached_game_online["result"]["team1"]["teamId"],
cached_game_online["result"]["team2"]["teamId"],
):
return ["background-color: #FF4B4B"] * len(s)
else:
@@ -1259,10 +1242,16 @@ if cached_standings:
def highlight_teams(s):
try:
t1 = (cached_api_game["result"] or {}).get("team1", {}).get("teamId")
t2 = (cached_api_game["result"] or {}).get("team2", {}).get("teamId")
if s.iloc[0] in (t1, t2):
return ["background-color: #FF4B4B"] * len(s)
t1 = (
((cached_game_online or {}).get("result") or {})
.get("team1", {})
.get("teamId")
)
t2 = (
((cached_game_online or {}).get("result") or {})
.get("team2", {})
.get("teamId")
)
if s.iloc[0] in (t1, t2):
return ["background-color: #FF4B4B"] * len(s)
except Exception:
@@ -1293,7 +1282,7 @@ if cached_standings:
column_config={"logo": st.column_config.ImageColumn("logo")},
hide_index=True,
height=610,
width="content",
width="content"
)
@@ -1359,7 +1348,7 @@ if isinstance(cached_scores_quarter, list) and len(cached_scores_quarter) >= 2:
if isinstance(cached_play_by_play, list) and isinstance(cached_game_online, dict):
plays = cached_game_online.get("plays") or []
plays = (cached_game_online.get("result") or {}).get("plays") or []
if plays:
tab_temp_6.table(cached_play_by_play)
@@ -1869,7 +1858,8 @@ try:
except (FileNotFoundError, json.JSONDecodeError):
play_type_id = []
teams_section = (cached_game_online or {}).get("teams") or {}
teams_section = ((cached_game_online or {}).get("result") or {}).get("teams") or {}
# Если teams_section — список (например, [{"starts": [...]}, {...}])
if isinstance(teams_section, list):
if len(teams_section) >= 2:
@@ -1882,7 +1872,6 @@ if isinstance(teams_section, list):
else:
starts1 = []
starts2 = []
# Если teams_section — словарь (обычно {"1": {...}, "2": {...}})
elif isinstance(teams_section, dict):
starts1 = (teams_section.get(1) or teams_section.get("1") or {}).get("starts") or []
@@ -1928,41 +1917,6 @@ def get_event_time(row):
return None
teams_section = (cached_game_online or {}).get("teams") or {}
# Если teams_section — список (например, [{"starts": [...]}, {...}])
if isinstance(teams_section, list):
if len(teams_section) >= 2:
starts1 = next(
(t.get("starts") for t in teams_section if t["teamNumber"] == 1), None
)
starts2 = next(
(t.get("starts") for t in teams_section if t["teamNumber"] == 2), None
)
else:
starts1 = []
starts2 = []
# Если teams_section — словарь (обычно {"1": {...}, "2": {...}})
elif isinstance(teams_section, dict):
starts1 = (teams_section.get(1) or teams_section.get("1") or {}).get("starts") or []
starts2 = (teams_section.get(2) or teams_section.get("2") or {}).get("starts") or []
else:
starts1 = []
starts2 = []
teams_temp = sorted(
[x for x in starts1 if isinstance(x, dict)], key=lambda x: x.get("playerNumber", 0)
) + sorted(
[x for x in starts2 if isinstance(x, dict)], key=lambda x: x.get("playerNumber", 0)
)
list_fullname = [None] + [
f"({x.get('displayNumber')}) {x.get('firstName','')} {x.get('lastName','')}".strip()
for x in teams_temp
if x.get("startRole") == "Player"
]
plays = plays if 'plays' in locals() else []
with tab_pbp:
plays = ((cached_game_online or {}).get("result") or {}).get("plays") or []
if plays: