При подтверждённом матче база папки (имя+год) наследуется от живой папки-якоря того же (provider, provider_id) вместо печати заново из выхода LLM — так второй/последующий сезон ложится в ТУ ЖЕ папку, а не заводит рядом почти одинаковую. Отдельная сущность «тайтл» не вводится. - layout: Plan.FolderBase перекрывает базу в папке и именах файлов; TitleFolder разбирает dst_path в папку тайтла и базу (снятие тега). - store: LiveTitleFolders — dst_path живых ссылок того же матча. - worker: resolveFolderBase (живость якоря — по наличию папки на диске, os.Lstat, а не по статусу ссылки в БД) в linkPlan и в превью ревью (превью=применение); рассинхрон нескольких живых папок → review из linking (deferred→review в графе нет). Схема БД не менялась. Change series-folder-convergence заархивирован, требования влиты в openspec/specs/file-layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
86 lines
8.6 KiB
Markdown
86 lines
8.6 KiB
Markdown
## ADDED Requirements
|
||
|
||
### Requirement: Сходимость базы папки при подтверждённом матче
|
||
|
||
Система SHALL при построении плана раскладки для загрузки с подтверждённым матчем (заданы `provider` и `provider_id`) наследовать **базу имени** (`Название (Год)` — строку без provider-тега) от существующей на диске папки-якоря того же тайтла, а НЕ печатать её заново из выхода распознавания. Кандидаты
|
||
в якорь — целевые пути (`dst_path`) ссылок со статусом `linked`/`copied`/`exists`
|
||
**других** загрузок (не текущей), чей current recognition имеет тот же
|
||
`(provider, provider_id)`.
|
||
|
||
Истина — папка на диске, а не запись в БД: кандидат SHALL считаться живым
|
||
якорем, только если его папка тайтла реально существует на файловой системе.
|
||
Статус ссылки в БД недостаточен — при ручном/Jellyfin-переименовании папки
|
||
запись `file_link` какое-то время остаётся `linked` (загрузка лишь позже уходит
|
||
в `target_missing` по сверке), а `dst_path` указывает на уже несуществующий путь.
|
||
Поэтому кандидат, чья папка тайтла отсутствует на диске, в якоря НЕ берётся; если
|
||
живых якорей не осталось, следующая загрузка снова печатает базу из распознавания.
|
||
|
||
Унаследованная база SHALL применяться **и к папке сериала/фильма, и к именам
|
||
файлов внутри** (episode/movie stem), чтобы серии разных сезонов совпадали по
|
||
базе (`Fargo (2014) S02E01` рядом с `Fargo (2014) S01E01`). Provider-тег на папке
|
||
(`[tvdbid-…]`) по-прежнему SHALL строиться из текущего `(provider, provider_id)`.
|
||
|
||
База тайтла извлекается из папки-якоря снятием хвостового provider-тега
|
||
` [...]`; извлечённая база прогоняется через ту же санитизацию, что и печатаемая.
|
||
Если папку-якорь нельзя разобрать (сегмент не под корнем библиотеки, база пуста),
|
||
кандидат в якоря НЕ берётся (безопасный fallback на печать из распознавания), а
|
||
не даёт искажённую базу.
|
||
|
||
Наследование SHALL происходить только при подтверждённом матче. Нет живого якоря
|
||
(первая загрузка тайтла) → база печатается из распознавания, как прежде. Нет
|
||
матча (`provider` пуст / `none`) → авто-раскладки нет (инвариант), база не
|
||
наследуется — папку на ревью выбирает человек. Правило не вводит новых сущностей
|
||
и не меняет схему БД: это выборка по существующим `file_link → download →
|
||
recognition(is_current)` плюс проверка существования папки на диске. Безопасность
|
||
по-прежнему держится на санитизации и проверке пути под библиотекой, а не на
|
||
доверии к выходу распознавания.
|
||
|
||
Разрешение базы SHALL быть единым для авто-раскладки и ручного «Применить», а
|
||
также для **предпросмотра** раскладки на ревью — чтобы превью показывало ту же
|
||
папку/имена, что даст применение (инвариант «превью = применение»). В
|
||
предпросмотре разрешение выполняется без побочных эффектов (в review из-за
|
||
рассинхрона переводит только применение, не показ).
|
||
|
||
#### Scenario: Второй сезон ложится в папку первого
|
||
|
||
- **GIVEN** первый сезон уже разложен в `series/Фарго (2014) [tvdbid-269613]/Season 01/…` (ссылки живые)
|
||
- **AND** новая загрузка со вторым сезоном имеет матч TVDB `269613`, но распознавание дало название «Fargo» и год `2017`
|
||
- **WHEN** строится план раскладки второго сезона
|
||
- **THEN** база наследуется от живого якоря: папка = `series/Фарго (2014) [tvdbid-269613]/`
|
||
- **AND** серия ложится как `Season 02/Фарго (2014) S02E01.mkv` (база в имени файла — унаследованная, а не из выхода LLM)
|
||
|
||
#### Scenario: Нет живого якоря — печатаем из распознавания
|
||
|
||
- **GIVEN** ни у одной загрузки нет живых ссылок с тем же `(provider, provider_id)`
|
||
- **WHEN** строится план раскладки при подтверждённом матче
|
||
- **THEN** база берётся из распознавания (название+год), как прежде — первая загрузка «печатает» имя папки
|
||
|
||
#### Scenario: Нет матча — сходимость не применяется
|
||
|
||
- **GIVEN** у загрузки нет подтверждённого матча (`provider` пуст / `none`)
|
||
- **WHEN** обрабатывается раскладка
|
||
- **THEN** авто-наследования базы не происходит, загрузка идёт через review (папку выбирает человек)
|
||
|
||
#### Scenario: Папка-якорь переименована на диске — печатаем заново
|
||
|
||
- **GIVEN** у загрузки-кандидата статус ссылок ещё `linked`, но её папка тайтла на диске переименована/удалена (по `dst_path` папки нет)
|
||
- **WHEN** строится план раскладки новой загрузки с тем же матчем
|
||
- **THEN** отсутствующая на диске папка в якоря не берётся
|
||
- **AND** при отсутствии других живых якорей база печатается из распознавания
|
||
|
||
#### Scenario: Превью на ревью совпадает с применением
|
||
|
||
- **GIVEN** есть живой якорь тайтла, а распознавание текущей загрузки дало иную базу
|
||
- **WHEN** на ревью открывается предпросмотр целевой раскладки
|
||
- **THEN** превью показывает папку/имена с унаследованной базой якоря — те же, что даст «Применить»
|
||
|
||
### Requirement: Рассинхрон живых папок тайтла уходит в review
|
||
|
||
Система MUST NOT молча выбирать якорь при обнаружении **нескольких РАЗНЫХ** живых целевых папок с одним `(provider, provider_id)` (рассинхрон, случившийся до внедрения правила сходимости): такая загрузка SHALL уходить в `review` с явной причиной «рассинхрон папок тайтла», чтобы человек выбрал/свёл папку вручную (по прецеденту коллизии, которая тоже уводит в review из раскладки).
|
||
|
||
#### Scenario: Две живые папки одного матча → review
|
||
|
||
- **GIVEN** для матча TVDB `269613` существуют две разные живые папки (`Фарго (2014) …` и `Fargo (2017) …`)
|
||
- **WHEN** строится план раскладки новой загрузки с этим матчем
|
||
- **THEN** раскладка не выполняется, задача переходит в `review` с причиной рассинхрона папок тайтла
|