спека: заведён change large-release-recognition для больших раздач

- контракт с моделью переводится на адресацию файла номером строки списка,
  усечение промпта снимается, лимиты уходят в конфиг
- раскладка показывает все файлы раздачи, покрытие плана блокирует авто
  только при непокрытом видеофайле
This commit is contained in:
av
2026-09-02 08:54:32 +03:00
parent 2ac26bf8ed
commit 01ac0430a9
6 changed files with 685 additions and 0 deletions
@@ -0,0 +1,249 @@
## 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 не является