Ingest: контекст распознавания из полей magnet-ссылки
Синтезируем контекст из полей самой magnet-ссылки (dn, xl, tr/xs, kt) без сети и дополняем им текст от транспорта: пользовательский текст первым, при пустом — синтез единственный. Обогащённый контекст идёт только в download.Context (его читают recognition и веб-UI); вход namer и отображаемое имя не меняются — строки-факты (Размер:/Трекер:) в display_name не текут. - magnet.Info: поля ExactLength/Sources/Keywords + Info.Context() (синтез, отсев dn-заглушек *-topic-<id>, домен трекера, человекочитаемый размер) - Parse устойчив к ссылке в процент-кодировке (разовый QueryUnescape) - ingest: mergeContext → download.Context, namer на сыром req.Context - Влита дельта capability ingest в openspec/specs, change заархивирован Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-07-07
|
||||
@@ -0,0 +1,154 @@
|
||||
## Context
|
||||
|
||||
Ingest (`internal/ingest/ingest.go`) сейчас принимает `Request{Source,
|
||||
Context}`, где `Context` — опциональный текст от транспорта (Telegram-парсер
|
||||
чистит пересланное сообщение бота; HTTP-форма отдаёт поле `context`). Из
|
||||
magnet парсер (`internal/magnet/magnet.go`) достаёт только `xt` (хеши), `dn`,
|
||||
`tr`. `req.Context` кладётся в `download.Context` и подаётся в
|
||||
`namer.DeriveName(ctx, req.Context, info.DisplayName)` — `dn` идёт отдельной
|
||||
«подсказкой». Важно: `dn` в `download.Context` **не** попадает, поэтому
|
||||
recognition его сейчас не видит вовсе.
|
||||
|
||||
`download.Context` имеет двух потребителей: recognition-промпт
|
||||
(`internal/recognize/prompt.go:87`) и веб-UI страницы загрузки
|
||||
(`internal/httpapi/download.go:100`) — синтез станет виден обоим.
|
||||
|
||||
При приёме голого magnet без текста `download.Context` почти пуст, и
|
||||
recognition теряет дешёвый сигнал (имя релиза, размер, происхождение), который
|
||||
физически лежит в самой ссылке.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Выжать в контекст распознавания максимум из полей magnet (`dn`, `xl`,
|
||||
`tr`/`xs`, `kt`) без сетевых запросов.
|
||||
- Дополнять этим контекстом пользовательский текст (не терять его, ставить
|
||||
первым), а при пустом тексте — синтезировать контекст целиком.
|
||||
- Обогащённый контекст идёт **только в `download.Context`** (recognition +
|
||||
UI). Вход namer и отображаемое имя не меняем.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- Дообогащение со страницы трекера (`*-topic-<id>` → HTTP). Отдельная задача.
|
||||
- Изменение схемы БД, API транспортов, сигнатуры `naming.DeriveName`.
|
||||
- Изменение вывода отображаемого имени: строки-факты (`Размер:`, `Трекер:`)
|
||||
не должны становиться `display_name`.
|
||||
- Использование дерева файлов торрента (оно приходит от qBittorrent позже, не
|
||||
из ссылки) — вне этого change.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Р1. Расширяем `magnet.Info`, синтез контекста — в `internal/magnet`
|
||||
|
||||
`Info` получает поля `ExactLength int64` (из `xl`), `Sources []string` (из
|
||||
`xs`), `Keywords []string` (из `kt`). `DisplayName`, `Trackers` уже есть.
|
||||
|
||||
Функцию синтеза человекочитаемого контекста (`func (Info) Context() string`
|
||||
или `SynthContext`) размещаем в пакете `magnet` — она чисто выводится из
|
||||
полей `Info`, легко юнит-тестируется в изоляции и не тянет зависимостей.
|
||||
Ingest лишь склеивает результат с `req.Context`.
|
||||
|
||||
_Альтернатива:_ синтез внутри ingest. Отвергнуто — раздувает ingest и хуже
|
||||
тестируется; знание формата полей magnet логичнее держать в `magnet`.
|
||||
|
||||
Значения полей (`dn`, `xs`, `kt`) приходят уже раскодированными: `url.Query()`
|
||||
снимает процент-кодировку — ручной urldecode не нужен. Дополнительно `Parse`
|
||||
устойчив к ссылке, скопированной целиком в процент-кодировке
|
||||
(`magnet%3A%3Fxt%3D…`, например вытащенной из другого URL): при неудаче
|
||||
прямого разбора делаем разовый `QueryUnescape` и пробуем снова. Unescape
|
||||
применяется только на этом фолбек-пути, поэтому значимый `+` в query обычной
|
||||
ссылки не затрагивается.
|
||||
|
||||
### Р2. Формат синтезированного контекста — строки-факты
|
||||
|
||||
Синтез собирает набор коротких строк (по одной на факт) и склеивает через
|
||||
`\n`, в духе того, что уже кладёт Telegram-парсер:
|
||||
|
||||
```
|
||||
<dn как есть, если не заглушка>
|
||||
Размер: ≈ 2.1 GiB
|
||||
Трекер: rutracker.org
|
||||
Ключевые слова: <kt>
|
||||
```
|
||||
|
||||
Это дружелюбно и к LLM (namer/recognition), и к алгоритмическому фолбеку
|
||||
namer, который берёт «первую содержательную строку» — поэтому строка `dn`
|
||||
идёт первой. Точные ярлыки строк уточняются при apply; спека фиксирует состав,
|
||||
не формулировки.
|
||||
|
||||
### Р3. Слияние — «дополняет всегда», результат только в `download.Context`
|
||||
|
||||
Итоговый контекст = `join(nonEmpty(userText, synthFromMagnet))`. Если
|
||||
`userText` пуст — остаётся только синтез; если синтез пуст — только текст;
|
||||
если пусты оба — пустая строка (приём штатно проходит с пустым контекстом,
|
||||
как сейчас). Пользовательский текст идёт **первым** (он содержательнее).
|
||||
|
||||
Результат кладётся **только** в `download.Context` (его читают recognition и
|
||||
UI). В namer он **не** передаётся — см. Р5.
|
||||
|
||||
_Альтернатива:_ синтез только при пустом тексте. Отвергнуто пользователем —
|
||||
теряем размер/происхождение, когда текст есть, но беден.
|
||||
|
||||
### Р4. Отсев заглушки `dn`
|
||||
|
||||
`dn` вида `*-topic-<id>` (частый случай рутрекера: `dn=rutracker-topic-6514485`)
|
||||
как строку-название не берём — это не имя, а идентификатор темы. Детектим
|
||||
простым паттерном (`(?i)-topic-\w+$` / `^\w+-topic-`). Остальные поля (размер,
|
||||
домен) синтезируем в любом случае. Домен трекера при этом всё равно
|
||||
сообщит происхождение (`rutracker.org`).
|
||||
|
||||
### Р5. Namer НЕ получает синтез — вход именования не меняем
|
||||
|
||||
`namer.DeriveName(ctx, req.Context, info.DisplayName)` остаётся как сейчас:
|
||||
пользовательский текст + `dn`-подсказка. Синтезированные строки-факты в namer
|
||||
**не** попадают.
|
||||
|
||||
_Почему:_ фолбек namer (`fallbackName` → `firstMeaningfulLine`) берёт первую
|
||||
содержательную строку контекста как название. Если подать туда синтез, то на
|
||||
самом частом сценарии (голый rutracker-magnet: `dn=*-topic-<id>` — заглушка,
|
||||
`xl`/`kt` нет) первой строкой окажется `Трекер: t-ru.org`, и она станет и
|
||||
`qBit rename`, и `download.display_name` (заголовок карточки в UI) — регресс
|
||||
против нынешнего `rutracker-topic-<id>`. Значимое имя namer и так получает
|
||||
через `dn`-hint, а размер/домен для *имени* бесполезны. Поэтому синтез —
|
||||
только в `download.Context` для recognition; именование не трогаем.
|
||||
|
||||
_Альтернатива:_ учить `fallbackName` отбрасывать строки `^<Ярлык>:\s`.
|
||||
Отвергнуто — лишняя связанность namer с форматом синтеза; чище просто не
|
||||
подавать синтез в namer.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Синтетический контекст «зашумляет» вход recognition — размер/домен могут
|
||||
сбить LLM] → Факты подаём короткими помеченными строками (`Размер:`,
|
||||
`Трекер:`), отделимыми от названия; recognition уже толерантен к «грязному»
|
||||
контексту (Telegram-пересылки). Держим синтез лаконичным.
|
||||
- [Синтез виден в веб-UI (страница загрузки рендерит `download.Context`)] →
|
||||
Ожидаемо и полезно (пользователь видит, откуда взят контекст). Держим
|
||||
строки-факты короткими и человекочитаемыми ради этого же.
|
||||
- [Дубль факта: `xl` даёт `Размер:`, а в тексте Telegram размер уже есть] →
|
||||
На практике редко (реальные rutracker-magnet'ы `xl` не несут). Дедупликацию
|
||||
не делаем — recognition толерантен к повтору; помечаем как известный
|
||||
трейд-офф.
|
||||
- [Ложное срабатывание отсева `dn`-заглушки на реальном имени с «-topic-»] →
|
||||
Паттерн якорим на `-topic-<токен>` в начале/конце строки; при сомнении
|
||||
трактуем консервативно (лучше включить лишнее имя, чем потерять). Покрываем
|
||||
тестом на `rutracker-topic-*` и на обычное имя.
|
||||
- [`xl` в разных единицах/формах] → По спецификации magnet `xl` — байты
|
||||
(десятичное целое). Парсим строго; при ошибке поле пропускаем (best-effort,
|
||||
приём не валим).
|
||||
- [Разные транспорты дублируют логику склейки] → Склейку делаем один раз в
|
||||
ingest (общий use-case для всех транспортов), транспорты не трогаем.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Изменение аддитивное и обратносовместимое: новые поля `Info` опциональны,
|
||||
контекст лишь обогащается. Схема БД не меняется, миграций нет. Откат —
|
||||
ревертом кода; уже созданные `download.Context` остаются валидными.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Точные ярлыки строк-фактов (`Размер:` vs `size ~`) и локаль размера —
|
||||
решаем при apply, на спеку не влияет.
|
||||
- Стоит ли нормализовать домен трекера (убирать `www.`, `bt.`, `ann`-хосты) —
|
||||
мелочь реализации, вынесем в helper с тестом.
|
||||
@@ -0,0 +1,63 @@
|
||||
## Why
|
||||
|
||||
Приём по «голому» magnet (без сопроводительного текста бота) уже проходит, но
|
||||
контекст распознавания при этом пуст: `download.Context` уходит в recognition
|
||||
почти пустым, и LLM-матч работает почти вслепую до докачки метаданных
|
||||
qBittorrent. При этом сама magnet-ссылка несёт полезные поля (имя релиза `dn`,
|
||||
размер `xl`, трекеры `tr`/источники `xs`), которые сейчас в контекст не
|
||||
попадают: парсим лишь `xt`, `dn`, `tr`, причём `dn` идёт только подсказкой в
|
||||
namer и в `download.Context` (а значит и в recognition) не сохраняется. Выжав
|
||||
эти поля в контекст распознавания, мы даём recognition реальный сигнал даже
|
||||
когда пользователь прислал один magnet.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Парсер `internal/magnet` извлекает дополнительные поля ссылки: `xl` (размер
|
||||
в байтах), `xs` (exact source), `kt` (keywords). `dn` и `tr` уже парсятся.
|
||||
- Ingest **синтезирует текст контекста из полей magnet** и **дополняет** им
|
||||
контекст, пришедший из транспорта: факты из полей добавляются к
|
||||
пользовательскому тексту (пользовательский — первым), а при пустом тексте
|
||||
становятся единственным контекстом. Синтез — из самой ссылки, без сетевых
|
||||
запросов.
|
||||
- В синтез входят: имя релиза (`dn`, если это содержательное имя, а не
|
||||
заглушка-идентификатор вида `*-topic-<id>`), размер (человекочитаемо из
|
||||
`xl`), происхождение по домену трекера/источника (`tr`/`xs`) как слабый
|
||||
сигнал языка/типа, ключевые слова (`kt`).
|
||||
- Обогащённый контекст сохраняется в `download.Context` — его читают
|
||||
**recognition** (LLM-промпт) и **веб-UI** (страница загрузки). **Вывод
|
||||
отображаемого имени (namer) не меняется**: он по-прежнему получает
|
||||
пользовательский текст и `dn`-подсказку, а помеченные строки-факты
|
||||
(`Размер:`, `Трекер:`) в имя не попадают.
|
||||
- Явно фиксируем поддержку приёма **только по magnet** (пустой текст) как
|
||||
штатный сценарий во всех транспортах.
|
||||
|
||||
Вне объёма (сознательно): дообогащение со страницы трекера
|
||||
(`*-topic-<id>` → HTTP-запрос) — отдельная будущая задача; контекст берём
|
||||
только из полей ссылки. Поведение namer/отображаемого имени не меняем.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
_Нет._ Изменение укладывается в существующую capability `ingest`.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `ingest`: добавляется требование «синтез контекста распознавания из полей
|
||||
magnet и дополнение им контекста транспорта» (обогащённый контекст → только
|
||||
`download.Context`; отображаемое имя не затрагивается) и явно фиксируется
|
||||
приём при пустом тексте. Существующие требования по выводу отображаемого
|
||||
имени остаются **без изменений** (namer получает тот же вход).
|
||||
|
||||
## Impact
|
||||
|
||||
- Код: `internal/magnet` (новые поля `Info` + синтез контекста),
|
||||
`internal/ingest` (слияние `req.Context` + синтез в `download.Context` до
|
||||
`CreateDownload`). `internal/naming` **не затрагивается** — вход namer тот
|
||||
же (`req.Context`, `dn`-hint).
|
||||
- Данные: `download.Context` начинает содержать синтезированный текст —
|
||||
влияет на вход recognition и на отображение контекста в веб-UI; схема БД не
|
||||
меняется.
|
||||
- Транспорты (`httpapi`, `tgbot`): поведение при пустом контексте становится
|
||||
штатным; изменений API не требуется.
|
||||
- Внешние системы: без новых зависимостей и сетевых вызовов.
|
||||
@@ -0,0 +1,95 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Синтез контекста распознавания из полей magnet
|
||||
|
||||
При приёме система SHALL извлекать из полей magnet-ссылки дополнительный
|
||||
контекст и **дополнять** им контекст, пришедший из транспорта: факты из полей
|
||||
SHALL добавляться к пользовательскому тексту (пользовательский текст —
|
||||
первым), а при пустом тексте SHALL становиться единственным контекстом.
|
||||
Синтез SHALL выполняться только из самой ссылки, без сетевых запросов.
|
||||
|
||||
В синтез SHALL включаться следующие поля, когда они присутствуют:
|
||||
|
||||
- `dn` (display name) — как строка названия релиза, **если** это содержательное
|
||||
имя, а не заглушка-идентификатор вида `*-topic-<id>` (например
|
||||
`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-<id>` (например `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-<id>` и трекером, без
|
||||
пользовательского текста
|
||||
- **WHEN** выполняется приём
|
||||
- **THEN** `download.display_name` не выводится из строк-фактов (не равен
|
||||
`Трекер: …`/`Размер: …`)
|
||||
- **AND** отображаемое имя определяется прежним путём (подсказка `dn` или его
|
||||
отсутствие → без `rename`)
|
||||
|
||||
#### Scenario: Синтез без сети
|
||||
|
||||
- **WHEN** выполняется извлечение контекста из полей magnet
|
||||
- **THEN** не делается ни одного сетевого запроса (только разбор строки
|
||||
ссылки)
|
||||
|
||||
#### Scenario: Полей для контекста нет
|
||||
|
||||
- **WHEN** текст пользователя пуст и ни одно пригодное поле magnet не даёт
|
||||
содержательного контекста (нет `dn`-имени, `xl`, распознаваемого домена,
|
||||
`kt`)
|
||||
- **THEN** `download.Context` остаётся пустым
|
||||
- **AND** приём проходит штатно (пустой контекст допустим)
|
||||
@@ -0,0 +1,44 @@
|
||||
## 1. Парсинг полей magnet
|
||||
|
||||
- [x] 1.1 Расширить `magnet.Info` полями `ExactLength int64` (из `xl`),
|
||||
`Sources []string` (из `xs`), `Keywords []string` (из `kt`)
|
||||
- [x] 1.2 В `Parse` заполнить новые поля из `vals`; `xl` парсить строго как
|
||||
десятичное целое (байты), при ошибке — оставлять 0 (best-effort)
|
||||
- [x] 1.3 Тесты парсинга: magnet с `xl`/`xs`/`kt`, гибридный, невалидный `xl`,
|
||||
отсутствие полей
|
||||
- [x] 1.4 Устойчивость `Parse` к ссылке, скопированной целиком в
|
||||
процент-кодировке (`magnet%3A%3F…`): разовый `QueryUnescape` на фолбек-пути +
|
||||
тест; тест на реальной рутрекер-ссылке (dn с кириллицей → Context)
|
||||
|
||||
## 2. Синтез контекста из полей
|
||||
|
||||
- [x] 2.1 Реализовать `func (Info) Context() string` (или `SynthContext`) в
|
||||
пакете `magnet`: строки-факты через `\n` — название (`dn`, если не заглушка),
|
||||
`Размер:` из `xl` (человекочитаемо), `Трекер:`/происхождение из домена
|
||||
`tr`/`xs`, `Ключевые слова:` из `kt`
|
||||
- [x] 2.2 Детектор заглушки `dn` вида `*-topic-<id>` (не включать как название)
|
||||
- [x] 2.3 Helper нормализации домена трекера/источника из URL (`tr`/`xs`)
|
||||
- [x] 2.4 Тесты синтеза: содержательный `dn`; `dn`-заглушка `rutracker-topic-*`;
|
||||
только размер; только домен; все поля вместе; пустой `Info` → пустая строка;
|
||||
проверить отсутствие сетевых вызовов (чистая функция)
|
||||
|
||||
## 3. Слияние в ingest
|
||||
|
||||
- [x] 3.1 В `Ingest` собрать `enrichedContext = join(nonEmpty(req.Context,
|
||||
info.Context()))` — пользовательский текст первым, синтез следом
|
||||
- [x] 3.2 Класть `enrichedContext` **только** в `download.Context` (вместо
|
||||
сырого `req.Context`); вызов `namer.DeriveName(ctx, req.Context,
|
||||
info.DisplayName)` оставить как есть — namer синтез НЕ получает
|
||||
- [x] 3.3 Тесты ingest: magnet-only (пустой `req.Context`) → `download.Context`
|
||||
синтезирован; непустой текст + поля → оба присутствуют, текст первым; пустой
|
||||
текст и бедный magnet → пустой контекст, приём проходит
|
||||
- [x] 3.4 Регресс-тест: голый rutracker-magnet (`dn=rutracker-topic-<id>`,
|
||||
только `tr`, без текста) → `download.display_name` НЕ равен `Трекер: …`
|
||||
(именование не деградирует), а `download.Context` содержит домен трекера
|
||||
|
||||
## 4. Транспорты и проверка
|
||||
|
||||
- [x] 4.1 Убедиться, что приём голого magnet (пустой контекст) штатно проходит
|
||||
в `httpapi` и `tgbot` (при необходимости — тест на пустой `context`)
|
||||
- [x] 4.2 `task test` и `task lint` зелёные
|
||||
- [x] 4.3 `openspec validate magnet-context-extraction --strict` проходит
|
||||
@@ -210,3 +210,97 @@ btih (v1), и btmh (v2); `kind` определяется по длине hex (40
|
||||
- **THEN** переход отклоняется с пояснением, #1 остаётся в `failed`
|
||||
- **AND** активной по `h` остаётся #2
|
||||
|
||||
### Requirement: Синтез контекста распознавания из полей magnet
|
||||
|
||||
При приёме система SHALL извлекать из полей magnet-ссылки дополнительный
|
||||
контекст и **дополнять** им контекст, пришедший из транспорта: факты из полей
|
||||
SHALL добавляться к пользовательскому тексту (пользовательский текст —
|
||||
первым), а при пустом тексте SHALL становиться единственным контекстом.
|
||||
Синтез SHALL выполняться только из самой ссылки, без сетевых запросов.
|
||||
|
||||
В синтез SHALL включаться следующие поля, когда они присутствуют:
|
||||
|
||||
- `dn` (display name) — как строка названия релиза, **если** это содержательное
|
||||
имя, а не заглушка-идентификатор вида `*-topic-<id>` (например
|
||||
`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-<id>` (например `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-<id>` и трекером, без
|
||||
пользовательского текста
|
||||
- **WHEN** выполняется приём
|
||||
- **THEN** `download.display_name` не выводится из строк-фактов (не равен
|
||||
`Трекер: …`/`Размер: …`)
|
||||
- **AND** отображаемое имя определяется прежним путём (подсказка `dn` или его
|
||||
отсутствие → без `rename`)
|
||||
|
||||
#### Scenario: Синтез без сети
|
||||
|
||||
- **WHEN** выполняется извлечение контекста из полей magnet
|
||||
- **THEN** не делается ни одного сетевого запроса (только разбор строки
|
||||
ссылки)
|
||||
|
||||
#### Scenario: Полей для контекста нет
|
||||
|
||||
- **WHEN** текст пользователя пуст и ни одно пригодное поле magnet не даёт
|
||||
содержательного контекста (нет `dn`-имени, `xl`, распознаваемого домена,
|
||||
`kt`)
|
||||
- **THEN** `download.Context` остаётся пустым
|
||||
- **AND** приём проходит штатно (пустой контекст допустим)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user