Кейс «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>
43 lines
3.1 KiB
Markdown
43 lines
3.1 KiB
Markdown
## 1. Homoglyph-таблица и fold
|
||
|
||
- [x] 1.1 В `internal/recognize` завести курируемую таблицу кирилло-латинских
|
||
двойников (~15–20 пар, строчные и заглавные) как единственный источник правды
|
||
- [x] 1.2 Реализовать потокенную `foldHomoglyphs(s string) string`: одно-скриптовый
|
||
токен не трогаем, в смешанном — доминирующий скрипт по числу буквенных рун,
|
||
меньшинство мапим по таблице; при равенстве оставляем как есть
|
||
- [x] 1.3 Юнит-тесты `foldHomoglyphs`: «Hаrold»→«Harold», честная кириллица
|
||
нетронута, смешанное двуязычное название, равенство скриптов, пустой ввод
|
||
|
||
## 2. Санитайзинг полей плана (recognition)
|
||
|
||
- [x] 2.1 Реализовать `sanitizeTitle`: strip control/zero-width (C0/C1, U+200B и
|
||
родственные, BOM), collapse пробелов + trim, затем `foldHomoglyphs`
|
||
- [x] 2.2 Применить санитайзинг к `title`, `original_title`, `provider_hint` на
|
||
границе разбора плана — после unmarshal, ДО структурной валидации и сверки;
|
||
`files[].src` не трогать
|
||
- [x] 2.3 `slog.Debug` when поле реально переписано (before/after), логгер из ctx
|
||
- [x] 2.4 Тесты: поля чистятся, `files[].src` неприкосновенны, лог при изменении
|
||
|
||
## 3. Fold в гейте нормализации (metadata-match)
|
||
|
||
- [x] 3.1 Добавить `foldHomoglyphs` в `normalize` (`internal/recognize/metadata.go`)
|
||
после `ё→е`, переиспользуя ту же таблицу
|
||
- [x] 3.2 Тест: план и кандидат, различающиеся лишь скриптом буквы, нормализованно
|
||
совпадают
|
||
|
||
## 4. Безгодовой второй проход сверки (metadata-match)
|
||
|
||
- [x] 4.1 Обернуть перебор в `matchMetadata` в два прохода: pass 1 — как сейчас
|
||
(`Search` с годом), pass 2 — только при `plan.Year > 0` и отсутствии матча в
|
||
pass 1, тот же перебор `searchKeys × providers` с `Year = 0`
|
||
- [x] 4.2 Кандидаты копятся через оба прохода с существующей дедупликацией по
|
||
`provider:id` и потолком `maxCandidates`; гейт `strongMatches` не менять
|
||
- [x] 4.3 Тесты: кривой год находится без года; при матче в pass 1 второго прохода
|
||
нет; при `year = 0` второго прохода нет; несколько кандидатов из pass 2 → матч
|
||
не подтверждён
|
||
|
||
## 5. Проверка и вычитка
|
||
|
||
- [x] 5.1 `task test` и `task lint` — зелёные
|
||
- [x] 5.2 `openspec validate --strict sanitize-llm-titles-yearless-retry`
|