## Why LLM возвращает названия с «грязью», из-за которой сверка с метабазой промахивается и загрузка уходит в review без единого кандидата (реальный кейс — «Harold and the Purple Crayon» с кириллической буквой-двойником вместо латинской: сырое название ушло в query-параметр TVDB дословно, база не нашла, а гейт сильного матча кир/лат двойники не сворачивает). Плюс год из плана уходит в запрос как жёсткий фильтр — при ошибке модели в годе база не находит запись, которая на деле есть. Итог: Jellyfin остаётся без официального id, а человек вбивает id руками. Цель — поднять hit-rate кандидатов метабазы, не трогая инвариант авто-матча. ## What Changes - **Санитайзинг названий от LLM** (`recognition`): на границе разбора плана, после unmarshal и ДО структурной валидации/сверки, чистим человекочитаемые поля `title`, `original_title`, `provider_hint`: (1) strip control/zero-width символов, (2) collapse пробелов + trim, (3) свёртка кирилло-латинских homoglyph-двойников по курируемой таблице (потокенно, только для смешанных токенов). `files[].src` не трогаем — они обязаны байт-в-байт совпадать с файлами торрента. - **Homoglyph-fold в гейте матча** (`metadata-match`): нормализация названий при сравнении получает ту же свёртку двойников как defense-in-depth на случай грязи со стороны кандидата базы. - **Ретрай сверки без года** (`metadata-match`): если поиск с годом не дал подтверждённого матча, второй проход тем же перебором названий×провайдеров, но без года. Гейт (exact-normalized название + год ±1) остаётся прежним — precision не падает, кандидаты копятся через оба прохода. - Наблюдаемость: `slog.Debug`, когда санитайзинг реально переписал поле. Не в scope (отдельные задачи при необходимости): fuzzy/edit-distance матч, транслитерация ru↔en, срез подзаголовка после «:», санитайзинг входного контекста и накопленных подсказок. ## Capabilities ### New Capabilities _Нет._ ### Modified Capabilities - `recognition`: добавляется требование санитайзинга человекочитаемых полей плана (`title`/`original_title`/`provider_hint`) на границе разбора ответа LLM. - `metadata-match`: нормализация названий при сравнении расширяется свёрткой кирилло-латинских homoglyph-двойников; сверка получает безгодовой второй проход как fallback, когда поиск с годом не дал подтверждённого матча. ## Impact - Код: `internal/recognize` (новый шаг санитайзинга при разборе плана, `normalize` и `matchMetadata` в `metadata.go`). Провайдеры `internal/metadata/*` не меняются (год уже опционален в `Query`). - Поведение: ранее промахивавшиеся из-за homoglyph/кривого года раздачи теперь находят кандидата (авто-матч при единичном сильном совпадении, иначе — заполненный список для выбора в review). Инвариант «авто только при подтверждённом единичном сильном матче» и неприкосновенность `files[].src`/источника не затрагиваются. - Зависимостей не добавляется (таблица двойников — небольшой литерал в коде).