поправлены еще раз удаления
This commit is contained in:
3
app.py
3
app.py
@@ -29,7 +29,7 @@ from ui_builder import install_ui_builder
|
|||||||
from khl_site.khl_data_center import APP as khl_site_app
|
from khl_site.khl_data_center import APP as khl_site_app
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
BUILD_VERSION = "2026.08.20.01"
|
BUILD_VERSION = "2026.08.20.12"
|
||||||
# compatibility: BUILD_VERSION = "2026.08.19.28"
|
# compatibility: BUILD_VERSION = "2026.08.19.28"
|
||||||
# compatibility: BUILD_VERSION = "2026.08.19.24"
|
# compatibility: BUILD_VERSION = "2026.08.19.24"
|
||||||
# compatibility: BUILD_VERSION = "2026.08.19.22"
|
# compatibility: BUILD_VERSION = "2026.08.19.22"
|
||||||
@@ -226,6 +226,7 @@ async def vmix_command(
|
|||||||
|
|
||||||
install_ui_builder(
|
install_ui_builder(
|
||||||
app,
|
app,
|
||||||
|
build_version=BUILD_VERSION,
|
||||||
settings_dir=BASE_DIR / "settings",
|
settings_dir=BASE_DIR / "settings",
|
||||||
data_providers={
|
data_providers={
|
||||||
"hockey_stat2tv": hockey_stat2tv_data,
|
"hockey_stat2tv": hockey_stat2tv_data,
|
||||||
|
|||||||
@@ -5537,13 +5537,22 @@ class HockeyDataService:
|
|||||||
elif state_key not in {"5x5", ""}:
|
elif state_key not in {"5x5", ""}:
|
||||||
state_label = state_key.replace("x", " on " if language_key == "en" else " на ")
|
state_label = state_key.replace("x", " on " if language_key == "en" else " на ")
|
||||||
|
|
||||||
# BUILD89: television output has only one power-play plate. Equal
|
# BUILD91: Mapping captions describe both sides independently. The
|
||||||
# coincidental strength never gets a side caption. As soon as a real
|
# numerical-advantage side keeps the configured PP/state caption, while
|
||||||
# numerical advantage exists (including 4x3 / 3x4 with penalties on both
|
# a penalized side that is not the advantage side gets a plain Penalty
|
||||||
# benches), expose the configured state label only on the advantage side.
|
# caption. This does not control whether the television penalty plate is
|
||||||
# `state_label` itself remains available in Mapping for custom graphics.
|
# on air; that remains the single-plate logic in the runtime.
|
||||||
home_label = state_label if advantage_side == "home" else ""
|
penalty_caption = "Penalty" if language_key == "en" else "Удаление"
|
||||||
away_label = state_label if advantage_side == "away" else ""
|
home_label = (
|
||||||
|
state_label if advantage_side == "home"
|
||||||
|
else penalty_caption if home_penalties > 0
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
away_label = (
|
||||||
|
state_label if advantage_side == "away"
|
||||||
|
else penalty_caption if away_penalties > 0
|
||||||
|
else ""
|
||||||
|
)
|
||||||
phase_labels = {
|
phase_labels = {
|
||||||
"regulation": ("Основное время", "Regulation"),
|
"regulation": ("Основное время", "Regulation"),
|
||||||
"regular_overtime": ("Овертайм регулярки", "Regular OT"),
|
"regular_overtime": ("Овертайм регулярки", "Regular OT"),
|
||||||
|
|||||||
@@ -62,5 +62,5 @@ def test_coincidental_penalties_do_not_fill_side_powerplay_labels():
|
|||||||
value = HockeyDataService._strength_payload(settings, stage="regular", current_period="1", timer_state=timer_state, language="ru")
|
value = HockeyDataService._strength_payload(settings, stage="regular", current_period="1", timer_state=timer_state, language="ru")
|
||||||
assert value["advantage_side"] == ""
|
assert value["advantage_side"] == ""
|
||||||
assert value["state_key"] == "4x4"
|
assert value["state_key"] == "4x4"
|
||||||
assert value["home_label"] == ""
|
assert value["home_label"] == "Удаление"
|
||||||
assert value["away_label"] == ""
|
assert value["away_label"] == "Удаление"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ def test_complex_two_vs_one_keeps_real_strength_and_outputs_caption_on_advantage
|
|||||||
)
|
)
|
||||||
assert value["strength_label"] == "3×4"
|
assert value["strength_label"] == "3×4"
|
||||||
assert value["advantage_side"] == "away"
|
assert value["advantage_side"] == "away"
|
||||||
assert value["home_label"] == ""
|
assert value["home_label"] == "Удаление"
|
||||||
assert value["away_label"] == "Играют в большинстве"
|
assert value["away_label"] == "Играют в большинстве"
|
||||||
|
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ def test_single_remaining_penalty_outputs_pp_caption_on_opposite_team():
|
|||||||
language="ru",
|
language="ru",
|
||||||
)
|
)
|
||||||
assert value["advantage_side"] == "away"
|
assert value["advantage_side"] == "away"
|
||||||
assert value["home_label"] == ""
|
assert value["home_label"] == "Удаление"
|
||||||
assert value["away_label"] == "Играют в большинстве"
|
assert value["away_label"] == "Играют в большинстве"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ def test_equal_coincidental_penalties_have_no_side_caption():
|
|||||||
)
|
)
|
||||||
assert payload["strength_label"] == "4×4"
|
assert payload["strength_label"] == "4×4"
|
||||||
assert payload["advantage_side"] == ""
|
assert payload["advantage_side"] == ""
|
||||||
assert payload["home_label"] == ""
|
assert payload["home_label"] == "Удаление"
|
||||||
assert payload["away_label"] == ""
|
assert payload["away_label"] == "Удаление"
|
||||||
|
|
||||||
|
|
||||||
def test_two_vs_one_penalties_caption_only_advantage_team():
|
def test_two_vs_one_penalties_caption_only_advantage_team():
|
||||||
@@ -55,7 +55,7 @@ def test_two_vs_one_penalties_caption_only_advantage_team():
|
|||||||
)
|
)
|
||||||
assert payload["strength_label"] == "3×4"
|
assert payload["strength_label"] == "3×4"
|
||||||
assert payload["advantage_side"] == "away"
|
assert payload["advantage_side"] == "away"
|
||||||
assert payload["home_label"] == ""
|
assert payload["home_label"] == "Удаление"
|
||||||
assert payload["away_label"] == "Играют в большинстве"
|
assert payload["away_label"] == "Играют в большинстве"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
81
tests/test_build91_runtime_version_pbp_penalty_labels.py
Normal file
81
tests/test_build91_runtime_version_pbp_penalty_labels.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from hockey_data.service import HockeyDataService
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
RUNTIME = (ROOT / "ui_builder/static/runtime.html").read_text(encoding="utf-8")
|
||||||
|
INDEX = (ROOT / "ui_builder/static/index.html").read_text(encoding="utf-8")
|
||||||
|
INTEGRATION = (ROOT / "ui_builder/integration.py").read_text(encoding="utf-8")
|
||||||
|
APP_JS = (ROOT / "ui_builder/static/app.js").read_text(encoding="utf-8")
|
||||||
|
APP = (ROOT / "app.py").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _penalty(side: str, remaining_ms: int = 120000):
|
||||||
|
return {
|
||||||
|
"side": side,
|
||||||
|
"infraction": {"id": "minor"},
|
||||||
|
"preset": "2m",
|
||||||
|
"durationMs": 120000,
|
||||||
|
"remainingMs": remaining_ms,
|
||||||
|
"finished": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _strength(home=0, away=0, language="ru"):
|
||||||
|
timer_state = {
|
||||||
|
"penalty_board": {
|
||||||
|
"penalties": [_penalty("home") for _ in range(home)] + [_penalty("away") for _ in range(away)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings = {
|
||||||
|
"strength_regulation_skaters": 5,
|
||||||
|
"strength_regular_overtime_skaters": 3,
|
||||||
|
"strength_playoff_overtime_skaters": 5,
|
||||||
|
"strength_min_skaters": 3,
|
||||||
|
"strength_state_labels": {
|
||||||
|
"regulation": {
|
||||||
|
"5x4": {"ru": "Играют в большинстве", "en": "Power play"},
|
||||||
|
"4x4": {"ru": "4 на 4", "en": "4 on 4"},
|
||||||
|
"4x3": {"ru": "Большинство 4 на 3", "en": "4 on 3 power play"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return HockeyDataService._strength_payload(
|
||||||
|
settings,
|
||||||
|
stage="regular",
|
||||||
|
current_period="1",
|
||||||
|
timer_state=timer_state,
|
||||||
|
language=language,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_runtime_badge_is_server_build_version_placeholder():
|
||||||
|
assert "BUILD __UI_BUILDER_BUILD_VERSION__" in RUNTIME
|
||||||
|
assert "BUILD __UI_BUILDER_BUILD_VERSION__" in INDEX
|
||||||
|
assert '.replace("__UI_BUILDER_BUILD_VERSION__", str(build_version or asset_version))' in INTEGRATION
|
||||||
|
assert "build_version=BUILD_VERSION" in APP
|
||||||
|
assert 'BUILD_VERSION = "2026.08.20.12"' in APP
|
||||||
|
|
||||||
|
|
||||||
|
def test_play_by_play_defaults_collapsed_until_operator_opens_it():
|
||||||
|
assert "Object.prototype.hasOwnProperty.call(state.formValues, collapsedKey)" in APP_JS
|
||||||
|
assert ": true;" in APP_JS
|
||||||
|
|
||||||
|
|
||||||
|
def test_penalized_side_gets_penalty_caption_and_advantage_side_keeps_pp_caption():
|
||||||
|
payload = _strength(home=1, away=0)
|
||||||
|
assert payload["home_label"] == "Удаление"
|
||||||
|
assert payload["away_label"] == "Играют в большинстве"
|
||||||
|
|
||||||
|
|
||||||
|
def test_equal_coincidental_penalties_expose_penalty_caption_on_both_mapping_sides():
|
||||||
|
payload = _strength(home=1, away=1)
|
||||||
|
assert payload["advantage_side"] == ""
|
||||||
|
assert payload["home_label"] == "Удаление"
|
||||||
|
assert payload["away_label"] == "Удаление"
|
||||||
|
|
||||||
|
|
||||||
|
def test_english_penalty_caption():
|
||||||
|
payload = _strength(home=1, away=0, language="en")
|
||||||
|
assert payload["home_label"] == "Penalty"
|
||||||
|
assert payload["away_label"] == "Power play"
|
||||||
@@ -24,6 +24,7 @@ def _seed_config(source: Path, destination: Path) -> None:
|
|||||||
def install_ui_builder(
|
def install_ui_builder(
|
||||||
app: FastAPI,
|
app: FastAPI,
|
||||||
*,
|
*,
|
||||||
|
build_version: str = "",
|
||||||
settings_dir: Path,
|
settings_dir: Path,
|
||||||
data_providers: dict[str, DataProvider],
|
data_providers: dict[str, DataProvider],
|
||||||
source_labels: dict[str, str] | None = None,
|
source_labels: dict[str, str] | None = None,
|
||||||
@@ -108,6 +109,7 @@ def install_ui_builder(
|
|||||||
html = (
|
html = (
|
||||||
template.replace("__UI_BUILDER_ASSETS__", assets_url.rstrip("/"))
|
template.replace("__UI_BUILDER_ASSETS__", assets_url.rstrip("/"))
|
||||||
.replace("__UI_BUILDER_ASSET_VERSION__", asset_version)
|
.replace("__UI_BUILDER_ASSET_VERSION__", asset_version)
|
||||||
|
.replace("__UI_BUILDER_BUILD_VERSION__", str(build_version or asset_version))
|
||||||
.replace("__UI_BUILDER_API__", page_api)
|
.replace("__UI_BUILDER_API__", page_api)
|
||||||
.replace("__UI_BUILDER_AUTH_API__", auth_api)
|
.replace("__UI_BUILDER_AUTH_API__", auth_api)
|
||||||
.replace("__UI_BUILDER_EDITOR__", editor_url)
|
.replace("__UI_BUILDER_EDITOR__", editor_url)
|
||||||
|
|||||||
@@ -9574,7 +9574,9 @@ function renderStandaloneHockeyPlayByPlayWindow() {
|
|||||||
let selectedPeriod = String(state.formValues[stateKey] || "all");
|
let selectedPeriod = String(state.formValues[stateKey] || "all");
|
||||||
if (!segments.includes(selectedPeriod)) selectedPeriod = "all";
|
if (!segments.includes(selectedPeriod)) selectedPeriod = "all";
|
||||||
rememberUiNavigationState("hockey_play_by_play", "period", selectedPeriod, hockeyStatisticsSegmentLabel(selectedPeriod, language), { emit: false });
|
rememberUiNavigationState("hockey_play_by_play", "period", selectedPeriod, hockeyStatisticsSegmentLabel(selectedPeriod, language), { emit: false });
|
||||||
const collapsed = Boolean(state.formValues[collapsedKey]);
|
const collapsed = Object.prototype.hasOwnProperty.call(state.formValues, collapsedKey)
|
||||||
|
? Boolean(state.formValues[collapsedKey])
|
||||||
|
: true;
|
||||||
const labels = hockeyEventCategoryLabels(language);
|
const labels = hockeyEventCategoryLabels(language);
|
||||||
const items = sourceItems
|
const items = sourceItems
|
||||||
.filter((item) => selectedPeriod === "all" || String(item?.period || "") === selectedPeriod)
|
.filter((item) => selectedPeriod === "all" || String(item?.period || "") === selectedPeriod)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
editorUrl: "__UI_BUILDER_EDITOR__",
|
editorUrl: "__UI_BUILDER_EDITOR__",
|
||||||
runtimeUrl: "__UI_BUILDER_RUNTIME__",
|
runtimeUrl: "__UI_BUILDER_RUNTIME__",
|
||||||
editorHotkey: "__UI_BUILDER_EDITOR_HOTKEY__",
|
editorHotkey: "__UI_BUILDER_EDITOR_HOTKEY__",
|
||||||
|
buildVersion: "__UI_BUILDER_BUILD_VERSION__",
|
||||||
mode: "__UI_BUILDER_MODE__"
|
mode: "__UI_BUILDER_MODE__"
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -156,7 +157,7 @@
|
|||||||
<header class="runtime-topbar">
|
<header class="runtime-topbar">
|
||||||
<div>
|
<div>
|
||||||
<div class="eyebrow">RUNTIME INTERFACE</div>
|
<div class="eyebrow">RUNTIME INTERFACE</div>
|
||||||
<div class="runtime-title-line"><strong id="runtimeTitle">Интерфейс</strong><small class="runtime-build-badge">BUILD 2026.08.19.28</small></div>
|
<div class="runtime-title-line"><strong id="runtimeTitle">Интерфейс</strong><small class="runtime-build-badge">BUILD __UI_BUILDER_BUILD_VERSION__</small></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="runtimeTabs" class="runtime-tabs"></div>
|
<div id="runtimeTabs" class="runtime-tabs"></div>
|
||||||
<div class="runtime-actions">
|
<div class="runtime-actions">
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
editorUrl: "__UI_BUILDER_EDITOR__",
|
editorUrl: "__UI_BUILDER_EDITOR__",
|
||||||
runtimeUrl: "__UI_BUILDER_RUNTIME__",
|
runtimeUrl: "__UI_BUILDER_RUNTIME__",
|
||||||
editorHotkey: "__UI_BUILDER_EDITOR_HOTKEY__",
|
editorHotkey: "__UI_BUILDER_EDITOR_HOTKEY__",
|
||||||
|
buildVersion: "__UI_BUILDER_BUILD_VERSION__",
|
||||||
mode: "runtime"
|
mode: "runtime"
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
<header class="runtime-topbar">
|
<header class="runtime-topbar">
|
||||||
<div>
|
<div>
|
||||||
<div class="eyebrow">RUNTIME INTERFACE</div>
|
<div class="eyebrow">RUNTIME INTERFACE</div>
|
||||||
<div class="runtime-title-line"><strong id="runtimeTitle">Интерфейс</strong><small class="runtime-build-badge">BUILD 2026.08.19.28</small></div>
|
<div class="runtime-title-line"><strong id="runtimeTitle">Интерфейс</strong><small class="runtime-build-badge">BUILD __UI_BUILDER_BUILD_VERSION__</small></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="runtimeTabs" class="runtime-tabs"></div>
|
<div id="runtimeTabs" class="runtime-tabs"></div>
|
||||||
<div class="runtime-actions">
|
<div class="runtime-actions">
|
||||||
|
|||||||
Reference in New Issue
Block a user