Поиск по нескольким названиям при сверке с базой (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>
This commit is contained in:
av
2026-06-29 12:16:47 +03:00
co-authored by Claude Opus 4.8
parent cc7e51b3a4
commit 4fc8c41b3a
15 changed files with 611 additions and 58 deletions
@@ -0,0 +1,62 @@
## Why
Сверка распознавания с базой метаданных промахивается на иностранных
фильмах с русским релиз-именем: реальный кейс — «Тёмный рыцарь» (The Dark
Knight), поиск не нашёл ничего, раскладку пришлось делать вручную. Базы
(TMDB/TVDB) индексированы прежде всего по оригинальным названиям, а поиск
сейчас идёт по одной строке `provider_hint || title` и игнорирует
`original_title`. Из-за этого сильное звено сверки — оригинальное название —
не используется ни в запросе, ни (когда модель оставила его пустым) в гейте
сравнения.
## What Changes
- **Поиск по нескольким названиям** в сверке с базой: стратегия
«оригинал → fallback» — сначала запрос по `original_title`, при единичном
сильном матче стоп; иначе запрос по локализованному `title`; третьим
фолбеком — `provider_hint`. Кандидатов для review копим из всех заходов
(дедуп по `provider:id`). Избыточный запрос-дубль (когда нормализованные
названия совпадают) пропускаем.
- **Контракт LLM на оба названия:** промпт требует всегда заполнять и
`title`, и `original_title`. Нет отдельного оригинала / российское
происхождение → продублировать `title`. Не уверен в оригинале → дублировать,
а **не выдумывать** (защита от ложного авто-матча по галлюцинации). Схема
разбора остаётся мягкой: пустой `original_title` не отбраковываем, а
graceful-фолбэк на `title`.
- **Локаль TMDB:** TMDB-поиск всегда передаёт `language` (по умолчанию
`ru-RU`), параметризуемый конфигом. Локализованный `Title` приходит
по-русски — сходится русская сторона гейта и аккуратнее карточки кандидатов
в review. `original_title` у TMDB остаётся на языке оригинала, английская
сторона гейта не страдает.
- **Нормализация названий:** в сравнении сводим `ё``е`, чтобы «Тёмный» и
«Темный» считались одним названием.
Гейты авто-раскладки не ослабляются: авто по-прежнему только при
подтверждённом единичном матче в базе + структурной валидации +
согласованности сигналов. Изменение лишь повышает попадаемость сверки.
## Capabilities
### New Capabilities
- `recognition`: распознавание контента раздачи (фильм/сериал, название,
год, сезон/серия), сверка с базами метаданных и решение «авто или review».
Первый перенос capability из `docs/specs/recognition.md` в OpenSpec; в этот
change фиксируем требования к сверке с базой и контракту названий,
затронутые изменением (остальное мигрируется отдельно).
### Modified Capabilities
<!-- Нет: recognition в OpenSpec ещё не было, заводим как новую. -->
## Impact
- `internal/recognize/metadata.go``matchMetadata` (многозапросный поиск),
`normalize` (`ё``е`).
- `internal/recognize/prompt.go` — правила промпта по `title`/`original_title`.
- `internal/metadata/tmdb.go` — параметр `language` в запросе поиска.
- Конфигурация — новое поле `[metadata.tmdb].language` (дефолт `ru-RU`),
валидация на старте.
- `docs/specs/recognition.md` — синхронизируем с дельтой (источник истины до
полного переноса).
- Внешнее API/схема ответа LLM: `provider_hint` сохраняется; новые требования
к заполнению `original_title`. Обратная совместимость хранимых данных не
затрагивается.