Переработал черновик логической модели и беклог по итогам разбора (docs)
Итог explore-сессии: сущность title не вводим — download остаётся мостом qBittorrent ↔ файлы, «второй сезон» решается правилом сходимости папки, группировка тайтла вычисляется. Черновик перекроен под это решение (отвергнутые варианты и триггер пересмотра зафиксированы), задачи беклога обновлены и приоритезированы по калибровке болей. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+101
-61
@@ -14,17 +14,75 @@ _(идея)_ — их сперва надо проработать.
|
||||
### Проблема второго сезона
|
||||
|
||||
Если первый сезон сериала уже разложен, а мы добавляем второй/третий/…,
|
||||
распознавание должно привязать новый сезон к **тому же** названию и папке,
|
||||
а не завести рядом почти одинаковую вторую папку. Ключ — стабильный
|
||||
`provider_id`: один и тот же `[tvdbid-…]` → одна папка сериала, новые
|
||||
`Season NN` доливаются внутрь. Нужно: при матче учитывать уже существующие
|
||||
в библиотеке сериалы (или прошлые распознавания с тем же провайдер-id) и
|
||||
склонять LLM/выбор кандидата к согласованности с ними.
|
||||
новый сезон должен лечь в **ту же** папку сериала, а не завести рядом почти
|
||||
одинаковую вторую. Разбор ([drafts/logical-title-model.md](drafts/logical-title-model.md))
|
||||
показал: проблема не в группировке, а в **сходимости папки** — папка каждый
|
||||
раз печатается заново из выхода LLM, и совпадение `provider_id` не
|
||||
гарантирует совпадение строки («Fargo» vs «Фарго», год сезона vs год
|
||||
сериала). Отдельная сущность «тайтл» **не вводится**; решение — правило
|
||||
сходимости при построении плана: при подтверждённом матче наследовать базу
|
||||
папки от живых `file_link`'ов загрузок с тем же `(provider, provider_id)`,
|
||||
игнорируя LLM-выход; якоря нет — папка из распознавания, как сейчас (первая
|
||||
загрузка «печатает» имя).
|
||||
|
||||
Связано: [recognition.md](specs/recognition.md) (модель уверенности,
|
||||
- [ ] lookup живых ссылок по `(provider, provider_id)` через current recognition
|
||||
- [ ] наследование базы папки (имя + год) при построении плана раскладки
|
||||
- [ ] рассинхрон (несколько живых папок с одним матчем) → review, не молча
|
||||
- [ ] тесты: сходимость, отсутствие якоря (свежая папка), смена провайдера
|
||||
|
||||
Связано: [drafts/logical-title-model.md](drafts/logical-title-model.md) §5.2,
|
||||
[recognition.md](specs/recognition.md) (модель уверенности,
|
||||
матч в базе), [jellyfin-layout.md](specs/jellyfin-layout.md) (папка
|
||||
сериала с провайдер-id).
|
||||
|
||||
### Идентичность загрузки: ULID + множество инфохэшей
|
||||
|
||||
Фундамент для «второго сезона», «докачивания» и истории переходов (шаг 1 в
|
||||
[drafts/logical-title-model.md](drafts/logical-title-model.md)). Сейчас
|
||||
загрузка идентифицируется хешем торрента — это хрупко: v1/v2/гибрид дают
|
||||
разные значения, перезалив/репак/докачка — другой хеш, у одной логической
|
||||
загрузки хешей несколько. Вводим ULID, генерируемый при приёме, как
|
||||
первичный ключ домена; инфохэши — таблица `download_infohash` «многие к
|
||||
одному», на которую переезжают дедуп и поиск.
|
||||
|
||||
- [ ] `download.id` → TEXT ULID (миграция с заполнением существующих строк)
|
||||
- [ ] таблица `download_infohash` (`download_id`, `infohash`, `kind` v1|v2, `UNIQUE(infohash)`)
|
||||
- [ ] дедуп/идемпотентность через `download_infohash` (вместо `idempotency_key`)
|
||||
- [ ] поиск при приёме и в поллинге — по любому из хешей
|
||||
- [ ] обновить ER-схему [database.md](specs/database.md)
|
||||
|
||||
Связано: [drafts/logical-title-model.md](drafts/logical-title-model.md) §5.1,
|
||||
[database.md](specs/database.md) (PK `download`, `infohash`),
|
||||
[architecture.md](specs/architecture.md) → «Идентификация торрента», пакет
|
||||
`store`.
|
||||
|
||||
### Раздачи с докачиванием (слияние при повторном добавлении)
|
||||
|
||||
Свежий сериал раздают по мере выхода: торрент содержит 5 эпизодов из 10,
|
||||
позже его перезаливают целиком, и пользователь добавляет раздачу повторно.
|
||||
Решение проработано ([drafts/logical-title-model.md](drafts/logical-title-model.md)
|
||||
§6.2): новая загрузка приходит в ту же папку за счёт правила сходимости, а
|
||||
раскладка становится **merge** — доложить только недостающее. Существующие
|
||||
пути не трогаем (never-overwrite, владение остаётся у старой загрузки),
|
||||
новые кладём (владеет новая). Split-ownership сезона (серии поделены между
|
||||
загрузками) принят как норма per-path модели; обе раздачи сидируют
|
||||
независимо.
|
||||
|
||||
- [ ] в плане раскладки отличать «путь занят живой ссылкой того же матча»
|
||||
(→ пропустить) от настоящей коллизии (→ review, как сейчас)
|
||||
- [ ] merge-раскладка: существующее пропустить, недостающее доложить
|
||||
- [ ] показать итог в карточке: сколько доложено, сколько уже было
|
||||
- [ ] решить «слияние загрузок» при перезаливе той же вещи (одна строка
|
||||
`download` + новый infohash vs новая загрузка) — открытый вопрос
|
||||
черновика §10
|
||||
|
||||
Зависит от правила сходимости ([«Проблема второго
|
||||
сезона»](#проблема-второго-сезона)) и выигрывает от ULID-идентичности.
|
||||
|
||||
Связано: [jellyfin-layout.md](specs/jellyfin-layout.md) (раскладка,
|
||||
идемпотентность), [workflow.md](specs/workflow.md) (повторный прогон
|
||||
загрузки).
|
||||
|
||||
### Удаление средствами jellybit («единое окно», path 2)
|
||||
|
||||
Распознавание **ручного** удаления (источник из qBittorrent / цель из
|
||||
@@ -35,13 +93,26 @@ preflight перед действиями. См. `openspec/specs/state-reconcili
|
||||
[workflow.md](specs/workflow.md) → «Сверка с реальностью».
|
||||
|
||||
Осталось (path 2) — продолжение «единого окна»: удалять просмотренное
|
||||
**из самого jellybit**, не идя руками в qBittorrent/Jellyfin. Нужно
|
||||
продумать: команду удаления (снять наши хардлинки + опц. удалить раздачу из
|
||||
qBittorrent с файлами), подтверждение осознанности (а не случайный клик) и
|
||||
как это сочетается с инвариантом «источник неприкосновенен», когда
|
||||
пользователь сам просит убрать источник.
|
||||
**из самого jellybit**, не идя руками в qBittorrent/Jellyfin. Решения из
|
||||
разбора ([drafts/logical-title-model.md](drafts/logical-title-model.md)
|
||||
§5.3, §6.4): «тайтл» — вычисляемая группа загрузок по
|
||||
`(provider, provider_id)` / общей папке, без новой сущности; удаление
|
||||
целиком — обход загрузок группы штатным undo; удаление раздачи из
|
||||
qBittorrent — осознанный выход за инвариант «источник неприкосновенен»,
|
||||
только по явному подтверждению (не случайному клику).
|
||||
|
||||
Связано: [ADR-2026-06-13-hardlinks](adr/ADR-2026-06-13-hardlinks.md),
|
||||
- [ ] удаление одной загрузки: снять её живые хардлинки (штатный undo,
|
||||
`superseded` пропускаем, `nlink`-гард) + опц. удалить раздачу из
|
||||
qBittorrent с файлами — с осознанным подтверждением
|
||||
- [ ] вычисляемая группа «тайтл» в UI: состав сериала/фильма (загрузки,
|
||||
сезоны, файлы) одним экраном
|
||||
- [ ] удаление тайтла целиком: обход загрузок группы + опц. снос
|
||||
опустевшей папки
|
||||
- [ ] после полного удаления память о тайтле не остаётся (линза без
|
||||
содержимого не нужна)
|
||||
|
||||
Связано: [drafts/logical-title-model.md](drafts/logical-title-model.md),
|
||||
[ADR-2026-06-13-hardlinks](adr/ADR-2026-06-13-hardlinks.md),
|
||||
[architecture.md](specs/architecture.md) → «Раскладка файлов»,
|
||||
[workflow.md](specs/workflow.md).
|
||||
|
||||
@@ -167,22 +238,6 @@ qBittorrent, пул LLM-вызовов и запись в SQLite спроект
|
||||
[docs/conventions](conventions/README.md),
|
||||
[«Словарь единого языка»](#словарь-единого-языка-ubiquitous-language).
|
||||
|
||||
### Автогенерируемый идентификатор загрузки (ULID/UUID)
|
||||
|
||||
Сейчас загрузка фактически идентифицируется хешем торрента (`infohash`). Это
|
||||
хрупко: у одной логической загрузки может быть **несколько** хешей
|
||||
(перезаливы, докачивание, репаки, v1/v2 infohash), и привязка домена к хешу
|
||||
мешает слиянию и истории. Ввести собственный стабильный идентификатор
|
||||
(ULID/UUID), генерируемый при приёме, как первичный ключ домена;
|
||||
`infohash`(ы) — отдельный атрибут/таблица «многие к одному», по которому
|
||||
**остаётся** поиск и дедуп для обратной совместимости. Enabler для
|
||||
«докачивания», «второго сезона», «версий/качества» и истории переходов.
|
||||
|
||||
Связано: [database.md](specs/database.md) (PK `download`, `infohash`),
|
||||
[«Раздачи с докачиванием»](#раздачи-с-докачиванием-слияние-при-повторном-добавлении),
|
||||
[architecture.md](specs/architecture.md) → «Идентификация торрента», пакет
|
||||
`store`.
|
||||
|
||||
### История переходов загрузки
|
||||
|
||||
Сохранять полную историю переходов состояний загрузки (что/когда/почему/кто
|
||||
@@ -193,7 +248,10 @@ qBittorrent, пул LLM-вызовов и запись в SQLite спроект
|
||||
Естественно ложится на собственный идентификатор загрузки.
|
||||
|
||||
Связано: детальный экран загрузки (`/download/{id}`) уже реализован — лог
|
||||
переходов ложится в него; [workflow.md](specs/workflow.md) (граф состояний),
|
||||
переходов ложится в него;
|
||||
[drafts/logical-title-model.md](drafts/logical-title-model.md) §5.4 (схема
|
||||
`state_transition`, actor `worker|human|reconcile`),
|
||||
[workflow.md](specs/workflow.md) (граф состояний),
|
||||
[«Наблюдаемость: метрики»](#наблюдаемость-метрики-и-учёт-стоимости-llm)
|
||||
(длительности стадий), [database.md](specs/database.md), пакеты `worker`,
|
||||
`store`.
|
||||
@@ -222,23 +280,6 @@ qBittorrent, пул LLM-вызовов и запись в SQLite спроект
|
||||
веб = точные правки), [architecture.md](specs/architecture.md) →
|
||||
«Транспорты».
|
||||
|
||||
### Раздачи с докачиванием (слияние при повторном добавлении)
|
||||
|
||||
Свежий сериал часто раздают по мере выхода: торрент содержит 5 эпизодов из
|
||||
10. Позже его перезаливают целиком (или добавляют недостающие серии), и
|
||||
пользователь повторно добавляет тот же торрент. Нужно распознать, что это
|
||||
**та же** раздача/сезон, и повторить раскладку с **слиянием**: доложить
|
||||
недостающие хардлинки, не дублируя уже разложенное и не перезаписывая
|
||||
существующее (инвариант «существующее не трогаем»). Перекликается с
|
||||
«Проблемой второго сезона», но здесь доливаются эпизоды внутри одного
|
||||
сезона, а не новый сезон. Нужно продумать: как опознать повторное
|
||||
добавление (хеш торрента / провайдер-id + сезон), как сверять состав файлов
|
||||
и доливать только новые.
|
||||
|
||||
Связано: [«Проблема второго сезона»](#проблема-второго-сезона),
|
||||
[jellyfin-layout.md](specs/jellyfin-layout.md) (раскладка, идемпотентность),
|
||||
[workflow.md](specs/workflow.md) (повторный прогон загрузки).
|
||||
|
||||
### Улучшения UI: показывать матч с записью метабазы
|
||||
|
||||
Web-сторона реализована: страница загрузки `/download/{id}` и экран ревью
|
||||
@@ -286,20 +327,6 @@ qBittorrent, без исходящих запросов на пользоват
|
||||
Связано: [architecture.md](specs/architecture.md) → «Деплой» (data-том,
|
||||
«бекапить-и-не-терять»), пакет `store`.
|
||||
|
||||
### Версии/качество одного тайтла (репаки, апгрейд 1080p → 2160p)
|
||||
|
||||
Фильм уже разложен, позже добавили раздачу лучшего качества — сейчас это
|
||||
просто новая задача, упирающаяся в «коллизию цели → review», без понятия
|
||||
«это та же вещь, заменить версию». Нужно осознанно обработать апгрейд
|
||||
качества: распознать тот же тайтл, предложить замену существующей раскладки
|
||||
либо сосуществование версий (Jellyfin поддерживает несколько версий одного
|
||||
фильма). Близко к «докачиванию», но про качество, а не про эпизоды.
|
||||
|
||||
Связано: [«Раздачи с докачиванием»](#раздачи-с-докачиванием-слияние-при-повторном-добавлении),
|
||||
[jellyfin-layout.md](specs/jellyfin-layout.md) (never-overwrite, коллизия),
|
||||
[architecture.md](specs/architecture.md) → «Идентификация торрента»
|
||||
(репаки = разные infohash → разные задачи).
|
||||
|
||||
### Глубокий healthcheck и статус зависимостей
|
||||
|
||||
`/healthz` проверяет только сам сервис. Если qBittorrent, LLM или метабаза
|
||||
@@ -336,6 +363,19 @@ qBittorrent, без исходящих запросов на пользоват
|
||||
Связано: [architecture.md](specs/architecture.md) → «Транспорты»,
|
||||
[review-ux.md](specs/review-ux.md), пакет `httpapi`.
|
||||
|
||||
### Версии/качество одного тайтла (репаки, апгрейд 1080p → 2160p)
|
||||
|
||||
По калибровке болей (2026-07-02,
|
||||
[drafts/logical-title-model.md](drafts/logical-title-model.md) §6.3) —
|
||||
**не боль**, из приоритета выпало. Сосуществование версий доступно уже
|
||||
сейчас (Jellyfin multi-version, другой целевой путь), коллизия на тот же
|
||||
путь штатно уходит в review. Явный replace (undo старого хардлинка → lay
|
||||
нового → супересид владения путём) — отдельный change, если/когда станет
|
||||
болью.
|
||||
|
||||
Связано: [«Раздачи с докачиванием»](#раздачи-с-докачиванием-слияние-при-повторном-добавлении),
|
||||
[jellyfin-layout.md](specs/jellyfin-layout.md) (never-overwrite, коллизия).
|
||||
|
||||
### Многоступенчатая верификация привязки _(идея)_
|
||||
|
||||
Несколько раз извлекать данные из раздачи и контекста разными промптами,
|
||||
|
||||
Reference in New Issue
Block a user