build 122 - стартовые пятерки
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user