Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
# Поиск и подключение каналов (Discovery) — дизайн
|
||||
|
||||
> Исторический документ (дизайн Discovery прототипа LeadRadar, 2026-09-04). Актуальное состояние — `docs/superpowers/STATUS.md` и `docs/technical/Техническая-документация-Дейл.md`.
|
||||
|
||||
Дата: 2026-09-04
|
||||
Статус: согласован с пользователем (правки от 2026-09-04 учтены)
|
||||
|
||||
## 1. Цель
|
||||
|
||||
Пользователь даёт системе «задание»: найти Telegram-каналы и группы, в которых мы ещё
|
||||
**не состоим**, по описанию цели (например, «вакансии и фриланс для разработки») и
|
||||
подключить их к мониторингу. Система сама ищет кандидатов, оценивает их (по метаданным,
|
||||
языку и содержанию сообщений) и показывает человеку список «на рассмотрение»; человек
|
||||
решает — вступить и мониторить или отклонить. Возможен режим авто-вступления в рамках
|
||||
суточных квот и с паузами против бана.
|
||||
|
||||
Ключевое правило: **источники, в которых мы уже состоим (вступили/мониторим), исключаются
|
||||
сразу и безусловно — независимо от запроса, ключей и настроек задачи.** Это глобальное
|
||||
правило системы: действует на всех этапах (поиск → оценка → вступление) и для всех задач.
|
||||
|
||||
## 2. Ограничения Telegram API (факты, на которых строится дизайн)
|
||||
|
||||
1. Глобального «поиска по критериям» в API нет. `contacts.search(q)` возвращает
|
||||
публичные каналы/группы/боты по **имени/username/запросу** — без фильтров по
|
||||
участникам, языку и содержимому. Всю дальнейшую фильтрацию делаем сами.
|
||||
2. Число участников/описание — через `channels.getFullChannel`. Для публичных каналов
|
||||
доступно без вступления; для групп часто доступно только членам.
|
||||
3. Чтение истории без вступления: публичные **каналы** — обычно можно; публичные
|
||||
**группы** — только если история открыта; иначе — только членам.
|
||||
4. Массовый поиск/чтение/вступления с юзер-аккаунта ограничены эмпирически — нужны
|
||||
квоты, паузы и обработка `FloodWaitError`.
|
||||
|
||||
## 3. Понятия
|
||||
|
||||
- **Задача (task)** — конфиг поиска: описание цели, ключи, фильтры, план, режим
|
||||
авто-вступления, статус/счётчики. Задач может быть несколько.
|
||||
- **Кандидат (candidate)** — найденный источник (канал/группа, для форумов — оценка по
|
||||
темам). Проходит стадии: `new → evaluated → review → joined | rejected`.
|
||||
- **Метки кандидата** — человекочитаемые пометки: «закрытая группа/канал», «форум»,
|
||||
«не прочитано», «участники не подтверждены», «язык не подтверждён», «есть проходные
|
||||
темы».
|
||||
- **Чёрный список** — источники, отклонённые пользователем; поиск их больше не
|
||||
возвращает (снимается вручную).
|
||||
- **BanGuard** — единый менеджер квот и пауз для всех действий discovery
|
||||
(search/read/join/leave), общий для задач.
|
||||
|
||||
## 4. Задача: конфигурация и правила создания
|
||||
|
||||
Поля задачи:
|
||||
|
||||
| Поле | Назначение | По умолчанию |
|
||||
| --- | --- | --- |
|
||||
| `name` | название задачи | — |
|
||||
| `description` | описание цели (что ищем) | — |
|
||||
| `keywords` | поисковые ключи (генерирует ИИ, редактируются перед стартом) | [] |
|
||||
| `minSubscribers` | минимум участников (0 = не важно) | 0 |
|
||||
| `lang` | язык источников (`ru` / `any`) | `ru` |
|
||||
| `threshold` | доля подходящих сообщений, % | 40 |
|
||||
| `sampleSize` | сколько сообщений смотреть при оценке | 10 |
|
||||
| `planJoins` | план вступлений N | 1..50 |
|
||||
| `autoJoin` | авто-вступление подходящих | false |
|
||||
| `status` | `draft → running → paused → done | failed` | draft |
|
||||
|
||||
Правила создания:
|
||||
|
||||
- **Бюджет планов:** сумма `planJoins` всех задач в статусе не `done/failed` + `planJoins`
|
||||
новой ≤ суточного лимита вступлений (по умолчанию 50). Задача с планом 50 не даёт
|
||||
создать другую; план 25 оставляет максимум 25.
|
||||
- Запуск возможен только после генерации/подтверждения ключей.
|
||||
- При редактировании активной задачи план нельзя увеличить сверх свободного бюджета.
|
||||
|
||||
## 5. Пайплайн поиска (каскад фильтров)
|
||||
|
||||
Выполняется фоновым воркером задачи строго через BanGuard (по одному действию, с паузами).
|
||||
|
||||
Для каждого кандидата фильтры идут **по нарастающей стоимости**; при первом «нет»
|
||||
источник пропускается и берётся следующий:
|
||||
|
||||
1. **Поиск** — `contacts.search` по каждому ключу (с паузами). Кандидаты
|
||||
дедуплицируются по `dialog_id`/username.
|
||||
2. **«Мы не состоим» — глобальный фильтр, применяется сразу и безусловно:** как только
|
||||
источник найден (независимо от запроса/ключей), он отбрасывается, если уже есть
|
||||
в `dialogs` (вступили/мониторим), в чёрном списке или уже обрабатывается/вступил/ждёт
|
||||
рассмотрения в другой задаче (глобальная дедупликация кандидатов). Остальные фильтры
|
||||
(участники/язык/контент) применяются уже после этого. Проверка повторяется
|
||||
непосредственно перед вступлением (между оценкой и join'ом кандидат мог быть добавлен
|
||||
вручную).
|
||||
3. **Число участников** — если `minSubscribers` задано:
|
||||
- значение получено и меньше минимума → пропуск;
|
||||
- значение получить не удалось → **не пропускаем**, ставим метку «участники не
|
||||
подтверждены».
|
||||
4. **Язык** — если `lang=ru`: по выборке сообщений эвристикой кириллицы (без ИИ);
|
||||
не удалось прочитать → метка «язык не подтверждён» (не пропуск).
|
||||
5. **Содержимое** — оценка выборки сообщений (см. §6).
|
||||
|
||||
Пометки «не подтверждено» — не ошибка, а сигнал человеку на экране рассмотрения.
|
||||
|
||||
## 6. Оценка содержимого (по темам, для форумов)
|
||||
|
||||
- **Что считается «подходящим сообщением»:** сообщение проходит те же правила, что в
|
||||
основной системе (этап 1 → ML → ИИ), но **профиль оценки = профиль задачи**
|
||||
(описание + ключи задачи), а не глобальные настройки дашборда. Оценка ничего не
|
||||
создаёт: ни карточек, ни очереди, ни обучения ML.
|
||||
- Если ИИ выключен — оценка локальным разбором/ML.
|
||||
- **Каналы:** читаем до `sampleSize` последних сообщений; доля подходящих ≥ `threshold`
|
||||
→ в «на рассмотрение».
|
||||
- **Открытые группы:** то же; чтение не удалось → «на рассмотрение» с меткой
|
||||
«открытая группа, не прочитана».
|
||||
- **Закрытые группы** (нашлись по ключам, история скрыта): сразу «на рассмотрение» с
|
||||
меткой «закрытая группа/канал» (+ метки неподтверждённых фильтров). Пользователь
|
||||
вступает сам.
|
||||
- **Форумы (группы с темами):** группа раскладывается по темам (`reply_to_top_id`):
|
||||
читаем выборку по активным темам, оценка считается **по темам** («тема: подходит
|
||||
X из N»). Группа подходящая, если есть ≥1 проходная тема. В превью — список тем с
|
||||
пометками проходная/нет. Имена тем, если API не отдаёт без членства, подставляем
|
||||
сниппетом первого сообщения темы.
|
||||
- Порог «40%» применяется к сообщениям темы/канала; если в выборке меньше 3
|
||||
содержательных сообщений — кандидат идёт «на рассмотрение» с меткой «мало сообщений».
|
||||
|
||||
## 7. «На рассмотрение» и действия человека
|
||||
|
||||
Экран по задаче содержит списки: **В обработке / На рассмотрении / Вступили /
|
||||
Отклонены**, плюс история.
|
||||
|
||||
Кандидат на рассмотрении показывает: тип (канал/группа/форум), число участников,
|
||||
метки, долю «подходит X из N» и **почему подошло** (перечень подходящих сообщений/тем
|
||||
с причинами — как блок «попала по фильтру» в карточках), превью сообщений (для
|
||||
форумов — по темам).
|
||||
|
||||
Действия:
|
||||
|
||||
- **«Вступить и мониторить»** — `channels.joinChannel` (по username), добавление в
|
||||
`dialogs` с `monitor=1`, backfill последних ~10 сообщений. Ручной клик — **вне квот**.
|
||||
После вступления источник автоматически попадает под правило «мы состоим» и из
|
||||
поиска исключается.
|
||||
- **«Отклонить»** — источник в чёрный список (исключается из поиска во всех задачах).
|
||||
Если для оценки пришлось вступать — выходим (`channels.leaveChannel`) в рамках квот.
|
||||
Чёрный список редактируется вручную (можно снять).
|
||||
- **Закрытые группы:** вместо авто-вступления — кнопка-ссылка `t.me/<username>`; система
|
||||
замечает вступление при синхронизации диалогов и предлагает добавить источник в
|
||||
мониторинг (метка «вступили, добавить в мониторинг?»).
|
||||
|
||||
## 8. Авто-вступление, квоты и анти-бан (BanGuard)
|
||||
|
||||
- Суточный лимит вступлений — **50** (настройка), общий для всех задач, считаются только
|
||||
автоматические вступления. Ручные — без ограничений.
|
||||
- Авто-вступление включается на задачу (`autoJoin`). Подходящие кандидаты вступают сами.
|
||||
- Интервалы между автоматическими вступлениями: **случайно 50–70 секунд**; по одному
|
||||
действию, без параллелей. Поиск и чтение — мягкие паузы (единицы секунд + джиттер,
|
||||
переиспользуем значения анти-бана из telegram.py).
|
||||
- Задача «выполнена» при достижении плана вступлений. Если за сутки упёрлись в общий
|
||||
бюджет — авто-режим продолжает на следующий день (новый суточный бюджет).
|
||||
- `FloodWaitError` → пауза по секундам из ответа + запас; авто-вступления останавливаются
|
||||
до следующего дня при флуде. Общий «стоп-кран» — пауза всего discovery.
|
||||
- Все квоты/интервалы — настройки в UI.
|
||||
|
||||
## 9. Хранилище
|
||||
|
||||
| Таблица | Назначение / ключевые поля |
|
||||
| --- | --- |
|
||||
| `disc_tasks` | задачи: name, description, keywords(JSON), min_subscribers, lang, threshold, sample_size, plan_joins, auto_join, status, counters (found/evaluated/joined/rejected), created/updated |
|
||||
| `disc_candidates` | dialog_id/username/name/kind(channel|group|forum)/hue, participants, lang_ru, join_failures, marks(JSON), topics(JSON: {topicId,title,fitCount,total,fitRatio,passed}), fit_ratio, status(new/review/joined/rejected), task_id, times |
|
||||
| `disc_blacklist` | dialog_id, name, reason, created_at |
|
||||
| `disc_log` | история задачи: task_id, event(search/evaluate/join/leave/flood/error/review/blacklist), text, created_at |
|
||||
|
||||
Дубли кандидатов не создаются; источник, попавший в другую задачу или `dialogs`,
|
||||
из поиска исключается (правило «мы состоим» — глобальное).
|
||||
|
||||
Реализация: у кандидата нет транзитного статуса `evaluated` (счётчик оценённых — на
|
||||
задаче); `join_failures` — неудачные авто-вступления подряд, после 3 кандидат удаляется.
|
||||
|
||||
## 10. API
|
||||
|
||||
- `GET/POST/PATCH/DELETE /api/discovery/tasks` (создание с валидацией бюджета планов),
|
||||
`POST /api/discovery/tasks/{id}/start|pause`
|
||||
- `POST /api/discovery/tasks/{id}/generate-keywords` — ИИ генерирует ключи по описанию
|
||||
- `GET /api/discovery/tasks/{id}/candidates?status=review|joined|rejected`
|
||||
- `POST /api/discovery/candidates/{id}/join` (вступить и мониторить), `.../reject`
|
||||
- `GET /api/discovery/blacklist`, `DELETE /api/discovery/blacklist/{dialog_id}`
|
||||
- `GET /api/discovery/tasks/{id}/log`
|
||||
|
||||
## 11. UI
|
||||
|
||||
Подвкладка **«Поиск»** на экране «Каналы»:
|
||||
- список задач (статус, прогресс, план/вступили, авто-режим) + «Новая задача»;
|
||||
- мастер задачи: описание → «Сгенерировать ключи ИИ» → редактирование ключей →
|
||||
фильтры/план/авто-режим → запуск;
|
||||
- по задаче: статус-лента (поиск → оценка → вступление), вкладки «В обработке /
|
||||
На рассмотрении / Вступили / Отклонены», история;
|
||||
- кандидат на рассмотрении раскрывается с превью и действиями; форум — по темам;
|
||||
- настройки квот (лимит/интервалы) — в том же экране или «Настройки → Telegram».
|
||||
|
||||
## 12. Интеграция с существующим кодом
|
||||
|
||||
- Фоновый воркер discovery — отдельный цикл в `main.py` (как `_pipeline_loop`),
|
||||
сервис `app/services/discovery.py`, Telegram-действия — методы `TelegramManager`
|
||||
(поиск/join/leave/чтение) с общим pacing.
|
||||
- Переиспользуем: `rules/stage1/ML/AI` для оценки сообщений (новый лёгкий вызов с
|
||||
профилем задачи, без записи карточек), список `dialogs` для фильтра «мы состоим»,
|
||||
синхронизацию диалогов для авто-добавления закрытых групп.
|
||||
- К основному пайплайну карточек, ML и ТЗ-логике не прикасаемся.
|
||||
|
||||
## 13. Вне рамок (сейчас)
|
||||
|
||||
- Агрегаторы-каталоги как источник кандидатов.
|
||||
- «Похожие каналы» (`getChannelRecommendations`) от наших подписок — отдельная опция позже.
|
||||
- Авто-вступление в закрытые группы по инвайт-ссылкам (глобальным поиском они не находятся).
|
||||
|
||||
## 14. Значения по умолчанию (настраиваются в UI)
|
||||
|
||||
суточный лимит вступлений = 50; интервал авто-вступлений = 50–70 с; выборка = 10
|
||||
сообщений; порог = 40%; мин. содержательных сообщений для оценки = 3; язык = ru.
|
||||
Reference in New Issue
Block a user