Кейс «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>
4.6 KiB
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/источника не затрагиваются. - Зависимостей не добавляется (таблица двойников — небольшой литерал в коде).