This commit is contained in:
2025-10-22 10:40:32 +00:00
24 changed files with 720 additions and 3691605 deletions

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

13
match_id.json Normal file
View 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"}
}

View File

@@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -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": ""
}
]

View File

@@ -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
}
]

View File

@@ -1 +0,0 @@
[]

View File

@@ -1 +0,0 @@
[]

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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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)

11252
temp.json

File diff suppressed because it is too large Load Diff

322
visual.py
View File

@@ -181,7 +181,31 @@ def process_player_data(team_json, player_index):
"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 = {
@@ -190,24 +214,24 @@ config = {
"num": st.column_config.TextColumn("#", width=27),
"NameGFX": st.column_config.TextColumn(width=170),
"isOn": st.column_config.TextColumn("🏀", width=27),
"pts": st.column_config.NumberColumn("PTS", width=27),
"pt-2": st.column_config.TextColumn("2-PT", width=45),
"pt-3": st.column_config.TextColumn("3-PT", width=45),
"pt-1": st.column_config.TextColumn("FT", width=45),
"fg": st.column_config.TextColumn("FG", width=45),
"ast": st.column_config.NumberColumn("AS", width=27),
"stl": st.column_config.NumberColumn("ST", width=27),
"blk": st.column_config.NumberColumn("BL", width=27),
"blkVic": st.column_config.NumberColumn("BV", width=27),
"dreb": st.column_config.NumberColumn("DR", width=27),
"oreb": st.column_config.NumberColumn("OR", width=27),
"reb": st.column_config.NumberColumn("R", width=27),
"to": st.column_config.NumberColumn("TO", width=27),
"foul": st.column_config.NumberColumn("F", width=27),
"fouled": st.column_config.NumberColumn("Fed", width=27),
"plusMinus": st.column_config.NumberColumn("+/-", width=27),
"dunk": st.column_config.NumberColumn("DUNK", width=27),
"kpi": st.column_config.NumberColumn("KPI", width=27),
"pts": st.column_config.TextColumn("PTS", width="content", help="⭐ = Career High"),
"pt-2": st.column_config.TextColumn("2-PT", width="content"),
"pt-3": st.column_config.TextColumn("3-PT", width="content"),
"pt-1": st.column_config.TextColumn("FT", width="content"),
"fg": st.column_config.TextColumn("FG", width="content"),
"ast": st.column_config.TextColumn("AS", width="content"),
"stl": st.column_config.TextColumn("ST", width="content"),
"blk": st.column_config.TextColumn("BL", width="content"),
"blkVic": st.column_config.TextColumn("BV", width="content"),
"dreb": st.column_config.TextColumn("DR", width="content"),
"oreb": st.column_config.TextColumn("OR", width="content"),
"reb": st.column_config.TextColumn("R", width="content"),
"to": st.column_config.TextColumn("TO", width="content"),
"foul": st.column_config.TextColumn("F", width="content"),
"fouled": st.column_config.TextColumn("Fed", width="content"),
"plusMinus": st.column_config.TextColumn("+/-", width="content"),
"dunk": st.column_config.TextColumn("DUNK", width="content"),
"kpi": st.column_config.TextColumn("KPI", width="content"),
"time": st.column_config.TextColumn("TIME"),
"game_count": st.column_config.TextColumn("G", width=27),
"start_count": st.column_config.TextColumn("S", width=27),
@@ -813,19 +837,127 @@ 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)."""
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_data.style.apply(highlight_grey, axis=1)
.apply(highlight_foul, subset="foul")
.apply(highlight_max, subset="pts")
.apply(highlight_max, subset="kpi")
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"])
)
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_max, subset="pts")
.apply(highlight_max, 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):
@@ -863,29 +995,28 @@ if cached_team1 and cached_team2:
# Сортировка от последнего матча к первому
df_filtered = df_filtered.sort_values(by="game.gameDate", ascending=False)
# Указать нужные колонки для вывода
columns_to_show = [
"season",
"game.gameDate",
"game.team1Name",
"game.team2Name",
"game.score",
"stats.points",
"stats.shot2Percent",
"stats.shot3Percent",
"stats.shot23Percent",
"stats.shot1Percent",
"stats.assist",
"stats.steal",
"stats.blockShot",
"stats.defRebound",
"stats.offRebound",
"stats.rebound",
"stats.turnover",
"stats.foul",
"stats.playedTime",
"stats.plusMinus",
"Сезон",
"Дата",
"Команда 1",
"Команда 2",
"Счёт",
"PTS",
"2-PTS%",
"3-PTS%",
"FG%",
"FT%",
"AST",
"STL",
"BLK",
"DR",
"OR",
"REB",
"TO",
"F",
"TIME",
"+/-",
]
numeric_cols = [
"stats.points",
@@ -897,24 +1028,63 @@ if cached_team1 and cached_team2:
# df_filtered[numeric_cols] = df_filtered[numeric_cols].apply(
# 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 = df_filtered.rename(columns=rename_map)
df_filtered["Дата"] = df_filtered["Дата"].dt.strftime("%d.%m.%Y")
styled = (
df_filtered[columns_to_show]
.style
.apply(highlight_max, subset=["stats.points"])
.apply(highlight_max, subset=["stats.assist"])
.apply(highlight_max, subset=["stats.steal"])
.apply(highlight_max, subset=["stats.blockShot"])
.apply(highlight_max, subset=["stats.rebound"])
.style.apply(highlight_max, subset=["PTS"])
.apply(highlight_max, subset=["AST"])
.apply(highlight_max, subset=["STL"])
.apply(highlight_max, subset=["BLK"])
.apply(highlight_max, subset=["REB"])
.format(
{
"Дата": lambda x: x, # уже строка, просто оставляем как есть
"PTS": "{:,.0f}".format,
"AST": "{:,.0f}".format,
"STL": "{:,.0f}".format,
"BLK": "{:,.0f}".format,
"REB": "{:,.0f}".format,
}
)
)
return styled
# Вывод данных
col_player1, col_player2 = tab_temp_1.columns((5, 5))
config_copy = config.copy()
event1 = col_player1.dataframe(
team1_styled,
column_config=config,
column_config=config_copy,
hide_index=True,
height=460,
on_select="rerun",
@@ -960,7 +1130,8 @@ if cached_team1 and cached_team2:
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"):
selected_index2 = event2.selection["rows"][0]
@@ -988,7 +1159,8 @@ if cached_team1 and cached_team2:
column_config=config_season,
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))
if isinstance(cached_team_stats, list) and len(cached_team_stats) >= 34:
@@ -1085,12 +1257,31 @@ if cached_standings:
pass
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(
styled,
column_config={"logo": st.column_config.ImageColumn("logo")},
hide_index=True,
height=610,
width="content"
)
@@ -1534,23 +1725,22 @@ with tab_online:
live_data_map = {}
# Собираем live-данные по каждому game.id
for _, row in df_filtered.iterrows():
game_id = row["game.id"]
try:
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()
except Exception as ex:
# json_data = {
# "period": None,
# "timeToGo": 0.0,
# }
print(ex)
# Берём содержимое result (словарь с gameId и данными)
result = json_data.get("result", {})
if result and "gameId" in result:
live_data_map[result["gameId"]] = result
if json_data:
result = json_data.get("result", {})
if result and "gameId" in result:
live_data_map[result["gameId"]] = result
# Создаём колонки для live-данных
df_filtered["live_period"] = None

View File

@@ -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 нет.")