# metadata-match Specification ## Purpose Сверка распознанного плана с внешними базами метаданных (TMDB/TVDB/TVMaze): поиск записи по нескольким названиям с нормализацией, локаль запроса, подтверждение единичного сильного матча (официальный `provider_id` + каноническое имя/год) и сбор кандидатов с URL для ручного выбора в `review`. Разбор сигналов моделью — в `recognition`. ## Requirements ### Requirement: Сверка с базой по нескольким названиям При сверке плана с включёнными базами метаданных система SHALL искать по нескольким названиям в порядке убывания силы ключа: сначала по `original_title`, затем по локализованному `title`, затем по `provider_hint`. Поиск SHALL останавливаться, как только очередной запрос дал единичный сильный матч (ровно один кандидат с совпадением названия и года). Запрос с названием, нормализованно совпадающим с уже выполненным, система SHALL пропускать, чтобы не обращаться к базе повторно с тем же ключом. Кандидаты для ручного выбора в review система SHALL собирать из всех выполненных заходов с дедупликацией по `provider:id` и общим потолком. #### Scenario: Иностранный фильм находится по оригинальному названию - **GIVEN** план с `title` «Тёмный рыцарь», `original_title` «The Dark Knight», год 2008 - **WHEN** выполняется сверка с базой - **THEN** первый запрос идёт по «The Dark Knight» - **AND** при единичном сильном матче дальнейшие запросы (по `title`, `provider_hint`) не выполняются #### Scenario: Фолбэк на локализованное название - **GIVEN** план, для которого запрос по `original_title` не дал единичного сильного матча - **WHEN** продолжается сверка - **THEN** выполняется запрос по локализованному `title` - **AND** при отсутствии матча и там — запрос по `provider_hint` #### Scenario: Дублирующий запрос пропускается - **GIVEN** план, у которого `original_title` нормализованно совпадает с `title` - **WHEN** выполняется сверка - **THEN** база запрашивается этим названием один раз, повторный заход по `title` не делается ### Requirement: Подтверждение матча и каноническое имя При единичном сильном матче система SHALL брать из записи базы официальный `provider` (`tmdb`|`tvdb`|`tvmaze`) и `provider_id`, а также каноническое название и год, и подменять ими соответствующие поля плана (для сериала — с учётом внешнего тега TVDB/IMDb из `externals`, идущего в имя папки). Матч SHALL считаться подтверждённым только при ровно одном сильном кандидате; при нуле или нескольких кандидатах подтверждённого матча быть SHALL NOT (авто-раскладка не разрешается, кандидаты уходят в review). Работа с базами опциональна: при выключенных базах сверка не выполняется и подтверждённого матча нет. #### Scenario: Единичный матч даёт id и каноническое имя - **GIVEN** поиск вернул ровно одного сильного кандидата TMDB для фильма - **WHEN** матч подтверждается - **THEN** план получает `provider`=`tmdb`, `provider_id`, каноническое название и год #### Scenario: Несколько кандидатов — матч не подтверждён - **GIVEN** поиск вернул более одного подходящего кандидата - **WHEN** оценивается матч - **THEN** подтверждённого матча нет, кандидаты собираются для выбора в review ### Requirement: Локаль запроса к TMDB Запрос поиска к TMDB SHALL передавать параметр `language`, по умолчанию `ru-RU`, со значением, настраиваемым конфигом `[metadata.tmdb].language`. Это влияет только на локализованное поле `Title`/`Name`; поле `original_title`/`original_name` остаётся на языке оригинала, поэтому оригинальная сторона сравнения не затрагивается. #### Scenario: Локализованный заголовок приходит по-русски - **GIVEN** TMDB включён, `language` не задан в конфиге - **WHEN** выполняется поиск фильма с русской локализацией - **THEN** запрос содержит `language=ru-RU` - **AND** в кандидате `Title` приходит на русском, а `OriginalTitle` — на языке оригинала ### Requirement: Нормализация названий при сравнении Нормализация названий для гейта сильного матча SHALL сводить букву `ё` к `е`, чтобы написания, различающиеся только `ё`/`е`, считались одним названием. #### Scenario: «Тёмный» и «Темный» совпадают - **GIVEN** план с названием «Тёмный рыцарь» и кандидат базы «Темный рыцарь» - **WHEN** сравниваются нормализованные названия - **THEN** они считаются совпадающими ### Requirement: Кандидат несёт URL для внешней проверки Каждый кандидат внешней базы метаданных (`metadata.Candidate`) SHALL нести поле `URL` — ссылку на страницу элемента (фильма/сериала) на сайте провайдера. URL SHALL формироваться клиентом провайдера при поиске (`Search`) и сохраняться в таблице `metadata_candidate`. Отображение этой ссылки на экране ревью — забота `review`/`web-ui`, не данного требования. Формат URL для каждого провайдера: - **TMDB**: `https://www.themoviedb.org/movie/{id}` (фильм) или `https://www.themoviedb.org/tv/{id}` (сериал) — тип контента известен из запроса `Query.Type` - **TVDB**: `https://www.thetvdb.com/dereferrer/movie/{id}` (фильм) или `https://www.thetvdb.com/dereferrer/series/{id}` (сериал) — тип контента известен из запроса `Query.Type` - **TVMaze**: `https://www.tvmaze.com/shows/{id}` — URL SHALL использовать нативный id TVMaze, а не внешний тег (TVDB/IMDb), чтобы ссылка вела на TVMaze-страницу #### Scenario: Кандидат TMDB с корректной ссылкой - **GIVEN** TMDB найден кандидат-фильм с id `603` («Матрица») - **WHEN** клиент TMDB формирует Candidate - **THEN** `URL` = `https://www.themoviedb.org/movie/603` #### Scenario: Кандидат TVMaze с нативной ссылкой - **GIVEN** TVMaze найден сериал с id `169` («Фарго»), внешний тег — TVDB id `269613` - **WHEN** клиент TVMaze формирует Candidate - **THEN** `URL` = `https://www.tvmaze.com/shows/169` - **AND** `TagProvider`/`TagID` остаются `tvdb`/`269613` (тег папки Jellyfin не меняется) #### Scenario: URL сохраняется в БД - **GIVEN** результат поиска с кандидатами - **WHEN** кандидаты сохраняются в таблицу `metadata_candidate` - **THEN** значение `url` SHALL быть записано в колонку `url` - **AND** при последующей загрузке данных ревью url доступен без повторной генерации