## ADDED Requirements ### Requirement: Синтез контекста распознавания из полей magnet При приёме система SHALL извлекать из полей magnet-ссылки дополнительный контекст и **дополнять** им контекст, пришедший из транспорта: факты из полей SHALL добавляться к пользовательскому тексту (пользовательский текст — первым), а при пустом тексте SHALL становиться единственным контекстом. Синтез SHALL выполняться только из самой ссылки, без сетевых запросов. В синтез SHALL включаться следующие поля, когда они присутствуют: - `dn` (display name) — как строка названия релиза, **если** это содержательное имя, а не заглушка-идентификатор вида `*-topic-` (например `rutracker-topic-6514485`); такие заглушки в контекст-название включаться SHALL NOT. - `xl` (exact length) — как человекочитаемый размер (например «≈ 2.1 GiB»); нечисловое/некорректное значение игнорируется. - `tr`/`xs` (трекеры / exact source) — как сигнал происхождения по домену (хост трекера/источника), помогающий определить язык и тип контента. - `kt` (keyword topic) — как ключевые слова. Обогащённый контекст система SHALL сохранять в `download.Context` — его читают recognition (LLM-промпт) и веб-UI (страница загрузки). Синтез и слияние SHALL выполняться до создания загрузки. Синтезированные строки-факты (размер, домен трекера, ключевые слова) SHALL NOT влиять на вывод отображаемого имени (`download.display_name` / параметр `rename` qBittorrent): вход вывода имени остаётся прежним (пользовательский текст и подсказка `dn`), см. требование «Отображаемое имя торрента из контекста». Приём **только по magnet** (пустой текст контекста) SHALL быть штатным сценарием во всех транспортах (HTTP, Telegram, CLI). #### Scenario: dn — содержательное имя релиза - **WHEN** magnet содержит `dn` с релиз-именем (например `Dune.Part.Two.2024.2160p.BluRay`) - **THEN** это имя добавляется в `download.Context` - **AND** становится доступно recognition #### Scenario: dn — заглушка-идентификатор темы - **WHEN** `dn` имеет вид `*-topic-` (например `rutracker-topic-6514485`) - **THEN** система не включает его как строку-название в контекст - **AND** остальные поля magnet (размер, домен трекера) всё равно синтезируются #### Scenario: Размер из xl - **WHEN** magnet содержит корректный числовой `xl` - **THEN** в `download.Context` добавляется человекочитаемый размер загрузки #### Scenario: Происхождение по домену трекера - **WHEN** magnet содержит `tr` и/или `xs` с распознаваемым хостом - **THEN** в `download.Context` добавляется сигнал происхождения (домен), пригодный как подсказка языка/типа контента #### Scenario: Дополнение непустого пользовательского контекста - **WHEN** транспорт передал непустой текст контекста, а magnet несёт поля - **THEN** `download.Context` содержит и текст пользователя, и факты из полей magnet (текст пользователя не теряется) - **AND** текст пользователя идёт первым #### Scenario: Приём только по magnet - **WHEN** magnet принят с пустым текстом контекста - **THEN** приём проходит штатно - **AND** `download.Context` синтезируется из полей magnet и сохраняется #### Scenario: Строки-факты не становятся отображаемым именем - **GIVEN** голый magnet с заглушкой `dn=rutracker-topic-` и трекером, без пользовательского текста - **WHEN** выполняется приём - **THEN** `download.display_name` не выводится из строк-фактов (не равен `Трекер: …`/`Размер: …`) - **AND** отображаемое имя определяется прежним путём (подсказка `dn` или его отсутствие → без `rename`) #### Scenario: Синтез без сети - **WHEN** выполняется извлечение контекста из полей magnet - **THEN** не делается ни одного сетевого запроса (только разбор строки ссылки) #### Scenario: Полей для контекста нет - **WHEN** текст пользователя пуст и ни одно пригодное поле magnet не даёт содержательного контекста (нет `dn`-имени, `xl`, распознаваемого домена, `kt`) - **THEN** `download.Context` остаётся пустым - **AND** приём проходит штатно (пустой контекст допустим)