Files
jellybit/openspec/specs/recognition/spec.md
T
av a2d8140e22 recognition: глобальный переключатель языка вывода [general].language
- новое поле [general].language (ru|en, дефолт en) — единый источник языка
  локализованного title детектора и локали запросов к метабазам; original_title
  всегда на языке оригинала
- локаль TMDB (поиск + credits) выводится из него, [metadata.tmdb].language убран
- промпт LLM явно задаёт язык title с fallback на оригинал
2026-07-24 14:09:04 +03:00

17 KiB
Raw Blame History

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 сверка использует titleprovider_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), а не к «починке» пути