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

43 lines
6.9 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 5 — Отчёт: Оценка контента (язык, темы, fit по профилю задачи)
## Статус
✅ Реализовано и проверено (py_compile + офлайн-сценарий на временной БД в контейнере).
## Файлы
- Создан: `backend/app/services/discovery_eval.py` — чистая логика оценки (без карточек/очередей/обучения), поверх `store`, `ml_client`, `ai_service.chat_json`, `pipeline.clean_short`.
## Что сделано
### Интерфейсы брифа
- `detect_lang_ru(texts)` — суммарная доля кириллицы (блок U+0400–U+04FF) среди всех `str.isalpha()`-букв выборки: `>=0.15 → True`, `<=0.03 → False`, между порогами или 0 букв → `None`.
- `group_by_topic(messages)` — группировка по `topic_id` (`None → "main"`); `[{"topic_id", "title", "messages": [...]}]`; title — сниппет первого непустого текста темы (≤60 симв., whitespace схлопнут); группы отсортированы по числу сообщений (убыв.), порядок сообщений внутри группы — входной (хронологический из `discovery_read`).
- `evaluate_message(task, text)` async — каскад:
1. текст пустой/`len(strip) < 10``{"fit": False, "reason": "слишком короткое", "source": "heuristic"}`;
2. ML: `ml_client.is_enabled()``predict(text)`; `take` и `label=='spam'``False`, reason «ML: спам», `source: "ml"`; иначе ниже;
3. ИИ: если `aiEnabled` **и** доступен ключ/локальный провайдер (см. Concerns) — один `ai_service.chat_json(промпт, user="Сообщение:\n"+text[:4000])`; `{fit, reason}` из ответа; любая ошибка → шаг 4;
4. эвристика: любой ключ входит в `clean_short(text).casefold()`; reason `совпал ключ "…"` / `нет совпадений с ключами`, `source: "heuristic"`.
- `evaluate_sample(task, messages)` async — последовательно по сообщениям; `{"fit_count", "total", "fit_ratio", "per_message": [{"text", "fit", "reason", "topic_id"}]}`; `topic_id` в per_message нормализован `None → "main"` (стыкуется с ключами `group_by_topic`).
- `passed(ev, task)``ev["total"] >= 3` и `ev["fit_ratio"]*100 >= task["threshold"]`.
- Промпт ИИ — константа `_AI_PROMPT` точно по тексту брифа (description/keywords подставляются через `.format`).
## Вывод проверок
1. `cd /c/telbase && python -m py_compile backend/app/services/discovery_eval.py``PY_COMPILE OK` (без ошибок).
2. Сборка и офлайн-прогон на временной БД:
- `docker compose build app` → образ собран;
- `MSYS_NO_PATHCONV=1 docker compose run --rm --no-deps -e PYTHONPATH=/srv -e LEADRADAR_DATA=/tmp/lr_eval --entrypoint python app /data/task5_check.py``DISCOVERY_EVAL OK`:
- `detect_lang_ru(["Ищем python разработчика"]) is True`; `detect_lang_ru(["we need a python developer"]) is False`; смесь 1/20 кириллицы → `None`; текст без букв → `None`;
- `group_by_topic`: темы `main`/111/222, объединение 2 сообщений в 111, сортировка `[111, main, 222]`, title «Топик A первый» (≤60), входной порядок внутри группы сохранён;
- `evaluate_message` на задаче без ИИ/ML (`aiEnabled=False`, `mlEnabled=False`) → эвристика: `{"fit": True, "reason": "совпал ключ "python"", "source": "heuristic"}`; без ключа → False «нет совпадений с ключами»; «короче» → False «слишком короткое»;
- `aiEnabled=True` без ключа провайдера → ИИ-ветка не вызывается (без сети), отвечает эвристика;
- `evaluate_sample`: total=4/fit=2/ratio=0.5, per_message topic_id `["main","main",5,5]`; `passed`: threshold 40 → True, 60 → False, выборка из 2 → False, пустая → ratio 0.0/False.
- Временный файл `data/task5_check.py` удалён после прогона.
3. Диагностика файла — без ошибок и предупреждений.
## Concerns
1. **`source` для слишком коротких сообщений** — `"heuristic"`: это первая ступень каскада (до ML/ИИ), но `source` по брифу — объединение `heuristic|ml|ai`, отдельного значения нет. Воркеру (Task 6) это не мешает; при необходимости подсчёта «отсевов по длине» лучше ориентироваться на `reason`.
2. **ИИ-ветка дополнительно проверяет наличие ключа** (`ai_service.provider_status()`: `keySet` или `local`-провайдер), а не только `aiEnabled` — иначе `chat_json` при отсутствии ключа тратит ~6 c на ретраи и сыплет warning в лог на каждое сообщение. При любой ошибке/недоступности статуса — всё равно fallback на эвристику (как и требует бриф).
3. **`topic_id` в `per_message` нормализован** `None → "main"`, чтобы результат `evaluate_sample` стыковался с ключами `group_by_topic`. Task 6 при подсчёте «подходит X из N» по темам форума должен сравнивать ключ `"main"`, а не `None`.
4. **`title` темы** — сниппет первого **непустого** текста темы (первого во входном порядке), а не обязательно первого сообщения; `topic_title` из сообщений не используется (в `discovery_read` Task 4 его и нет). Если позже понадобится название темы из Telegram — добавить как fallback для пустых текстов.
5. **Лимиты на границе**: тексту в ИИ обрезается до 4000 симв. (как в `ai.filter_incoming`), причина из ИИ — до 200 симв.; эвристика и длина считаются по полному тексту.
6. Решения оценки нигде не логируются и не учитываются в счётчиках `ml_client.track_decisions` — модуль чистый; учёт/логирование при необходимости добавить в Task 6 на уровне воркера.