BUILD119 — ручной счёт + SQL + составы
This commit is contained in:
@@ -5469,10 +5469,17 @@ class HockeyDataService:
|
||||
if not assigned:
|
||||
continue
|
||||
|
||||
# BUILD113: as soon as the operator has fully assigned a penalty, it
|
||||
# already affects the scorebug numerical strength. The penalty clock may
|
||||
# still be stopped; Start controls only the countdown, not the displayed
|
||||
# 5x4 / 4x3 state. Incomplete drafts are filtered out by `assigned` above.
|
||||
# BUILD96: a prepared penalty must not change numerical strength until
|
||||
# the operator explicitly starts it. New Runtime snapshots always send
|
||||
# startedOnce. Legacy snapshots (without the marker) keep the old
|
||||
# assigned-immediately behaviour for backward compatibility.
|
||||
has_started_marker = "startedOnce" in item or "started_once" in item
|
||||
if has_started_marker:
|
||||
started = bool(item.get("startedOnce", item.get("started_once", False))) or bool(item.get("running", False))
|
||||
else:
|
||||
started = bool(item.get("running", False)) or remaining_ms < duration_ms or assigned
|
||||
if not started:
|
||||
continue
|
||||
if side == "home":
|
||||
home += 1
|
||||
else:
|
||||
@@ -5709,6 +5716,41 @@ class HockeyDataService:
|
||||
result[clean_key] = str(value if value is not None else "")[:512]
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _score_control_payload(game: Game, row: GameControlState | None) -> dict[str, Any]:
|
||||
"""Return the authoritative operator score for one game.
|
||||
|
||||
While manual mode is off, LIVE follows the current DB/API score. The first
|
||||
operator +/- action freezes LIVE into match-scoped runtime values so later
|
||||
Stat2TV updates cannot overwrite an on-air correction.
|
||||
"""
|
||||
game_home = max(0, int(getattr(game, "home_score", 0) or 0))
|
||||
game_away = max(0, int(getattr(game, "away_score", 0) or 0))
|
||||
values = HockeyDataService._runtime_values_from_row(row) if row is not None else {}
|
||||
external_valid = str(values.get("score.external_valid", "0") or "0").strip().lower() in {"1", "true", "yes", "on"}
|
||||
manual = str(values.get("score.manual", "0") or "0").strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
def _stored(key: str, fallback: int) -> int:
|
||||
try:
|
||||
return max(0, int(float(str(values.get(key, fallback) or fallback).strip())))
|
||||
except (TypeError, ValueError):
|
||||
return fallback
|
||||
|
||||
external_home = _stored("score.external_home", game_home) if external_valid else game_home
|
||||
external_away = _stored("score.external_away", game_away) if external_valid else game_away
|
||||
live_home = _stored("score.home", external_home) if manual else external_home
|
||||
live_away = _stored("score.away", external_away) if manual else external_away
|
||||
return {
|
||||
"home": live_home,
|
||||
"away": live_away,
|
||||
"external_home": external_home,
|
||||
"external_away": external_away,
|
||||
"external_source": str(values.get("score.external_source", "game") or "game"),
|
||||
"manual": manual,
|
||||
"source": "manual" if manual else "external",
|
||||
"differs": live_home != external_home or live_away != external_away,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _timer_state_from_row(
|
||||
cls,
|
||||
@@ -5848,6 +5890,7 @@ class HockeyDataService:
|
||||
"timer_rules": timer_rules,
|
||||
"strength": strength,
|
||||
"scoreboard_team_states": scoreboard_team_states,
|
||||
"score": self._score_control_payload(game, control),
|
||||
"timers": timer_state,
|
||||
"flags": self._runtime_flags_from_row(control),
|
||||
"values": self._runtime_values_from_row(control),
|
||||
@@ -6003,6 +6046,62 @@ class HockeyDataService:
|
||||
raise ValueError("Матч не найден")
|
||||
return result
|
||||
|
||||
def set_game_score(
|
||||
self,
|
||||
game_external_id: str,
|
||||
*,
|
||||
command: str,
|
||||
updated_by: str = "",
|
||||
language: str = "ru",
|
||||
) -> dict[str, Any]:
|
||||
command = str(command or "").strip().lower()
|
||||
aliases = {
|
||||
"home_plus": "home_plus", "home+": "home_plus", "home_inc": "home_plus",
|
||||
"home_minus": "home_minus", "home-": "home_minus", "home_dec": "home_minus",
|
||||
"away_plus": "away_plus", "away+": "away_plus", "away_inc": "away_plus",
|
||||
"away_minus": "away_minus", "away-": "away_minus", "away_dec": "away_minus",
|
||||
"sync": "sync", "resync": "sync", "external": "sync",
|
||||
}
|
||||
command = aliases.get(command, command)
|
||||
if command not in {"home_plus", "home_minus", "away_plus", "away_minus", "sync"}:
|
||||
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("Матч не найден")
|
||||
row = self._get_or_create_game_control(session, game.external_id)
|
||||
current = self._runtime_values_from_row(row)
|
||||
score = self._score_control_payload(game, row)
|
||||
|
||||
if command == "sync":
|
||||
current["score.manual"] = "0"
|
||||
current["score.home"] = str(score["external_home"])
|
||||
current["score.away"] = str(score["external_away"])
|
||||
else:
|
||||
home = int(score["home"])
|
||||
away = int(score["away"])
|
||||
if command == "home_plus":
|
||||
home += 1
|
||||
elif command == "home_minus":
|
||||
home = max(0, home - 1)
|
||||
elif command == "away_plus":
|
||||
away += 1
|
||||
elif command == "away_minus":
|
||||
away = max(0, away - 1)
|
||||
current["score.manual"] = "1"
|
||||
current["score.home"] = str(home)
|
||||
current["score.away"] = str(away)
|
||||
|
||||
row.runtime_values_json = json.dumps(current, ensure_ascii=False, separators=(",", ":"))
|
||||
row.updated_by = str(updated_by or "")[:128]
|
||||
row.updated_at = datetime.utcnow()
|
||||
|
||||
result = self.game_control(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