- новое поле [general].language (ru|en, дефолт en) — единый источник языка локализованного title детектора и локали запросов к метабазам; original_title всегда на языке оригинала - локаль TMDB (поиск + credits) выводится из него, [metadata.tmdb].language убран - промпт LLM явно задаёт язык title с fallback на оригинал
17 KiB
recognition Specification
Purpose
Распознавание: разбор недоверенных сигналов раздачи моделью в структурированный
план (тип фильм/сериал, каноническое название и год, файлы → серии). Capability
описывает пред-парс имени, контракт и провайдер LLM со структурированным
выводом, роли файлов на краях и модель уверенности (решение auto/review). Сверка
с внешними базами метаданных — в metadata-match.
Requirements
Requirement: Контракт LLM на оригинальное и локализованное названия
Промпт распознавания SHALL требовать от модели всегда заполнять и title, и
original_title. Если отдельного оригинального названия нет или контент
российского происхождения, модель SHALL дублировать title в
original_title. При неуверенности в оригинальном названии модель SHALL
дублировать title, а не выдумывать название (защита от ложного авто-матча).
Промпт SHALL явно задавать язык локализованного title из глобальной
настройки language (ru|en, дефолт en): модель SHALL возвращать title
на выбранном языке, а при отсутствии перевода — на языке оригинала (fallback).
Языковая директива SHALL касаться ТОЛЬКО title; original_title остаётся на
языке оригинала независимо от language (правила дублирования выше не
меняются). provider_hint и структурные поля (files[].src, роли, нумерация)
языковой директивой не затрагиваются.
Разбор ответа SHALL оставаться устойчивым к пустому original_title: пустое
значение не отбраковывается и не вызывает correction-ретрай; сверка
gracefully использует доступные названия.
Scenario: Российский фильм — дублирование
- GIVEN раздача российского фильма без отдельного оригинального названия
- WHEN модель возвращает план
- THEN
titleиoriginal_titleзаполнены одинаковым каноническим названием
Scenario: Пустой original_title не ломает разбор
- GIVEN ответ модели с пустым
original_title - WHEN план разбирается
- THEN разбор успешен без correction-ретрая
- AND сверка использует
title(иprovider_hint)
Scenario: title приходит на языке из настройки
- GIVEN
language=enи иностранный фильм с известным английским названием - WHEN модель возвращает план
- THEN промпт требовал
titleна английском - AND
original_titleостаётся на языке оригинала (директива его не касается)
Scenario: Промпт задаёт fallback на оригинал при отсутствии перевода
- GIVEN
language=en - WHEN собирается промпт распознавания
- THEN промпт требует при отсутствии перевода вернуть
titleна языке оригинала, а не выдумывать название
Scenario: Дефолтный язык — английский
- GIVEN
languageне задан в конфиге - WHEN собирается промпт распознавания
- THEN промпт требует
titleна английском (дефолтen)
Requirement: Пред-парс имени релиза
Перед вызовом LLM система SHALL выполнять дешёвый пред-парс имени торрента
(go-ptn): извлекать черновые название, год, сезон, серию и качество. Результат
пред-парса SHALL использоваться как вспомогательный сигнал в промпте и как
сторона проверки согласованности при решении auto/review, но НЕ SHALL считаться
итоговым распознаванием.
Scenario: Пред-парс даёт черновые поля
- WHEN на вход распознавания поступает имя релиза
Fargo.S02.2015.WEB-DL.1080p - THEN пред-парс возвращает черновые
title,year,season,quality - AND эти значения передаются в промпт LLM как подсказка
Requirement: Разбор сигналов LLM в структурированный план
Система SHALL передавать LLM недоверенные сигналы (имя торрента, дерево файлов с
размерами, текстовый контекст и накопленные подсказки, пред-парс) и получать
структурированный план в схеме: type (movie|series), title,
original_title, year, provider_hint, files[] и confidence. Каждый
элемент files[] SHALL нести src, role
(main|episode|subtitle|extra|sample|ignore) и, для сериала,
per-file season/episode (отдельного скалярного season быть SHALL NOT — так
выражаются мультисезонные паки и спецвыпуски). План SHALL приниматься только
если каждый files[].src совпадает с реальным файлом торрента.
План 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: Несуществующий src отклоняется
- GIVEN ответ LLM, где
files[].srcне совпадает ни с одним файлом торрента - 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 не разобран».
Scenario: Неразобранный ответ уходит в review
- GIVEN LLM, чей ответ не проходит валидацию схемы после всех ретраев
- WHEN завершается распознавание
- THEN задача переходит в
reviewс причиной «ответ LLM не разобран» - AND задача НЕ переходит в
failed
Requirement: Модель уверенности и решение auto/review
Система SHALL раскладывать автоматически (без review) только при выполнении
ВСЕГО: (1) подтверждённый единичный сильный матч в базе (metadata-match) с
provider_id; (2) структурная валидация без предупреждений (фильм — ровно один
основной видеофайл; сериал — число серий бьётся с базой, нумерация S·E
консистентна); (3) согласованность пред-парса и LLM по типу/названию/году. Иначе
задача SHALL уходить в review с явной причиной. Самооценку LLM (confidence)
система SHALL учитывать лишь как вспомогательный сигнал, НЕ как единственный гейт.
Scenario: Нет матча в базе — всегда review
- GIVEN план без подтверждённого матча в базе (база выключена или матча нет)
- WHEN принимается решение auto/review
- THEN задача уходит в
review, авто-раскладка не делается
Scenario: Матч и чистая валидация — авто
- GIVEN подтверждённый единичный матч, чистая структурная валидация и согласованность сигналов
- WHEN принимается решение
- THEN допускается авто-раскладка (при отсутствии
force_review)
Requirement: Роли файлов на краях раздачи
Система SHALL относить семплы, «экстра» и мусор к роли ignore (эвристики размер/
имя + LLM), а внешние субтитры (.srt, .ass, пары VobSub .idx+.sub) —
привязывать к соответствующему видео. Любую неоднозначность нумерации (дыры,
дубли, спорные спецвыпуски) система SHALL эскалировать в review, а не разрешать
молча.
Scenario: Семпл помечается ignore
- GIVEN раздача с файлом
sample.mkvмалого размера - WHEN строится план
- THEN этот файл получает роль
ignoreи в раскладку не попадает
Requirement: Санитайзинг человекочитаемых полей плана
Перед структурной валидацией плана и сверкой с базами система SHALL санитизировать
человекочитаемые поля плана — title, original_title, provider_hint — как
недоверенный вывод LLM. Санитайзинг SHALL: (1) удалять управляющие и zero-width
символы (C0/C1, U+200B и родственные, BOM U+FEFF); (2) сводить внутренние
последовательности пробельных к одиночному пробелу и обрезать края; (3) сворачивать
кирилло-латинские homoglyph-двойники (см. ниже).
Свёртка двойников SHALL работать потокенно (по словам, разделённым не-буквенными символами): токен, все буквы которого принадлежат одному скрипту, система SHALL оставлять без изменений (билингвальность реальна — кириллические названия неприкосновенны); в токене смешанного скрипта система SHALL определять доминирующий скрипт по числу буквенных рун и заменять буквы-меньшинство их визуальными двойниками из доминирующего скрипта по курируемой таблице. При отсутствии доминирующего скрипта (равенство) токен SHALL оставаться без изменений.
Санитайзинг SHALL применяться ТОЛЬКО к перечисленным человекочитаемым полям.
files[].src система SHALL NOT санитизировать — эти значения обязаны совпадать с
реальными файлами торрента, и расхождение (в т.ч. homoglyph) SHALL оставаться
основанием отклонить план, а не поводом «чинить» путь.
Когда санитайзинг реально изменил значение поля, система SHALL логировать это на
уровне Debug (названия не относятся к секретам).
Scenario: Кириллический двойник в англоязычном названии сворачивается
- GIVEN план, где
title=Hаrold and the Purple Crayon(букваав первом слове — кириллическаяU+0430) - WHEN план санитизируется
- THEN первое слово становится
Harold(все буквы латинские) - AND в запрос к базе и в сравнение уходит латинское название
Scenario: Честное кириллическое название не трогается
- GIVEN план российского фильма с
title=Тёмный рыцарь, где все буквы каждого слова кириллические - WHEN план санитизируется
- THEN название остаётся кириллическим без замены букв
Scenario: Токен без доминирующего скрипта не трогается
- GIVEN план, где короткий токен содержит поровну латинских и кириллических букв (доминирующего скрипта нет)
- WHEN план санитизируется
- THEN этот токен остаётся без замены букв (осознанный trade-off: двухбуквенный homoglyph-typo не сворачивается)
Scenario: Zero-width и лишние пробелы вычищаются
- GIVEN план, где
titleсодержит zero-width символ и сдвоенные пробелы - WHEN план санитизируется
- THEN zero-width удалён, внутренние пробелы сведены к одиночным, края обрезаны
Scenario: files[].src не санитизируется
- GIVEN ответ LLM, где
files[].srcсодержит символ-двойник и не совпадает ни с одним реальным файлом торрента - WHEN план обрабатывается
- THEN
files[].srcНЕ изменяется санитайзингом - AND несовпадение src приводит к отклонению плана (эскалация в review), а не к «починке» пути