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 зелёные.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# Discovery — финальная волна правок: отчёт
|
||||
|
||||
**Статус: ГОТОВО** — все findings закрыты, проверки (компиляция, сборки, смоук) зелёные.
|
||||
|
||||
## Что исправлено
|
||||
|
||||
| Finding | Файлы | Суть |
|
||||
| --- | --- | --- |
|
||||
| I1 + M1 + m2 (авто-join) | `db.py`, `discovery.py`, `discovery_worker.py` | Колонка `disc_candidates.join_failures` (в `_SCHEMA` + `_MIGRATIONS`), `joinFailures` во view кандидата. `_join_step`: после `wait_join_delay()` кандидат перечитывается (SELECT по dialog_id) и join выполняется только если запись есть, `status='review'`, задача `running`+`autoJoin`, `_we_are_in()` False — иначе выход без join. Ошибка join (не flood): `join_failures += 1`; на 3-й неудаче `delete_candidate` + лог skip «не удалось вступить (3 попытки): {err}». FloodWaitError — как было (note_flood + лог flood, кандидат остаётся review). После успеха: `mark_joined` → `add_dialog_monitored` → `await tg.backfill_dialog(dialog_id)` → `remove_blacklist`. |
|
||||
| I2 (flood/ошибки поиска) | `discovery_worker.py` | `tg.discovery_search` обёрнут в try/except: FloodWaitError → `note_flood()` + лог flood + return; прочие Exception → лог error «поиск «{keyword}»: {err}» + return; `advance_search` только после успешного поиска. |
|
||||
| I3 (backfill после join) | `discovery_worker.py`, `discovery_routes.py` | В обоих местах join: `await tg.backfill_dialog(dialog_id)` после `add_dialog_monitored` (best-effort: исключение не роняет шаг/API — `log.warning`). |
|
||||
| I4 (отсев «чатов») | `discovery_worker.py` | В `_search_step` результаты с `kind='чат'` (люди/личные чаты/боты) пропускаются: `continue` + лог skip «{name}: личный чат/бот»; каналы/группы/форумы — как раньше. |
|
||||
| M4/m1 (идемпотентность reject + ЧС) | `discovery.py`, `discovery_worker.py`, `discovery_routes.py` | `mark_rejected`: ранний return при `status='rejected'` (счётчик/лог/чёрный список не трогаются). После успешного join (worker и routes) — `discovery.remove_blacklist(dialog_id)`. |
|
||||
| M3 (фронт-чистота) | `store.js`, `DiscoveryView.vue` | Удалён мёртвый `state.discCandidateStatus` (проверено: читается только в docs; вью использует локальный `activeTab`) вместе с записями в `loadDiscCandidates`/`resetLocal`. `resetLocal` сбрасывает `discCounts` (и `discQuota`). Квоты переехали в store: `state.discQuota {limit,delayMin,delayMax,paused}` + `loadDiscQuota()` (GET /api/settings), `saveDiscQuota(patch)` (PATCH), `toggleDiscPaused()`; прямой `import { api, ApiError }` из вью убран. |
|
||||
| M5 (инвариант пауз) | `settings_routes.py` | `patch_settings`: если пришли оба `discJoinDelayMin`/`discJoinDelayMax` — после клампов (5..600), при min>max значения меняются местами (как делает фронт). |
|
||||
| m6 (потеря имени) | `discovery_worker.py` | `_eval_step`: name/username обновляются только при успешном резолве `discovery_info` (`resolved`); fallback (`name=dialog_id`) больше не затирает имя кандидата. |
|
||||
| Синхронизация дизайн-дока | `docs/superpowers/specs/2026-09-04-channel-discovery-design.md` | §9: у кандидата `lang_ru` (не lang_detected), добавлен `join_failures`; topics = {topicId,title,fitCount,total,fitRatio,passed}; нет транзитного статуса `evaluated` (счётчик на задаче). |
|
||||
|
||||
## Проверки (команды и вывод)
|
||||
|
||||
1. Компиляция бэкенда:
|
||||
```
|
||||
$ cd /c/telbase && python -m py_compile backend/app/services/discovery_worker.py \
|
||||
backend/app/services/discovery.py backend/app/routers/discovery_routes.py \
|
||||
backend/app/db.py backend/app/routers/settings_routes.py
|
||||
PY_COMPILE_OK
|
||||
```
|
||||
2. Сборка образа (обязательно после правок бэкенда/db.py):
|
||||
```
|
||||
$ docker compose build app
|
||||
[+] build 1/1
|
||||
✔ Image telbase-app Built 6.4s
|
||||
```
|
||||
(внутри образа повторно собран и фронтенд: `vite build` → 42 modules transformed, ok)
|
||||
3. Сборка фронтенда:
|
||||
```
|
||||
$ cd /c/telbase/frontend && npm run build
|
||||
vite v6.4.3 building for production...
|
||||
✓ 42 modules transformed.
|
||||
✓ built in 1.40s
|
||||
```
|
||||
4. Смоук на временной БД в контейнере (все Telegram-вызовы замоканы):
|
||||
```
|
||||
$ MSYS_NO_PATHCONV=1 docker compose run --rm --no-deps -e PYTHONPATH=/srv \
|
||||
-e LEADRADAR_DATA=/tmp/lr_fix --entrypoint python app /data/lr_fix_smoke.py
|
||||
A_REJECT_IDEMPOTENT_OK # mark_rejected повторно: rejected=1, 1 reject-лог, 1 запись ЧС
|
||||
B_JOIN_FAILURES_OK # join_failures 1→2, на 3-й неудаче кандидат удалён + skip «3 попытки»
|
||||
B_JOIN_RECHECK_OK # задача на паузе во время delay → join не вызывается (action none)
|
||||
C_SEARCH_ERROR_TICK_OK # ошибка поиска: tick жив (action error), searchIdx не двигается
|
||||
D_AUTOJOIN_OK # успешный авто-join: joined/auto_joined, dialogs(monitor), backfill вызван
|
||||
LR_FIX_SMOKE_OK
|
||||
```
|
||||
Временный скрипт `data/lr_fix_smoke.py` удалён после прогона; `progress.md` не трогался.
|
||||
|
||||
## Concerns
|
||||
|
||||
1. Ветка flood-ошибки поиска и реальные сетевые ошибки join офлайн не воспроизводятся (нужен живой аккаунт); покрыты код-ревью и логикой (в `tg.discovery_join` flood фиксируется и пробрасывается, worker дублирует `note_flood` идемпотентно).
|
||||
2. При отсеве «личный чат/бот» пишется skip-лог на каждого человека в выдаче поиска — история задачи может стать многословной на «широких» ключах (по ТЗ-рекомендации инструкции; при желании можно убрать лог, оставив continue).
|
||||
3. `_join_step` при нарушении условий re-check после паузы выходит молча (`{"action":"none"}`) без лога — намеренно (ситуация штатная: задача поставлена на паузу/кандидат отклонён человеком). Ошибки же join всегда логируются.
|
||||
4. В `db.py` и `settings_routes.py` остались предсуществующие замечания линтера, не связанные с этой волной: db.py — сортировка импортов (L7), try/except-pass и «голый» Exception в нетронутых местах Store (миграции/close/all_settings); settings_routes.py — неиспользуемые импорты `json`/`HTTPException`/`tg` и неиспользуемая `provider_id` в `ai_check`. Новые правки этих замечаний не добавляют (проверено: файлы воркера/сервиса/роута discovery чисты).
|
||||
5. `join_failures` не сбрасывается при переводе кандидата обратно в review после удачной паузы — не требуется: при повторном добавлении (после rejected) запись создаётся заново с `DEFAULT 0`, а успешный join переводит кандидата в `joined`.
|
||||
Reference in New Issue
Block a user