подгрузка данных для кубка россии. тест 2

This commit is contained in:
2026-07-02 12:56:24 +03:00
parent 57907fd86b
commit e98d014243
5 changed files with 180 additions and 21 deletions

10
app.py
View File

@@ -59,7 +59,7 @@ from repositories.match_session_repository import (
from repositories.match_view_repository import get_match_lineups_grouped from repositories.match_view_repository import get_match_lineups_grouped
from vmix.vmix_service import build_vmix_project_bytes, build_vmix_filename from vmix.vmix_service import build_vmix_project_bytes, build_vmix_filename
from repositories.match_referee_repository import get_match_referees from repositories.match_referee_repository import get_match_referees
from repositories.match_repository import get_tour_schedule_by_match_id, ensure_match_source_key_column from repositories.match_repository import get_tour_schedule_by_match_id, ensure_match_source_key_column, resolve_match_source_key
from repositories.standings_repository import get_standings_by_match_id from repositories.standings_repository import get_standings_by_match_id
from repositories.match_coach_repository import get_match_coaches_grouped from repositories.match_coach_repository import get_match_coaches_grouped
from repositories.player_repository import ( from repositories.player_repository import (
@@ -706,7 +706,7 @@ def download_vmix_project(request: Request, session_token: str):
operator_login = current_user.get("username") or None operator_login = current_user.get("username") or None
try: try:
source_key = session_row[21] if len(session_row) > 21 else None source_key = resolve_match_source_key(session_row[1], session_row[21] if len(session_row) > 21 else None)
vmix_bytes = build_vmix_project_bytes( vmix_bytes = build_vmix_project_bytes(
session_token=session_token, session_token=session_token,
@@ -1474,7 +1474,7 @@ def get_roster_data(session_token: str, name: str, count_player: int):
home_team_name = session_row[14].replace("«", "").replace("»", "") home_team_name = session_row[14].replace("«", "").replace("»", "")
away_team_id = session_row[17] away_team_id = session_row[17]
away_team_name = session_row[18].replace("«", "").replace("»", "") away_team_name = session_row[18].replace("«", "").replace("»", "")
source_key = session_row[21] if len(session_row) > 21 else None source_key = resolve_match_source_key(match_id, session_row[21] if len(session_row) > 21 else None)
# print(session_row) # print(session_row)
data = build_lineup_json( data = build_lineup_json(
@@ -1764,7 +1764,7 @@ def vmix_home_formations(session_token: str):
home_team_id = session_row[13] home_team_id = session_row[13]
home_team = session_row[14].replace("«", "").replace("»", "") home_team = session_row[14].replace("«", "").replace("»", "")
source_key = session_row[21] if len(session_row) > 21 else None source_key = resolve_match_source_key(session_row[1], session_row[21] if len(session_row) > 21 else None)
rows = get_vmix_team_formations(session_row, home_team_id) rows = get_vmix_team_formations(session_row, home_team_id)
return build_vmix_formation_response(rows, home_team_id, home_team, source_key=source_key) return build_vmix_formation_response(rows, home_team_id, home_team, source_key=source_key)
@@ -1777,7 +1777,7 @@ def vmix_away_formations(session_token: str):
away_team_id = session_row[17] away_team_id = session_row[17]
away_team = session_row[18].replace("«", "").replace("»", "") away_team = session_row[18].replace("«", "").replace("»", "")
source_key = session_row[21] if len(session_row) > 21 else None source_key = resolve_match_source_key(session_row[1], session_row[21] if len(session_row) > 21 else None)
rows = get_vmix_team_formations(session_row, away_team_id) rows = get_vmix_team_formations(session_row, away_team_id)
return build_vmix_formation_response(rows, away_team_id, away_team, source_key=source_key) return build_vmix_formation_response(rows, away_team_id, away_team, source_key=source_key)

View File

@@ -3,6 +3,7 @@ from numpy import place
from db import get_connection from db import get_connection
from repositories.team_repository import get_team_id_by_external_id from repositories.team_repository import get_team_id_by_external_id
from repositories.stadium_repository import get_or_create_stadium from repositories.stadium_repository import get_or_create_stadium
from parsers.parser_sources import get_default_source_key, list_parser_sources
def ensure_match_source_key_column() -> None: def ensure_match_source_key_column() -> None:
@@ -24,6 +25,121 @@ def ensure_match_source_key_column() -> None:
conn.close() conn.close()
def _normalize_source_key(value: str | None) -> str:
return str(value or "").upper().strip()
def infer_match_source_key(
explicit_source_key: str | None = None,
season: str | None = None,
tour: str | None = None,
) -> str:
"""Определяет источник матча, даже если старые строки matches.source_key ещё пустые.
Приоритет:
1. matches.source_key;
2. точное совпадение season с parser_sources.season;
3. кубковые слова в tour;
4. обычный дефолт из настроек проекта.
"""
explicit = _normalize_source_key(explicit_source_key)
if explicit:
return explicit
season_value = str(season or "").strip()
tour_value = str(tour or "").lower().strip()
try:
sources = list_parser_sources()
except Exception:
sources = []
if season_value and sources:
matches = [
_normalize_source_key(source.get("key"))
for source in sources
if str(source.get("season") or "").strip() == season_value
]
matches = [key for key in matches if key]
if len(matches) == 1:
return matches[0]
cup_markers = (
"кубок",
"финал",
"полуфинал",
"четвертьфинал",
"1/2",
"1/4",
"1/8",
"1/16",
"групп",
"этап",
"стад",
)
if any(marker in tour_value for marker in cup_markers):
known_keys = {_normalize_source_key(source.get("key")) for source in sources}
if "RUSSIAN_CUP" in known_keys:
return "RUSSIAN_CUP"
try:
return _normalize_source_key(get_default_source_key()) or "SUPERLEAGUE"
except Exception:
return "SUPERLEAGUE"
def resolve_match_source_key(match_id: int | str | None, fallback: str | None = None) -> str:
"""Возвращает source_key для матча и, если возможно, записывает его в matches.
Это нужно для старых матчей Кубка России, которые могли быть загружены до появления
колонки source_key. Без этого vMix скачивает пресет и собирает пути как для Суперлиги.
"""
fallback_key = _normalize_source_key(fallback)
if not match_id:
return fallback_key or infer_match_source_key(fallback_key)
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
SELECT source_key, season, tour
FROM matches
WHERE id = %s
LIMIT 1;
""",
(match_id,),
)
row = cur.fetchone()
if not row:
return fallback_key or infer_match_source_key(fallback_key)
explicit_key, season, tour = row
source_key = infer_match_source_key(explicit_key or fallback_key, season, tour)
if source_key and not _normalize_source_key(explicit_key):
cur.execute(
"""
UPDATE matches
SET source_key = %s, updated_at = NOW()
WHERE id = %s
AND (source_key IS NULL OR TRIM(source_key) = '');
""",
(source_key, match_id),
)
conn.commit()
return source_key
except Exception:
conn.rollback()
return fallback_key or infer_match_source_key(fallback_key)
finally:
conn.close()
def upsert_match( def upsert_match(
external_id: str, external_id: str,
home_team_id: int, home_team_id: int,

View File

@@ -2,6 +2,7 @@ import secrets
from db import get_connection from db import get_connection
from parsers.parser_sources import build_logo_path from parsers.parser_sources import build_logo_path
from repositories.match_repository import resolve_match_source_key
def create_match_session( def create_match_session(
@@ -88,7 +89,9 @@ def get_match_session_by_token(session_token: str):
return None return None
row = list(row) row = list(row)
source_key = row[21] if len(row) > 21 else None source_key = resolve_match_source_key(row[1], row[21] if len(row) > 21 else None)
if len(row) > 21:
row[21] = source_key
row[16] = build_logo_path(source_key, row[16]) row[16] = build_logo_path(source_key, row[16])
row[20] = build_logo_path(source_key, row[20]) row[20] = build_logo_path(source_key, row[20])
return tuple(row) return tuple(row)

View File

@@ -1,6 +1,7 @@
# services/vmix_json_service.py # services/vmix_json_service.py
from db import get_connection from db import get_connection
from repositories.match_lineup_repository import get_match_lineup_for_vmix from repositories.match_lineup_repository import get_match_lineup_for_vmix
from repositories.match_repository import resolve_match_source_key
from parsers.parser_sources import build_empty_photo_path, build_logo_path, build_logo_variant_path, build_photo_path from parsers.parser_sources import build_empty_photo_path, build_logo_path, build_logo_variant_path, build_photo_path
@@ -9,11 +10,17 @@ EMPTY_PHOTO_PATH = DEFAULT_PHOTO_BASE_PATH + r"\EMPTY.png"
def _session_source_key(session_row) -> str | None: def _session_source_key(session_row) -> str | None:
try:
match_id = session_row[1]
except Exception:
match_id = None
try: try:
value = session_row[21] value = session_row[21]
except Exception: except Exception:
value = None value = None
return str(value).strip() if value else None
return resolve_match_source_key(match_id, value)
def build_generated_player_photo_path( def build_generated_player_photo_path(
@@ -386,7 +393,9 @@ def get_vmix_match_info_by_token(session_token: str):
return None return None
row = list(row) row = list(row)
source_key = row[30] if len(row) > 30 else None source_key = resolve_match_source_key(row[0], row[30] if len(row) > 30 else None)
if len(row) > 30:
row[30] = source_key
row[14] = build_logo_path(source_key, row[14]) row[14] = build_logo_path(source_key, row[14])
row[15] = build_logo_path(source_key, row[15]) row[15] = build_logo_path(source_key, row[15])
row[20] = build_logo_variant_path(source_key, row[20], "white") row[20] = build_logo_variant_path(source_key, row[20], "white")

View File

@@ -7,13 +7,23 @@ import os
from urllib.parse import urlparse from urllib.parse import urlparse
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv(override=True)
def _refresh_env() -> None:
# Читаем .env заново перед скачиванием, чтобы изменение SYNO_PATH_VMIX_2
# применялось без пересборки кода.
load_dotenv(override=True)
def get_syno_settings() -> tuple[str | None, str | None, str | None]:
_refresh_env()
return (
os.getenv("SYNO_URL"),
os.getenv("SYNO_USERNAME"),
os.getenv("SYNO_PASSWORD"),
)
SYNO_URL = os.getenv("SYNO_URL")
SYNO_USERNAME = os.getenv("SYNO_USERNAME")
SYNO_PASSWORD = os.getenv("SYNO_PASSWORD")
SYNO_PATH_VMIX = os.getenv("SYNO_PATH_VMIX")
SYNO_PATH_VMIX_2 = os.getenv("SYNO_PATH_VMIX_2")
def normalize_source_key(source_key: str | None) -> str: def normalize_source_key(source_key: str | None) -> str:
@@ -26,14 +36,17 @@ def get_vmix_preset_path(source_key: str | None = None) -> str | None:
SUPERLEAGUE -> SYNO_PATH_VMIX SUPERLEAGUE -> SYNO_PATH_VMIX
RUSSIAN_CUP -> SYNO_PATH_VMIX_2, если он заполнен RUSSIAN_CUP -> SYNO_PATH_VMIX_2, если он заполнен
Если второй путь не задан, используем основной, чтобы скачивание не ломалось. Значения читаются из .env прямо перед скачиванием.
""" """
_refresh_env()
key = normalize_source_key(source_key) key = normalize_source_key(source_key)
primary_path = os.getenv("SYNO_PATH_VMIX")
cup_path = os.getenv("SYNO_PATH_VMIX_2")
if key == "RUSSIAN_CUP" and SYNO_PATH_VMIX_2: if key == "RUSSIAN_CUP" and cup_path:
return SYNO_PATH_VMIX_2 return cup_path
return SYNO_PATH_VMIX return primary_path
def get_fqdn(): def get_fqdn():
@@ -63,6 +76,15 @@ def rebuild_vmix_url(old_url: str, new_base_url: str, session_token: str) -> str
endpoint = match.group(1) endpoint = match.group(1)
return f"{new_base_url}/vmix/session/{session_token}/{endpoint}" return f"{new_base_url}/vmix/session/{session_token}/{endpoint}"
# Старые пресеты могли хранить ссылки без session-токена:
# /vmix/home-lineup, /vmix/info.json и т.п.
old_style_match = re.match(r"^/vmix/(?!session/)(.+)$", path)
if old_style_match:
endpoint = old_style_match.group(1).strip("/")
if endpoint.endswith(".json"):
endpoint = endpoint[:-5]
return f"{new_base_url}/vmix/session/{session_token}/{endpoint}"
# fallback: если структура другая, просто меняем host # fallback: если структура другая, просто меняем host
return re.sub(r"https?://[^/]+", new_base_url, old_url) return re.sub(r"https?://[^/]+", new_base_url, old_url)
@@ -73,6 +95,7 @@ def change_vmix_datasource_urls(
session_token: str, session_token: str,
match_id: str | int | None = None, match_id: str | int | None = None,
operator_login: str | None = None, operator_login: str | None = None,
source_key: str | None = None,
) -> bytes: ) -> bytes:
if isinstance(xml_data, dict): if isinstance(xml_data, dict):
candidate = None candidate = None
@@ -155,6 +178,9 @@ def change_vmix_datasource_urls(
# value2 = match_id # value2 = match_id
get_or_create_dynamic_value(1).text = "" if match_id is None else str(match_id) get_or_create_dynamic_value(1).text = "" if match_id is None else str(match_id)
# value3 = source_key (SUPERLEAGUE / RUSSIAN_CUP)
get_or_create_dynamic_value(2).text = "" if not source_key else normalize_source_key(source_key)
# value4 = operator_login # value4 = operator_login
get_or_create_dynamic_value(3).text = "" if not operator_login else str(operator_login) get_or_create_dynamic_value(3).text = "" if not operator_login else str(operator_login)
@@ -168,14 +194,18 @@ def build_vmix_project_bytes(
operator_login: str | None = None, operator_login: str | None = None,
source_key: str | None = None, source_key: str | None = None,
) -> bytes: ) -> bytes:
source_key = normalize_source_key(source_key)
vmix_preset_path = get_vmix_preset_path(source_key) vmix_preset_path = get_vmix_preset_path(source_key)
if not vmix_preset_path: if not vmix_preset_path:
raise RuntimeError("Не задан путь к vMix-пресету: SYNO_PATH_VMIX или SYNO_PATH_VMIX_2") raise RuntimeError("Не задан путь к vMix-пресету: SYNO_PATH_VMIX или SYNO_PATH_VMIX_2")
syno_url, syno_username, syno_password = get_syno_settings()
print(f"[vmix] source_key={source_key or '-'} preset_path={vmix_preset_path}")
vmix_bio = nasio.load_bio( vmix_bio = nasio.load_bio(
user=SYNO_USERNAME, user=syno_username,
password=SYNO_PASSWORD, password=syno_password,
nas_ip=SYNO_URL, nas_ip=syno_url,
nas_port="443", nas_port="443",
path=vmix_preset_path, path=vmix_preset_path,
) )
@@ -186,6 +216,7 @@ def build_vmix_project_bytes(
session_token, session_token,
match_id, match_id,
operator_login, operator_login,
source_key,
) )
if isinstance(edited_vmix, str): if isinstance(edited_vmix, str):