first commit

This commit is contained in:
2026-04-20 11:56:11 +03:00
commit 8d80fadb56
103 changed files with 19756 additions and 0 deletions

View File

@@ -0,0 +1,460 @@
from db import get_connection
from repositories.match_view_repository import get_match_lineups_grouped
from repositories.match_coach_repository import get_match_coaches_grouped
def replace_match_lineups(match_id: int, rows: list[dict]) -> None:
delete_query = "DELETE FROM match_lineups WHERE match_id = %s;"
insert_query = """
INSERT INTO match_lineups (
match_id,
team_id,
player_id,
player_name,
number,
position,
is_captain,
lineup_type,
source,
created_at,
updated_at
)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW());
"""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(delete_query, (match_id,))
for row in rows:
cur.execute(
insert_query,
(
row["match_id"],
row["team_id"],
row.get("player_id"),
row["player_name"],
row.get("number"),
row.get("position"),
row.get("is_captain"),
row["lineup_type"],
row.get("source", "parser"),
),
)
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
def _player_to_editor_dict(p: dict) -> dict:
return {
"player_id": p.get("player_id"),
"player_name": p.get("player_name")
or f"{p.get('last_name', '')} {p.get('first_name', '')}".strip(),
"last_name": p.get("last_name", "") or p.get("player_name", "") or "",
"first_name": p.get("first_name", "") or "",
"number": str(p.get("number", "") or ""),
"position": p.get("position", "") or "",
"is_captain": bool(p.get("is_captain")),
}
def _coach_to_editor_dict(c: dict) -> dict:
return {
"coach_id": c.get("coach_id"),
"coach_name": c.get("coach_name", "") or "",
"role": c.get("role", "") or "",
}
def get_match_lineup_for_editor(
match_id: int,
home_team_id: int,
away_team_id: int,
) -> dict:
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
SELECT
mlp.side,
mlp.role,
mlp.sort_order,
p.id AS player_id,
COALESCE(p.full_name, TRIM(COALESCE(p.last_name, '') || ' ' || COALESCE(p.first_name, ''))) AS player_name,
COALESCE(p.last_name, '') AS last_name,
COALESCE(p.first_name, '') AS first_name,
COALESCE(mlp.number::text, '') AS number,
COALESCE(mlp.position, '') AS position,
COALESCE(mlp.is_captain, FALSE) AS is_captain
FROM match_lineup_players mlp
JOIN players p
ON p.id = mlp.player_id
WHERE mlp.match_id = %s
ORDER BY mlp.side, mlp.role, mlp.sort_order, mlp.id
""",
(match_id,),
)
player_rows = cur.fetchall()
cur.execute(
"""
SELECT
mc.side,
mc.sort_order,
c.id AS coach_id,
COALESCE(c.player, '') AS coach_name,
COALESCE(mc.role, c.amplua, '') AS role
FROM match_coaches mc
JOIN coaches c
ON c.id = mc.coach_id
WHERE mc.match_id = %s
ORDER BY mc.side, mc.sort_order, mc.id
""",
(match_id,),
)
coach_rows = cur.fetchall()
finally:
conn.close()
result = {
"home_starting": [],
"home_bench": [],
"away_starting": [],
"away_bench": [],
"home_coaches": [],
"away_coaches": [],
}
# Если редактор уже сохранял состав — берём игроков из новой таблицы
if player_rows:
for row in player_rows:
player = {
"player_id": row[3],
"player_name": row[4] or "",
"last_name": row[5] or "",
"first_name": row[6] or "",
"number": row[7] or "",
"position": row[8] or "",
"is_captain": bool(row[9]),
}
side = row[0]
role = row[1]
if side == "home" and role == "starting":
result["home_starting"].append(player)
elif side == "home" and role == "bench":
result["home_bench"].append(player)
elif side == "away" and role == "starting":
result["away_starting"].append(player)
elif side == "away" and role == "bench":
result["away_bench"].append(player)
else:
# Иначе берём загруженный с сайта состав из старой таблицы
lineups = get_match_lineups_grouped(
match_id=match_id,
home_team_id=home_team_id,
away_team_id=away_team_id,
)
result["home_starting"] = [
_player_to_editor_dict(p) for p in lineups.get("home_starting", [])
]
result["home_bench"] = [
_player_to_editor_dict(p) for p in lineups.get("home_bench", [])
]
result["away_starting"] = [
_player_to_editor_dict(p) for p in lineups.get("away_starting", [])
]
result["away_bench"] = [
_player_to_editor_dict(p) for p in lineups.get("away_bench", [])
]
# Тренеров берём из match_coaches, если они уже есть
if coach_rows:
for row in coach_rows:
coach = {
"coach_id": row[2],
"coach_name": row[3] or "",
"role": row[4] or "",
}
side = row[0]
if side == "home":
result["home_coaches"].append(coach)
elif side == "away":
result["away_coaches"].append(coach)
else:
coaches = get_match_coaches_grouped(
match_id=match_id,
home_team_id=home_team_id,
away_team_id=away_team_id,
)
result["home_coaches"] = [
_coach_to_editor_dict(c) for c in coaches.get("home", [])
]
result["away_coaches"] = [
_coach_to_editor_dict(c) for c in coaches.get("away", [])
]
return result
def get_match_lineup_for_vmix(
match_id: int,
home_team_id: int,
away_team_id: int,
) -> dict:
conn = get_connection()
try:
with conn.cursor() as cur:
# Сначала пробуем взять ручную версию из редактора
cur.execute(
"""
SELECT
mlp.side,
mlp.role,
mlp.sort_order,
p.id AS player_id,
COALESCE(
p.full_name,
TRIM(COALESCE(p.last_name, '') || ' ' || COALESCE(p.first_name, ''))
) AS player_name,
COALESCE(p.last_name, '') AS last_name,
COALESCE(p.first_name, '') AS first_name,
COALESCE(mlp.number::text, '') AS number,
COALESCE(mlp.position, '') AS position,
COALESCE(mlp.is_captain, FALSE) AS is_captain,
COALESCE(p.position, '') AS pos
FROM match_lineup_players mlp
JOIN players p
ON p.id = mlp.player_id
WHERE mlp.match_id = %s
ORDER BY
CASE
WHEN LOWER(COALESCE(p.position, '')) IN ('вр', 'вр.', 'gk', 'goalkeeper', 'вратарь') THEN 0
ELSE 1
END,
CASE
WHEN COALESCE(p.number::text, '') ~ '^[0-9]+$' THEN p.number::integer
ELSE 999
END,
COALESCE(p.last_name, ''),
COALESCE(p.first_name, ''),
p.id
""",
(match_id,),
)
player_rows = cur.fetchall()
cur.execute(
"""
SELECT
mc.side,
mc.sort_order,
c.id AS coach_id,
COALESCE(c.player, '') AS coach_name,
COALESCE(mc.role, c.amplua, '') AS role
FROM match_coaches mc
JOIN coaches c
ON c.id = mc.coach_id
WHERE mc.match_id = %s
ORDER BY mc.side, mc.sort_order, mc.id
""",
(match_id,),
)
coach_rows = cur.fetchall()
finally:
conn.close()
result = {
"home_starting": [],
"home_bench": [],
"away_starting": [],
"away_bench": [],
"home_coaches": [],
"away_coaches": [],
}
# 1. Если есть ручной состав — берём его
if player_rows:
for row in player_rows:
player = {
"player_id": row[3],
"player_name": row[4] or "",
"last_name": row[5] or "",
"first_name": row[6] or "",
"number": row[7] or "",
"position": row[8] or "",
"is_captain": bool(row[9]),
"pos": row[10] or "",
}
side = row[0]
role = row[1]
if side == "home" and role == "starting":
result["home_starting"].append(player)
elif side == "home" and role == "bench":
result["home_bench"].append(player)
elif side == "away" and role == "starting":
result["away_starting"].append(player)
elif side == "away" and role == "bench":
result["away_bench"].append(player)
else:
# 2. Иначе — fallback на сайт
lineups = get_match_lineups_grouped(
match_id=match_id,
home_team_id=home_team_id,
away_team_id=away_team_id,
)
result["home_starting"] = lineups.get("home_starting", [])
result["home_bench"] = lineups.get("home_bench", [])
result["away_starting"] = lineups.get("away_starting", [])
result["away_bench"] = lineups.get("away_bench", [])
if coach_rows:
for row in coach_rows:
coach = {
"coach_id": row[2],
"coach_name": row[3] or "",
"role": row[4] or "",
}
if row[0] == "home":
result["home_coaches"].append(coach)
elif row[0] == "away":
result["away_coaches"].append(coach)
else:
coaches = get_match_coaches_grouped(
match_id=match_id,
home_team_id=home_team_id,
away_team_id=away_team_id,
)
result["home_coaches"] = coaches.get("home", [])
result["away_coaches"] = coaches.get("away", [])
return result
def save_match_lineup_for_editor(
match_id: int,
home_team_id: int,
away_team_id: int,
home_starting: list[dict],
home_bench: list[dict],
away_starting: list[dict],
away_bench: list[dict],
home_coaches: list[dict],
away_coaches: list[dict],
) -> None:
def is_goalkeeper(position: str) -> bool:
if not position:
return False
return str(position).strip().lower() in {
"вр", "вр.", "вратарь", "gk", "goalkeeper"
}
def safe_number(value) -> int:
try:
return int(str(value).strip())
except Exception:
return 9999
def lineup_sort_key(player: dict):
return (
0 if is_goalkeeper(player.get("position", "")) else 1,
safe_number(player.get("number")),
(player.get("last_name") or player.get("player_name") or "").lower(),
(player.get("first_name") or "").lower(),
player.get("player_id") or 0,
)
# Пересортировка перед сохранением
home_starting = sorted(home_starting, key=lineup_sort_key)
home_bench = sorted(home_bench, key=lineup_sort_key)
away_starting = sorted(away_starting, key=lineup_sort_key)
away_bench = sorted(away_bench, key=lineup_sort_key)
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"DELETE FROM match_lineup_players WHERE match_id = %s",
(match_id,),
)
cur.execute(
"DELETE FROM match_coaches WHERE match_id = %s",
(match_id,),
)
def insert_players(side: str, role: str, players: list[dict]) -> None:
for sort_order, p in enumerate(players, start=1):
cur.execute(
"""
INSERT INTO match_lineup_players (
match_id,
side,
role,
sort_order,
player_id,
number,
position,
is_captain
)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""",
(
match_id,
side,
role,
sort_order,
p.get("player_id"),
p.get("number", "") or None,
p.get("position", "") or None,
bool(p.get("is_captain")),
),
)
def insert_coaches(side: str, coaches: list[dict]) -> None:
for sort_order, c in enumerate(coaches, start=1):
cur.execute(
"""
INSERT INTO match_coaches (
match_id,
side,
sort_order,
coach_id,
role
)
VALUES (%s, %s, %s, %s, %s)
""",
(
match_id,
side,
sort_order,
c.get("coach_id"),
c.get("role", "") or None,
),
)
insert_players("home", "starting", home_starting)
insert_players("home", "bench", home_bench)
insert_players("away", "starting", away_starting)
insert_players("away", "bench", away_bench)
insert_coaches("home", home_coaches)
insert_coaches("away", away_coaches)
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()