DispelEvilBot/docs/events.md

7.7 KiB
Raw Blame History

События аналитики (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.

Хранение

Что Где Сколько
События 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).

Примеры запросов

-- события по дням
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;