190 lines
20 KiB
Markdown
190 lines
20 KiB
Markdown
# Архитектура
|
||
|
||
## Модули
|
||
|
||
| Каталог | Назначение |
|
||
| --- | --- |
|
||
| `hockey_data/` | Stat2TV, PostgreSQL, матч, статистика, роли, mapping и Agent bridge |
|
||
| `ui_builder/` | Действующий runtime/редактор UI и активные статические ресурсы |
|
||
| `broadcast_settings/` | Переносимые настройки команд и vMix JSON |
|
||
| `khl_site/` | Карточки КХЛ, карьера, обновления и локальные данные |
|
||
| `agent/` | Windows Agent и исходники Go |
|
||
| `settings/local/` | Активная конфигурация UI, таймеров, обновлений и восстановление |
|
||
| `tests/` | Регрессии серверной логики и браузерных функций |
|
||
| `logs/` | Закрытый постоянный журнал программы в месячных SQLite-файлах |
|
||
|
||
## Поток данных
|
||
|
||
BUILD202 разделяет обработку гола на снятие, ожидание, скрытую подготовку и фиксацию.
|
||
На затронутых физических Input сначала завершаются уже отправленные записи, затем
|
||
отправляется отдельный OUT. До его подтверждения и окончания настроенного интервала
|
||
новые DATA/Mapping, native-команды и IN этих Input удерживаются. Сервер использует
|
||
`goal_hide_settle_seconds`, клиент получает признак `hide_settled`; настройка цели
|
||
`goal_hide_delay_seconds` задаёт 0–60 с, по умолчанию 1 с. Для нескольких привязок
|
||
одного физического Input используется наибольшее значение. ACK не является событием
|
||
завершения GT TransitionOut: длительность задаёт оператор по своему титру.
|
||
|
||
После ожидания время готовится скрыто и подтверждается перед фиксацией карточки.
|
||
У первой половины 2+2 остаётся 2:00; актуальный Mapping не перезаписывает native-поля,
|
||
после его подтверждения восстанавливаются ранее занятые слои. Общий Input нескольких
|
||
известных удалений снимается целиком; оставшиеся часы подготавливаются по текущему
|
||
состоянию без завершения соседних карточек. Основной счёт, чужой титр и неоднозначное
|
||
владение полем не снимаются этим путём. Ошибка или неизвестный исход сохраняет
|
||
карточку и блокировку затронутого Input до явной ручной проверки; OUT не повторяется
|
||
автоматически. Независимые Input могут продолжать целевое обновление данных.
|
||
|
||
Сервер сопоставляет ключ, GUID, имя и номер Input с единственным физическим
|
||
Input текущей inventory. Во время снятия/неподтверждённого результата команды
|
||
без Input или с неразрешимой привязкой отклоняются, чтобы не обойти защиту.
|
||
Входящий кадр регистрируется атомарно на всех его Input; перед OUT дожидаются
|
||
подтверждений уже отправленных записей. Разделение кадров сохраняет порядок
|
||
команд одного Overlay, даже если он переключается между разными Input.
|
||
|
||
После неуспешного отправленного OUT сервер освобождает временные блокировки,
|
||
но оставляет Input в карантине: отдельный DATA/native/IN-запрос получает
|
||
`409 goal_hide_unconfirmed`. Составной пакет может вернуть `200` с `ok: false`
|
||
и позиционной причиной, сохранив ACK успешных независимых Input.
|
||
Карантин снимает успешный явно запрошенный OUT с подтверждением и интервалом
|
||
снятия; затем клиент выполняет исходную ручную команду управления удалением.
|
||
Инвалидация поколения доставки устройства делает старое ожидание и карантин
|
||
неактуальными: это смена соединения, подтверждённое отключение vMix, новое
|
||
назначение, принятая inventory проекта или передача контроля. Простое сохранение
|
||
конфига/сессии карантин не снимает. Прежний ответ Agent не может подтвердить
|
||
новое назначение.
|
||
Автоматического повтора OUT для восстановления нет.
|
||
|
||
Расчёт численного состава исключает дисциплинарные 10/20, явно отмеченные
|
||
`queued`/`awaitingStart` и компенсированные записи (`offsetting`/`affectsStrength: false`).
|
||
Эти признаки сохраняются вместе с карточкой. Сам `coincidental` не исключает
|
||
штраф из состава, но запрещает автоматическое завершение голом; модель не
|
||
заменяет решение судьи о взаимозачёте, очереди и возвращении состава в овертайме.
|
||
После последнего влияющего на состав штрафа цикл плашки завершается, даже если
|
||
в панели осталась дисциплинарная запись; новый цикл не наследует старого владельца.
|
||
|
||
BUILD201 удаляет ручной переключатель «ШБ» и исключает устаревший флаг
|
||
`score_penalty_shot` из определения обычного гола. Явно переданный тип текущего
|
||
гола `penalty_shot` сохраняет своё отдельное значение.
|
||
|
||
BUILD200 сохраняет владельца плашки удаления вместе с её циклом показа, чтобы
|
||
переход 4on4 → PP не переносил текущий титр на другую сторону. Подтверждение
|
||
нужных команд vMix предшествует фиксации завершения штрафа в панели; ошибка
|
||
доставки сохраняет карточку для проверки, неизвестный исход не воспроизводится.
|
||
|
||
BUILD199 хранит разрешение связанных плашек в флаге матча
|
||
`scoreboard_auxiliaries_enabled`. Checkbox и F2 используют один контролируемый
|
||
запрос; на отправке заново проверяются матч, роль, версия намерения и конкретные
|
||
Input/Overlay. Gate отделён от часов и подготовки данных. Снятие не отменяет
|
||
native Countdown, а повторное включение сохраняет исходный срок задержки счёта.
|
||
|
||
Обработка гола выполняется у действующего оператора по новой команде +1 или
|
||
одиночному приросту уже наблюдаемого SQL/API-счёта. Первый снимок и изменение
|
||
источника задают базу. История хранит идентификатор обработанного перехода и
|
||
снимок изменения штрафа для защиты от повторов и осторожной отмены гола.
|
||
Для подтверждаемого изменения штрафа native-команды формируются перед самой
|
||
отправкой по текущему состоянию конкретного Input/SelectedName, чтобы поздний
|
||
Start не возобновил уже приостановленные часы и не затронул соседнее поле.
|
||
|
||
Stat2TV XML/JSON разбираются сервисом и сохраняются в нормализованных таблицах
|
||
PostgreSQL: турниры, команды, игроки, составы, события и статистика.
|
||
Карточки/карьера КХЛ имеют отдельный локальный импорт и синхронизацию.
|
||
Рабочее состояние матча и права доступа относятся к матчу; персональный канал,
|
||
назначение устройства и mapping определяют получателя данных vMix.
|
||
|
||
SQL Mapping получает контекст матча/выбора, формирует SetText/SetImage и передаёт
|
||
их нужному Agent. Отправка команды и её подтверждение — разные этапы.
|
||
Команды действий (например, Toggle/Cut) нельзя повторять без проверки результата.
|
||
Восстановление связи должно заново синхронизировать данные, не воспроизводя эфирные действия.
|
||
|
||
Mapping имеет раздельное ожидаемое и подтверждённое состояние. На Agent ожидается
|
||
ACK не более одного пакета Mapping. Agent с явной capability `mapping_title_batch_v1`
|
||
получает команды, сгруппированные по Input, до 500 команд и 48 КиБ; прежние версии
|
||
получают до шести независимых установок. Новый Agent сохраняет порядок записей
|
||
одной цели, выполняет независимые цели ограниченным пулом и выделяет отдельный
|
||
обработчик для интерактивных команд. WebSocket reader продолжает читать назначения,
|
||
heartbeat и управление во время массовой отправки. Очереди и время обработки ограничены.
|
||
|
||
Ошибки отдельных полей не прерывают первый проход по остальным; безопасные повторы
|
||
выполняются после него. Частичный результат отличается от полной обработки. SQL-каталог ограничен
|
||
источниками действующих записей и правил, включая зависимости настроенного счёта.
|
||
Поколение соединения/назначения отбрасывает устаревшие подтверждения. Новый WebSocket
|
||
ждёт актуальную inventory перед восстановлением. После committed передачи управления
|
||
обновляется поколение доставки устройств матча.
|
||
|
||
Шаблон Mapping и опубликованный снимок устройства разделены. Снимок использует
|
||
существующие таблицы профилей/полей и хранится как технический профиль, связанный
|
||
с устройством. Сериализация устройства внутри транзакции передаёт её же session
|
||
в публикацию Mapping, без вложенного подключения с конкурирующей блокировкой.
|
||
Сохранение шаблона атомарно проверяет ожидаемую версию. Общие SQL
|
||
источники остаются общими; параметры контекста разрешаются для конкретного матча.
|
||
|
||
Эксплуатационный журнал независим от доступности PostgreSQL. Он получает
|
||
структурированные события приложения, стандартный Python logging, Uvicorn,
|
||
HTTP-аудит и браузерные замеры. Неблокирующая очередь ограничена, запись пакетная;
|
||
чтение использует короткие подключения и курсорную пагинацию. Секреты маскируются
|
||
до очереди записи. Запись startup/shutdown закрывается штатно и через atexit.
|
||
|
||
Сводка готовности к эфиру читает только назначение и опубликованный Mapping
|
||
выбранного устройства, доступного текущему аккаунту. Она сопоставляет цели с
|
||
inventory текущего WebSocket и использует подтверждения обработки Mapping
|
||
текущего поколения. Просмотр не исполняет SQL источников, не создаёт публикации
|
||
и не отправляет команды Agent. Эти подтверждения живут в памяти процесса;
|
||
после смены контекста или разрыва связи прежний успех не доказывает готовность.
|
||
|
||
Stat2TV использует общий ограниченный HTTP-пул, а не новое соединение на каждый
|
||
запрос. Авторизация и timeout передаются отдельно для каждого запроса;
|
||
при завершении сервера HTTP-клиенты закрываются после фоновых задач.
|
||
|
||
Состояние WebSocket/Agent и часть кэшей находятся в памяти одного процесса.
|
||
Поэтому запуск нескольких workers без общего хранилища назначения/очередей
|
||
не является способом масштабирования этой версии.
|
||
|
||
## Пользовательские данные и артефакты
|
||
|
||
UI хранится атомарно в `settings/local/` либо внешнем `HOCKEY_UI_DATA_DIR`.
|
||
Одинаковые сохранения не создают копий. Миграция прежней истории backup выполняется
|
||
однократно; исходные root-настройки сохраняются как bootstrap.
|
||
|
||
`hockey_data/snapshots/` — диагностические XML, а не база рабочего состояния.
|
||
Новые имена включают полный SHA-256 содержимого и тип ресурса. Повторные данные
|
||
используют прежний путь без перезаписи. История синхронизаций в БД может ссылаться
|
||
на эти файлы, поэтому возрастная очистка XML запрещена без сверки ссылок БД.
|
||
Автоматический retention относится только к самостоятельным JSON-отчётам discovery.
|
||
|
||
Флаги и их LICENSE, шаблоны vMix JSON, исходники Go/Agent.exe, SQL-примеры и
|
||
проверки входят в проект по назначению. Совпадение имён или содержимого файлов
|
||
default и локальной конфигурации не делает их взаимозаменяемыми.
|
||
|
||
Ответы браузерных запросов матча привязаны к поколению выбора и сессии. Загрузка
|
||
общих данных провайдера сохраняет рабочий контекст выбранного матча. Эффективный
|
||
SQL-счёт берётся из текущего Game либо проверенного результата настроенного ключа
|
||
для каждой стороны; ручные значения хранятся отдельно.
|
||
|
||
В BUILD198 право просмотра матча, DATA/Mapping своему Agent и право управления
|
||
разделены. Выбор роли завершается перед включением блокирующего loader;
|
||
устаревший выбор и объединённые запросы проверяют поколение запроса.
|
||
Операторские маршруты по-прежнему проверяют владельца управления. Заполнение
|
||
по опубликованному Mapping не предоставляет права выполнять произвольные команды.
|
||
|
||
Показ использует барьер подтверждённых данных конкретного Input. Явные setters
|
||
подтверждаются до IN/Preview; неполный Mapping не подтверждает готовность титра,
|
||
требующего данных. Ошибка подготовки блокирует последующий показ этой цели,
|
||
сохраняя обработку независимых титров. Все автоматические пути общей плашки
|
||
используют одну задержку от показа основного счёта. Снятие фиксирует связанные
|
||
цели, отменяет ожидающий IN и проверяет Input/владельца перед каждым OUT.
|
||
|
||
Agent 1.5.6 согласует `vmix_priority_v1`: bounded-очереди срочного управления,
|
||
обычных команд и Mapping. Общая HTTP-ёмкость равна 6, обычное управление и
|
||
Mapping совместно ограничены 5. Цепочки одной физической цели и Overlay сохраняют
|
||
причинный порядок между очередями. Новое явное обновление поля снимает право
|
||
устаревшего Mapping записывать это поле; пропуск не считается успешным ACK.
|
||
Корреляция `trace_id` связывает отдельные монотонные замеры браузера, сервера и
|
||
Agent. Часы разных компьютеров не вычитаются друг из друга.
|
||
|
||
В BUILD197 браузер связывает загрузку матча с поколением, аккаунтом, сессией,
|
||
языком и выбранным устройством. Mapping ждёт полных составов обеих команд.
|
||
Изменение упорядоченных данных игроков запускает полный apply выбранного Agent
|
||
со свежим разрешением SQL; одинаковые снимки объединяются. Признаки счёта,
|
||
времени, статистики и timestamps не входят в отпечаток состава. Ответ старой
|
||
отправки не закрывает окно нового матча. Полный apply сохраняет существующую
|
||
защиту native Countdown: поля таймеров исключаются до разрешения зависимостей.
|