Files
jellybit/openspec/specs/recognition/spec.md
T
avandClaude Opus 4.8 e2ea1840c9 Распознавание: санитайзинг названий от LLM + безгодовой фолбэк сверки
Кейс «Harold and the Purple Crayon»: LLM отдал title с кириллической
буквой-двойником, сырое название ушло в запрос TVDB дословно (не нашлось),
а гейт нормализации кир/лат двойники не сворачивал — двойной промах, пустой
список кандидатов, ручной ввод id.

- recognition: санитайзинг человекочитаемых полей плана (title/original_title/
  provider_hint) на границе разбора, до валидации: strip control/zero-width,
  collapse пробелов, потокенная свёртка homoglyph-двойников по курируемой
  кир↔лат таблице. files[].src не трогаем (обязаны биться с торрентом).
- metadata-match: тот же fold в normalize (гейт) как defense-in-depth;
  безгодовой второй проход сверки как fallback при известном годе и промахе
  первого — восстанавливает off-by-one авто-матчи и пополняет кандидатов
  review. В fallback требуем известный год кандидата (год-unknown → review,
  не авто); гейт год ±1 и инвариант авто-матча не двигаются.

Спеки recognition/metadata-match обновлены, change заархивирован.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 15:45:19 +03:00

14 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 оставаться устойчивым к пустому original_title: пустое значение не отбраковывается и не вызывает correction-ретрай; сверка gracefully использует доступные названия.

Scenario: Российский фильм — дублирование

  • GIVEN раздача российского фильма без отдельного оригинального названия
  • WHEN модель возвращает план
  • THEN title и original_title заполнены одинаковым каноническим названием

Scenario: Пустой original_title не ломает разбор

  • GIVEN ответ модели с пустым original_title
  • WHEN план разбирается
  • THEN разбор успешен без correction-ретрая
  • AND сверка использует titleprovider_hint)

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 совпадает с реальным файлом торрента.

Scenario: План сериала с per-file нумерацией

  • GIVEN сезон-пак из 10 видеофайлов
  • WHEN LLM возвращает план
  • THEN type = series, а каждый видеофайл несёт свои season/episode

Scenario: Несуществующий src отклоняется

  • GIVEN ответ LLM, где files[].src не совпадает ни с одним файлом торрента
  • WHEN план разбирается
  • THEN такой план не принимается как валидный

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), а не к «починке» пути