Files
telegram-helper-bot/.cursor/implementation-plan-features.md

24 KiB
Raw Blame History

План реализации фич Telegram Helper Bot

Документ создан: 28 февраля 2025
Ветка: dev-*
Статус: План утверждён


Обзор фич

  1. Пагинация заблокированных пользователей — сортировка по дате бана, единая логика текста и кнопок
  2. Обогащение сообщений пользователей админам — данные о пользователе при обращении в поддержку
  3. Причина бана «Последний пост» — замена «Спам» на «Последний пост» при быстром бане из поста
  4. Похожие посты за 24ч — проверка на дубликаты через RAG (threshold >0.9)
  5. Авто-публикация/отклонение по RAG — задел на будущее (>0.8 publish, <0.4 decline)
  6. ML Scoring Статистика — восстановить полный вывод (модель, примеры, device) вместо fallback (API URL, статус)

Решения по уточняющим вопросам

Вопрос Решение
Пагинация Единый источник данных, единый items_per_page
message_id при forward Осознанный workaround (n+1). При переходе на send — использовать returned_message.message_id
RAG similar Новый endpoint в RAG. Нужна отдельная коллекция для submitted-постов (не позитив/негатив)
Скор для авто-решений Только rag_score
Ветки dev-*

Проверка: message_id при forward

Telegram Bot API: forwardMessage возвращает объект Message — это новое сообщение в целевом чате со своим message_id. Telegram присваивает message_id в целевом чате — он не обязан быть n+1 от исходного.

Если всё работает — возможно, бот единственный отправитель в group_for_message, и id часто идут подряд. Рекомендация: при переходе на send_message обязательно сохранять message_id из возвращаемого объекта.


RAG: коллекция для похожих постов

  • Позитив/негатив — примеры для скоринга модерации
  • Похожие посты — сравнение с другими submitted-постами за 24ч

Endpoint /similar должен искать по отдельной коллекции submitted-постов (с created_at), а не по позитиву/негативу. Нужно:

  • Новая коллекция в RAG (например, posts_submitted) с полями: text, vector, created_at, post_id
  • Endpoint POST /similar: {"text": "...", "threshold": 0.9, "hours": 24} → список похожих постов
  • При каждом suggest — добавлять пост в эту коллекцию (или вызывать endpoint RAG для индексации)

Пошаговый план реализации

Этап 0: Подготовка

Шаг Действие
0.1 Создать ветку dev-N (N — следующий номер) от main
0.2 Убедиться, что локально проходит make code-quality (или isort, black, flake8, pytest)

Этап 1: Пагинация заблокированных пользователей

Шаг Действие
1.1 В BlacklistRepository: добавить ORDER BY created_at DESC в get_all_users и get_all_users_no_limit
1.2 Ввести единый items_per_page = 9 в create_keyboard_with_pagination и во всех местах пагинации
1.3 Создать единую функцию get_banned_users_data(page, items_per_page) в AdminService или helper_func, возвращающую (message_text, buttons_list) для заданной страницы
1.4 Обновить get_banned_users_list и get_banned_users_buttons — использовать общий источник и пагинацию
1.5 Обновить admin_handlers.get_banned_users и callback_handlers.change_page — вызывать единую функцию с page
1.6 Прогнать тесты test_keyboards_and_filters, test_callback_handlers, test_admin_handlers

Затронутые файлы:

  • database/repositories/blacklist_repository.py
  • helper_bot/keyboards/keyboards.py
  • helper_bot/utils/helper_func.py
  • helper_bot/handlers/admin/services.py
  • helper_bot/handlers/admin/admin_handlers.py
  • helper_bot/handlers/callback/callback_handlers.py

Этап 2: Обогащение сообщений пользователей админам

Шаг Действие
2.1 Добавить в AsyncBotDB/репозитории методы: get_posts_count_by_author, get_last_post_by_author, get_ban_history_count, get_last_ban_info, get_user_date_added
2.2 Добавить в BlacklistHistoryRepository методы для истории банов (количество, последний бан)
2.3 В UserService или отдельном сервисе: метод format_user_message_for_admins(user_id, message_text) — собирает текст с данными пользователя
2.4 В resend_message_in_group_for_message: заменить forward на send_message с обогащённым текстом (имя, ник, id, посты, последний пост, баны, дата регистрации)
2.5 Сохранять message_id из результата send_message в user_messages (вместо message.message_id + 1)
2.6 Проверить, что get_user_by_message_id по-прежнему возвращает user_id для ответа админа
2.7 Обновить/добавить тесты для resend_message_in_group_for_message и AdminReplyService

Данные для обогащения:

  • Количество постов от пользователя
  • Последний пост пользователя (текст)
  • Количество банов
  • Дата и причина последнего бана (если был)
  • Дата создания пользователя в БД (первый контакт с ботом)
  • Имя, ник, id пользователя (обязательно при send вместо forward)

Шаблон итогового сообщения для админов:

👤 От: Иван Петров (@ivan_petrov) | ID: 123456789

📊 Постов в базе: 5
📝 Последний пост: "Привет, хочу поделиться мыслями о..."
📅 В боте с: 15.01.2025

🚫 Банов: 2
   Последний: 20.02.2025, причина «Спам», истёк 27.02.2025

---
**Сообщение пользователя:**

**Почему удалили мой пост?**

Правила форматирования:

  • Секции «Банов: 0» и «Последний: …» — показывать только если были баны
  • «Последний пост» — обрезать до ~80 символов + «…» если длиннее; если постов нет — «Нет постов»
  • Даты в формате DD.MM.YYYY или DD.MM.YYYY HH:MM для разбана
  • Разделитель --- перед текстом сообщения пользователя

Для тестов: использовать этот шаблон как эталон; проверять наличие всех секций, порядок полей, экранирование HTML в имени/нике/тексте.

Затронутые файлы:

  • database/async_db.py
  • database/repositories/post_repository.py
  • database/repositories/blacklist_history_repository.py
  • database/repositories/user_repository.py
  • helper_bot/handlers/private/private_handlers.py
  • helper_bot/handlers/private/services.py
  • helper_bot/handlers/group/services.py

Этап 3: Причина бана «Последний пост»

Шаг Действие
3.1 В callback/services.py в ban_user_from_post: заменить message_for_user="Спам" на message_for_user="Последний пост"
3.2 Обновить тесты, где ожидается «Спам»

Затронутые файлы:

  • helper_bot/handlers/callback/services.py
  • tests/test_callback_services.py (если есть)

Этап 4: Похожие посты (RAG + бот)

Разделение работ:

  • RAG сервис — промпт в .cursor/prompt-stage-4-similar-posts.md
  • Подключение в боте — ниже, раздел 4.2

4.0. Текущая архитектура RAG сервиса

Путь: /Users/andrejkatyhin/Work/PycharmProjects/rag-service

Компонент Описание
VectorStore (app/storage/vector_store.py) In-memory хранилище векторов. _positive_vectors / _negative_vectors — для модерации. Персистентность: positive_embeddings.npy, negative_embeddings.npy или vectors.npz. Косинусное сходство через np.dot для нормализованных векторов.
RAGService (app/services/rag_service.py) Модель sentence-transformers/all-MiniLM-L12-v2 (384 dim). get_embedding(text), calculate_score(text), add_positive_example, add_negative_example.
API (app/api/routes.py) POST /api/v1/score, POST /api/v1/examples/positive, POST /api/v1/examples/negative, GET /api/v1/stats, POST /api/v1/warmup, GET/PUT /api/v1/scoring/params.
Config (app/config.py) RAG_VECTORS_PATH, RAG_MAX_EXAMPLES, vector_dim=384.

Важно: Позитив/негатив — отдельные коллекции без created_at. Для похожих постов нужна третья коллекция с временными метками.


4.1. Работы в RAG сервисе

4.1.1. Расширить VectorStore

Файл: app/storage/vector_store.py

Добавить третью коллекцию для submitted-постов:

# Новые атрибуты (аналогично _positive_vectors)
self._submitted_vectors: list = []
self._submitted_hashes: list = []
self._submitted_created_at: list = []   # Unix timestamps
self._submitted_post_ids: list = []    # post_id из бота
self._submitted_texts: list = []       # текст поста (для возврата в similar)
self._submitted_rag_scores: list = []  # rag_score на момент добавления

Новые методы:

Метод Описание
add_submitted(vector, text_hash, created_at, post_id=None, text="", rag_score=None) Добавить пост в коллекцию submitted. FIFO при превышении max_submitted (новый лимит, например 5000).
find_similar_submitted(vector, threshold, hours) Возвращает: List[dict] с полями similarity, created_at, post_id, text, rag_score. Фильтрует по created_at >= now - hours*3600. Сравнивает с _submitted_vectors через np.dot. Возвращает только те, где similarity >= threshold.

Персистентность: Добавить в save_to_disk / _load_from_disk сохранение submitted-коллекции. Файл submitted_embeddings.npz с полями: vectors, hashes, created_at, post_ids, texts, rag_scores.

Конфиг: Добавить RAG_MAX_SUBMITTED (default 5000), RAG_SUBMITTED_PATH (путь к файлу submitted).

4.1.2. Расширить RAGService

Файл: app/services/rag_service.py

Метод Описание
add_submitted_post(text, post_id=None, rag_score=None) Очистить текст, получить embedding, add_submitted в vector_store. Сохраняет text и rag_score для возврата в similar. Вызывается при каждом suggest (бот передаёт rag_score из скоринга).
find_similar_posts(text, threshold=0.9, hours=24) Получить embedding, вызвать vector_store.find_similar_submitted. Вернуть список похожих с полями: similarity, created_at, post_id, text, rag_score.
4.1.3. Добавить API endpoint

Файл: app/api/routes.py

POST /api/v1/similar

Возвращает: количество похожих постов, текст каждого, similarity (косинусное сходство), присвоенный rag_score на момент добавления.

# Request
class SimilarRequest(BaseModel):
    text: str = Field(..., min_length=1)
    threshold: float = Field(default=0.9, ge=0.0, le=1.0)
    hours: int = Field(default=24, ge=1, le=168)  # 1ч7дней

# Response
class SimilarPostItem(BaseModel):
    similarity: float      # косинусное сходство (0.01.0)
    created_at: int        # Unix timestamp
    post_id: Optional[int] = None
    text: str              # текст похожего поста
    rag_score: Optional[float] = None  # rag_score на момент добавления

class SimilarResponse(BaseModel):
    similar_count: int
    similar_posts: List[SimilarPostItem]

POST /api/v1/submitted

# Request
class SubmittedRequest(BaseModel):
    text: str = Field(..., min_length=1)
    post_id: Optional[int] = None
    rag_score: Optional[float] = None  # для возврата в similar

# Response
class SubmittedResponse(BaseModel):
    success: bool
    message: str
    submitted_count: int

Примечание: POST /submitted вызывается ботом при каждом suggest (после сохранения поста в БД). POST /similar вызывается ботом перед отправкой в группу модерации — чтобы проверить, есть ли похожие посты за последние сутки.

4.1.4. Схемы и исключения

Файл: app/schemas.py — добавить SimilarRequest, SimilarResponse, SimilarPostItem, SubmittedRequest, SubmittedResponse.

Файл: app/exceptions.py — при необходимости добавить SubmittedStoreError (если коллекция пуста и т.п.).

4.1.5. Автоочистка submitted (опционально)

В autosave_loop или отдельно: периодически удалять из _submitted_* записи старше N часов (например, 48), чтобы не раздувать память.


4.2. Подключение в Telegram Helper Bot

RAG сервис реализуется отдельно (промпт: .cursor/prompt-stage-4-similar-posts.md). Ниже — интеграция в бота.

4.2.1. RagApiClient (helper_bot/services/scoring/rag_client.py)

Добавить методы:

  • find_similar_posts(text, threshold=0.9, hours=24) — POST на {api_url}/similar, body {"text": text, "threshold": threshold, "hours": hours}. Вернуть SimilarResponse (или dataclass/dict) или None при ошибке.
  • add_submitted_post(text, post_id=None, rag_score=None) — POST на {api_url}/submitted, body {"text": text, "post_id": post_id, "rag_score": rag_score}. При ошибке — логировать, не падать.

Оба метода проверяют self._enabled и не делают запросы, если RAG отключён.

4.2.2. PostService (helper_bot/handlers/private/services.py)

В _process_post_background и _process_media_group_background:

Порядок вызовов:

  1. Получить скоры (_get_scores_with_error_handling) — уже есть rag_score.
  2. Перед отправкой: вызвать find_similar_posts(original_raw_text, 0.9, 24). Если RAG недоступен или ошибка — не падать, пропустить.
  3. Если similar_count > 0: добавить в post_text строку \n\n⚠ Похожий пост за последние 24ч (совпадение {max_similarity:.0%}).
  4. Отправить пост в группу модерации.
  5. Сохранить в БД.
  6. После успешной отправки: вызвать add_submitted_post(original_raw_text, sent_message.message_id, rag_score) — в фоне. rag_score из шага 1.

Важно: Проверка similar — до добавления текущего поста в submitted.

4.2.3. Доступ к RagApiClient

RagApiClient создаётся через ScoringManager или BaseDependencyFactory. PostService должен иметь доступ к rag_client (или scoring_manager). При необходимости добавить методы в ScoringManager как прокси к RAG.

4.2.4. Обработка ошибок

При недоступности RAG — не падать, не добавлять предупреждение и не индексировать.


4.3. Порядок вызовов в боте

1. Пользователь отправляет пост
2. PostService._process_post_background:
   a) Получить скоры (rag_score, confidence, ...)
   b) find_similar_posts(text, 0.9, 24) — есть ли похожие? (возвращает count, text, similarity, rag_score)
   c) Если да — добавить предупреждение в post_text
   d) Отправить пост в группу модерации
   e) Сохранить в БД (add_post)
   f) add_submitted_post(text, message_id, rag_score) — индексировать в RAG

Важно: Проверка similar делается до добавления текущего поста в submitted, иначе пост будет похож сам на себя.


4.4. Затронутые файлы

Репозиторий Файлы
rag-service app/storage/vector_store.py, app/services/rag_service.py, app/api/routes.py, app/schemas.py, app/config.py, app/main.py (описание в docs)
telegram-helper-bot helper_bot/services/scoring/rag_client.py, helper_bot/handlers/private/services.py

Этап 5: Авто-публикация/отклонение (задел на будущее)

Шаг Действие
5.1 В PostService._process_post_background: после получения rag_score проверять пороги
5.2 Если rag_score >= 0.8: не показывать кнопки модерации, сразу публиковать (или вызывать логику publish)
5.3 Если rag_score <= 0.4: сразу отклонять (decline)
5.4 Добавить флаги в .env (например, AUTO_PUBLISH_ENABLED, AUTO_DECLINE_ENABLED) — по умолчанию false
5.5 Реализацию оформить как выключенную по умолчанию; включение — через конфиг

Затронутые файлы:

  • helper_bot/handlers/private/services.py
  • helper_bot/config/ или .env

Этап 5.5: ML Scoring Статистика — восстановить полный вывод

Проблема: Раньше «📊 ML Статистика» показывала детали (модель, device, кол-во примеров, размерность). Теперь только API URL и статус.

Причина: Бот использует fallback (get_stats_sync) когда RagApiClient.get_stats() возвращает пустой результат. Это происходит при:

  • 401/403 (ошибка авторизации)
  • таймауте или ошибке соединения
  • неверном формате ответа от API

Задачи:

Шаг Действие
5.5.1 Проверить, что RAG API GET /stats доступен с бота (сеть, CORS, API key).
5.5.2 Убедиться, что RagApiClient.get_stats() передаёт заголовок X-API-Key и корректно обрабатывает 200.
5.5.3 Проверить контракт ответа: RAG возвращает model_name, model_loaded, device, vector_store (positive_count, negative_count, total_count, vector_dim, max_examples).
5.5.4 При ошибке API — логировать причину (status, body) и при необходимости улучшить fallback-сообщение (например, «API недоступен: …»).
5.5.5 Добавить тесты для get_ml_stats с моком API (успешный ответ и fallback).

Затронутые файлы:

  • helper_bot/handlers/admin/admin_handlers.py (get_ml_stats)
  • helper_bot/services/scoring/rag_client.py (get_stats, get_stats_sync)
  • helper_bot/services/scoring/scoring_manager.py (get_stats)

Этап 6: Тесты и качество кода

Шаг Действие
6.1 Прогнать все тесты: pytest tests/ -v
6.2 make code-quality (или isort, black, flake8)
6.3 При необходимости обновить моки и фикстуры

Этап 7: Release Notes и деплой

Шаг Действие
7.1 Создать docs/RELEASE_NOTES_DEV-N.md по шаблону из .cursor/rules/release-notes-template.md
7.2 Коммиты в формате: feat:, fix:, refactor:
7.3 Push в dev-N → CI (тесты, code quality)
7.4 Создать/обновить PR в main
7.5 После мержа — деплой по deploy.yml (если настроен в prod)

Зависимости между этапами

Этап 0 → Этап 1, 2, 3 (можно параллельно)
Этап 1, 2, 3 → Этап 6
Этап 4 зависит от RAG (отдельный сервис)
Этап 5 можно делать после 4 или независимо
Этап 5.5 — независимо (можно параллельно с 13)

Рекомендуемый порядок реализации

  1. Этап 0 — подготовка
  2. Этапы 1, 2, 3 — независимо, можно в любом порядке
  3. Этап 4 — после готовности RAG
  4. Этап 5 — после 4 или параллельно с 13
  5. Этап 5.5 — разобраться с ML Scoring Статистикой (можно параллельно)
  6. Этап 6 — перед PR
  7. Этап 7 — после ревью и мержа

Ссылки на документацию проекта

  • .cursor/rules/my-custom-rule.mdc — общие правила
  • .cursor/rules/architecture.md — архитектура
  • .cursor/rules/handlers-patterns.md — паттерны handlers
  • .cursor/rules/release-notes-template.md — шаблон Release Notes
  • prod/.cursor/rules/my-custom-rule.mdc — CI/CD, ветки, деплой