recognize: название из метабазы санитизируется перед попаданием в план

- чистка стоит на каждой точке входа значения метабазы в план — сборка матча,
  копия кандидата для ревью, набор закреплённых значений источника и его
  чтение: гарантия, поставленная только на запись, обходится данными,
  сохранёнными прежними версиями
- название, непригодное как имя каталога (пустое или без единой буквы и
  цифры), не подставляется — раздача уходит в review с названной причиной
- гейт подтверждения матча не сдвинут: сравнение с планом идёт по значениям
  провайдера, чистится только копия, уходящая дальше
This commit is contained in:
av
2026-08-10 10:41:16 +03:00
parent bb278e8744
commit 9aecf757e0
26 changed files with 1653 additions and 53 deletions
+17 -2
View File
@@ -72,13 +72,17 @@ func (r *Recognizer) matchMetadata(ctx context.Context, plan Plan) (*Match, []me
}
// Копим кандидатов для выбора (дедуп по провайдеру+id, потолок).
// Названия чистим на КОПИИ: выбор кандидата человеком — такой же
// путь подтверждения матча, как авто, и закреплённое название так
// же уезжает в имя каталога. Исходный cands не трогаем — по нему
// ниже ищется сильный матч, и сдвигать его гейт задача не заказывала.
for _, c := range cands {
ck := c.Provider + ":" + c.ID
if seen[ck] || len(candidates) >= maxCandidates {
continue
}
seen[ck] = true
candidates = append(candidates, c)
candidates = append(candidates, sanitizeCandidate(c))
}
// Единичный сильный матч ищем у первого подходящего провайдера.
@@ -139,10 +143,21 @@ func (r *Recognizer) buildMatch(ctx context.Context, p metadata.Provider, c meta
}
}
prov, pid := CandidateTag(c)
// Каноническое название чистим здесь — в единственной точке сборки Match, и
// уже ПОСЛЕ гейта сильного матча (strongMatches сравнивает по значениям
// провайдера). Дальше по потоку значение считается чистым: и подстановка в
// план, и решение auto/review, и диагностика сухого прогона берут его как
// есть, а не чистят каждый заново.
title := c.Title
if clean := SanitizeTitle(title); clean != title {
logctx.FromOr(ctx, r.log).Debug("metadata match title sanitized",
"before", title, "after", clean)
title = clean
}
return &Match{
Provider: prov,
ProviderID: pid,
Title: c.Title,
Title: title,
Year: c.Year,
Director: directorOf(ctx, r.log, p, mt, c.ID),
SeasonEpisodeCounts: counts,