Files
jellybit/openspec/changes/large-release-recognition/specs/recognition/spec.md
T
av 01ac0430a9 спека: заведён change large-release-recognition для больших раздач
- контракт с моделью переводится на адресацию файла номером строки списка,
  усечение промпта снимается, лимиты уходят в конфиг
- раскладка показывает все файлы раздачи, покрытие плана блокирует авто
  только при непокрытом видеофайле
2026-09-02 08:54:32 +03:00

250 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## MODIFIED Requirements
### Requirement: Разбор сигналов LLM в структурированный план
Система SHALL передавать LLM недоверенные сигналы (имя торрента, дерево файлов с
размерами, текстовый контекст и накопленные подсказки, пред-парс) и получать
структурированный план в схеме: `type` (`movie`|`series`), `title`,
`original_title`, `year`, `provider_hint`, `files[]` и `confidence`. Каждый
элемент `files[]` SHALL адресовать файл раздачи номером строки из напечатанного
системой списка файлов, а не копией пути, и нести `role`
(`main`|`episode`|`subtitle`|`extra`|`sample`|`ignore`) и, для сериала,
per-file `season`/`episode` (отдельного скалярного `season` быть SHALL NOT — так
выражаются мультисезонные паки и спецвыпуски). Путь файла (`src`) SHALL
подставлять сама система резолвом номера по своему списку. Ответ, где элемент
`files[]` несёт путь вместо номера, система SHALL принимать как запасной формат,
проверяя совпадение пути с реальным файлом торрента. Элемент, несущий и номер, и
путь, система SHALL резолвить по номеру; несовпадение присланного пути с
резолвом номера SHALL давать причину ухода в `review` (сигнал сдвига адресации),
а не молчаливое затирание.
Негодный элемент `files[]` — номер вне диапазона списка, повторная адресация уже
адресованного файла, отсутствие и номера, и пути — система SHALL отбрасывать с
причиной ухода в `review`, сохраняя остальной план; при повторной адресации в
плане SHALL оставаться первый элемент. Отбраковка отдельных элементов SHALL NOT
считаться ошибкой разбора и SHALL NOT порождать повторный запрос к модели. План,
в котором после отбраковки не осталось ни одного файла, система SHALL считать
неразобранным.
Порядок списка файлов SHALL быть свойством самого распознавания, а не
дисциплиной вызывающего: распознавание SHALL приводить полученный список к
детерминированному порядку само, печатать промпт и резолвить номера по одному и
тому же упорядоченному списку. Повторное распознавание той же раздачи SHALL
давать ту же нумерацию.
План MAY дополнительно нести опциональное скалярное поле `director` (режиссёр).
Это поле НЕ требуется от LLM и НЕ участвует в структурной валидации или гейте
авто-раскладки; его заполняют подтверждённый матч метабазы (авто) или закреплённый
в ревью выбранный источник (через override, см. `metadata-match`/`review`) как
недоверенное косметическое значение для вывода отображаемого имени. Как недоверенное
человекочитаемое поле, `director` SHALL NOT входить в plan-санитайзинг (он чистит
`title`/`original_title`/`provider_hint`); очистка режиссёра применяется при рендере
имени. Пустой `director` SHALL быть штатным (режиссёр неизвестен).
#### Scenario: План сериала с per-file нумерацией
- **GIVEN** сезон-пак из 10 видеофайлов
- **WHEN** LLM возвращает план
- **THEN** `type` = `series`, а каждый видеофайл несёт свои `season`/`episode`
#### Scenario: Номер файла резолвится в путь
- **GIVEN** список из 180 файлов, напечатанный в промпте
- **WHEN** модель возвращает элемент плана с номером 13
- **THEN** система подставляет в `src` путь тринадцатого файла своего списка
#### Scenario: Номер вне диапазона отбрасывает элемент, а не план
- **GIVEN** раздача из 180 файлов и ответ модели, где один элемент несёт номер 181,
а остальные 179 корректны
- **WHEN** план разбирается
- **THEN** негодный элемент отброшен, остальные 179 файлов остаются в плане
- **AND** задача уходит в `review` с причиной, называющей отброшенный элемент
- **AND** повторный запрос к модели не выполняется
#### Scenario: Повторная адресация одного файла
- **GIVEN** ответ модели, где два элемента адресуют один и тот же файл
- **WHEN** план разбирается
- **THEN** в плане остаётся первый элемент, второй отброшен
- **AND** задача уходит в `review` с причиной, называющей повторную адресацию
#### Scenario: Все элементы негодны — план не разобран
- **GIVEN** ответ модели, где ни один элемент `files[]` не адресует реальный файл
- **WHEN** план разбирается
- **THEN** план не принимается как валидный
#### Scenario: Путь вместо номера принимается как запасной формат
- **GIVEN** ответ модели, где элемент `files[]` несёт путь файла вместо номера
- **WHEN** план разбирается
- **THEN** разбор успешен, если путь совпадает с реальным файлом торрента
- **AND** correction-ретрай не выполняется
#### Scenario: Номер и путь одновременно — главенствует номер
- **GIVEN** элемент `files[]`, несущий и номер, и путь, которые указывают на разные файлы
- **WHEN** план разбирается
- **THEN** `src` берётся резолвом номера
- **AND** задача уходит в `review` с причиной о несовпадении присланного пути с резолвом
#### Scenario: Несуществующий src отклоняется
- **GIVEN** ответ LLM, где `files[].src` не совпадает ни с одним файлом торрента
- **WHEN** план разбирается
- **THEN** такой элемент отбрасывается с причиной, а посторонний путь в план не попадает
#### Scenario: Нумерация не зависит от вызывающего
- **GIVEN** два вызова распознавания одной раздачи, получившие список файлов в разном порядке
- **WHEN** собирается промпт
- **THEN** напечатанная нумерация в обоих вызовах одинакова
#### Scenario: Режиссёр не требуется от LLM и не влияет на гейт
- **GIVEN** ответ LLM без поля `director`
- **WHEN** план разбирается и оценивается
- **THEN** разбор успешен, `director` пуст
- **AND** отсутствие режиссёра не влияет на структурную валидацию и решение
auto/review
### Requirement: Провайдер LLM за абстракцией со структурированным выводом
Доступ к LLM SHALL быть за интерфейсом с выбором реализации по полю `[llm].type`
(первый тип — `openai-compat`). Система SHALL запрашивать JSON-режим
(`response_format: {"type":"json_object"}`), срезать ```-ограждения и
валидировать ответ в Go против схемы плана. При ошибке разбора система SHALL
ретраить до `[llm].max_retries`, передавая модели саму ошибку и схему. Если после
ретраев ответ не разобран, задача SHALL уходить в `review` (НЕ в `failed`) с
причиной «ответ LLM не разобран».
Повторный запрос SHALL NOT переприсылать список файлов раздачи: список назван в
первом сообщении диалога, и его номера сохраняют смысл на всех попытках. Размер
запроса SHALL NOT расти пропорционально числу попыток.
Признак завершения генерации (`finish_reason`) SHALL доходить до распознавания.
Ответ, оборванный по длине, система SHALL уводить в `review` с отдельной
причиной «ответ модели обрезан» и SHALL NOT повторять запрос ни тем же промптом,
ни его вариантом: обрыв означает, что ответ не поместился, а не что модель
ошиблась.
#### Scenario: Неразобранный ответ уходит в review
- **GIVEN** LLM, чей ответ не проходит валидацию схемы после всех ретраев
- **WHEN** завершается распознавание
- **THEN** задача переходит в `review` с причиной «ответ LLM не разобран»
- **AND** задача НЕ переходит в `failed`
#### Scenario: Повторная попытка не переприсылает список файлов
- **GIVEN** раздача из 180 файлов и ответ модели с ошибкой разбора
- **WHEN** выполняется correction-ретрай
- **THEN** список файлов раздачи повторно не печатается
- **AND** размер запроса второй попытки сопоставим с размером первой
#### Scenario: Обрыв по длине не ретраится
- **GIVEN** ответ модели с признаком обрыва генерации по длине
- **WHEN** завершается распознавание
- **THEN** задача переходит в `review` с причиной «ответ модели обрезан»
- **AND** повторных запросов к модели не выполняется
### Requirement: Модель уверенности и решение auto/review
Система SHALL раскладывать автоматически (без review) только при выполнении
ВСЕГО: (1) подтверждённый единичный сильный матч в базе (`metadata-match`) с
`provider_id`; (2) структурная валидация без предупреждений (фильм — ровно один
основной видеофайл; сериал — число серий бьётся с базой, нумерация S·E
консистентна); (3) согласованность пред-парса и LLM по типу/названию/году. Иначе
задача SHALL уходить в `review` с явной причиной. Самооценку LLM (`confidence`)
система SHALL учитывать лишь как вспомогательный сигнал, НЕ как единственный гейт.
Список причин распознавания SHALL быть неоднородным: кроме блокирующих причин он
MAY содержать информационные строки, которые показываются человеку и сохраняются
вместе с распознаванием, но авто-раскладку НЕ отменяют — такова сводка покрытия
плана (см. «Причины по нумерации серий и покрытию плана»). Поэтому решение
auto/review система SHALL считать по **блокирующим** причинам, а НЕ по длине
списка причин: непустой список сам по себе SHALL NOT означать `review`.
#### Scenario: Нет матча в базе — всегда review
- **GIVEN** план без подтверждённого матча в базе (база выключена или матча нет)
- **WHEN** принимается решение auto/review
- **THEN** задача уходит в `review`, авто-раскладка не делается
#### Scenario: Матч и чистая валидация — авто
- **GIVEN** подтверждённый единичный матч, чистая структурная валидация и
согласованность сигналов
- **WHEN** принимается решение
- **THEN** допускается авто-раскладка (при отсутствии `force_review`)
#### Scenario: Информационная причина авто не отменяет
- **GIVEN** подтверждённый матч и чистая валидация, но покрытие плана неполно
только за счёт файлов-спутников
- **WHEN** принимается решение auto/review
- **THEN** список причин непуст — в нём сводка покрытия
- **AND** авто-раскладка допускается
## ADDED Requirements
### Requirement: Полный список файлов раздачи в промпте
Промпт распознавания SHALL включать все файлы раздачи, а не фиксированную их
часть. Предельное число файлов и предельный размер ответа модели SHALL задаваться
настройками `[recognition].max_files` и `[recognition].max_tokens`; предел числа
файлов служит предохранителем от аномальной раздачи, а не рабочим ограничением, и
его значение по умолчанию SHALL соответствовать размеру промпта, который заведомо
принимает модель. Если список всё же усечён пределом, распознавание SHALL называть
усечение отдельной причиной ухода в `review`. Отказ модели, вызванный размером
запроса, распознавание SHALL называть причиной о размере запроса, а не текстом
провайдера.
#### Scenario: Раздача на сотни файлов показана модели целиком
- **GIVEN** раздача из 180 файлов и `max_files` = 500
- **WHEN** собирается промпт распознавания
- **THEN** в списке файлов промпта присутствуют все 180 файлов
#### Scenario: Усечение названо причиной
- **GIVEN** раздача, число файлов которой превышает `max_files`
- **WHEN** завершается распознавание
- **THEN** задача уходит в `review`, и среди причин названо усечение списка файлов
### Requirement: Причины по нумерации серий и покрытию плана компактны
Причины ухода в `review`, порождённые нумерацией серий, SHALL быть свёрнуты: на
сезон приходится не более одной причины, перечисляющей недостающие серии.
Распознавание SHALL называть покрытие плана — сколько файлов раздачи попало в
план из общего числа, — когда покрыты не все файлы. Блокировать авто-раскладку
SHALL только непокрытый видеофайл: файл, который модель не адресовала и который
по расширению является видео, означает потерянную серию или фильм. Непокрытые
файлы-спутники (субтитры, изображения, тексты, служебные файлы) SHALL
показываться в покрытии, но SHALL NOT входить в структурную валидацию и SHALL NOT
влиять на решение auto/review: модель вправе не перечислять то, что не
раскладывается.
#### Scenario: Пропуски сезона свёрнуты в одну причину
- **GIVEN** план сезона, где недостают серии E05, E07 и E11
- **WHEN** формируются причины ухода в review
- **THEN** этому сезону соответствует одна причина, перечисляющая недостающие серии
#### Scenario: Непокрытый видеофайл блокирует авто
- **GIVEN** раздача, где один видеофайл не адресован ни одним элементом плана
- **WHEN** принимается решение auto/review
- **THEN** задача уходит в `review` с причиной о непокрытом видеофайле
#### Scenario: Непокрытые файлы-спутники авто не блокируют
- **GIVEN** раздача, где не адресованы только `.nfo`, скриншоты и текстовый файл,
а все видеофайлы покрыты, матч подтверждён и валидация чиста
- **WHEN** принимается решение auto/review
- **THEN** авто-раскладка допускается
- **AND** покрытие плана показано человеку, но причиной ухода в review не является