display_name: слоистое разрешение полей + сохранение режиссёра из контекста
Единый источник полей отображаемого имени и один рендер полного ярлыка на всех путях (старт и «Обновить имя»/авто-перелив). Раньше старт давал полный «Название (режиссёр, год). Сезон N» но выбрасывал структуру, а перелив по распознаванию — усечённый «Title (Year)». - Слоистое разрешение скаляров имени: override → recognition(+match) → новый базовый слой «контекст» (download.parsed_context, JSON naming.Fields). - naming: публичные Fields/Label/Derive, вынесен единый рендер; удалён FormatTitleYear. Сводка сезонов вынесена в recognize.SeasonSummary. - Режиссёр из метабазы (решение A2): TMDB/TVDB credits через опциональный metadata.DirectorProvider; авто-матч кладёт в plan.Director, ручной выбор кандидата тянет credits и пиннит ovrDirector. Метабаза бьёт контекст. - refreshDisplayNameLocked строит полный ярлык из эффективных полей; инфо-панель ревью показывает загруженного режиссёра. - Миграция 0011_parsed_context + ER-схема. Всё косметика: на пути/раскладку не влияет, приём/вывод имени не валятся (best-effort). Закрывает беклог-задачу «Кнопка „Обновить имя“: полный формат ярлыка». OpenSpec: archive/2026-07-11-field-resolution-display-name (ingest, recognition, metadata-match, review). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -132,13 +132,24 @@ JSON-вывод), извлекая из контекста тип (movie/series)
|
||||
распознавание дало каноническое название, — переливая уже вычисленное имя (без
|
||||
нового вызова LLM) в `download.display_name` и в имя раздачи qBittorrent.
|
||||
|
||||
Источником имени SHALL быть **эффективное** распознанное название: пины
|
||||
`title`/`year` (если матч подтверждён), иначе `recognition.title`/`year` — то
|
||||
же название, что использует раскладка. Формат SHALL быть коротким детерминированным
|
||||
ярлыком `Title (Year)` (год опционален), с той же очисткой от управляющих
|
||||
символов и обрезкой по длине, что и вывод имени на шаге добавления. Пустой
|
||||
источник (нет распознавания или пустое название) SHALL приводить к отсутствию
|
||||
изменений (no-op).
|
||||
Источником имени SHALL быть **эффективные поля имени**, разрешённые по слоям
|
||||
сверху вниз (берётся первый непустой слой): (1) ручные правки `override`;
|
||||
(2) распознавание с вложенным подтверждённым матчем (`recognition`, куда матч
|
||||
метабазы уже вложил каноничные название/год/режиссёра); (3) сохранённая
|
||||
структура из контекста (`download.parsed_context`). Так каждое поле берётся из
|
||||
самого доверенного доступного источника, а данные из контекста (например
|
||||
режиссёр) не теряются, если распознавание/матч их не дали. Название из слоя
|
||||
распознавания SHALL совпадать с тем, что использует раскладка (эффективный
|
||||
`title`), чтобы отображаемое имя не расходилось с целевыми путями.
|
||||
|
||||
Формат SHALL быть тем же полным детерминированным ярлыком, что и на шаге
|
||||
добавления: «Название (режиссёр, год)», где режиссёр и год опциональны, а для
|
||||
сериала добавляется сводка сезонов («. Сезон N» для одного сезона; «. Сезоны …»
|
||||
для многосезонного пака; отметка спецвыпусков) — согласованная со сводкой сезонов
|
||||
на экране просмотра. Применяются та же очистка от управляющих символов и обрезка
|
||||
по ограничению длины. Пустой источник (нет ни распознавания, ни сохранённой
|
||||
структуры, дающих непустое название) SHALL приводить к отсутствию изменений
|
||||
(no-op).
|
||||
|
||||
Переименование раздачи в qBittorrent SHALL адресоваться по infohash своей
|
||||
раздачи и SHALL быть best-effort: сбой (раздача удалена, qBittorrent недоступен)
|
||||
@@ -154,12 +165,28 @@ SHALL NOT проваливать обновление — `download.display_name
|
||||
|
||||
#### Scenario: Перелив имени в загрузку и раздачу
|
||||
|
||||
- **GIVEN** загрузка с распознанным непустым каноническим названием
|
||||
- **GIVEN** загрузка с распознанным непустым каноническим названием и известным
|
||||
режиссёром (из матча или из сохранённого контекста)
|
||||
- **WHEN** запускается обновление отображаемого имени
|
||||
- **THEN** `download.display_name` устанавливается в `Title (Year)`
|
||||
- **THEN** `download.display_name` устанавливается в полный ярлык
|
||||
«Название (режиссёр, год)» (для сериала — со сводкой сезонов)
|
||||
- **AND** раздача в qBittorrent переименовывается в то же имя (по infohash своей
|
||||
раздачи)
|
||||
|
||||
#### Scenario: Режиссёр из контекста переживает распознавание без матча
|
||||
|
||||
- **GIVEN** загрузка, где режиссёр был извлечён из контекста, а распознавание
|
||||
прошло без подтверждённого матча (режиссёр из метабазы недоступен)
|
||||
- **WHEN** запускается обновление отображаемого имени
|
||||
- **THEN** в ярлыке используется режиссёр из сохранённого контекста
|
||||
- **AND** название/год берутся из распознавания
|
||||
|
||||
#### Scenario: Режиссёр из матча бьёт контекстного
|
||||
|
||||
- **GIVEN** загрузка, где режиссёр есть и в контексте, и в подтверждённом матче
|
||||
- **WHEN** формируется ярлык
|
||||
- **THEN** используется режиссёр из матча (более доверенный слой)
|
||||
|
||||
#### Scenario: qBittorrent недоступен — имя у загрузки всё равно обновлено
|
||||
|
||||
- **GIVEN** обновление отображаемого имени с выведенным непустым именем
|
||||
@@ -168,9 +195,10 @@ SHALL NOT проваливать обновление — `download.display_name
|
||||
- **THEN** `download.display_name` всё равно обновлён
|
||||
- **AND** ошибка внешнего вызова qBittorrent логируется, операция не проваливается
|
||||
|
||||
#### Scenario: Нет распознавания — обновление ничего не делает
|
||||
#### Scenario: Нет источника имени — обновление ничего не делает
|
||||
|
||||
- **GIVEN** загрузка без распознанного названия (пустой источник имени)
|
||||
- **GIVEN** загрузка без распознанного названия и без сохранённой структуры
|
||||
(пустой источник имени)
|
||||
- **WHEN** запускается обновление отображаемого имени
|
||||
- **THEN** ни `download.display_name`, ни имя раздачи не меняются (no-op)
|
||||
|
||||
@@ -561,3 +589,43 @@ NOT.
|
||||
- **WHEN** принимаемый `.torrent`-файл превышает ограничение размера
|
||||
- **THEN** приём отклоняется с ошибкой, загрузка не создаётся
|
||||
|
||||
### Requirement: Сохранение извлечённой из контекста структуры имени
|
||||
|
||||
Система SHALL сохранять структуру имени, извлечённую LLM со структурированным
|
||||
выводом на шаге добавления (тип, название, оригинальное название, год, режиссёр,
|
||||
сезон), у загрузки (`download.parsed_context`, JSON), чтобы её поля могли
|
||||
переиспользоваться при последующем выводе имени без повторного вызова LLM.
|
||||
Алгоритмический фолбек структуры не даёт (его выход — только строка имени), тогда
|
||||
`parsed_context` остаётся пустым — это штатно (нижний слой отсутствует). Сохранённая структура SHALL
|
||||
быть **базовым (наименее доверенным) слоем** источника полей имени: её значения
|
||||
берутся, только если более доверенный слой (распознавание/матч, ручные правки)
|
||||
соответствующего поля не дал.
|
||||
|
||||
Сохранение SHALL быть best-effort и косметическим: неудача записи `parsed_context`
|
||||
SHALL NOT проваливать добавление загрузки, а сама структура SHALL влиять только на
|
||||
отображаемое имя и SHALL NOT влиять на пути файлов, распознавание или раскладку.
|
||||
Пустая/невыведенная структура (нет контекста и подсказки) SHALL приводить к
|
||||
пустому `parsed_context` (нечего сохранять).
|
||||
|
||||
#### Scenario: Извлечённый режиссёр сохраняется у загрузки
|
||||
|
||||
- **GIVEN** контекст загрузки, из которого LLM извлёк режиссёра и год
|
||||
- **WHEN** система выводит имя на шаге добавления
|
||||
- **THEN** извлечённая структура (в т.ч. режиссёр) сохраняется в
|
||||
`download.parsed_context`
|
||||
- **AND** отображаемое имя формируется как и прежде (полный ярлык)
|
||||
|
||||
#### Scenario: Сбой сохранения структуры не валит добавление
|
||||
|
||||
- **GIVEN** запись `parsed_context` завершается ошибкой
|
||||
- **WHEN** идёт шаг добавления загрузки
|
||||
- **THEN** загрузка всё равно добавляется в qBittorrent (с `rename`, если имя
|
||||
выведено)
|
||||
- **AND** ошибка логируется, приём/добавление не проваливается
|
||||
|
||||
#### Scenario: Пустой вход — пустая структура
|
||||
|
||||
- **GIVEN** пойманная загрузка без контекста и без подсказки из полей источника
|
||||
- **WHEN** выполняется шаг добавления
|
||||
- **THEN** структура не выводится, `download.parsed_context` пуст
|
||||
|
||||
|
||||
Reference in New Issue
Block a user