Files
jellybit/openspec/specs/recognition/spec.md
T
avandClaude dfa182a5a9 Ссылки на внешние базы в ревью (recognition)
Каждый кандидат внешней базы метаданных (TMDB/TVDB/TVMaze) теперь несёт
URL на страницу элемента — при ревью можно кликнуть и проверить матч.
URL формируется клиентом провайдера при поиске, сохраняется в БД
(metadata_candidate.url) и отображается ссылкой в веб-интерфейсе.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 20:09:20 +03:00

145 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.
# 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 доступен без повторной
генерации