Files
avandClaude Opus 4.8 e2ea1840c9 Распознавание: санитайзинг названий от LLM + безгодовой фолбэк сверки
Кейс «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>
2026-07-10 15:45:19 +03:00

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