build 122 - стартовые пятерки

This commit is contained in:
2026-08-26 14:56:15 +03:00
parent 41ca064b3b
commit fe164c17f2
6 changed files with 529 additions and 22 deletions

View File

@@ -3050,6 +3050,7 @@ class HockeyDataService:
for row in existing_rosters
}
seen_rosters: set[tuple[str, str]] = set()
api_line_order: dict[tuple[str, str], int] = {}
for entry in details.rosters:
profile = player_profiles.get(entry.player_external_id)
if profile is None:
@@ -3080,7 +3081,14 @@ class HockeyDataService:
roster.role = entry.role
roster.position_ru = entry.position_ru
roster.position_en = entry.position_en
roster.line_number = entry.line_number
api_line = str(entry.line_number or "").strip()
api_order_key = (entry.side, api_line)
api_line_order[api_order_key] = api_line_order.get(api_order_key, 0) + 1
roster.api_line_number = api_line
roster.api_line_sort_order = api_line_order[api_order_key]
if not bool(roster.line_manual):
roster.line_number = api_line
roster.line_sort_order = roster.api_line_sort_order
roster.captain_role = entry.captain_role
roster.lineup_status = entry.lineup_status
roster.raw_payload = entry.raw_payload
@@ -4917,6 +4925,10 @@ class HockeyDataService:
),
"role": roster.role,
"line": roster.line_number,
"line_sort_order": int(roster.line_sort_order or 0),
"line_manual": bool(roster.line_manual),
"api_line": roster.api_line_number,
"api_line_sort_order": int(roster.api_line_sort_order or 0),
"captain_role": roster.captain_role,
"lineup_status": roster.lineup_status,
"birth_date": (
@@ -4937,12 +4949,29 @@ class HockeyDataService:
)
for items in serialized_players.values():
items.sort(
key=lambda item: (
self._number_sort_value(str(item["number"])),
str(item["name"]),
has_lines = any(str(item.get("line") or "").strip() for item in items)
if has_lines:
def line_sort_value(item: dict[str, Any]) -> tuple[Any, ...]:
raw_line = str(item.get("line") or "").strip()
try:
line_number = int(raw_line)
except (TypeError, ValueError):
line_number = 10_000 if not raw_line else 9_000
order = int(item.get("line_sort_order") or 0)
return (
line_number,
order if order > 0 else 10_000,
self._number_sort_value(str(item.get("number") or "")),
str(item.get("name") or ""),
)
items.sort(key=line_sort_value)
else:
items.sort(
key=lambda item: (
self._number_sort_value(str(item["number"])),
str(item["name"]),
)
)
)
serialized_officials: list[dict[str, Any]] = []
role_labels = {
@@ -6102,6 +6131,102 @@ class HockeyDataService:
raise ValueError("Матч не найден")
return result
def save_game_lines(
self,
game_external_id: str,
*,
side: str,
assignments: list[dict[str, Any]],
updated_by: str = "",
language: str = "ru",
) -> dict[str, Any]:
"""Persist the operator's effective line order for one team.
BUILD122 deliberately stores the result on ``hockey_game_rosters`` so
broadcast SQL can read the same roster table directly. ``line_manual``
protects these values from later Stat2TV roster refreshes while the
parallel api_* fields keep the newest API baseline available for reset.
"""
side = str(side or "").strip().lower()
if side not in {"home", "away"}:
raise ValueError("Неизвестная сторона команды")
normalised: dict[str, tuple[str, int]] = {}
for raw in assignments or []:
player_id = str(raw.get("player_external_id") or raw.get("player_id") or "").strip()
if not player_id:
continue
line = str(raw.get("line_number") or "").strip()
if line:
if not line.isdigit() or not (1 <= int(line) <= 99):
raise ValueError("Номер звена должен быть от 1 до 99")
line = str(int(line))
try:
sort_order = max(0, int(raw.get("sort_order") or 0))
except (TypeError, ValueError):
sort_order = 0
normalised[player_id] = (line, sort_order)
with self.database.session() as session:
game = session.scalar(select(Game).where(Game.external_id == str(game_external_id)))
if game is None:
raise ValueError("Матч не найден")
rosters = list(session.scalars(select(GameRoster).where(and_(
GameRoster.game_external_id == game.external_id,
GameRoster.side == side,
))))
roster_by_id = {str(row.player_external_id): row for row in rosters}
unknown = sorted(set(normalised) - set(roster_by_id))
if unknown:
raise ValueError(f"Игрок не найден в составе: {unknown[0]}")
for player_id, (line, sort_order) in normalised.items():
roster = roster_by_id[player_id]
# First manual edit after upgrading from an older build: keep the
# already stored API line as a reset baseline before overwriting it.
if not str(roster.api_line_number or "").strip() and str(roster.line_number or "").strip():
roster.api_line_number = str(roster.line_number or "").strip()
roster.api_line_sort_order = int(roster.line_sort_order or 0)
roster.line_number = line
roster.line_sort_order = sort_order
roster.line_manual = True
roster.synced_at = datetime.utcnow()
result = self.game_details(game_external_id, language=language)
if result is None:
raise ValueError("Матч не найден")
return result
def reset_game_lines(
self,
game_external_id: str,
*,
side: str,
updated_by: str = "",
language: str = "ru",
) -> dict[str, Any]:
"""Return one team's effective line layout to the latest API baseline."""
side = str(side or "").strip().lower()
if side not in {"home", "away"}:
raise ValueError("Неизвестная сторона команды")
with self.database.session() as session:
game = session.scalar(select(Game).where(Game.external_id == str(game_external_id)))
if game is None:
raise ValueError("Матч не найден")
rosters = list(session.scalars(select(GameRoster).where(and_(
GameRoster.game_external_id == game.external_id,
GameRoster.side == side,
))))
for roster in rosters:
roster.line_number = str(roster.api_line_number or "").strip()
roster.line_sort_order = int(roster.api_line_sort_order or 0)
roster.line_manual = False
roster.synced_at = datetime.utcnow()
result = self.game_details(game_external_id, language=language)
if result is None:
raise ValueError("Матч не найден")
return result
def set_game_period(
self,
game_external_id: str,