Поиск по нескольким названиям при сверке с базой (recognition)

Сверка с метабазой промахивалась на иностранных фильмах с русским
релиз-именем (кейс «Тёмный рыцарь»): поиск шёл по одной строке
provider_hint||title и игнорировал original_title, а базы индексированы
прежде всего по оригинальным названиям.

- matchMetadata ищет по ключам original_title → title → provider_hint с
  ранним стопом на первом единичном сильном матче; пустые и
  нормализованно-дублирующие ключи пропускаются, кандидаты для review
  копятся из всех заходов.
- Промпт требует всегда заполнять title и original_title (дублировать при
  отсутствии оригинала / российском контенте; при неуверенности дублировать,
  не выдумывать). Разбор остаётся мягким к пустому original_title.
- TMDB-поиск передаёт language (по умолчанию ru-RU, настраивается
  [metadata.tmdb].language); original_title не зависит от локали.
- Нормализация названий сводит ё→е.

Инварианты не ослаблены: авто только при подтверждённом единичном матче +
структурной валидации + согласованности сигналов.

Capability recognition впервые перенесена в OpenSpec; change архивирован.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
av
2026-06-29 12:16:47 +03:00
co-authored by Claude Opus 4.8
parent cc7e51b3a4
commit 4fc8c41b3a
15 changed files with 611 additions and 58 deletions
+20 -11
View File
@@ -13,22 +13,27 @@ import (
"git.vakhrushev.me/av/jellybit/internal/logging"
)
const tmdbDefaultBaseURL = "https://api.themoviedb.org/3"
const (
tmdbDefaultBaseURL = "https://api.themoviedb.org/3"
tmdbDefaultLanguage = "ru-RU"
)
// TMDBConfig — настройки клиента TMDB.
type TMDBConfig struct {
APIKey string
Proxy string
Timeout time.Duration
BaseURL string // пусто → api.themoviedb.org; задаётся в тестах
APIKey string
Proxy string
Timeout time.Duration
BaseURL string // пусто → api.themoviedb.org; задаётся в тестах
Language string // локаль возвращаемых названий; пусто → ru-RU
}
// TMDB — клиент The Movie Database (API v3, авторизация по api_key).
type TMDB struct {
apiKey string
baseURL string
hc *http.Client
log *slog.Logger
apiKey string
baseURL string
language string
hc *http.Client
log *slog.Logger
}
// NewTMDB собирает клиент TMDB. logger nil → slog.Default().
@@ -44,10 +49,14 @@ func NewTMDB(cfg TMDBConfig, logger *slog.Logger) (*TMDB, error) {
if base == "" {
base = tmdbDefaultBaseURL
}
lang := cfg.Language
if lang == "" {
lang = tmdbDefaultLanguage
}
if logger == nil {
logger = slog.Default()
}
return &TMDB{apiKey: cfg.APIKey, baseURL: strings.TrimRight(base, "/"), hc: hc, log: logger}, nil
return &TMDB{apiKey: cfg.APIKey, baseURL: strings.TrimRight(base, "/"), language: lang, hc: hc, log: logger}, nil
}
func (t *TMDB) Name() string { return "tmdb" }
@@ -67,7 +76,7 @@ type tmdbSearchResp struct {
// Search ищет фильм/сериал по названию и году.
func (t *TMDB) Search(ctx context.Context, q Query) ([]Candidate, error) {
var path string
params := url.Values{"api_key": {t.apiKey}, "query": {q.Title}, "include_adult": {"false"}}
params := url.Values{"api_key": {t.apiKey}, "query": {q.Title}, "include_adult": {"false"}, "language": {t.language}}
switch q.Type {
case Movie:
path = "/search/movie"