Имя: восстановление display_name после распознавания + гард пустого входа
Голый magnet без dn/контекста заставлял namer звать LLM на пустом входе, и модель галлюцинировала мусорное имя («Unknown»), которое писалось и в display_name, и в rename qBittorrent, а заодно ломало UI-фолбэк на распознанное название. Верное каноническое имя, вычисляемое позже при распознавании, никуда не переливалось. - naming: гард пустого входа в DeriveName (нет контекста и подсказки → "" без вызова LLM) + детерминированный форматтер FormatTitleYear. - qbt: операция RenameTorrent (переименование существующей раздачи). - store: SetDisplayName — обновление имени постфактум без гарда состояния. - worker: refreshDisplayNameLocked/RefreshDisplayName — перелив канонического имени (эффективный план) в display_name + best-effort rename раздачи по реальному t.Hash; авто-триггер при подтверждении матча (choose/manual add). - web-ui: кнопка «Обновить имя» на странице загрузки (htmx-своп заголовка, деградация без JS), видимая при наличии распознавания (вкл. done/orphaned). Спека: дельты ingest/review/web-ui влиты в openspec/specs; change refresh-display-name заархивирован. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,12 @@ SHALL обрезаться по ограничению длины.
|
||||
JSON-вывод), извлекая из контекста тип (movie/series), название, год,
|
||||
режиссёра и (для сериала) номер сезона. Год и режиссёр — опциональные поля.
|
||||
|
||||
Если и контекст, и подсказка из полей источника (`dn` magnet / имя `.torrent`)
|
||||
пусты, система SHALL считать имя не выведенным и SHALL NOT вызывать LLM (выводить
|
||||
имя не из чего — вызов на пустом входе способен лишь галлюцинировать). В этом
|
||||
случае загрузка добавляется без `rename`, а `download.display_name` остаётся
|
||||
пустым.
|
||||
|
||||
Название SHALL быть на русском языке для российского контента и на
|
||||
английском (оригинальном) — для остального.
|
||||
|
||||
@@ -89,6 +95,15 @@ JSON-вывод), извлекая из контекста тип (movie/series)
|
||||
результат либо недоступен
|
||||
- **THEN** система не прерывает приём и переходит к алгоритмическому фолбеку
|
||||
|
||||
#### Scenario: Пустой вход — LLM не вызывается
|
||||
|
||||
- **GIVEN** пойманная загрузка без пользовательского контекста и без подсказки
|
||||
из полей источника (голый magnet без `dn`)
|
||||
- **WHEN** система выводит отображаемое имя на шаге добавления
|
||||
- **THEN** LLM не вызывается, имя считается не выведенным
|
||||
- **AND** загрузка добавляется без `rename`, а `download.display_name` пуст
|
||||
(заголовок в веб-UI берётся из фолбека)
|
||||
|
||||
### Requirement: Алгоритмический фолбек вывода имени без сети
|
||||
|
||||
При неудаче LLM система SHALL выводить имя алгоритмически, без сетевых
|
||||
@@ -111,6 +126,54 @@ JSON-вывод), извлекая из контекста тип (movie/series)
|
||||
- **WHEN** ни LLM, ни алгоритмический фолбек не дали непустого имени
|
||||
- **THEN** система добавляет загрузку без параметра `rename`
|
||||
|
||||
### Requirement: Обновление отображаемого имени по распознаванию
|
||||
|
||||
Система SHALL уметь обновлять отображаемое имя загрузки после того, как
|
||||
распознавание дало каноническое название, — переливая уже вычисленное имя (без
|
||||
нового вызова LLM) в `download.display_name` и в имя раздачи qBittorrent.
|
||||
|
||||
Источником имени SHALL быть **эффективное** распознанное название: пины
|
||||
`title`/`year` (если матч подтверждён), иначе `recognition.title`/`year` — то
|
||||
же название, что использует раскладка. Формат SHALL быть коротким детерминированным
|
||||
ярлыком `Title (Year)` (год опционален), с той же очисткой от управляющих
|
||||
символов и обрезкой по длине, что и вывод имени на шаге добавления. Пустой
|
||||
источник (нет распознавания или пустое название) SHALL приводить к отсутствию
|
||||
изменений (no-op).
|
||||
|
||||
Переименование раздачи в qBittorrent SHALL адресоваться по infohash своей
|
||||
раздачи и SHALL быть best-effort: сбой (раздача удалена, qBittorrent недоступен)
|
||||
SHALL NOT проваливать обновление — `download.display_name` обновляется в любом
|
||||
случае, ошибка внешнего вызова логируется. Как и на шаге добавления,
|
||||
отображаемое имя SHALL влиять только на отображение и SHALL NOT влиять на пути
|
||||
файлов, распознавание или раскладку.
|
||||
|
||||
Обновление имени SHALL иметь две точки входа: **авто** — при подтверждённом
|
||||
матче (см. capability `review`); **ручную** — по явному действию пользователя.
|
||||
Ручное действие SHALL перезаписывать текущее имя всегда; авто SHALL перезаписывать,
|
||||
когда выведенное имя непусто.
|
||||
|
||||
#### Scenario: Перелив имени в загрузку и раздачу
|
||||
|
||||
- **GIVEN** загрузка с распознанным непустым каноническим названием
|
||||
- **WHEN** запускается обновление отображаемого имени
|
||||
- **THEN** `download.display_name` устанавливается в `Title (Year)`
|
||||
- **AND** раздача в qBittorrent переименовывается в то же имя (по infohash своей
|
||||
раздачи)
|
||||
|
||||
#### Scenario: qBittorrent недоступен — имя у загрузки всё равно обновлено
|
||||
|
||||
- **GIVEN** обновление отображаемого имени с выведенным непустым именем
|
||||
- **WHEN** переименование раздачи в qBittorrent завершается ошибкой (недоступен
|
||||
или раздача удалена)
|
||||
- **THEN** `download.display_name` всё равно обновлён
|
||||
- **AND** ошибка внешнего вызова qBittorrent логируется, операция не проваливается
|
||||
|
||||
#### Scenario: Нет распознавания — обновление ничего не делает
|
||||
|
||||
- **GIVEN** загрузка без распознанного названия (пустой источник имени)
|
||||
- **WHEN** запускается обновление отображаемого имени
|
||||
- **THEN** ни `download.display_name`, ни имя раздачи не меняются (no-op)
|
||||
|
||||
### Requirement: Приём источника и заведение загрузки
|
||||
|
||||
Приём SHALL быть единым **быстрым** use-case, общим для всех транспортов (HTTP,
|
||||
|
||||
@@ -181,6 +181,45 @@ qBittorrent, но и **готов к раскладке** — раздача в
|
||||
- **THEN** экран показывает сообщение об ошибке и не меняет текущий активный
|
||||
источник
|
||||
|
||||
### Requirement: Подтверждение матча обновляет отображаемое имя
|
||||
|
||||
Система SHALL при подтверждении матча в ревью запускать обновление отображаемого
|
||||
имени загрузки по подтверждённому распознаванию (см. capability `ingest`):
|
||||
переливать каноническое название (`Title (Year)`) в `download.display_name` и в
|
||||
имя раздачи qBittorrent, без нового вызова LLM. Подтверждением матча SHALL
|
||||
считаться как выбор кандидата из списка совпадений, так и ручное добавление
|
||||
источника по id/URL (оба закрепляют провайдера и каноническое название).
|
||||
|
||||
Обновление SHALL выполняться после успешного закрепления выбора кандидата и
|
||||
SHALL быть best-effort по отношению к qBittorrent: недоступность клиента SHALL
|
||||
NOT проваливать команду ревью. Это согласуется с инвариантом «авто-действие
|
||||
только при подтверждённом матче».
|
||||
|
||||
#### Scenario: Выбор кандидата обновляет имя
|
||||
|
||||
- **GIVEN** загрузка в ревью с пустым или неинформативным `display_name`
|
||||
(например, «Unknown») и списком кандидатов
|
||||
- **WHEN** пользователь выбирает кандидата, подтверждая матч
|
||||
- **THEN** выбор кандидата закрепляется как и прежде
|
||||
- **AND** `download.display_name` обновляется каноническим именем кандидата
|
||||
`Title (Year)`
|
||||
- **AND** раздача в qBittorrent переименовывается в то же имя
|
||||
|
||||
#### Scenario: Ручное добавление источника обновляет имя
|
||||
|
||||
- **GIVEN** загрузка в ревью без совпадений в списке
|
||||
- **WHEN** пользователь вручную добавляет источник по id/URL, подтверждая матч
|
||||
- **THEN** источник закрепляется как и прежде
|
||||
- **AND** `download.display_name` и имя раздачи в qBittorrent обновляются
|
||||
каноническим названием подтверждённого источника
|
||||
|
||||
#### Scenario: Недоступность qBittorrent не ломает выбор кандидата
|
||||
|
||||
- **GIVEN** выбор кандидата в ревью
|
||||
- **WHEN** переименование раздачи в qBittorrent завершается ошибкой
|
||||
- **THEN** выбор кандидата и обновление `download.display_name` выполнены
|
||||
- **AND** команда ревью не возвращает ошибку
|
||||
|
||||
### Requirement: Инфо и предпросмотр выбранного источника
|
||||
|
||||
В едином блоке выбора источника экран ревью SHALL показывать для **выбранного
|
||||
|
||||
@@ -174,6 +174,36 @@ jellybit (`created_at`). Порядок MUST быть согласован ме
|
||||
- **AND** если нет ни того, ни другого — заголовком служит усечённый до одной
|
||||
строки сырой источник, а не многострочный magnet
|
||||
|
||||
### Requirement: Действие «Обновить имя» на странице загрузки
|
||||
|
||||
Когда у загрузки есть распознавание, страница загрузки SHALL предлагать действие
|
||||
«Обновить имя», запускающее обновление отображаемого имени по распознаванию (см.
|
||||
capability `ingest`). Видимость действия SHALL определяться наличием распознавания
|
||||
(а не состоянием ревью): в частности, действие SHALL быть доступно и на терминальных
|
||||
состояниях с распознаванием (`done`/`orphaned`), где раздачу нужно переименовать
|
||||
постфактум. Действие SHALL обновлять заголовок на месте по htmx-паттерну
|
||||
(фрагмент заголовка перерисовывается ответом), без перезагрузки страницы, и SHALL
|
||||
деградировать без JS (обычная форма-POST с переходом на страницу загрузки).
|
||||
|
||||
Действие SHALL быть идемпотентным по эффекту: повторный запуск на неизменном
|
||||
распознавании даёт то же имя. Отсутствие распознавания (пустой источник имени)
|
||||
SHALL приводить к отсутствию видимых изменений.
|
||||
|
||||
#### Scenario: Ручное обновление имени перерисовывает заголовок
|
||||
|
||||
- **GIVEN** страница загрузки с распознанным каноническим названием и заголовком
|
||||
«Unknown»
|
||||
- **WHEN** пользователь запускает действие «Обновить имя»
|
||||
- **THEN** заголовок на странице перерисовывается каноническим именем `Title (Year)`
|
||||
на месте (htmx), без полной перезагрузки
|
||||
|
||||
#### Scenario: Деградация без JS
|
||||
|
||||
- **GIVEN** клиент без htmx/JS
|
||||
- **WHEN** пользователь отправляет действие «Обновить имя» обычной формой
|
||||
- **THEN** сервер выполняет обновление и отвечает страницей загрузки с новым
|
||||
заголовком
|
||||
|
||||
### Requirement: Матч с записью метабазы ссылкой
|
||||
|
||||
Веб-UI SHALL показывать подтверждённый матч с записью метабазы (TMDB/TVDB/IMDb)
|
||||
|
||||
Reference in New Issue
Block a user