Имя: восстановление 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:
@@ -0,0 +1,100 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Вывод имени через LLM со структурированным выводом
|
||||
|
||||
Система SHALL строить отображаемое имя с помощью LLM (структурированный
|
||||
JSON-вывод), извлекая из контекста тип (movie/series), название, год,
|
||||
режиссёра и (для сериала) номер сезона. Год и режиссёр — опциональные поля.
|
||||
|
||||
Если и контекст, и подсказка из полей источника (`dn` magnet / имя `.torrent`)
|
||||
пусты, система SHALL считать имя не выведенным и SHALL NOT вызывать LLM (выводить
|
||||
имя не из чего — вызов на пустом входе способен лишь галлюцинировать). В этом
|
||||
случае загрузка добавляется без `rename`, а `download.display_name` остаётся
|
||||
пустым.
|
||||
|
||||
Название SHALL быть на русском языке для российского контента и на
|
||||
английском (оригинальном) — для остального.
|
||||
|
||||
Система SHALL предпринять ограниченное число попыток получить от LLM валидный
|
||||
результат (корректный JSON с непустым названием); бюджет попыток —
|
||||
`[llm].max_retries` (по умолчанию 3). Транспортные ретраи провайдера LLM
|
||||
(сетевые сбои, 429, 5xx) в этот счёт не входят.
|
||||
|
||||
Недоступность или ошибка LLM SHALL NOT прерывать приём загрузки: система
|
||||
переходит к алгоритмическому фолбеку.
|
||||
|
||||
#### Scenario: LLM возвращает структурированное имя
|
||||
|
||||
- **WHEN** LLM по контексту возвращает валидный JSON с непустым названием
|
||||
- **THEN** система формирует отображаемое имя из его полей (название, год,
|
||||
для сериала — сезон)
|
||||
|
||||
#### Scenario: Российский контент — название на русском
|
||||
|
||||
- **WHEN** контент распознан как российский
|
||||
- **THEN** в отображаемом имени используется русское название
|
||||
|
||||
#### Scenario: Исчерпан бюджет попыток LLM
|
||||
|
||||
- **WHEN** LLM за отведённые попытки (`[llm].max_retries`) не вернул валидный
|
||||
результат либо недоступен
|
||||
- **THEN** система не прерывает приём и переходит к алгоритмическому фолбеку
|
||||
|
||||
#### Scenario: Пустой вход — LLM не вызывается
|
||||
|
||||
- **GIVEN** пойманная загрузка без пользовательского контекста и без подсказки
|
||||
из полей источника (голый magnet без `dn`)
|
||||
- **WHEN** система выводит отображаемое имя на шаге добавления
|
||||
- **THEN** LLM не вызывается, имя считается не выведенным
|
||||
- **AND** загрузка добавляется без `rename`, а `download.display_name` пуст
|
||||
(заголовок в веб-UI берётся из фолбека)
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### 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)
|
||||
Reference in New Issue
Block a user