Единый беклог задач вместо todo.md и ideas.md (docs)
Слил docs/todo.md и docs/drafts/ideas.md в docs/backlog.md: единый список будущих задач по приоритетам (Высокий/Средний/Низкий), спекулятивные пункты помечены _(идея)_. Реализованное из ideas.md (повторное распознавание, нотификации) не переносил. Добавил задачи: переработка ревью (выбор источника совпадения с предпросмотром), главная как список карточек вместо таблицы, отдельная страница просмотра загрузки (поднял из «Расширенной информации»), скрытие deleted-загрузок по умолчанию. Ссылки на drafts/ideas.md из docs/specs/* перенаправлены на backlog.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+373
@@ -0,0 +1,373 @@
|
||||
# Беклог
|
||||
|
||||
Единый список будущих задач по проекту: то, что уже решили сделать, и
|
||||
идеи, которые ещё надо обдумать. Это не план реализации (он — в
|
||||
[drafts/roadmap.md](drafts/roadmap.md)) и не источник истины: принятое и
|
||||
реализованное переезжает в `docs/specs`/`docs/adr`.
|
||||
|
||||
Приоритет — грубая оценка «ценность / стоимость», не обязательство к
|
||||
порядку. Спекулятивные пункты (ещё без решения «делаем») помечены
|
||||
_(идея)_ — их сперва надо проработать.
|
||||
|
||||
## Высокий
|
||||
|
||||
### Проблема второго сезона
|
||||
|
||||
Если первый сезон сериала уже разложен, а мы добавляем второй/третий/…,
|
||||
распознавание должно привязать новый сезон к **тому же** названию и папке,
|
||||
а не завести рядом почти одинаковую вторую папку. Ключ — стабильный
|
||||
`provider_id`: один и тот же `[tvdbid-…]` → одна папка сериала, новые
|
||||
`Season NN` доливаются внутрь. Нужно: при матче учитывать уже существующие
|
||||
в библиотеке сериалы (или прошлые распознавания с тем же провайдер-id) и
|
||||
склонять LLM/выбор кандидата к согласованности с ними.
|
||||
|
||||
Связано: [recognition.md](specs/recognition.md) (модель уверенности,
|
||||
матч в базе), [jellyfin-layout.md](specs/jellyfin-layout.md) (папка
|
||||
сериала с провайдер-id).
|
||||
|
||||
### Удаление средствами jellybit («единое окно», path 2)
|
||||
|
||||
Распознавание **ручного** удаления (источник из qBittorrent / цель из
|
||||
Jellyfin) и пометка рассинхрона уже сделаны: фоновая сверка по матрице
|
||||
«источник × цель» → состояния `target_missing`/`orphaned`/`deleted`,
|
||||
безопасный `undo` (не снимает последнюю копию, `nlink <= 1`), синхронный
|
||||
preflight перед действиями. См. `openspec/specs/state-reconciliation/`,
|
||||
[workflow.md](specs/workflow.md) → «Сверка с реальностью».
|
||||
|
||||
Осталось (path 2) — продолжение «единого окна»: удалять просмотренное
|
||||
**из самого jellybit**, не идя руками в qBittorrent/Jellyfin. Нужно
|
||||
продумать: команду удаления (снять наши хардлинки + опц. удалить раздачу из
|
||||
qBittorrent с файлами), подтверждение осознанности (а не случайный клик) и
|
||||
как это сочетается с инвариантом «источник неприкосновенен», когда
|
||||
пользователь сам просит убрать источник.
|
||||
|
||||
Связано: [ADR-2026-06-13-hardlinks](adr/ADR-2026-06-13-hardlinks.md),
|
||||
[architecture.md](specs/architecture.md) → «Раскладка файлов»,
|
||||
[workflow.md](specs/workflow.md).
|
||||
|
||||
### Ревью: выбор источника совпадения и предпросмотр
|
||||
|
||||
Переработать страницу ревью так, чтобы показывать **все** совпавшие
|
||||
результаты по метабазам списком и дать выбрать из них. Принцип: совпадение
|
||||
есть **всегда** — мы лишь выбираем источник. Поэтому матч нейронки — это
|
||||
отдельная строка в том же списке (наравне с кандидатами TMDB/TVDB), а не
|
||||
особый режим.
|
||||
|
||||
Возможности экрана:
|
||||
|
||||
- список кандидатов из баз + строка «распознано нейронкой»;
|
||||
- выбрать один кандидат, переключиться на другой, отменить матч с базой в
|
||||
пользу нейронки;
|
||||
- добавить кандидат вручную (по id/url базы), когда автопоиск промахнулся;
|
||||
- при выборе/переключении — **предпросмотр полей** (название, режиссёр,
|
||||
год) и **предпросмотр раскладки** (целевые пути) до применения.
|
||||
|
||||
Развивает «показывать матч с записью метабазы» (web-сторона), пересекается
|
||||
с «Расширенной информацией о загрузке» (детальный экран) и быстрым выбором
|
||||
в Telegram. Веб остаётся точкой точных правок.
|
||||
|
||||
Связано: [review-ux.md](specs/review-ux.md) (выбор кандидата, «без базы»,
|
||||
переключатель типа), [recognition.md](specs/recognition.md) (кандидаты
|
||||
матча, провайдер-id), [«Улучшения UI: показывать матч»](#улучшения-ui-показывать-матч-с-записью-метабазы),
|
||||
пакет `httpapi`.
|
||||
|
||||
### Наблюдаемость: метрики и учёт стоимости LLM
|
||||
|
||||
Сейчас единственное окно в систему — `slog`. Нет быстрых ответов на
|
||||
вопросы «сколько задач висит в review», «сколько токенов и денег съело
|
||||
распознавание», «какова медиана времени ingest → done». Нужны метрики:
|
||||
эндпоинт `/metrics` (Prometheus-формат) со счётчиками загрузок по
|
||||
состояниям, длительностями стадий и расходом LLM (токены/стоимость на
|
||||
задачу).
|
||||
|
||||
**Расход LLM уже снимается с провода** — `llm.openai` парсит `usage`
|
||||
(prompt/completion/total tokens и `cost`, который отдаёт шлюз) в
|
||||
`llm.Response.Usage` и пишет в лог. Не хватает только **персистентности и
|
||||
отображения**: сохранять `usage` у попытки распознавания (`recognition`) и
|
||||
показывать в карточке загрузки + агрегатом в `/metrics`. Где провайдер не
|
||||
шлёт `cost` — считать из токенов по таблице «модель → цена» в конфиге.
|
||||
|
||||
Отдельный LLM-прокси (LiteLLM и т.п.) для этого **не нужен** и противоречит
|
||||
принципам «один бинарь» / «минимум компонентов»: подсчёт токенов уже в коде,
|
||||
а роль мульти-модельного шлюза играет используемый OpenAI-совместимый
|
||||
эндпоинт (он и возвращает `cost`); разные модели подключаются сменой
|
||||
`[llm].model` или новым типом провайдера за интерфейсом `llm.Provider`.
|
||||
|
||||
Связано: [architecture.md](specs/architecture.md) → «Логирование»,
|
||||
[recognition.md](specs/recognition.md) (провайдер LLM, `[llm].type`),
|
||||
пакеты `worker`, `llm`, `httpapi`.
|
||||
|
||||
### Ретеншн и очистка БД
|
||||
|
||||
Терминальные задачи (`done`/`cancelled`/`failed`/`reverted`), их попытки
|
||||
`recognition` с сырыми ответами LLM и `metadata_candidate` копятся вечно —
|
||||
со временем БД и список загрузок распухают и становятся нечитаемыми. Нужна
|
||||
авточистка старше N дней (с настройкой в `[storage]` или `[worker]`) и/или
|
||||
ручное удаление. Маленькая задача, но без неё интерфейс деградирует по мере
|
||||
эксплуатации.
|
||||
|
||||
Связано: [architecture.md](specs/architecture.md) → «Хранилище» (таблицы
|
||||
`download`/`recognition`/`metadata_candidate`/`file_link`), пакет `store`.
|
||||
|
||||
### Eval-харнес распознавания (корпус кейсов + метрика точности)
|
||||
|
||||
Распознавание — ядро продукта, но смена модели или правка промпта сейчас
|
||||
вслепую: регрессий не видно. Нужен корпус размеченных кейсов (русские
|
||||
релизы, аниме, сезон-паки, репаки, спецвыпуски) и прогон распознавания по
|
||||
нему с метрикой точности (тип/название/год/нумерация). Тогда можно
|
||||
сравнивать LLM-провайдеры и версии промпта по числам, а не на ощупь.
|
||||
Прогон — отдельной командой (`jellybit eval` или тестом), на фикстурах, без
|
||||
реального qBittorrent.
|
||||
|
||||
Связано: [recognition.md](specs/recognition.md) (конвейер, модель
|
||||
уверенности), пакет `recognize`.
|
||||
|
||||
## Средний
|
||||
|
||||
### Главная: список загрузок вместо таблицы
|
||||
|
||||
Переделать главную страницу из таблицы в **список** карточек. Для каждой
|
||||
загрузки: название (как распознали при добавлении в qBittorrent), `infohash`
|
||||
с кнопкой быстрого копирования, статус и кнопки действий. Контекст (исходное
|
||||
сообщение/magnet) спрятать под спойлер или вынести на отдельный детальный
|
||||
экран (см. «Расширенная информация о загрузке»). Естественно сочетается с
|
||||
фильтром/поиском/пагинацией по мере роста БД.
|
||||
|
||||
Связано: [«Расширенная информация о загрузке в web-UI»](#расширенная-информация-о-загрузке-в-web-ui),
|
||||
[«Список загрузок: фильтр, поиск, пагинация»](#список-загрузок-фильтр-поиск-пагинация),
|
||||
[review-ux.md](specs/review-ux.md), пакет `httpapi`.
|
||||
|
||||
### Расширенная информация о загрузке в web-UI
|
||||
|
||||
Отдельная страница просмотра одной загрузки — целиком отображение того, что
|
||||
лежит в БД по задаче: актуальный статус (текущее состояние + лог переходов),
|
||||
исходный контекст и magnet, распознанные данные и матч в метабазе (см.
|
||||
«показывать матч»), а также **точная раскладка, если есть** — целевые пути и
|
||||
созданные хардлинки. Помогает разбираться, когда что-то пошло не так, без
|
||||
чтения логов сервера. Сюда же выносится контекст с карточки в списке
|
||||
загрузок.
|
||||
|
||||
Связано: [«Главная: список загрузок»](#главная-список-загрузок-вместо-таблицы),
|
||||
[review-ux.md](specs/review-ux.md), [architecture.md](specs/architecture.md)
|
||||
→ «Хранилище» (`download`/`recognition`/`file_link`), пакет `httpapi`.
|
||||
|
||||
### Машина состояний на go-библиотеке
|
||||
|
||||
Сейчас FSM реализована вручную в `worker`. Выбрать подходящую go-библиотеку
|
||||
для описания воркфлоу/машины состояний и перевести переходы на неё — ради
|
||||
декларативности, проверяемости переходов и единого места правды. Кандидаты
|
||||
для оценки: `looplab/fsm`, `qmuntal/stateless` (и аналоги). Граф и переходы
|
||||
уже формализованы — переносим один в один.
|
||||
|
||||
Связано: [workflow.md](specs/workflow.md) (текущий граф состояний).
|
||||
|
||||
### Привязка уведомлений к источнику в ботах (мульти-бот)
|
||||
|
||||
Уведомления и запросы подтверждения должен получать тот, кто прислал
|
||||
загрузку: автор сообщения о новой раздаче — адресат пингов и ревью по ней.
|
||||
Транспортов-ботов может быть несколько (Telegram, в перспективе Matrix и
|
||||
др.); каждый адресует «своему» отправителю. Веб-интерфейс остаётся
|
||||
**единым для всех** и точкой правды по функциональности (боты — тонкие
|
||||
адаптеры над тем же ядром). Нужно: хранить у загрузки источник/транспорт и
|
||||
идентификатор отправителя, маршрутизировать пинги по нему.
|
||||
|
||||
Связано: [review-ux.md](specs/review-ux.md) (разделение труда транспортов,
|
||||
веб = точные правки), [architecture.md](specs/architecture.md) →
|
||||
«Транспорты».
|
||||
|
||||
### Раздачи с докачиванием (слияние при повторном добавлении)
|
||||
|
||||
Свежий сериал часто раздают по мере выхода: торрент содержит 5 эпизодов из
|
||||
10. Позже его перезаливают целиком (или добавляют недостающие серии), и
|
||||
пользователь повторно добавляет тот же торрент. Нужно распознать, что это
|
||||
**та же** раздача/сезон, и повторить раскладку с **слиянием**: доложить
|
||||
недостающие хардлинки, не дублируя уже разложенное и не перезаписывая
|
||||
существующее (инвариант «существующее не трогаем»). Перекликается с
|
||||
«Проблемой второго сезона», но здесь доливаются эпизоды внутри одного
|
||||
сезона, а не новый сезон. Нужно продумать: как опознать повторное
|
||||
добавление (хеш торрента / провайдер-id + сезон), как сверять состав файлов
|
||||
и доливать только новые.
|
||||
|
||||
Связано: [«Проблема второго сезона»](#проблема-второго-сезона),
|
||||
[jellyfin-layout.md](specs/jellyfin-layout.md) (раскладка, идемпотентность),
|
||||
[workflow.md](specs/workflow.md) (повторный прогон загрузки).
|
||||
|
||||
### Улучшения UI: показывать матч с записью метабазы
|
||||
|
||||
Во всех транспортах (веб, Telegram) показывать, **с какой именно записью**
|
||||
метабазы (TMDB/TVDB) сматчилась загрузка: название, год, провайдер-id,
|
||||
ссылку. Сейчас результат распознавания непрозрачен — пользователь не видит,
|
||||
к чему привязались, и не может быстро поймать ошибочный матч. На web-стороне
|
||||
развивается в полноценный выбор источника — см. [«Ревью: выбор источника
|
||||
совпадения»](#ревью-выбор-источника-совпадения-и-предпросмотр).
|
||||
|
||||
Связано: [review-ux.md](specs/review-ux.md), [recognition.md](specs/recognition.md)
|
||||
(матч в базе), [architecture.md](specs/architecture.md) → «Транспорты».
|
||||
|
||||
### Аниме с абсолютной нумерацией
|
||||
|
||||
Релизы аниме часто нумеруют серии сквозным числом (`#137`) без сезонов, а
|
||||
Jellyfin ждёт `SxxEyy`. Нужен пересчёт абсолютной нумерации в сезон/серию —
|
||||
надёжнее всего через TVDB (там есть absolute order). Отдельный крайний
|
||||
случай распознавания; на стороне ревью — веб-хелпер «absolute → S·E».
|
||||
|
||||
Связано: [recognition.md](specs/recognition.md) (конвейер, сезон-паки),
|
||||
[jellyfin-layout.md](specs/jellyfin-layout.md) (нумерация серий),
|
||||
[review-ux.md](specs/review-ux.md) (крайние сценарии).
|
||||
|
||||
### Добавление торрентов файлом/ссылкой — «единое окно»
|
||||
|
||||
Поддержать источники помимо magnet: `.torrent`-файл и URL (отдаём их в
|
||||
qBittorrent, без исходящих запросов на пользовательский URL — SSRF
|
||||
исключён). Идеал — одно поле «единого окна»: кидаем туда текст или файл, а
|
||||
сервис сам разбирает, что это (magnet / ссылка / .torrent / сообщение
|
||||
бота), и заводит загрузку.
|
||||
|
||||
Связано: [architecture.md](specs/architecture.md) → «Транспорты»
|
||||
(`source_type = magnet|torrent|url` уже в схеме), пакет `ingest` (сейчас
|
||||
поддержан только magnet).
|
||||
|
||||
### Бэкап SQLite
|
||||
|
||||
`architecture.md` требует «бекапить data-том», но *как* — не описано. Без
|
||||
понятной стратегии сбой или редеплой стирают всё in-flight состояние.
|
||||
Зафиксировать решение и реализовать: периодический `VACUUM INTO` в
|
||||
`/data/backups` по расписанию (с ротацией) либо потоковая репликация
|
||||
(litestream). Лучше сделать, пока БД маленькая.
|
||||
|
||||
Связано: [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 или метабаза
|
||||
недоступны — узнаёшь лишь по застрявшим задачам. Нужна readiness-проверка
|
||||
ключевых зависимостей и отражение их состояния в UI (бейдж «qBittorrent
|
||||
недоступен»), чтобы причина простоя была видна сразу.
|
||||
|
||||
Связано: [architecture.md](specs/architecture.md) → «Деплой» (healthcheck),
|
||||
пакеты `qbt`, `llm`, `metadata`, `httpapi`.
|
||||
|
||||
### Обучение на правках человека (few-shot из прошлых ревью)
|
||||
|
||||
Когда человек поправил матч, тип или нумерацию — сохранять это как пример и
|
||||
подмешивать похожие в будущие промпты. Системно повышает точность на «твоих»
|
||||
трекерах и форматах имён без смены модели. Развитие идеи многоступенчатой
|
||||
верификации, но дешевле: учимся на уже собранных `hint`/`override`.
|
||||
|
||||
Связано: [recognition.md](specs/recognition.md) (конвейер, промпт),
|
||||
[«Многоступенчатая верификация»](#многоступенчатая-верификация-привязки-идея),
|
||||
[architecture.md](specs/architecture.md) → «Хранилище» (`hint`, `override`).
|
||||
|
||||
### Список загрузок: фильтр, поиск, пагинация
|
||||
|
||||
Прямое следствие роста БД (см. «Ретеншн»): плоский список загрузок со
|
||||
временем становится непригоден. Нужны фильтр по состоянию, поиск по
|
||||
названию и пагинация. Естественно ложится на список-карточки главной и на
|
||||
экран расширенной информации.
|
||||
|
||||
Частный случай как разумный дефолт: на главной **по умолчанию скрывать
|
||||
загрузки в статусе `deleted`** (терминальные, удалённые из источника и
|
||||
цели — шум в ленте), с переключателем/фильтром «показать всё». Маленькая
|
||||
часть, может приехать раньше полноценного фильтра.
|
||||
|
||||
Связано: [«Главная: список загрузок»](#главная-список-загрузок-вместо-таблицы),
|
||||
[«Расширенная информация о загрузке в web-UI»](#расширенная-информация-о-загрузке-в-web-ui),
|
||||
пакет `httpapi`.
|
||||
|
||||
## Низкий
|
||||
|
||||
### Многоступенчатая верификация привязки _(идея)_
|
||||
|
||||
Несколько раз извлекать данные из раздачи и контекста разными промптами,
|
||||
искать в метабазах, затем сводить результаты в общий вердикт
|
||||
(голосование/консенсус) — выше точность ценой нескольких вызовов LLM и
|
||||
запросов к базам. Требует проработки: когда включать, как мерджить
|
||||
расхождения, стоимость/латентность.
|
||||
|
||||
Связано: [recognition.md](specs/recognition.md) (конвейер и модель
|
||||
уверенности).
|
||||
|
||||
### Выбор из нескольких находок метабазы в Telegram
|
||||
|
||||
Когда распознавание даёт несколько подходящих кандидатов в метабазе,
|
||||
предлагать их в Telegram списком (кнопки) для ручного выбора, а не молча
|
||||
брать первый/лучший. Веб остаётся точкой точных правок (полный выбор
|
||||
источника — см. [«Ревью: выбор источника
|
||||
совпадения»](#ревью-выбор-источника-совпадения-и-предпросмотр)), бот —
|
||||
быстрый выбор из готового короткого списка.
|
||||
|
||||
Связано: [review-ux.md](specs/review-ux.md) (боты — быстрые действия, веб —
|
||||
точные правки), [recognition.md](specs/recognition.md) (кандидаты матча).
|
||||
|
||||
### Проверка свободного места перед copy-fallback
|
||||
|
||||
Когда хардлинк невозможен (`EXDEV`/`ENOTSUP`/…), `layout` копирует файл,
|
||||
дублируя место на диске. На забитом диске это упрётся в полку посреди
|
||||
раскладки. Перед копированием проверять доступное место и при нехватке
|
||||
внятно уходить в `failed` с понятной причиной, а не падать на полпути.
|
||||
|
||||
Связано: [architecture.md](specs/architecture.md) → «Раскладка файлов»
|
||||
(фолбэк-копирование), пакет `layout`.
|
||||
|
||||
### Кэш метабаз (и опционально LLM)
|
||||
|
||||
Повторные и ретраящиеся прогоны распознавания бьют TMDB/TVDB/TVMaze одним и
|
||||
тем же запросом. Кэш ответов с TTL экономит лимиты API и ускоряет «Распознать
|
||||
заново»/«Уточнить». При желании — кэш ответов LLM по хешу входа (но он менее
|
||||
полезен, т.к. вход меняется подсказками).
|
||||
|
||||
Связано: [recognition.md](specs/recognition.md) (сверка с базой), пакеты
|
||||
`metadata`, `llm`.
|
||||
|
||||
### guessit как сервис-спутник _(идея)_
|
||||
|
||||
`go-ptn` слабее питоновского `guessit`. Если точности пред-парса не
|
||||
хватит — завернуть `guessit` в крошечный HTTP-сервис (один файл,
|
||||
поставляется рядом с бинарём jellybit) и спрашивать его на шаге
|
||||
пред-парса. Сохраняет «доставку копированием»: два файла вместо одного.
|
||||
|
||||
Связано: [recognition.md](specs/recognition.md) → «На будущее» (пред-парс).
|
||||
|
||||
### Завершение загрузки через webhook _(идея)_
|
||||
|
||||
Сейчас завершение ловим поллингом qBittorrent раз в несколько секунд.
|
||||
Альтернатива: «Run external program on torrent completion» в qBittorrent
|
||||
дёргает эндпоинт jellybit. Реагирует быстрее, но связывает нас с конфигом
|
||||
qBittorrent. Решим по опыту эксплуатации.
|
||||
|
||||
Связано: [architecture.md](specs/architecture.md) → «Отслеживание загрузки»,
|
||||
пакет `worker`.
|
||||
|
||||
### Авторизация веб-UI (на будущее)
|
||||
|
||||
Решено для v1: без авторизации в доверенной LAN, опц. allowlist подсетей
|
||||
(`http.trusted_subnets`) — как умеет qBittorrent. Если понадобится защита:
|
||||
токен/Basic в самом приложении или вынос за reverse-proxy с
|
||||
аутентификацией.
|
||||
|
||||
Связано: [architecture.md](specs/architecture.md) → «Транспорты» (доступ к
|
||||
веб-UI), пакет `httpapi`.
|
||||
|
||||
### Современный Web-UI как PWA
|
||||
|
||||
Переделать веб-интерфейс в современное PWA-приложение (устанавливаемое,
|
||||
отзывчивое, удобное с телефона). Текущий server-rendered UI функционален,
|
||||
поэтому это улучшение, а не блокер; большой объём работы.
|
||||
|
||||
Связано: [review-ux.md](specs/review-ux.md) (веб = точные правки),
|
||||
пакет `httpapi`.
|
||||
Reference in New Issue
Block a user