Files
jellybit/openspec/changes/archive/2026-08-07-tvdb-title-locale/specs/metadata-match/spec.md
T
av fdbc781197 metadata: TVDB отдаёт локализованное название и оригинал
- локаль из [general].language применяется при разборе ответа /search, а в
  запрос не уходит: параметр language у TVDB — фильтр выдачи, а не селектор
  перевода (ADR-2026-08-07)
- Title берётся из блока translations с тотальным фолбэком на primary name,
  OriginalTitle — из primary name; форма ответа сверена по документации и
  живым прогоном не подтверждена (docs/research)
- неожиданная форма ответа даёт WARN: признак — отсутствие во всей выдаче
  ключей языка ожидаемого вида, а не неудача разбора блока
2026-08-07 15:17:05 +03:00

121 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## ADDED Requirements
### Requirement: Локализованное название кандидата TVDB
Кандидат TVDB SHALL нести локализованное название в поле `Title` и название на
языке оригинала в поле `OriginalTitle`. Язык локализации задаёт та же глобальная
настройка `language`, что и для TMDB, — правило единственного источника языка
живёт в требовании «Локаль запроса к TMDB» и здесь не переписывается.
Источник локализованного названия — блок переводов в ответе поиска TVDB (карта
«код языка → название»). `OriginalTitle` SHALL брать primary name записи — это
название на языке оригинала и ось сравнения при матче.
Фолбэк SHALL быть тотальным: если перевода на нужный язык нет, его значение
пусто после обрезки пробелов или блока переводов нет вовсе, `Title` SHALL быть
равен primary name. Пустого `Title` при непустом primary name быть SHALL NOT.
В `Title` SHALL попадать значение перевода после обрезки пробелов.
Ключ языка SHALL искаться регистронезависимо: молчаливый фолбэк из-за регистра
ключа неотличим от отсутствия перевода и в эксплуатации не диагностируется. Если
условию отвечает несколько ключей, выбор SHALL быть детерминированным — один и
тот же ответ провайдера обязан давать один и тот же `Title`.
Локаль TVDB SHALL влиять только на разбор ответа и SHALL NOT сужать выдачу
поиска: параметр языка в запрос поиска не передаётся. Причина — в
`docs/research/tvdb-search-translations.md`; здесь заказано поведение, а не
устройство чужого API. Тем самым разница с TMDB намеренна: у TMDB локаль едет в
запрос, у TVDB читается из ответа.
Негодная форма блока переводов (блок пришёл не картой, значения не строки) SHALL
приводить к тому же тотальному фолбэку, а не проваливать разбор ответа поиска
целиком: ответ метабазы — недоверенный вход.
Подозрение на иную форму ответа SHALL оставлять диагностический след. Признаком
служит **отсутствие во всей выдаче хотя бы одного ключа языка ожидаемого вида**,
а не неудача разбора блока: `null`, пустая карта и словарь кодов другого вида
разбираются без ошибки и потому признаком быть SHALL NOT. Штатное «перевода на
этот язык нет» (ключи ожидаемого вида есть, нужного среди них нет) следа
оставлять SHALL NOT — иначе сигнал неотличим от рутины. Уровень следа задают
конвенции логирования проекта и здесь не нормируются; требуется наблюдаемость,
а не конкретный уровень.
Настоящее требование не изменяет условий подтверждения матча — они заданы
требованиями «Подтверждение матча и каноническое имя» и «Безгодовой второй
проход сверки как fallback». Заполнение `OriginalTitle` расширяет множество
названий кандидата, по которым идёт сравнение, с одного до двух. Исход гейта при
этом монотонным SHALL NOT считаться: там, где сильный кандидат был один, их
может стать двое, и тогда подтверждённого матча нет, а записи уходят кандидатами
в review — по действующему требованию, без исключений для TVDB.
#### Scenario: Перевод на язык настройки есть
- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** поиск возвращает запись с primary name `哪吒之魔童降世` и переводом `rus` = «Нэчжа»
- **THEN** `Candidate.Title` = «Нэчжа»
- **AND** `Candidate.OriginalTitle` = `哪吒之魔童降世`
#### Scenario: Перевода на язык настройки нет — фолбэк на оригинал
- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** поиск возвращает запись с primary name `Fargo` и переводами без ключа `rus`
- **THEN** `Candidate.Title` = `Fargo`
- **AND** `Candidate.OriginalTitle` = `Fargo`
#### Scenario: Перевод есть, но пустой
- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** поиск возвращает запись с primary name `Fargo` и переводом `rus` из одних пробелов
- **THEN** `Candidate.Title` = `Fargo`
- **AND** `Candidate.OriginalTitle` = `Fargo`
#### Scenario: Язык по умолчанию — английский
- **GIVEN** TVDB включён, глобальный `language` не задан в конфиге
- **WHEN** поиск возвращает запись с переводами `eng` и `rus`
- **THEN** `Candidate.Title` берётся из перевода `eng`
#### Scenario: Ключ перевода в другом регистре
- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** поиск возвращает запись с переводом под ключом `RUS` вместо `rus`
- **THEN** `Candidate.Title` берётся из этого перевода
#### Scenario: Блок переводов отсутствует или пришёл негодной формой
- **GIVEN** TVDB включён с любым значением `language`
- **WHEN** поиск возвращает записи без блока переводов либо с блоком, который не разбирается картой
- **THEN** `Candidate.Title` = primary name записи
- **AND** `Candidate.OriginalTitle` = primary name записи
- **AND** разбор ответа поиска не проваливается, кандидаты возвращаются
- **AND** остаётся диагностический след о подозрении на иную форму ответа
#### Scenario: Коды языка не того вида — след остаётся
- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** вся выдача поиска несёт блоки переводов с ключами другого вида (например, двухбуквенными), либо `null`, либо пустые
- **THEN** `Candidate.Title` = primary name у каждой записи
- **AND** остаётся диагностический след о подозрении на иную форму ответа
#### Scenario: Перевода на язык нет — следа не остаётся
- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** выдача несёт блоки переводов с ключами ожидаемого вида, но без нужного языка
- **THEN** `Candidate.Title` = primary name у каждой записи
- **AND** диагностического следа не остаётся: это штатный исход, а не подозрение
#### Scenario: Запрос поиска не сужается языком
- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** выполняется поиск
- **THEN** строка запроса поиска не содержит параметра языка
- **AND** строка запроса совпадает с той, что уходит при `language` = `en`
#### Scenario: Перевод сделал сильных кандидатов двумя — матч не подтверждён
- **GIVEN** план с `title` «Нэчжа» и `original_title` `Ne Zha`, год известен
- **WHEN** поиск TVDB возвращает две записи в пределах года ±1: одну с primary
name `哪吒之魔童降世` и переводом `rus` «Нэчжа», другую с primary name `Ne Zha`
- **THEN** гейт даёт двух сильных кандидатов вместо одного
- **AND** подтверждённого матча нет, обе записи уходят кандидатами в review