Compare commits

..

7 Commits

3 changed files with 123 additions and 25 deletions

3
.gitignore vendored
View File

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

View File

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

View File

@@ -1,5 +1,5 @@
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 contextlib import asynccontextmanager
import requests, uvicorn, json
@@ -14,7 +14,7 @@ from dotenv import load_dotenv
from pprint import pprint
import nasio
import io, os, platform, time
import xml.etree.ElementTree as ETя
import xml.etree.ElementTree as ET
import re
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
@@ -202,6 +202,8 @@ URLS = {
"live-status": "{host}api/abc/games/live-status?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}",
"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",
}
@@ -687,11 +689,11 @@ def results_consumer():
"game over",
]
# 1) матч ЗАКОНЧЕН → запускаем ОТСРОЧЕННЫЙ переход
# 1) матч ЗАКОНЧЕН → запускаем ОТСРОЧЕННЫЙ переход
# ##TODO - Усложненый код? Нужен, если статус бывает сложнее, чем "result",
# а что-то в стиле "result 1:0", т.е. слова из finished_markers являются
# а что-то в стиле "result 1:0", т.е. слова из finished_markers являются
# состовной частью настоящего статуса
# В противном случае вполне рабочий вариант:
# В противном случае вполне рабочий вариант:
# if raw_ls_status_low in finished_markers:
if any(m in raw_ls_status_low for m in finished_markers):
now_ts = time.time()
@@ -1398,6 +1400,37 @@ async def lifespan(app: FastAPI):
)
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. запускаем "длинные" потоки (они у тебя и так всегда)
thread_result_consumer = threading.Thread(
target=results_consumer,
@@ -1473,16 +1506,6 @@ app = FastAPI(
redoc_url=None, # ❌ отключает /redoc
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:
@@ -1709,6 +1732,7 @@ async def status(request: Request):
),
}
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",
)
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-стата
"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)
# 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()
pattern = r'https?:\/\/[^\/]+'
pattern = r"https?:\/\/[^\/]+"
new_url = re.sub(pattern, new_base_url, old_url, count=0, flags=0)
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".
"""
# async with SHOTMAPS_LOCK:
data = SHOTMAP_CACHE.get(player_id_shots)
if not data:
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:
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() # наивное "сейчас"
best = None # {"dt": ..., "opp": ..., "place": "home"/"away"}
@@ -3779,11 +3835,23 @@ async def last_5_games():
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 {
"opponent": best["opp"],
"date": best["dt"].strftime("%Y-%m-%d %H:%M"),
"place": best["place"], # "home" / "away"
"place_ru": place_ru, # "дома" / "в гостях"
"formatted": formatted, # 🆕 "wednesday, march 26, at home against astana"
}
# последние 5 игр и результаты
@@ -3806,6 +3874,7 @@ async def last_5_games():
"nextGameDate": next1["date"],
"nextGamePlace": next1["place_ru"], # "дома" / "в гостях"
"nextGameHomeAway": next1["place"], # "home" / "away" (если нужно в логике)
"nextGameFormatted": next1["formatted"],
},
{
"teamName": team2_name,
@@ -3815,6 +3884,7 @@ async def last_5_games():
"nextGameDate": next2["date"],
"nextGamePlace": next2["place_ru"],
"nextGameHomeAway": next2["place"],
"nextGameFormatted": next2["formatted"],
},
]
return data
@@ -3916,7 +3986,8 @@ async def commentary():
plus_minus = p.get("plusMinus", "")
kpi = p.get("kpi", "")
rows.append(f"""
rows.append(
f"""
<tr>
<td>{num}</td>
<td class="player-name"
@@ -3939,7 +4010,8 @@ async def commentary():
<td class="extra-col">{plus_minus}</td>
<td class="extra-col">{kpi}</td>
</tr>
""")
"""
)
return f"""
<h3>{title}</h3>
@@ -4458,7 +4530,7 @@ async def dashboard():
# порядок вывода метрик в центральном столбце (как на твоём дашборде)
center_stats_order = [
"pt-1", # штрафные
"pt-1", # штрафные
"pt-1_pro",
"pt-2",
"pt-2_pro",
@@ -4901,6 +4973,34 @@ async def dashboard():
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__":
uvicorn.run(
"get_data:app", host="0.0.0.0", port=8000, reload=True, log_level="debug"