DispelEvilBot/docs/events.md

128 lines
7.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.

# События аналитики (Postgres)
Бот пишет значимые действия в Postgres, схема `analytics`. Дашборд (Superset) и ETL —
отдельный финальный этап; здесь описан **контракт** событий: какие бывают `event_type`
и что лежит в `payload`.
> Источник правды в коде — `common/analytics/events.py` (`EventType`).
> Расхождение со списком ниже ловит `tests/test_analytics.py`.
> Добавляешь событие — добавь строку в таблицу.
## Схема в Postgres
DDL — `sql/V26__analytics_schema.sql`.
- `analytics.events` — события, `PARTITION BY RANGE (event_time)` по дням.
Партиции создаёт приложение (бот и админка) на несколько дней вперёд;
**старые партиции не удаляются** — история хранится бессрочно.
- `analytics.messages_daily` — количество сообщений пользователя по дням:
`day`, `user_id`, `username`, `chat_id`, `count`; PK `(day, user_id, chat_id)`.
На каждое сообщение выполняется `INSERT ... ON CONFLICT DO UPDATE count + 1`,
поэтому вместо строки на каждое сообщение растёт только счётчик за день.
Сообщения **не** пишутся в `analytics.events` отдельным событием `message`.
Построчная таблица `bot.messages` удалена миграцией `V27`: старые строки один раз
пересчитаны в `analytics.messages_daily`, новые пишутся только счётчиком.
## Хранение
| Что | Где | Сколько |
| --- | --- | --- |
| События | Postgres, `analytics.events` | бессрочно (партиции по дням) |
| Счётчики сообщений | Postgres, `analytics.messages_daily` | бессрочно (партиции по дням) |
Бэкап — общий бэкап Postgres. Отдельного пайплайна ClickHouse/Parquet больше нет.
При росте базы нужно следить за диском: автоматической очистки нет.
## Уведомления в чат логов
Помимо записи в Postgres часть событий дублируется сообщением в `ALERT_CHAT_ID`
(если он задан; `0` — выключено). Отправка не критична: ошибки только логируются.
| Уведомление | Когда | Событие |
| --- | --- | --- |
| Вход участника | кто-то вошёл в основной чат | `join` |
| Выход участника | кто-то вышел из основного чата | `leave` |
| Очищение | успешное `/очищение` (`+опыт`, `+монеты`) | `purification` |
| Пинг «бот живой» | раз в час | `bot_alive` |
| Попытка доступа извне | не участник чата стучится в бота | `access_denied` |
## Поля события
| Поле | Тип | Описание |
| --- | --- | --- |
| **event_time** | `timestamptz` | Момент события (UTC) |
| **event_type** | `text` | Тип события (см. каталог ниже) |
| **user_id** | `bigint` | Telegram-ID пользователя (0 — системное событие) |
| **username** | `text` | `@username` на момент события (может быть пустым) |
| **chat_id** | `bigint` | ID чата (обычно основной чат клана) |
| **payload** | `jsonb` | Дополнительные данные события |
| **bot_version** | `text` | Версия бота из `common/version.py` |
| **prod** | `boolean` | `true` — прод-контур, `false` — тестовый |
## Каталог событий
| Тип события | Когда пишется | `payload` |
| --- | --- | --- |
| `command` | Использование команды (`/инфо`, `/очищение`, …) | `command`, `args_count` |
| `purification` | Ежедневное очищение (`/очищение`) | `success`, `exp_add`, `money_add` |
| `exp_accrual` | Начисление опыта за очищение | `amount`, `streak` |
| `level_up` | Повышение уровня по итогам очищения | `level_from`, `level_to` |
| `money_accrual` | Начисление валюты (монетка за активность, награда за очищение) | `amount`, `reason`, `action_id` |
| `money_spend` | Трата валюты (покупка, обнуление баланса при удалении) | `amount`, `reason`, `product_id` |
| `eggs` | Получение яичек (`/яички`) | `amount` |
| `purchase_title` | Покупка звания | `title_id`, `price`, `success` |
| `purchase_donat` | Покупка доната | `donat_id`, `price`, `success` |
| `join` | Вход участника в основной чат | `chat_title` |
| `leave` | Выход/исключение участника | `chat_title`, `kicked`, `by_id`, `by_username` |
| `nick_set` | Смена ника (`/ник`) | `nick` |
| `subscribe` | Подписка на рассылки (`/подписка`) | — |
| `unsubscribe` | Отписка от рассылок (`/отписка`) | — |
| `access_denied` | Отказ в доступе (не участник чата и не админ) | `reason` |
| `bot_start` | Старт бота | `app_env` |
| `bot_stop` | Остановка бота | `app_env`, `uptime_seconds` |
| `bot_alive` | Ежечасный пинг «бот живой» | `app_env`, `uptime_seconds` |
| `error` | Необработанная ошибка при обработке апдейта | `error_type`, `error`, `update_type` |
## Поведение при недоступности Postgres
Аналитика — **некритичная** подсистема: её сбой не должен влиять на бота.
- события и счётчики копятся в буфере и записываются батчами (по размеру и по таймеру);
- если Postgres недоступен, буфер не растёт бесконечно: при переполнении самые
старые события отбрасываются;
- если записать батч не удалось, он выводится в stdout (`WARNING`, первые несколько
строк + общее количество) и отбрасывается — данные не пропадают молча;
- при остановке бота выполняется финальный сброс буфера;
- выключение целиком — `ANALYTICS_ENABLED=false` (тогда `record()`/`record_message()` — no-op).
## Примеры запросов
```sql
-- события по дням
SELECT date_trunc('day', event_time) AS day, count(*)
FROM analytics.events GROUP BY day ORDER BY day;
-- топ команд за неделю
SELECT payload->>'command' AS cmd, count(*) AS uses
FROM analytics.events
WHERE event_type = 'command' AND event_time >= now() - interval '7 days'
GROUP BY cmd ORDER BY uses DESC;
-- покупки за месяц
SELECT event_type, count(*), sum((payload->>'price')::int)
FROM analytics.events
WHERE event_type IN ('purchase_title', 'purchase_donat')
AND event_time >= now() - interval '30 days'
GROUP BY event_type;
-- сообщения по дням
SELECT day, sum(count) FROM analytics.messages_daily
WHERE day >= current_date - 30 GROUP BY day ORDER BY day;
-- ошибки
SELECT event_time, payload->>'error_type', payload->>'error'
FROM analytics.events WHERE event_type = 'error'
ORDER BY event_time DESC LIMIT 50;
```