# review Specification ## Purpose Ревью раскладки человеком после распознавания и матча: петля «догадка → подсказка → перераспознавание», команды (Применить/Уточнить/Распознать заново/ Игнор/Позже/Отклонить/Undo/Привязать заново; тип — read-only, корректируется через «Уточнить»), мягкие подсказки vs жёсткие `override`, единый список источников совпадения с ручным добавлением и предпросмотром (превью = применение), разделение труда транспортов (веб — точные правки, Telegram — быстрые действия и эскалация в веб). ## Requirements ### Requirement: Вход в review с явной причиной Когда модель уверенности не разрешает авто-раскладку, система SHALL переводить загрузку в `review` и SHALL показывать **конкретную причину** (низкая самооценка LLM; нет матча в базе или несколько кандидатов; предупреждение структурной валидации; неразобранный ответ LLM), а не обобщённое «не уверен». Поверхность решения SHALL быть единой для всех транспортов и содержать источник (имя, контекст, дерево файлов), догадку системы (тип, название, год, матч) и превью целевой раскладки. #### Scenario: Причина видна в интерфейсе - **GIVEN** загрузка ушла в `review` из-за отсутствия матча в базе - **WHEN** пользователь открывает ревью - **THEN** показана конкретная причина (напр. «нет в TMDB · уверенность 0.46») ### Requirement: Команды ревью и их эффекты Экран ревью SHALL предоставлять команды: **Применить** (создать хардлинки по эффективному плану), **Уточнить** (добавить подсказку → перераспознать), **Распознать заново** (повторный прогон без новой подсказки), **Игнор файла**, **Позже** (`deferred`), **Отклонить** (`cancelled`), **Undo** (снять созданные ссылки → `reverted`) и **Привязать заново** (из `reverted`/`cancelled`/`target_missing` → перераспознавание с ручным подтверждением). Экран ревью MUST NOT содержать команду переключения типа movie↔series: тип показывается read-only, а его корректировка выполняется мягкой подсказкой через **Уточнить**. Команды из любого транспорта SHALL сериализоваться worker'ом под единой блокировкой; применяется последняя валидная команда. Команды, которым нужен источник, SHALL проверять его наличие синхронно перед действием. #### Scenario: Применение создаёт раскладку - **GIVEN** загрузка в `review` с эффективным планом - **WHEN** пользователь выбирает «Применить» - **THEN** создаются хардлинки по плану, задача переходит к раскладке #### Scenario: Отклонить и привязать заново - **GIVEN** загрузка в `review` - **WHEN** пользователь «Отклонить», затем «Привязать заново» - **THEN** задача уходит в `cancelled`, а затем снова на распознавание с ручным подтверждением (авто-раскладка не делается) #### Scenario: Тип не переключается кнопкой - **GIVEN** загрузка в `review` с распознанным типом - **WHEN** пользователь открывает экран ревью - **THEN** отдельной команды/кнопки переключения movie↔series на экране нет - **AND** тип показан read-only в инфо-части выбранного источника ### Requirement: Подсказка мягкая, override жёсткий Подсказка (`hint`) SHALL быть мягким сигналом — её интерпретирует LLM при перераспознавании. Ручная правка поля SHALL быть жёстким **override**: система берёт значение как есть и «пиннит» его; перераспознавание НЕ SHALL затирать уже поправленное поле. Накопленные подсказки и правки SHALL переживать перераспознавание и накладываться на новый план. #### Scenario: Override переживает перераспознавание - **GIVEN** пользователь зафиксировал тип `series` как override - **WHEN** запускается перераспознавание по новой подсказке - **THEN** в новом эффективном плане тип остаётся `series` ### Requirement: Единый список источников совпадения на ревью Экран ревью (`/review/{id}`) SHALL показывать совпавшие источники **единым списком**, в котором распознавание нейронкой (без базы) — такая же строка, как кандидаты метабаз (TMDB/TVDB/TVMaze), а не отдельный режим сверху. Ровно один источник в списке SHALL быть отмечен активным (эффективный матч). Активный источник SHALL выбираться **одним кликом/тапом по строке варианта** (радиокнопка), без отдельной кнопки подтверждения выбора. Выбор источника SHALL сохранять его как эффективный матч (persist) и SHALL выполняться через раундтрип на сервер (форма/htmx), без клиентского пересчёта доменного состояния; при этом инфо-часть и предпросмотр раскладки SHALL немедленно обновляться под выбранный источник (частичный своп блока, без полной перезагрузки страницы). Экран SHALL позволять операции над этим списком: выбрать кандидата базы, переключиться на другого кандидата и снять матч с базы обратно на нейронку («без базы»). Список источников SHALL показываться только при наличии плана распознавания. #### Scenario: Нейронка — строка в общем списке - **GIVEN** загрузка в `review` с распознаванием нейронкой и одним или несколькими кандидатами метабаз - **WHEN** пользователь открывает `GET /review/{id}` - **THEN** источники показаны единым списком, где строка «распознано нейронкой» стоит наравне с кандидатами баз - **AND** активным отмечен ровно один источник (текущий эффективный матч) #### Scenario: Выбор кандидата одним кликом - **GIVEN** на экране ревью выбран один кандидат метабазы - **WHEN** пользователь кликает/тапает строку другого кандидата - **THEN** выбранный кандидат сохраняется активным, прочие — неактивны, без отдельного нажатия кнопки «выбрать» - **AND** инфо-часть и предпросмотр раскладки сразу обновляются под выбранного кандидата без полной перезагрузки страницы #### Scenario: Снятие матча в пользу нейронки - **GIVEN** на экране ревью активен кандидат метабазы с названием «Fargo» - **WHEN** пользователь кликает строку «распознано нейронкой» - **THEN** матч с базой снимается (источник — нейронка, «без базы»), тег папки провайдера не проставляется - **AND** поля источника — из распознавания нейронкой, без унаследованных от прежнего кандидата название/год ### Requirement: Ручное добавление источника по id или URL Когда автопоиск по базам промахнулся, экран ревью SHALL позволять добавить источник вручную — по идентификатору записи метабазы или, где применимо, по её URL. Ввод SHALL разбираться и валидироваться в пару `(provider, provider_id)` на входной границе (`internal/httpapi`); допустимые провайдеры — `tmdb`, `tvdb`, `imdb`. Добавленный источник SHALL появляться в списке как выбираемая строка; при совпадении `provider:id` с уже присутствующим источником новая строка NOT создаётся, а выбирается существующая. Некорректный ввод SHALL отклоняться с сообщением, не меняя текущий активный источник. #### Scenario: Добавление кандидата по URL TMDB - **GIVEN** загрузка в `review`, где нужной записи нет среди автокандидатов - **WHEN** пользователь вводит URL записи TMDB и подтверждает добавление - **THEN** из URL извлекаются провайдер и id, источник добавляется в список выбираемой строкой #### Scenario: Дубль id выбирает существующую строку - **GIVEN** в списке уже есть кандидат с данным `provider:id` - **WHEN** пользователь добавляет вручную тот же `provider:id` - **THEN** новая строка не создаётся, активным становится существующий кандидат #### Scenario: Некорректный ввод отклонён - **WHEN** пользователь вводит нераспознаваемый id/URL - **THEN** экран показывает сообщение об ошибке и не меняет текущий активный источник ### Requirement: Инфо и предпросмотр выбранного источника В едином блоке выбора источника экран ревью SHALL показывать для **выбранного (активного)** источника две части: **инфо** — тип (read-only, movie/series), название, оригинальное название, год, для сериала — сводку сезонов (один сезон, диапазон/список для многосезонного пака или «Спецвыпуски»), с зарезервированным местом под режиссёра; и **предпросмотр раскладки** — целевые пути хардлинков этого источника. Обе части SHALL относиться именно к активному источнику и SHALL обновляться при смене выбора. Отрисовка блока (показ инфо и предпросмотра) MUST NOT создавать хардлинки: раскладка создаётся только явным действием «Применить». Совпадение целевых путей предпросмотра с результатом применения регулируется требованием «Превью раскладки через единую логику именования» (`web-ui`). #### Scenario: Инфо и предпросмотр относятся к активному источнику - **GIVEN** в списке активен кандидат метабазы - **WHEN** пользователь смотрит инфо-часть и предпросмотр раскладки - **THEN** показаны тип, название, ориг. название, год (и сводка сезонов для сериала) именно этого источника и предпросмотр его целевых путей #### Scenario: Просмотр блока не создаёт раскладку - **GIVEN** экран ревью с показанным блоком выбора источника - **WHEN** пользователь только просматривает инфо и предпросмотр, не нажимая «Применить» - **THEN** хардлинки не создаются, файлы под `paths.movies`/`series` не меняются #### Scenario: Зарезервированное место под режиссёра - **GIVEN** режиссёр из метабазы пока не загружается - **WHEN** отображается инфо-часть выбранного источника - **THEN** в ней присутствует место под режиссёра, показанное пустым (или прочерком), не ломая вёрстку ### Requirement: Разделение труда транспортов в ревью Состояние ревью SHALL быть единым (в SQLite) для всех транспортов. Веб-UI SHALL быть поверхностью точных правок (маппинг файлов, выбор/ввод источника, предпросмотр). Telegram SHALL давать быстрые действия (одобрить, подсказать, переключить тип, «Позже»/«Отклонить») и эскалацию в веб deep-link'ом на ту же страницу; точечные правки, не помещающиеся в чат, SHALL делаться в вебе. #### Scenario: Эскалация из Telegram в веб - **GIVEN** загрузка в `review`, требующая точечного маппинга файлов - **WHEN** пользователь в Telegram выбирает «В вебе» - **THEN** бот даёт deep-link на страницу ревью той же загрузки