ci / build-test (push) Canceled after 0s
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 зелёные.
9.6 KiB
9.6 KiB
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).
- План выполнен (
joined >= planJoinsу любой running-задачи) →status='done'+ логdone(«план выполнен: вступили X из Y»). Идёт ДО поиска/оценки/join, чтобы задачу с выполненным планом не продолжать обрабатывать (и чтобы освободился бюджет планов). - Шаг поиска: первая running-задача с
searchDone=False→ ключkeywords[searchIdx]→tg.discovery_search→ каждый результатdiscovery.add_candidate(kind нормализуется:канал/группа/чат → channel/group/forum) →advance_search→ при переходе вsearchDoneлогsearch«поиск завершён: N кандидатов» (N — счётчикfound). Паузы между поисками — внутриdiscovery_search. Пустые/съехавшие ключи закрываются без сетевого вызова. - Шаг оценки: первый кандидат
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).
- Авто-вступление (отдельный проход, приоритет ниже оценки): задача
autoJoin=True+ кандидатreview+ban_guard.can_auto_join()(иначеnone):- повторная проверка «мы не состоим» прямым SQL по
dialogs/disc_blacklist(интерфейсы Task 3 не менялись) — если уже вступили/в чёрном списке →mark_rejected+ логreject, actionreject; await ban_guard.wait_join_delay()(50–70 с);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для ретрая).
- повторная проверка «мы не состоим» прямым SQL по
Возвращаемые 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не заполняется.
Вывод проверок
cd /c/telbase && python -m py_compile backend/app/services/discovery_worker.py backend/app/main.py→PY_COMPILE_OK(без ошибок).docker compose build app→Image telbase-app Built(3 c, кэш).- Офлайн-сценарий в контейнере на временной БД (
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
- Диагностика
discovery_worker.py— без ошибок и предупреждений (ruff I/SIM/default — чисто; импорты/_we_are_in/suppressприведены к правилам).
Concerns
- Полный прогон на живом аккаунте — Task 10 вручную. Ветки
floodиerror(реальные FloodWaitError/сетевые ошибки join) офлайн не воспроизводятся — только код-ревью и логика Task 4 (discovery_joinсам фиксирует flood и пробрасывает). - Ветка «повторная проверка перед join» использует
mark_rejected, который по контракту Task 3 добавляет источник вdisc_blacklist— даже когда «уже вступили между оценкой и join». Для поиска это безвредно (источник и так отсекается поdialogs), но чёрный список формально пополняется. Если это нежелательно — можно ввести отдельный helper (интерфейсы Task 3 не менялись). minSubscribers-ветка «участники не подтверждены» помечает кандидата только когда минимум задан (minSubscribers>0); приmin=0отсутствие participants не метка (участники не критерий).- Стоп-кран
discPaused(ban_guard.global_paused()) останавливает весь tick — включая поиск и оценку, не только авто-join (по требованию задания). - Running-задача без работы (поиск завершён, кандидатов нет,
autoJoin=False) остаётся running и даёт{"action":"none"}каждые 5 c — завершение/удаление такой задачи за пользователем (по брифу). fitRatioфорума — агрегат по всей выборке (fit из N), а не максимум темы; вердикт форума — «есть ≥1 проходная тема». Формат зафиксирован в docstring и в этом отчёте (UI Task 9 показывает темы с per-topic X из N).