## ADDED Requirements ### Requirement: Отображаемое имя торрента из контекста При добавлении загрузки в qBittorrent система SHALL выводить из контекста загрузки человекочитаемое отображаемое имя и передавать его в qBittorrent (параметр `rename` API `/torrents/add`), чтобы задача в списке qBit не показывалась безликим `dn` magnet-ссылки. Имя SHALL быть коротким читаемым ярлыком (название, опционально режиссёр и год; для сериала — номер сезона, если он определён), а не куском сырого контекста. Имя SHALL очищаться от управляющих символов и переводов строк и SHALL обрезаться по ограничению длины. Вывод имени SHALL выполняться синхронно перед отдачей источника в qBittorrent (параметр `rename` действует только в момент добавления). Отображаемое имя SHALL влиять только на отображение в qBittorrent и SHALL NOT влиять на пути файлов на диске, распознавание или раскладку — реальные пути система по-прежнему читает из qBit API. #### Scenario: Имя из контекста передаётся в qBittorrent - **WHEN** загрузку добавляют с непустым контекстом, из которого удалось получить имя - **THEN** система передаёт это имя в qBittorrent в параметре `rename` - **AND** имя — короткий читаемый ярлык вида «название (режиссёр, год)», где режиссёр и год опциональны #### Scenario: Контекст пуст или имя не получено - **WHEN** контекста нет либо ни один способ вывода не дал непустого имени - **THEN** система добавляет загрузку без параметра `rename` - **AND** qBittorrent оставляет собственное имя (из `dn`/торрента) ### Requirement: Вывод имени через LLM со структурированным выводом Система SHALL строить отображаемое имя с помощью LLM (структурированный JSON-вывод), извлекая из контекста тип (movie/series), название, год, режиссёра и (для сериала) номер сезона. Год и режиссёр — опциональные поля. Название SHALL быть на русском языке для российского контента и на английском (оригинальном) — для остального. Система SHALL предпринять ограниченное число попыток получить от LLM валидный результат (корректный JSON с непустым названием); бюджет попыток — `[llm].max_retries` (по умолчанию 3). Транспортные ретраи провайдера LLM (сетевые сбои, 429, 5xx) в этот счёт не входят. Недоступность или ошибка LLM SHALL NOT прерывать приём загрузки: система переходит к алгоритмическому фолбеку. #### Scenario: LLM возвращает структурированное имя - **WHEN** LLM по контексту возвращает валидный JSON с непустым названием - **THEN** система формирует отображаемое имя из его полей (название, год, для сериала — сезон) #### Scenario: Российский контент — название на русском - **WHEN** контент распознан как российский - **THEN** в отображаемом имени используется русское название #### Scenario: Исчерпан бюджет попыток LLM - **WHEN** LLM за отведённые попытки (`[llm].max_retries`) не вернул валидный результат либо недоступен - **THEN** система не прерывает приём и переходит к алгоритмическому фолбеку ### Requirement: Алгоритмический фолбек вывода имени без сети При неудаче LLM система SHALL выводить имя алгоритмически, без сетевых запросов: брать первую содержательную строку контекста (без ссылок, команд бота и UI-мусора), отсекать технические характеристики, очищать и обрезать по длине. Если и фолбек не дал непустого имени, система SHALL добавить загрузку без параметра `rename`. #### Scenario: Фолбек извлекает имя из контекста - **WHEN** LLM недоступен или исчерпал попытки, а контекст содержательный - **THEN** система берёт первую содержательную строку контекста, отсекает технические характеристики и использует результат как отображаемое имя - **AND** при этом не делается ни одного сетевого запроса #### Scenario: Фолбек тоже пуст - **WHEN** ни LLM, ни алгоритмический фолбек не дали непустого имени - **THEN** система добавляет загрузку без параметра `rename`