Files
Deal/.superpowers/sdd/channel-discovery/task-6-report.md
T
Rustam Khalimov 27c7831910
ci / build-test (push) Canceled after 0s
Deal — единая кодовая база
SaaS-мониторинг Telegram: ядро (модули Cards/Kanban/Pipeline/Tenants/Settings/
Discovery, Api, Infrastructure), сервисы telegram/ai/ml/storage, фронт Vue,
контракты и grpc-hosting, деплой-конфиги (dev/prod/observability/CI-раннер),
Gitea Actions CI, документация (ТЗ, техдок, api-map, код-стайл, планы, бэклог).

Текущее состояние: все этапы роадмапа 0–12 закрыты, сборка 5 sln 0/0,
тесты 1340/130/52/38/9 зелёные.
2026-09-11 23:56:47 +03:00

61 lines
9.6 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.
# Task 6 — Отчёт: воркер Discovery (поиск → оценка → авто-вступление)
## Статус
✅ Реализовано и проверено (py_compile + сборка образа + офлайн-прогон на временной БД с моками Telegram/пауз).
## Файлы
- Создан: `backend/app/services/discovery_worker.py``async def tick() -> dict` + шаги.
- Изменён: `backend/app/main.py` — фоновый цикл `_discovery_loop` (каждые 5 c: `await tick()`, исключения — `log.exception`) и запуск в lifespan в списке задач рядом с `_pipeline_loop`.
## Что сделано
### Структура `tick()` (одно действие за вызов, возврат `{"action": ..., "taskId": ...}`)
Приоритеты (как в брифе + пожелание про стоп-кран):
0. `ban_guard.global_paused()` → сразу `{"action": "none"}` (стоп-кран останавливает весь tick).
1. **План выполнен** (`joined >= planJoins` у любой running-задачи) → `status='done'` + лог `done` («план выполнен: вступили X из Y»). Идёт ДО поиска/оценки/join, чтобы задачу с выполненным планом не продолжать обрабатывать (и чтобы освободился бюджет планов).
2. **Шаг поиска**: первая running-задача с `searchDone=False` → ключ `keywords[searchIdx]``tg.discovery_search` → каждый результат `discovery.add_candidate` (kind нормализуется: `канал/группа/чат → channel/group/forum`) → `advance_search` → при переходе в `searchDone` лог `search` «поиск завершён: N кандидатов» (N — счётчик `found`). Паузы между поисками — внутри `discovery_search`. Пустые/съехавшие ключи закрываются без сетевого вызова.
3. **Шаг оценки**: первый кандидат `status='new'`:
- `discovery_info``participants`, `kind` (`forum`, если `is_forum`; иначе маппинг RU-kind), обновляются name/username/hue;
- `minSubscribers`>0: participants меньше → `delete_candidate` + лог `skip` «мало участников (X < min)»; participants не получены → метка «участники не подтверждены»;
- `discovery_read`: `ok=False``review` + метка «канал: история недоступна» (channel) или «закрытая группа (история скрыта) — вступите сами» (group/forum); контент не оценивается, для ru-задач добавляется метка «язык не подтверждён»;
- язык (только ru-задачи): `False``delete_candidate` + skip «язык не русский»; `None` → метка «язык не подтверждён»;
- объём: содержательных <3 → `review` + метка «мало сообщений» (решает человек);
- контент: не-форум — `evaluate_sample` по всей выборке, `fitRatio` общий; форум — `group_by_topic`, `evaluate_sample` по каждой теме, заполняется `topics` (`{topicId, title, fitCount, total, fitRatio, passed}`), вердикт — есть ≥1 проходная тема, `fitRatio` — агрегат fit из N по всей выборке; `passed()``review` (метки/fitRatio/topics), иначе `delete_candidate` + skip «мало подходящих (X из N)»;
- `bump_counter(evaluated)` при выходе кандидата из `new` (review или delete).
4. **Авто-вступление** (отдельный проход, приоритет ниже оценки): задача `autoJoin=True` + кандидат `review` + `ban_guard.can_auto_join()` (иначе `none`):
- повторная проверка «мы не состоим» прямым SQL по `dialogs`/`disc_blacklist` (интерфейсы Task 3 не менялись) — если уже вступили/в чёрном списке → `mark_rejected` + лог `reject`, action `reject`;
- `await ban_guard.wait_join_delay()` (5070 с);
- `tg.discovery_join(username)``mark_joined(auto=True)``tg.add_dialog_monitored(dialogId, name, username, kind, hue)`;
- `FloodWaitError``ban_guard.note_flood()` (идемпотентно) + лог `flood`; прочие ошибки → лог `error` (кандидат остаётся `review` для ретрая).
Возвращаемые action: `search|review|skip|join|reject|flood|error|done|none`. Метки/topics-контракт продублирован в docstring модуля.
### Контракт меток и тем (в docstring `discovery_worker.py`)
- `marks` — список строк: «участники не подтверждены», «язык не подтверждён», «канал: история недоступна», «закрытая группа (история скрыта) — вступите сами», «мало сообщений».
- `topics` — список dict для форумов: `{"topicId": str|int, "title": str, "fitCount": int, "total": int, "fitRatio": float, "passed": bool}`. Общий вердикт форума — есть хотя бы одна проходная тема; `fitRatio` кандидата — агрегат по всей выборке; для не-форумов `topics` не заполняется.
## Вывод проверок
1. `cd /c/telbase && python -m py_compile backend/app/services/discovery_worker.py backend/app/main.py``PY_COMPILE_OK` (без ошибок).
2. `docker compose build app``Image telbase-app Built` (3 c, кэш).
3. Офлайн-сценарий в контейнере на временной БД (`LEADRADAR_DATA=/tmp/lr_w6`, `MSYS_NO_PATHCONV=1`, `--entrypoint sh`), Telegram-методы и `wait_join_delay` замоканы, оценка — реальная (эвристика: `aiEnabled/mlEnabled=False`):
```
SEARCH_OK # пустая система → none; поиск: кандидат добавлен, «уже мониторится» пропущен, «поиск завершён: 1 кандидатов»
EVAL_REVIEW_OK # оценка: review, fitRatio 0.75, langRu=True
EVAL_LANG_SKIP_OK # язык не русский → delete + skip
EVAL_FORUM_OK # форум: kind=forum, topics по темам (passed/нет), fitRatio агрегат
EVAL_FEW_OK # <3 сообщений → review + «мало сообщений»
EVAL_NO_HISTORY_OK # история недоступна → review + «канал: история недоступна» + «язык не подтверждён»
AUTOJOIN_DONE_OK # join → joined/autoJoined + dialogs(monitor) + join_auto; план → done + лог done
REJECT_RECHECK_OK # «вступили между оценкой и join» → mark_rejected + reject (без join)
TASK6_OFFLINE_OK
```
4. Диагностика `discovery_worker.py` — без ошибок и предупреждений (ruff I/SIM/default — чисто; импорты/`_we_are_in`/`suppress` приведены к правилам).
## Concerns
1. Полный прогон на живом аккаунте — Task 10 вручную. Ветки `flood` и `error` (реальные FloodWaitError/сетевые ошибки join) офлайн не воспроизводятся — только код-ревью и логика Task 4 (`discovery_join` сам фиксирует flood и пробрасывает).
2. Ветка «повторная проверка перед join» использует `mark_rejected`, который по контракту Task 3 добавляет источник в `disc_blacklist` — даже когда «уже вступили между оценкой и join». Для поиска это безвредно (источник и так отсекается по `dialogs`), но чёрный список формально пополняется. Если это нежелательно — можно ввести отдельный helper (интерфейсы Task 3 не менялись).
3. `minSubscribers`-ветка «участники не подтверждены» помечает кандидата только когда минимум задан (`minSubscribers>0`); при `min=0` отсутствие participants не метка (участники не критерий).
4. Стоп-кран `discPaused` (`ban_guard.global_paused()`) останавливает весь tick — включая поиск и оценку, не только авто-join (по требованию задания).
5. Running-задача без работы (поиск завершён, кандидатов нет, `autoJoin=False`) остаётся running и даёт `{"action":"none"}` каждые 5 c — завершение/удаление такой задачи за пользователем (по брифу).
6. `fitRatio` форума — агрегат по всей выборке (fit из N), а не максимум темы; вердикт форума — «есть ≥1 проходная тема». Формат зафиксирован в docstring и в этом отчёте (UI Task 9 показывает темы с per-topic X из N).