Каждый кандидат внешней базы метаданных (TMDB/TVDB/TVMaze) теперь несёт URL на страницу элемента — при ревью можно кликнуть и проверить матч. URL формируется клиентом провайдера при поиске, сохраняется в БД (metadata_candidate.url) и отображается ссылкой в веб-интерфейсе. Co-Authored-By: Claude <noreply@anthropic.com>
145 lines
9.4 KiB
Markdown
145 lines
9.4 KiB
Markdown
# recognition Specification
|
||
|
||
## Purpose
|
||
|
||
Распознавание: сопоставление загрузки с конкретным фильмом/сериалом во
|
||
включённых базах метаданных. Capability описывает контракт LLM на названия,
|
||
порядок и нормализацию сверки по нескольким названиям, локаль запроса к TMDB
|
||
и сбор кандидатов для ручного выбора в review.
|
||
|
||
## 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: Контракт LLM на оригинальное и локализованное названия
|
||
|
||
Промпт распознавания SHALL требовать от модели всегда заполнять и `title`, и
|
||
`original_title`. Если отдельного оригинального названия нет или контент
|
||
российского происхождения, модель SHALL дублировать `title` в
|
||
`original_title`. При неуверенности в оригинальном названии модель SHALL
|
||
дублировать `title`, а не выдумывать название (защита от ложного авто-матча).
|
||
|
||
Разбор ответа SHALL оставаться устойчивым к пустому `original_title`: пустое
|
||
значение не отбраковывается и не вызывает correction-ретрай; сверка
|
||
gracefully использует доступные названия.
|
||
|
||
#### Scenario: Российский фильм — дублирование
|
||
|
||
- **GIVEN** раздача российского фильма без отдельного оригинального названия
|
||
- **WHEN** модель возвращает план
|
||
- **THEN** `title` и `original_title` заполнены одинаковым каноническим названием
|
||
|
||
#### Scenario: Пустой original_title не ломает разбор
|
||
|
||
- **GIVEN** ответ модели с пустым `original_title`
|
||
- **WHEN** план разбирается
|
||
- **THEN** разбор успешен без correction-ретрая
|
||
- **AND** сверка использует `title` (и `provider_hint`)
|
||
|
||
### 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`. На странице ревью
|
||
URL SHALL отображаться кликабельной ссылкой, открывающейся в новой вкладке
|
||
браузера.
|
||
|
||
Формат URL для каждого провайдера:
|
||
|
||
- **TMDB**: `https://www.themoviedb.org/movie/{id}` (фильм) или
|
||
`https://www.themoviedb.org/tv/{id}` (сериал) — тип контента известен из
|
||
запроса `Query.Type`
|
||
- **TVDB**: `https://www.thetvdb.com/dereferrer/series/{id}`
|
||
- **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: Ссылка в интерфейсе ревью
|
||
|
||
- **GIVEN** загрузка в состоянии `review` с кандидатами, у которых заполнен `url`
|
||
- **WHEN** рендерится страница ревью
|
||
- **THEN** в таблице кандидатов каждый кандидат SHALL отображаться со
|
||
ссылкой на внешний сайт
|
||
- **AND** ссылка открывается в новой вкладке (`target="_blank"`)
|
||
- **AND** текстом ссылки служит провайдер или сокращённый url
|
||
|
||
#### Scenario: URL сохраняется в БД
|
||
|
||
- **GIVEN** результат поиска с кандидатами
|
||
- **WHEN** кандидаты сохраняются в таблицу `metadata_candidate`
|
||
- **THEN** значение `url` SHALL быть записано в колонку `url`
|
||
- **AND** при последующей загрузке данных ревью url доступен без повторной
|
||
генерации
|
||
|