Сверка с метабазой промахивалась на иностранных фильмах с русским релиз-именем (кейс «Тёмный рыцарь»): поиск шёл по одной строке 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>
93 lines
7.4 KiB
Markdown
93 lines
7.4 KiB
Markdown
## 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
|
||
|
||
- Нет.
|