Files
Deal/.superpowers/sdd/channel-discovery/task-6-brief.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

3.6 KiB
Raw Blame History

Task 6: Воркер Discovery (поиск → оценка → авто-вступление)

Files:

  • Create: backend/app/services/discovery_worker.py
  • Modify: backend/app/main.py (фоновый цикл _discovery_loop, каждые 5 c)

Interfaces:

  • Consumes: discovery (Task 3), tg.discovery_* (Task 4), discovery_eval (Task 5), ban_guard (Task 2).
  • Produces: async def tick() -> dict — выполняет ОДНО действие и возвращает {"action": ..., "taskId": ...} (или {"action": "none"}).

Логика tick (по одной задаче за вызов, начиная с самой старой running):

  1. Если задача search_done=False: взять ключ keywords[search_idx], вызвать tg.discovery_search; для каждого результата discovery.add_candidate; discovery.advance_search(task_id); если search_done стал True — лог search «поиск завершён: N кандидатов». Возврат.
  2. Иначе взять первого кандидата статуса new задачи:
    • info = tg.discovery_info; participants, kind (forum если is_forum); при заданном min_subscribers и participants НЕ None и меньше минимума — set_candidate_status(...) нет: просто discovery.delete_candidate + лог skip; если participants None — метка «участники не подтверждены» (идём дальше).
    • read = tg.discovery_read(dialog_id, sample_size).
      • Если read.ok=False (история недоступна без членства): kind==channel → review с меткой «канал: история недоступна»; группа/форум → review с меткой «закрытая группа (история скрыта) — вступите сами»; оценка контента не производится, неподтверждённые фильтры помечаются.
    • Язык: если прочитано и task.lang=='ru': lang_ru=detect_lang_ru(...); False → удалить кандидата, лог skip «язык не русский»; None → метка «язык не подтверждён».
    • Оценка: evaluate_sample; passed → метки topics/fit → review + лог review; иначе удалить кандидата, лог skip «мало подходящих (X из N)».
  3. Авто-вступление (отдельный проход tick, приоритет ниже оценки): если у running-задачи auto_join и есть кандидат review и ban_guard.can_auto_join():
    • повторная проверка «мы не состоим» (dialogs/blacklist) → если вступили уже → mark_rejected с логом;
    • await ban_guard.wait_join_delay() (рандом 50–70 с — спейсинг авто-вступлений; ручные join из API паузу не делают);
    • tg.discovery_join(username)discovery.mark_joined(dialog_id, auto=True)tg.add_dialog_monitored(...); при FloodWaitError → ban_guard.note_flood() + лог flood.
  4. Если task.joined >= task.plan_joins → статус done, лог done.
  • Step 1: Реализовать discovery_worker.py и цикл в main.py.
  • Step 2: Проверить компиляцию и запуск без падений (воркер с пустыми таблицами делает none). Полный прогон — Task 10 вручную.