Files
jellybit/openspec/changes/archive/2026-06-29-recognition-multi-title-match/design.md
T
avandClaude Opus 4.8 4fc8c41b3a Поиск по нескольким названиям при сверке с базой (recognition)
Сверка с метабазой промахивалась на иностранных фильмах с русским
релиз-именем (кейс «Тёмный рыцарь»): поиск шёл по одной строке
provider_hint||title и игнорировал original_title, а базы индексированы
прежде всего по оригинальным названиям.

- matchMetadata ищет по ключам original_title → title → provider_hint с
  ранним стопом на первом единичном сильном матче; пустые и
  нормализованно-дублирующие ключи пропускаются, кандидаты для review
  копятся из всех заходов.
- Промпт требует всегда заполнять title и original_title (дублировать при
  отсутствии оригинала / российском контенте; при неуверенности дублировать,
  не выдумывать). Разбор остаётся мягким к пустому original_title.
- TMDB-поиск передаёт language (по умолчанию ru-RU, настраивается
  [metadata.tmdb].language); original_title не зависит от локали.
- Нормализация названий сводит ё→е.

Инварианты не ослаблены: авто только при подтверждённом единичном матче +
структурной валидации + согласованности сигналов.

Capability recognition впервые перенесена в OpenSpec; change архивирован.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 12:16:47 +03:00

7.4 KiB
Raw Blame History

Context

Сверка распознавания (internal/recognize/metadata.go, matchMetadata) сейчас формирует один поисковый ключ searchTitle = provider_hint || title и шлёт его всем включённым провайдерам. Гейт strongMatches сравнивает нормализованные {plan.Title, plan.OriginalTitle} против {cand.Title, cand.OriginalTitle} и требует год ±1. Базы TMDB/TVDB индексированы прежде всего по оригинальным названиям, поэтому русское релиз-имя часто не находится, а если original_title пуст — английская сторона гейта вообще не работает. TMDB-поиск не передаёт language, так что локализованный Title приходит в дефолтной локали.

Источник истины по recognition пока docs/specs/recognition.md (capability ещё не перенесена в OpenSpec) — дельту синхронизируем с ним.

Goals / Non-Goals

Goals:

  • Повысить попадаемость сверки на иностранных фильмах с русским релиз-именем, не ослабляя гейты авто-раскладки.
  • Сделать оригинальное название всегда доступным для запроса и сравнения.
  • Минимальные, локальные правки в существующих функциях.

Non-Goals:

  • Не меняем модель уверенности и условия авто (матч в базе + структурная валидация + согласованность сигналов остаются как есть).
  • Не вводим fuzzy-сравнение названий (только точечная нормализация ёе).
  • Не добавляем поле страны/языка происхождения в Plan.
  • Не трогаем TVDB/TVMaze-клиентов по части локали (вне объёма).

Decisions

1. Стратегия поиска «оригинал → fallback», а не мёрж всех запросов. Перебираем ключи [original_title, title, provider_hint] по порядку, останавливаемся на первом, давшем единичный сильный матч. Оригинал — сильнейший ключ баз, обычно хватает первого захода; меньше обращений к API/квотам. Альтернатива — гнать все запросы и мёржить кандидатов — даёт чуть полнее список для review, но дороже по обращениям; отвергнута как избыточная. Кандидатов для review всё равно копим из всех фактически выполненных заходов. Дубль-ключи (нормализованно равные уже выполненному) пропускаем.

2. provider_hint остаётся третьим фолбэком, а не удаляется. Два канонических названия покрывают основной кейс, но hint иногда сформулирован удачнее (очищен от мусора релиз-имени) — дешёвая страховка, когда оба названия не нашлись. Удаление поля — лишняя правка схемы без явной выгоды.

3. «Всегда заполнять оба названия» — через промпт, не через жёсткую схему. Требование к модели: заполнять title и original_title, при отсутствии отдельного оригинала или для российского контента — дублировать title; при неуверенности — дублировать, а не выдумывать. Это и есть защита от ложного авто-матча: «не знаю» схлопывается в безопасное дублирование русского названия, а не в галлюцинацию английского, которая могла бы случайно сматчиться с реальным фильмом и привести к авто-раскладке не того тайтла. parsePlan остаётся мягким: пустой original_title не отбраковываем (graceful-фолбэк на title), чтобы не плодить correction-ретраи и не уходить в review зря.

4. language=ru-RU для TMDB, всегда, параметризуемо конфигом. Поле original_title/original_name у TMDB не зависит от language — английская сторона гейта не страдает. Локализованный Title приходит по-русски: сходится русская сторона гейта и аккуратнее карточки кандидатов в review. Деление на «российский/нероссийский» не нужно — ru-RU полезен именно зарубежке, а для русского контента нейтрален. Дефолт ru-RU, поле [metadata.tmdb].language.

5. ёе в normalize, и только это. Узкая правка под реальный класс расхождений написания. йи и прочие свёртки НЕ делаем — меняют смысл, риск ложных совпадений.

Risks / Trade-offs

  • Модель всё же выдумывает оригинал вопреки промпту → гейт по-прежнему требует год ±1 и единичность матча; авто только при подтверждённом матче. Промпт явно предписывает дублирование при неуверенности. Остаточный риск низкий и не выше текущего (галлюцинация title возможна и сейчас).
  • ru-RU для контента без русской локализации → TMDB отдаёт fallback (оригинал/английский), хуже текущего поведения не становится.
  • Лишние обращения к базам при фолбэках → ограничены порядком из 1–3 запросов на провайдера, дубль-ключи пропускаются, ранний стоп на первом сильном матче. Ошибки провайдера по-прежнему не валят распознавание.

Migration Plan

  • Изменения обратносовместимы по хранимым данным и API. Новое поле конфига [metadata.tmdb].language опционально (дефолт ru-RU).
  • Откат — ревёрт; персистентных миграций БД нет.
  • После apply синхронизировать docs/specs/recognition.md с дельтой.

Open Questions

  • Нет.