Compare commits

..

7 Commits

3 changed files with 123 additions and 25 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@
*.venv *.venv
*.env *.env
/shotmaps/* /shotmaps/*
get_data copy.py

View File

@@ -507,9 +507,6 @@ main() {
# Загрузка кода # Загрузка кода
download_code "$release" download_code "$release"
# Проверка файлов
check_required_files
# Настройка виртуального окружения # Настройка виртуального окружения
setup_venv setup_venv

View File

@@ -1,5 +1,5 @@
from fastapi import FastAPI, HTTPException, Request from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import Response, HTMLResponse, StreamingResponse from fastapi.responses import Response, HTMLResponse, StreamingResponse, JSONResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
import requests, uvicorn, json import requests, uvicorn, json
@@ -14,7 +14,7 @@ from dotenv import load_dotenv
from pprint import pprint from pprint import pprint
import nasio import nasio
import io, os, platform, time import io, os, platform, time
import xml.etree.ElementTree as ETя import xml.etree.ElementTree as ET
import re import re
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
from io import BytesIO from io import BytesIO
@@ -202,6 +202,8 @@ URLS = {
"live-status": "{host}api/abc/games/live-status?id={game_id}", "live-status": "{host}api/abc/games/live-status?id={game_id}",
"box-score": "{host}api/abc/games/box-score?id={game_id}", "box-score": "{host}api/abc/games/box-score?id={game_id}",
"play-by-play": "{host}api/abc/games/play-by-play?id={game_id}", "play-by-play": "{host}api/abc/games/play-by-play?id={game_id}",
"players-stats-league": "{host}/api/abc/comps/players-stats?tag={league}&lang={lang}&maxResultCount=10000",
"players-stats-season": "{host}/api/abc/comps/players-stats?tag={league}&season={season}&lang={lang}&maxResultCount=10000",
} }
@@ -1398,6 +1400,37 @@ async def lifespan(app: FastAPI):
) )
thread_excel.start() thread_excel.start()
try:
season_stats = requests.get(
URLS["players-stats-season"].format(
host=HOST, league=LEAGUE, season=season, lang=LANG
)
).json()
except Exception as ex:
season_stats = None
try:
league_stats = requests.get(
URLS["players-stats-league"].format(host=HOST, league=LEAGUE, lang=LANG)
).json()
except Exception as ex:
league_stats = None
# 👇 ДОБАВЬ ЭТО
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
if season_stats is not None:
latest_data["season_stats"] = {
"ts": ts,
"data": season_stats,
}
if league_stats is not None:
latest_data["league_stats"] = {
"ts": ts,
"data": league_stats,
}
# 👆 ДО СЮДА
# 4. запускаем "длинные" потоки (они у тебя и так всегда) # 4. запускаем "длинные" потоки (они у тебя и так всегда)
thread_result_consumer = threading.Thread( thread_result_consumer = threading.Thread(
target=results_consumer, target=results_consumer,
@@ -1473,16 +1506,6 @@ app = FastAPI(
redoc_url=None, # ❌ отключает /redoc redoc_url=None, # ❌ отключает /redoc
openapi_url=None, # ❌ отключает /openapi.json openapi_url=None, # ❌ отключает /openapi.json
) )
# раздаём /shotmaps как статику из SHOTMAP_DIR
# app.mount("/shotmaps", StaticFiles(directory=SHOTMAP_DIR), name="shotmaps")
# @app.get("/shotmaps/{filename}")
# async def get_shotmap(filename: str):
# data = SHOTMAP_CACHE.get(filename)
# if not data:
# # если вдруг перезапустился процесс или такой карты нет
# raise HTTPException(status_code=404, detail="Shotmap not found")
# return Response(content=data, media_type="image/png")
def format_time(seconds: float | int) -> str: def format_time(seconds: float | int) -> str:
@@ -1709,6 +1732,7 @@ async def status(request: Request):
), ),
} }
for item in sorted_keys for item in sorted_keys
if item not in ["league_stats", "season_stats"]
], ],
} }
@@ -2246,7 +2270,12 @@ async def team(who: str):
f"{item.get('displayNumber')}.png", f"{item.get('displayNumber')}.png",
) )
if item.get("startRole") == "Player" if item.get("startRole") == "Player"
else "" else os.path.join(
"D:\\Photos",
result["league"]["abcName"],
result[who]["name"],
f'Head Coach_{(item.get("lastName") or "").strip()} {(item.get("firstName") or "").strip()}.png',
)
), ),
# live-стата # live-стата
"pts": _as_int(stats.get("points")), "pts": _as_int(stats.get("points")),
@@ -3237,9 +3266,11 @@ def change_vmix_datasource_urls(xml_data, new_base_url: str) -> bytes:
root = ET.fromstring(text) root = ET.fromstring(text)
# 4. Меняем URL # 4. Меняем URL
for url_tag in root.findall(".//datasource[@friendlyName='JSON']//instance//state/xml/url"): for url_tag in root.findall(
".//datasource[@friendlyName='JSON']//instance//state/xml/url"
):
old_url = url_tag.text.strip() old_url = url_tag.text.strip()
pattern = r'https?:\/\/[^\/]+' pattern = r"https?:\/\/[^\/]+"
new_url = re.sub(pattern, new_base_url, old_url, count=0, flags=0) new_url = re.sub(pattern, new_base_url, old_url, count=0, flags=0)
url_tag.text = new_url url_tag.text = new_url
@@ -3633,6 +3664,7 @@ async def get_shotmap_image(player_id_shots: str):
Отдаёт картинку карты бросков из оперативной памяти. Отдаёт картинку карты бросков из оперативной памяти.
player_id_shots должен совпадать с ключом в SHOTMAP_CACHE, например "23_shots". player_id_shots должен совпадать с ключом в SHOTMAP_CACHE, например "23_shots".
""" """
# async with SHOTMAPS_LOCK:
data = SHOTMAP_CACHE.get(player_id_shots) data = SHOTMAP_CACHE.get(player_id_shots)
if not data: if not data:
raise HTTPException(status_code=404, detail="Shotmap not found in memory") raise HTTPException(status_code=404, detail="Shotmap not found in memory")
@@ -3738,6 +3770,30 @@ async def last_5_games():
if not CALENDAR or "items" not in CALENDAR: if not CALENDAR or "items" not in CALENDAR:
return {"opponent": "", "date": "", "place": "", "place_ru": ""} return {"opponent": "", "date": "", "place": "", "place_ru": ""}
WEEKDAYS_EN = [
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
"sunday",
]
MONTHS_EN = [
"january",
"february",
"march",
"april",
"may",
"june",
"july",
"august",
"september",
"october",
"november",
"december",
]
now = datetime.now() # наивное "сейчас" now = datetime.now() # наивное "сейчас"
best = None # {"dt": ..., "opp": ..., "place": "home"/"away"} best = None # {"dt": ..., "opp": ..., "place": "home"/"away"}
@@ -3779,11 +3835,23 @@ async def last_5_games():
place_ru = "дома" if best["place"] == "home" else "в гостях" place_ru = "дома" if best["place"] == "home" else "в гостях"
# 🆕 формируем английскую строку
dt = best["dt"]
weekday_en = WEEKDAYS_EN[dt.weekday()] # monday..sunday
month_en = MONTHS_EN[dt.month - 1] # january..december
day = dt.day # 1..31
place_en = "home" if best["place"] == "home" else "away"
formatted = (
f"{weekday_en}, {month_en} {day}, at {place_en} against {best['opp']}"
)
return { return {
"opponent": best["opp"], "opponent": best["opp"],
"date": best["dt"].strftime("%Y-%m-%d %H:%M"), "date": best["dt"].strftime("%Y-%m-%d %H:%M"),
"place": best["place"], # "home" / "away" "place": best["place"], # "home" / "away"
"place_ru": place_ru, # "дома" / "в гостях" "place_ru": place_ru, # "дома" / "в гостях"
"formatted": formatted, # 🆕 "wednesday, march 26, at home against astana"
} }
# последние 5 игр и результаты # последние 5 игр и результаты
@@ -3806,6 +3874,7 @@ async def last_5_games():
"nextGameDate": next1["date"], "nextGameDate": next1["date"],
"nextGamePlace": next1["place_ru"], # "дома" / "в гостях" "nextGamePlace": next1["place_ru"], # "дома" / "в гостях"
"nextGameHomeAway": next1["place"], # "home" / "away" (если нужно в логике) "nextGameHomeAway": next1["place"], # "home" / "away" (если нужно в логике)
"nextGameFormatted": next1["formatted"],
}, },
{ {
"teamName": team2_name, "teamName": team2_name,
@@ -3815,6 +3884,7 @@ async def last_5_games():
"nextGameDate": next2["date"], "nextGameDate": next2["date"],
"nextGamePlace": next2["place_ru"], "nextGamePlace": next2["place_ru"],
"nextGameHomeAway": next2["place"], "nextGameHomeAway": next2["place"],
"nextGameFormatted": next2["formatted"],
}, },
] ]
return data return data
@@ -3916,7 +3986,8 @@ async def commentary():
plus_minus = p.get("plusMinus", "") plus_minus = p.get("plusMinus", "")
kpi = p.get("kpi", "") kpi = p.get("kpi", "")
rows.append(f""" rows.append(
f"""
<tr> <tr>
<td>{num}</td> <td>{num}</td>
<td class="player-name" <td class="player-name"
@@ -3939,7 +4010,8 @@ async def commentary():
<td class="extra-col">{plus_minus}</td> <td class="extra-col">{plus_minus}</td>
<td class="extra-col">{kpi}</td> <td class="extra-col">{kpi}</td>
</tr> </tr>
""") """
)
return f""" return f"""
<h3>{title}</h3> <h3>{title}</h3>
@@ -4458,7 +4530,7 @@ async def dashboard():
# порядок вывода метрик в центральном столбце (как на твоём дашборде) # порядок вывода метрик в центральном столбце (как на твоём дашборде)
center_stats_order = [ center_stats_order = [
"pt-1", # штрафные "pt-1", # штрафные
"pt-1_pro", "pt-1_pro",
"pt-2", "pt-2",
"pt-2_pro", "pt-2_pro",
@@ -4901,6 +4973,34 @@ async def dashboard():
return HTMLResponse(content=html) return HTMLResponse(content=html)
@app.get("/game_history")
async def game_history():
pregame = get_latest_game_safe("pregame")
if not pregame:
return [{"Данных об истории команд нет!"}]
pregame_data = pregame["data"] if "data" in pregame else pregame
result = pregame_data.get("result", {}).get("gameHistory", {}) or {}
history = []
for row in result:
row_team1 = get_excel_row_for_team(row["team1"]["name"]) or {}
row_team2 = get_excel_row_for_team(row["team2"]["name"]) or {}
history.append(
{
"team1": row_team1.get("TeamTLA", ""),
"team2": row_team2.get("TeamTLA", ""),
"team1_logo": row_team1.get("TeamLogo", ""),
"team2_logo": row_team2.get("TeamLogo", ""),
"score1": row["game"]["score1"],
"score2": row["game"]["score2"],
"localDate": row["game"]["localDate"],
"team1Win": pregame_data.get("result", {}).get("team1Win"),
"team2Win": pregame_data.get("result", {}).get("team2Win"),
}
)
return history
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run( uvicorn.run(
"get_data:app", host="0.0.0.0", port=8000, reload=True, log_level="debug" "get_data:app", host="0.0.0.0", port=8000, reload=True, log_level="debug"