Разработка Telegram-бота для «теста Тьюринга» с аналитикой и дашбордом

Курс о том, как спроектировать и реализовать Telegram-бота, который проводит мини «тест Тьюринга»: пользователь выбирает, где текст человека, а где нейросети, и получает результат. Отдельный фокус — хранение данных, статистика по пользователям и визуализация метрик в виде диаграмм и отчётов.

1. Постановка задачи: сценарии теста, роли, метрики успеха

Постановка задачи: сценарии теста, роли, метрики успеха

Зачем мы делаем этого бота

Цель курса — разработать Telegram-бота, в котором пользователь проходит упрощённый «тест Тьюринга»: в каждом раунде он видит два текста (один написан человеком, другой — нейросетью) и пытается угадать, где какой. По итогам сессии бот показывает результат, а владельцу продукта доступна аналитика по прохождениям и качеству вопросов в виде метрик и дашборда.

Важное уточнение: это не классический тест Тьюринга из научной постановки (диалог с судьёй), а игровой A/B-формат на распознавание источника текста. Мы используем популярную идею «угадай, где AI», чтобы:

  • быстро собрать много ответов
  • измерять «человечность» генераций на разных типах текстов
  • строить аналитику по сложности вопросов и ошибкам пользователей
  • Справка о первоисточнике идеи: Turing test.

    Роли и заинтересованные стороны

    Чтобы система была продуктоориентированной, сначала фиксируем роли и их потребности.

    | Роль | Кто это | Что хочет | Что считается успехом | |---|---|---|---| | Игрок (пользователь бота) | Любой пользователь Telegram | Быстро начать тест, понять правила, получить результат | Прошёл тест без путаницы, увидел итог, захотел повторить | | Администратор (владелец продукта) | Вы (создатель) | Видеть статистику, управлять контентом, улучшать качество | Дашборд отвечает на ключевые вопросы, контент легко обновлять | | Контент-менеджер (опционально) | Вы или помощник | Добавлять пары текстов, помечать метки/темы/сложность | Контент пополняется без ошибок, есть модерация | | Система аналитики | База + ETL/скрипты + дашборд | Получать события и агрегировать их | События полные, консистентные, пригодны для графиков |

    В рамках курса мы обычно совмещаем роли администратора и контент-менеджера.

    Объекты и термины проекта

    Чтобы дальше (в коде, базе и дашборде) не путаться, фиксируем базовые сущности.

  • Сессия теста — один запуск прохождения (например, 10 раундов подряд).
  • Раунд — один вопрос: два текста + ответ пользователя.
  • Пара текстов — «человек» и «нейросеть», объединённые в один вопрос.
  • Правильность — угадал пользователь источник или нет.
  • Метаданные вопроса — тема, язык, длина, стиль, источник, дата добавления, сложность (если задаём вручную).
  • Пользовательские сценарии (user flows)

    Ниже — сценарии, которые определяют функциональность первой версии (MVP). Чем лучше вы их пропишете сейчас, тем легче будет проектировать архитектуру, хранение данных и дашборд.

    !Блок-схема прохождения теста пользователем

    Сценарий игрока: первое знакомство

  • Пользователь пишет боту /start.
  • Бот объясняет правила в 2–4 строках.
  • Бот предлагает выбрать режим (например, 5 или 10 раундов) и кнопку «Начать».
  • Пользователь начинает тест.
  • Ключевой принцип: в первые 15 секунд пользователь должен понять, что делать, иначе конверсия в прохождение будет низкой.

    Сценарий игрока: прохождение раундов

    Каждый раунд должен быть максимально однозначным.

  • Бот показывает два текста, помеченные как Текст A и Текст B.
  • Бот задаёт вопрос: «Какой текст написал человек?»
  • Пользователь выбирает:
  • - «A — человек» - «B — человек» - (опционально) «Пропустить»
  • Бот фиксирует ответ, показывает короткую обратную связь (опционально) и переходит к следующему раунду.
  • Отдельное решение, которое повлияет на метрики и данные: показывать ли правильный ответ сразу или только в конце. Для аналитики полезны оба варианта, но для «игрового» опыта часто лучше показывать в конце, чтобы не ломать вовлечённость.

    Сценарий игрока: результат и повтор

  • После последнего раунда бот показывает итог:
  • - сколько верных ответов из общего числа - сколько ошибок
  • (Опционально) показывает список раундов, где пользователь ошибся (с раскрытием «правильного источника»).
  • Предлагает действия:
  • - «Пройти ещё раз» - «Поделиться результатом» - «Выбрать другой режим»

    Сценарий администратора: аналитика

    Администратор должен отвечать на вопросы:

  • Сколько пользователей проходит тесты ежедневно/еженедельно?
  • Где люди чаще ошибаются: в каких темах, стилях, длинах?
  • Какие вопросы слишком лёгкие/слишком сложные?
  • Есть ли “перекос”: например, пользователи почти всегда выбирают A?
  • Админ-доступ можно реализовать разными способами:

  • отдельный web-дашборд
  • закрытый раздел в самом Telegram (команды + таблицы/графики как изображения)
  • В рамках курса удобнее делать web-дашборд, потому что он масштабируется и лучше подходит для визуализаций.

    Сценарий контент-менеджера: управление вопросами

    Минимальный набор операций:

  • добавить пару текстов (human + ai)
  • задать метки: тема, язык, примерная сложность
  • выключить пару, если она некорректная или «слишком палится»
  • В MVP допускается, что контент добавляется напрямую в базу или через простую админ-форму.

    Ограничения и правила контента

    Чтобы тест был честным и измеримым, важно заранее определить правила.

    Требования к паре текстов

  • Оба текста должны быть про одно и то же (или быть сопоставимыми по жанру), иначе пользователь будет угадывать по теме, а не по «человечности».
  • Желательно близкая длина (например, разница не более 20–30%).
  • Запрещены явные маркеры:
  • - «Как искусственный интеллект, я…» - подписи «Сгенерировано нейросетью» - ссылки на источник, по которым легко вычислить автора
  • Желательно хранить язык, стиль и тему как метаданные, чтобы потом строить сегментацию.
  • Что считаем «человечностью» в рамках продукта

    Мы измеряем не философскую «разумность», а практическую неотличимость текста. Если пользователи часто принимают AI-текст за человеческий, значит:

  • либо генерация очень качественная
  • либо вопрос составлен некорректно (например, человеческий текст слишком «машинный»)
  • И то, и другое важно видеть в метриках.

    Метрики успеха: продуктовые, контентные, аналитические

    Метрики нужны в двух местах:

  • в продукте (понимать, нравится ли пользователям)
  • в контенте (понимать качество вопросов)
  • Продуктовые метрики (вовлечённость)

    | Метрика | Как трактовать | Зачем нужна | |---|---|---| | Количество стартов | сколько раз люди начали | верх воронки | | Конверсия в прохождение | доля пользователей, дошедших до конца | показатель понятности и интереса | | Среднее число раундов за сессию | сколько реально проходят | ранний индикатор скуки/сложности | | Retention (возврат) | возвращаются ли на следующий день/неделю | ценность продукта |

    Если вам нужна простая формализация точности в тесте, используйте долю верных ответов:

    Где:

  • — количество верных ответов пользователя
  • — общее число раундов, которые пользователь ответил (без пропусков)
  • Эта метрика пригодится и для итогового экрана, и для дашборда.

    Контентные метрики (качество вопросов)

    | Метрика | Как трактовать | Типичные выводы | |---|---|---| | Доля выбора A | как часто выбирают «A — человек» | если сильно > 50%, вероятен перекос в оформлении | | Сложность вопроса | доля ошибок на конкретной паре | можно отсортировать вопросы от лёгких к сложным | | Дискриминативность | различаются ли результаты между группами | помогает искать «нечестные» вопросы |

    Простой ориентир: если пользователи угадывают «человека» почти всегда, вопрос слишком лёгкий (или AI-текст слишком «палится»). Если угадывают почти никогда, возможно, человеческий текст выглядит «как AI».

    Аналитические метрики (качество данных)

    Плохая аналитика чаще всего не из-за графиков, а из-за недособранных событий. Поэтому заранее определяем, какие события обязаны логироваться.

    | Событие | Когда отправляем | Минимальные поля | |---|---|---| | start | пользователь начал | user_id, timestamp, источник (команда/кнопка) | | session_created | создана сессия | session_id, user_id, mode, planned_rounds | | round_shown | показан раунд | session_id, round_id, pair_id | | answer_submitted | пользователь ответил | session_id, round_id, pair_id, choice, is_correct, response_time_ms | | session_finished | сессия завершена | session_id, answered_rounds, correct_answers |

    response_time_ms (время ответа) — очень полезное поле: оно помогает понять, где пользователь реально думает, а где кликает случайно.

    Критерии готовности MVP

    Чтобы не разрастись в бесконечный проект, фиксируем «готово», когда выполняется следующий минимум.

  • Игрок может:
  • - запустить тест - пройти заданное число раундов - увидеть итоговый результат
  • Администратор может:
  • - увидеть число сессий и пользователей за период - увидеть среднюю точность и распределение результатов - увидеть топ-10 самых сложных/самых лёгких пар
  • Система собирает события так, чтобы ответы можно было связать с конкретной парой текстов и сессией.
  • Риски и как их учесть в постановке

  • Смещение данных: пользователи могут угадывать по длине, форматированию, пунктуации. Решение — стандартизировать отображение (одинаковые кавычки, абзацы, длины, отсутствие ссылок).
  • Повторяемость: если пользователь получает те же пары, он запоминает ответы. Решение — выдача раундов без повторов в пределах пользователя (как минимум на коротком горизонте).
  • Этика и приватность: минимизировать персональные данные. В Telegram достаточно user_id и технических событий. Ознакомьтесь с базовой документацией платформы: Telegram Bot API.
  • Что будет дальше в курсе

    После постановки задачи логичный следующий шаг — спроектировать структуру данных (сессии, раунды, пары текстов, события), а затем перейти к реализации:

  • бот: команды, состояния, обработка ответов
  • хранилище: база данных для контента и событий
  • аналитика: расчёт метрик, витрины данных
  • дашборд: графики и фильтры для администратора
  • Эта статья — «контракт» проекта: если сценарии, роли и метрики зафиксированы, все последующие решения (архитектура, таблицы БД, события, дизайн дашборда) будут согласованными.

    2. Архитектура решения: Telegram API, бэкенд, база данных, хостинг

    Архитектура решения: Telegram API, бэкенд, база данных, хостинг

    Зачем нам архитектура именно сейчас

    В предыдущей статье мы зафиксировали сценарии, роли и события, которые обязаны логироваться: session_created, round_shown, answer_submitted, session_finished и другие. Архитектура — это практический ответ на вопрос: как именно эти события будут появляться в базе данных, как мы будем раздавать пользователю раунды, и как администратор получит дашборд.

    Наша цель — собрать систему, которая:

  • надёжно принимает сообщения из Telegram
  • умеет вести пользователя по состояниям теста (режим → раунды → результат)
  • сохраняет контент и ответы в базе так, чтобы строилась аналитика
  • предоставляет удобный доступ к статистике (дашборд)
  • Компоненты системы

    Минимальная архитектура состоит из четырёх слоёв:

  • Telegram Bot API: Telegram отправляет вашему приложению входящие обновления (сообщения, нажатия кнопок).
  • Бэкенд (бот-сервис): обрабатывает обновления, хранит состояния сессий, выдаёт раунды, пишет события.
  • База данных: хранит пары текстов, сессии, раунды, ответы, события.
  • Аналитика и дашборд: читает данные из базы и визуализирует метрики.
  • !Общая схема: как сообщения попадают в бэкенд, а данные — в базу и дашборд

    Telegram Bot API: как сообщения попадают в ваш код

    Telegram предоставляет два способа доставки обновлений:

  • Long polling: ваше приложение само регулярно спрашивает Telegram: «Есть новые сообщения?»
  • Webhook: Telegram сам отправляет обновления на ваш публичный HTTPS-адрес
  • Официальная документация:

  • Telegram Bot API
  • Webhook
  • Long polling: когда подходит

    Long polling удобен, когда:

  • вы разрабатываете локально
  • у вас нет публичного домена и HTTPS
  • вы хотите максимально простой старт
  • Минусы для продакшена:

  • сложнее масштабировать
  • сервис должен постоянно «крутиться» и держать цикл опроса
  • Webhook: рекомендуемый вариант для хостинга

    Webhook подходит, когда:

  • вы разворачиваете бота на сервере/платформе с публичным доменом
  • хотите более «правильную» серверную интеграцию
  • Критичные требования:

  • публичный URL
  • HTTPS
  • быстрая обработка запросов (Telegram ожидает ответ быстро; тяжёлые операции лучше выносить в фон)
  • Бэкенд: что он делает в нашем проекте

    Бэкенд — это приложение, которое:

  • принимает обновления от Telegram
  • определяет, в каком состоянии пользователь (выбирает режим, проходит раунд, завершает сессию)
  • достаёт из базы контент (пары текстов)
  • сохраняет результаты ответов и события
  • (опционально) даёт админ-методы для добавления/отключения вопросов
  • Логика бота как машина состояний

    Чтобы тест был управляемым, поведение бота удобно представлять как состояния.

    Типичные состояния:

  • idle: пользователь ещё не начал
  • choosing_mode: выбирает 5/10 раундов
  • in_round: видит A/B и отвечает
  • finished: показан результат
  • Важный продуктовый нюанс: состояния должны опираться на данные в базе (сессия, номер текущего раунда), а не только на память процесса. Иначе при перезапуске сервиса вы потеряете прогресс.

    Чем писать бэкенд

    Частый стек для Python:

  • веб-фреймворк: FastAPI (FastAPI)
  • библиотека для Telegram: aiogram (aiogram) или python-telegram-bot (python-telegram-bot)
  • В курсе можно выбрать любой вариант. Для архитектуры важно не название библиотеки, а то, что у вас будет:

  • слой приёма обновлений
  • слой бизнес-логики
  • слой работы с базой
  • Рекомендация для MVP

    Для первого запуска часто удобно:

  • начать с long polling (быстрее запуск)
  • затем перейти на webhook при деплое
  • Либо сразу делать webhook, если вы уверенно чувствуете себя с доменом/HTTPS.

    Минимальный контур обработки события

    Любое входящее действие пользователя в итоге должно превратиться в запись в базе. Например, при ответе на раунд вы фиксируете:

  • какой был session_id
  • какая была pair_id
  • какой был выбор (A или B)
  • правильность
  • время ответа (если собираете)
  • Это напрямую связано с метриками из постановки задачи: без этих полей дашборд будет «слепым».

    База данных: что и как хранить

    Для проекта с аналитикой почти всегда разумно выбирать реляционную базу.

    Почему PostgreSQL

    PostgreSQL хорошо подходит, потому что:

  • надёжный транзакционный слой (не потеряете ответы)
  • удобные агрегации для аналитики (GROUP BY, фильтры, окна)
  • легко подключается к BI-инструментам
  • Официальный сайт:

  • PostgreSQL
  • Какие сущности хранить

    Мы приводим структуру к терминам из постановки задачи:

  • users — минимальные данные о пользователе (например, telegram_user_id, дата первого старта)
  • pairs — пары текстов (human/ai) + метаданные (тема, язык, длина, активность)
  • sessions — сессии теста (режим, план раундов, время старта/финиша)
  • rounds — выданные раунды в рамках сессии (какая пара, порядок)
  • answers или events — ответы пользователя и события
  • Практический совет: для MVP можно объединить «ответ» и «событие ответа» в одну таблицу answers, а остальные события (start, session_created, session_finished) хранить в таблице events. Это даёт баланс простоты и расширяемости.

    Два подхода к логированию: события и «нормализованные» таблицы

    Есть два распространённых подхода:

  • Event log (события): пишем поток событий (кто, что, когда). Плюсы: гибко, удобно расширять. Минусы: сложнее строить некоторые отчёты.
  • Нормализованные таблицы: отдельно sessions, rounds, answers. Плюсы: проще строить отчёты по тестам. Минусы: при расширении логики нужны миграции.
  • Для курса оптимален гибрид:

  • ключевые сущности (sessions/rounds/answers) — как таблицы
  • дополнительные события — как events
  • Аналитика и дашборд: как администратор увидит метрики

    Есть два основных пути.

    BI-инструмент поверх PostgreSQL

    Вы подключаете инструмент к базе и собираете графики и таблицы.

    Популярные варианты:

  • Metabase — быстро поднимается, удобен для MVP (Metabase)
  • Apache Superset — мощнее и гибче, но тяжелее в настройке (Apache Superset)
  • Grafana — хороша для метрик и тайм-серий, может читать PostgreSQL (Grafana)
  • Плюсы:

  • почти не нужно писать фронтенд
  • быстро получить дашборд по SQL
  • Минусы:

  • ограниченная кастомизация пользовательских сценариев
  • Свой веб-дашборд

    Вы делаете отдельный веб-интерфейс (например, на React/Vue) и API для метрик.

    Плюсы:

  • максимальная гибкость
  • Минусы:

  • сильно больше времени
  • Для курса и MVP обычно лучше BI-инструмент.

    Хостинг: где всё это будет работать

    Что нужно хостить

    Обычно у вас будет минимум два сервиса:

  • bot-service (принимает обновления Telegram)
  • db (PostgreSQL)
  • Опционально:

  • dashboard (Metabase/Superset/Grafana)
  • Варианты размещения

    Есть три практичных варианта.

  • Один VPS: вы арендуете сервер и запускаете всё в Docker.
  • PaaS для приложения + управляемая БД: приложение на платформе, база как сервис.
  • Полностью managed: и приложение, и база, и BI — в управляемых сервисах (часто дороже или сложнее состыковать).
  • Для учебного проекта часто выбирают:

  • VPS + Docker Compose (понятно, прозрачно)
  • или PaaS + managed Postgres (меньше DevOps)
  • Docker как единый способ сборки

    Docker помогает гарантировать, что локально и на сервере у вас одинаковая среда.

    Официальные ссылки:

  • Docker
  • Docker Compose
  • Пример минимального docker-compose.yml для бота + PostgreSQL:

    Важно: токен бота нельзя «зашивать» в репозиторий. Его нужно передавать через переменные окружения.

    Безопасность и эксплуатация: минимальные правила

    Секреты

  • токен Telegram храните в переменной окружения (TELEGRAM_TOKEN)
  • доступ к базе — тоже через переменные (DATABASE_URL)
  • не логируйте токены и пароли
  • Резервные копии

    Если вы храните результаты тестов для продукта/исследования, бэкапы базы — обязательны.

    Минимум для MVP:

  • ежедневный дамп pg_dump
  • хранение дампов вне сервера (например, в облачном хранилище)
  • Миграции схемы

    Схема базы будет меняться (добавятся поля, таблицы). Поэтому нужны миграции.

    Для Python-стека часто используют:

  • Alembic (Alembic)
  • Даже если вы не углубляетесь в инструменты, важно понимать принцип: изменения базы должны быть повторяемыми и версионированными.

    Как собрать архитектуру MVP (рекомендуемый маршрут)

    Ниже — практичный маршрут, который хорошо стыкуется с постановкой задачи.

  • Выбираем стек
  • - Python + библиотека для Telegram - PostgreSQL - Metabase для дашборда
  • Делаем бота без дашборда, но с логированием
  • - сохраняем sessions, rounds, answers и базовые events
  • Подключаем BI-инструмент к PostgreSQL
  • - строим первые графики: сессии по дням, средняя точность, топ сложных пар
  • Переезжаем на webhook (если начинали с polling)
  • - деплой на сервер с HTTPS

    Что будет дальше

    Следующий логичный шаг после архитектуры — спроектировать таблицы базы данных и связи между ними так, чтобы:

  • выдача раундов была корректной (без повторов, с учётом активности пар)
  • ответы связывались с конкретной парой и сессией
  • метрики из постановки задачи считались простыми SQL-запросами
  • После этого можно переходить к реализации бота и наполнению контентом (парами текстов), чтобы начать собирать реальные данные для аналитики.

    3. Проектирование контента теста: пары текстов, уровни сложности, разметка

    Проектирование контента теста: пары текстов, уровни сложности, разметка

    Почему контент — это ядро продукта

    В предыдущих статьях мы:

  • зафиксировали сценарий теста (раунды с выбором между двумя текстами) и метрики (точность, сложность вопросов, перекос выбора A/B)
  • набросали архитектуру (бот-сервис, PostgreSQL, события и дашборд)
  • Теперь важнейшая часть, от которой зависит и игровой опыт, и качество аналитики: как именно устроен контент.

    Если пары текстов составлены неаккуратно, пользователь будет угадывать по случайным признакам (длина, форматирование, “палевные” фразы), а ваши графики будут показывать не “насколько AI похож на человека”, а “насколько мы ошиблись в дизайне вопросов”.

    В этой статье мы спроектируем:

  • формат пары текстов
  • правила создания “честных” пар
  • подход к уровням сложности
  • систему разметки (метаданные), которая сразу пригодна для дашборда
  • Справочная страница про идею теста: Turing test.

    Что такое “пара текстов” в нашем тесте

    Пара — это один вопрос (один раунд), состоящий из двух вариантов:

  • Текст A
  • Текст B
  • Где один вариант — человеческий, второй — сгенерирован нейросетью.

    Критически важно: пользователь отвечает не “какой текст лучше”, а на один строго определённый вопрос, например: “Какой текст написал человек?”. Это позволяет собирать однозначные ответы и строить метрики.

    Почему пары должны быть сопоставимыми

    Если тексты несопоставимы (про разное, в разном жанре, с разной длиной), пользователь будет угадывать по нецелевым признакам. Тогда:

  • сложность вопроса станет искусственной
  • сравнение тем и жанров в аналитике будет неверным
  • появятся перекосы выбора A/B
  • Правила “честной” пары: чек-лист качества

    Ниже — практические требования, которые помогают делать вопросы измеримыми.

    Сопоставимость содержания

    Оба текста должны быть:

  • про одну тему или одну ситуацию
  • в одном жанре (оба как отзыв, оба как короткая заметка, оба как объяснение)
  • Примеры хорошей сопоставимости:

  • два ответа на один и тот же вопрос
  • два пересказа одной новости (без ссылок и уникальных фактов, которые легко проверить)
  • два описания одного товара/сервиса
  • Примеры плохой сопоставимости:

  • один текст — личная история, второй — “энциклопедическая справка”
  • один текст — 2 предложения, второй — 2 абзаца
  • один текст — с фактами и цифрами, второй — общие слова
  • Сопоставимость формы

    Чтобы не было угадывания по “обёртке”, стандартизируйте:

  • длину (лучше делать близкой по объёму)
  • разбиение на абзацы (если один с абзацами, второй тоже)
  • пунктуацию и кавычки (в идеале одинаковый стиль)
  • отсутствие списков в одном и их наличие в другом (если не задумано специально)
  • Простое правило для MVP: если вы визуально с расстояния видите, какой текст “другой” по форме — пара плохая.

    Запрет “палевных” маркеров

    Уберите или не допускайте:

  • фразы вроде “как искусственный интеллект…”
  • шаблонные дисклеймеры
  • явные следы промпта (“Вот ваш текст:”, “Конечно! Ниже…”)
  • ссылки на источники, по которым легко вычислить происхождение
  • “Единый голос интерфейса”

    Интерфейс показа должен быть одинаковым для обоих вариантов:

  • одинаковые заголовки: Текст A, Текст B
  • одинаковые префиксы и оформление
  • одинаковая кодировка и отсутствие странных символов
  • Это не мелочь: перекос выбора “A — человек” часто возникает из-за того, что A визуально выглядит “более человеческим”, хотя содержание не виновато.

    Откуда брать тексты: стратегии наполнения

    Есть несколько рабочих подходов. Выбирайте тот, который соответствует вашим ресурсам и рискам.

    Стратегия “человек → AI-имитация”

  • Берёте человеческий текст.
  • Просите нейросеть написать похожий по смыслу и жанру текст.
  • Плюсы:

  • проще обеспечить сопоставимость по теме
  • легче контролировать длину и стиль
  • Минусы:

  • если человеческий текст очень “яркий” и индивидуальный, AI может проигрывать заметнее
  • Стратегия “общий промпт → два автора”

  • Формулируете задание: тема, жанр, тон, длина.
  • Человек пишет один вариант.
  • Нейросеть пишет второй вариант по тому же заданию.
  • Плюсы:

  • максимально честное сравнение “по одному ТЗ”
  • Минусы:

  • сложнее организовать человеческие тексты (нужно время/авторы)
  • Стратегия “редактура для выравнивания”

    Если тексты уже есть, можно слегка их выравнивать:

  • приводить длину к диапазону
  • убирать уникальные имена/ссылки
  • исправлять явные опечатки, которые делают “человека” слишком узнаваемым
  • Важное правило: редактирование не должно превращаться в “полировку под ответ”. Вы улучшаете сопоставимость, а не “усложняете специально”.

    Длина, сложность текста и “уровни”

    На старте хочется сразу сделать “лёгкий/средний/сложный”. Но ручная оценка сложности часто не совпадает с тем, как отвечают реальные пользователи. Поэтому используем двухконтурный подход:

  • предварительный уровень (ручной, чтобы упорядочить контент)
  • фактическая сложность (из аналитики по ошибкам)
  • Предварительные уровни сложности

    Можно ввести 3 уровня, чтобы управлять выдачей и балансом.

    | Уровень | Как выглядит пара | Типичные признаки | |---|---|---| | Лёгкий | AI заметно шаблонный | много общих фраз, ровный тон, мало конкретики | | Средний | оба текста правдоподобны | есть детали, стиль похож, нет явных маркеров | | Сложный | AI “почти человек” или человек “почти AI” | нестандартные обороты, ровная грамотность в обоих, похожая структура |

    Важно: это гипотеза до данных.

    Фактическая сложность по ответам пользователей

    Когда бот начнёт собирать ответы, определяйте сложность пары через долю ошибок.

    Например, можно считать ошибочность пары так:

    Где:

  • — сколько раз пользователи ошиблись на этой паре
  • — сколько раз пользователи ответили на эту пару (без пропусков)
  • Интерпретация простая:

  • чем выше , тем пара сложнее для пользователей
  • пары с очень низким не стоит ранжировать жёстко: мало данных
  • Эта метрика напрямую ложится в дашборд “самые сложные/самые лёгкие вопросы”.

    Разметка (метаданные): чтобы аналитика работала

    Разметка нужна, чтобы отвечать на продуктовые вопросы:

  • в каких темах люди чаще ошибаются
  • влияет ли длина
  • отличаются ли результаты по языкам
  • есть ли перекос по жанрам
  • Минимальный набор полей для каждой пары

    | Поле | Что хранит | Зачем нужно в продукте и аналитике | |---|---|---| | pair_id | уникальный идентификатор | связать показы, ответы и дашборд | | human_text | текст человека | отображение пользователю, аудит качества | | ai_text | текст нейросети | отображение пользователю, аудит качества | | language | язык пары (ru, en) | фильтры и сегменты на дашборде | | topic | тема (например, “путешествия”, “технологии”) | анализ “где AI лучше маскируется” | | genre | жанр (отзыв, заметка, объяснение) | сравнение по форматам текста | | length_bucket | категория длины (короткий/средний/длинный) | контроль сопоставимости и аналитика | | difficulty_label | предварительная сложность (easy/medium/hard) | баланс выдачи до накопления статистики | | is_active | активна ли пара | быстро отключать проблемный контент | | created_at | дата добавления | контроль обновлений и свежести набора |

    Для MVP этого достаточно, чтобы получить полезный дашборд.

    Дополнительные поля, если хотите глубже

    | Поле | Пример | Что даст | |---|---|---| | style | “официальный”, “разговорный” | анализ по регистру речи | | sentiment | positive/neutral/negative | проверка, влияет ли эмоциональность | | source_type | “написал автор”, “перефраз”, “по промпту” | контроль качества набора | | notes | комментарий редактора | быстрее разбирать спорные пары |

    Не добавляйте всё сразу. Хорошее правило: каждое поле должно отвечать на конкретный будущий график или фильтр.

    !Прототип карточки контента: какие поля храним и как пара выглядит для пользователя

    Контроль качества: как не испортить датасет

    В тесте с аналитикой качество контента — это качество данных. Вам нужен простой, повторяемый процесс проверки.

    Процесс модерации пары

  • Проверка на “палевные” маркеры и ссылки.
  • Проверка сопоставимости по теме, жанру и длине.
  • Проверка на перекос формы (абзацы, пунктуация, явные шаблоны).
  • Присвоение меток: язык, тема, жанр, длина, предварительная сложность.
  • Пометка is_active = true только после проверки.
  • Пилотирование перед массовым запуском

    Перед тем как “лить трафик”, прогоните пилот:

  • 20–50 первых пользователей
  • 30–100 ответов на каждую пару (если получается)
  • По итогу отключите пары, которые:

  • дают почти 100% угадывания (слишком лёгкие)
  • дают почти 0% угадывания (возможно, человеческий текст слишком “машинный” или AI слишком сильный)
  • вызывают жалобы (“оба похожи на AI”, “обман”, “нечестно”)
  • Баланс и выдача: как не получить смещённую статистику

    Даже идеальный контент можно испортить неправильной выдачей.

    Баланс позиций A/B

    Если человеческий текст всегда в A, пользователи начнут “читать паттерн”, а вы увидите ложный рост точности.

    Правило:

  • позиция человека должна быть случайной
  • в данных обязательно храните, где был человек: в A или B
  • Это поле может жить не в паре, а в сущности раунда (потому что одна и та же пара может показываться с разной перестановкой).

    Избегайте повторов для одного пользователя

    Если пользователь встречает одну и ту же пару, он запоминает ответ. Поэтому:

  • не показывайте одну и ту же пару одному пользователю хотя бы на горизонте нескольких сессий
  • храните факт показа пары пользователю (через round_shown и rounds)
  • План по объёму контента для MVP

    Чтобы тест не казался коротким и повторяющимся, ориентируйтесь:

  • минимум 30–50 пар на запуск
  • лучше 100+ пар, если хотите устойчивые графики по темам
  • При режиме 10 раундов это означает:

  • 50 пар — хватит на разнообразие, но повторы начнутся быстрее
  • 100 пар — заметно лучше для удержания и качества данных
  • Этические и юридические моменты

    Контент — это тексты, а значит возможны вопросы авторских прав и персональных данных.

    Минимальные правила для учебного продукта:

  • не используйте тексты, которые содержат персональные данные (телефоны, адреса, реальные ФИО без необходимости)
  • избегайте копирования больших фрагментов защищённых материалов
  • лучше использовать тексты, написанные вами, вашими участниками, или специально созданные для проекта
  • Как контент связывается с событиями и дашбордом

    Чтобы ваши метрики из первой статьи считались автоматически, на уровне данных нужно обеспечить связность:

  • round_shown должен ссылаться на pair_id
  • answer_submitted должен содержать выбор пользователя и правильность
  • sessions должны объединять раунды в прохождение
  • Тогда на дашборде вы сможете:

  • строить топ сложных пар по
  • сравнивать точность по topic, genre, length_bucket, language
  • находить перекосы, если пользователи слишком часто выбирают A
  • Что дальше

    После того как вы:

  • определили формат пары
  • ввели разметку
  • описали процесс проверки и пилота
  • можно переходить к следующему практическому шагу: проектированию схемы базы данных и реализации загрузки/выдачи пар так, чтобы события, пары и ответы связывались без потерь. Это станет основой для стабильной аналитики и дашборда.

    4. Разработка Telegram-бота: команды, состояния, кнопки, обработка ответов

    Разработка Telegram-бота: команды, состояния, кнопки, обработка ответов

    Как эта статья связана с предыдущими

    В постановке задачи мы определили сценарий теста (сессия → раунды → результат) и список обязательных событий (session_created, round_shown, answer_submitted, session_finished). В архитектуре выбрали общую схему: Telegram Bot API → бэкенд → PostgreSQL → дашборд. В проектировании контента описали, что такое пара текстов, какие метаданные нужны и почему важно рандомизировать позиции A/B.

    Теперь собираем «сердце» продукта: логику Telegram-бота.

    Цель статьи: спроектировать и реализовать поведение бота так, чтобы:

  • пользователю было понятно, что делать (команды, кнопки, понятные сообщения)
  • у бота была чёткая модель состояний (чтобы не «ломаться» между шагами)
  • ответы записывались в БД вместе с контекстом (чтобы аналитика считалась SQL-запросами)
  • Выбор подхода к реализации: библиотека и стиль обработки

    Есть два популярных пути на Python:

  • aiogram (асинхронный фреймворк для ботов)
  • python-telegram-bot (тоже популярен, в актуальных версиях поддерживает async)
  • В рамках курса удобно использовать aiogram, потому что он хорошо ложится на обработку коллбэков кнопок и конечные автоматы.

    Полезные ссылки:

  • Telegram Bot API
  • aiogram documentation
  • python-telegram-bot documentation
  • Дальше примеры будут на aiogram, но логика одинаковая для любой библиотеки.

    Команды и «контракт» интерфейса

    Минимальный набор команд для MVP

    Рекомендуемый набор:

  • /start — вход в продукт, объяснение правил, выбор режима
  • /help — короткая справка
  • /test — начать тест (если пользователь не хочет читать приветствие)
  • /cancel — прервать текущую сессию (полезно для «застрявших» состояний)
  • Опционально для админа:

  • /admin — ссылка на дашборд или краткая статистика
  • Ключевой принцип UX

    Пользователь должен проходить тест почти полностью кнопками. Текстовый ввод оставляем только для команд.

    Причины:

  • меньше ошибок ввода
  • легче поддерживать состояния
  • проще логировать события (кнопка = однозначное действие)
  • Состояния: как сделать поведение бота предсказуемым

    Почему нам нужна модель состояний

    В Telegram бот получает отдельные «кусочки» диалога: сообщение, нажатие кнопки, команда. Без состояния вы быстро столкнётесь с проблемами:

  • пользователь нажал кнопку от старого сообщения
  • пользователь открыл два чата и кликает в разном порядке
  • сервер перезапустился, а пользователь продолжил тест
  • Поэтому состояние должно храниться не «в памяти процесса», а в базе данных через сущности sessions и rounds.

    Минимальная машина состояний

    С точки зрения продукта нам достаточно следующих состояний:

  • idle — нет активной сессии
  • choosing_mode — выбирает 5/10 раундов
  • in_session — идёт прохождение раундов
  • finished — сессия завершена, показан результат
  • Технически можно не хранить поле state явно, а вычислять его из данных:

  • есть активная session со статусом in_progress → значит пользователь в in_session
  • нет активной сессии → idle
  • Это часто надёжнее, чем отдельное поле состояния.

    !Машина состояний бота и переходы между ними

    Кнопки: reply-клавиатура и inline-клавиатура

    В Telegram есть два основных типа клавиатур.

  • Reply keyboard — кнопки «под строкой ввода», как будто пользователь отправляет текст.
  • Inline keyboard — кнопки прямо под сообщением, при нажатии приходит callback_query.
  • Для теста лучше inline:

  • кнопка привязана к конкретному сообщению с вопросом
  • в callback_data можно передать идентификаторы (например, round_id и выбор)
  • меньше риска, что пользователь отправит «лишний текст»
  • Официальная документация:

  • InlineKeyboardMarkup
  • CallbackQuery
  • Как проектировать callback_data

    Правило: callback_data должно быть коротким и однозначным.

    Рекомендуемый минимум для ответа на раунд:

  • round_id — какой именно раунд
  • choice — что выбрал пользователь (A или B)
  • Сами тексты в callback_data не передаём.

    Данные, без которых аналитика не взлетит

    Чтобы из ответов собрать метрики, при каждом раунде важно сохранять:

  • session_id — к какой сессии относится ответ
  • pair_id — какая пара текстов использована
  • human_position — где был человеческий текст в этом показе (A или B)
  • choice — что выбрал пользователь
  • is_correct — правильность
  • shown_at и answered_at или response_time_ms
  • Ключевой момент из статьи про контент: одна и та же пара может показываться с разной перестановкой A/B. Поэтому позиция человека должна быть атрибутом раунда, а не пары.

    Практическая реализация потока: от /start до результата

    Ниже — эталонный поток обработки.

    /start: приветствие и выбор режима

    Логика:

  • создать пользователя в users, если его ещё нет
  • показать короткие правила
  • вывести кнопки выбора режима (например, 5 или 10)
  • Пример (aiogram, упрощённо):

    Что логируем:

  • событие start
  • Выбор режима: создаём сессию и первый раунд

    Пользователь нажимает кнопку режима → приходит callback_query.

    Логика:

  • Создать запись в sessions со статусом in_progress.
  • Подобрать набор пар (pairs) под нужное число раундов.
  • Создать записи rounds (или создавать по одной «на лету», но тогда сложнее избегать повторов).
  • Показать первый раунд и записать round_shown.
  • Важно: подбор пар должен учитывать:

  • is_active = true
  • желательно не повторять пары для пользователя (хотя бы за последние N показов)
  • Пример обработчика выбора режима:

    Показ раунда: как сформировать сообщение и кнопки

    Как хранить A/B перестановку

    Есть два корректных подхода:

  • хранить в rounds поля text_a_source и text_b_source (например, human или ai)
  • хранить одно поле human_position (A или B), а тексты подставлять по нему
  • Для MVP проще второе.

    Формат сообщения

    Сообщение должно быть стандартизировано, чтобы не появлялся перекос по «внешнему виду».

    Рекомендуемый шаблон:

  • заголовок «Раунд X из N»
  • блок «Текст A»
  • блок «Текст B»
  • вопрос «Где текст человека?»
  • Пример функции показа раунда:

    Примечание: в реальном коде лучше избегать round_obj["..."] и использовать dataclass или pydantic-модель, но для учебной логики достаточно.

    Что логируем:

  • событие round_shown с session_id, round_id, pair_id
  • время показа shown_at
  • Обработка ответа: правильность, защита от повторных кликов, переход к следующему раунду

    Основные риски при обработке

  • пользователь нажал кнопку два раза
  • пользователь нажал кнопку от старого раунда, когда уже начался новый
  • пользователь нажал кнопку после завершения сессии
  • Поэтому обработчик ответа должен быть идемпотентным: повторный клик не должен портить данные.

    Алгоритм обработки ответа

  • Распарсить round_id и choice.
  • Достать раунд из БД.
  • Проверить, что раунд принадлежит активной сессии этого пользователя.
  • Проверить, что у раунда ещё нет ответа.
  • Посчитать is_correct.
  • Записать answer_submitted (и/или строку в answers).
  • Если это был последний раунд, завершить сессию и показать результат.
  • Иначе показать следующий раунд.
  • Пример обработчика ответа (упрощённо):

    Как считать время ответа

    Самый простой способ:

  • при round_shown сохранять shown_at
  • при answer_submitted сохранять answered_at
  • response_time_ms посчитать как разницу на уровне приложения или SQL
  • Почему это полезно:

  • можно отделить «быстро кликаю наугад» от «долго думаю»
  • можно искать пары, где пользователи стабильно тратят много времени
  • Результат сессии: что показываем пользователю и что пишем в БД

    На финальном экране показываем:

  • сколько верных из N
  • сколько ошибок
  • кнопку «Пройти ещё раз»
  • В БД фиксируем:

  • session_finished
  • finished_at
  • агрегаты correct_answers, answered_rounds (если вы храните их в sessions)
  • Пример текста результата:

  • «Готово: 7 верных из 10. Ошибок: 3. Хочешь ещё раз?»
  • Правила надёжности: то, что часто забывают в MVP

    Рандомизация позиции человека

    Если человеческий текст будет всегда в A, аналитика станет бесполезной: вы увидите «успех», который объясняется паттерном интерфейса.

    Поэтому при создании раунда:

  • случайно выбирайте human_position ∈ {A, B}
  • текст A и B подставляйте по этой позиции
  • Проверка «активности» пары

    Если вы отключили пару (is_active = false), она не должна попадать в новые сессии.

    Дополнительно полезно хранить:

  • причину отключения (например, поле notes)
  • Обработка /cancel

    Команда должна:

  • завершить активную сессию со статусом cancelled (это полезно для аналитики воронки)
  • сбросить пользователя в состояние idle
  • Ответ на callback_query

    Telegram ожидает, что вы ответите на callback_query методом answerCallbackQuery.

    Практическое правило: всегда делайте await cb.answer() даже если вы уже отправили сообщение.

    Документация:

  • answerCallbackQuery
  • Минимальная структура модулей в коде

    Чтобы проект не превратился в один файл, разделите ответственность:

  • handlers/ — обработчики команд и кнопок
  • services/ — бизнес-логика (создать сессию, выдать раунд, завершить)
  • db/ — запросы к базе (репозитории)
  • models/ — структуры данных
  • Так проще тестировать и расширять.

    Как это связано с аналитикой и будущим дашбордом

    Если вы реализуете обработку так, как описано выше, то дашборд (Metabase или другой BI) сможет считать ключевые метрики напрямую из PostgreSQL:

  • сессии по дням (sessions.created_at)
  • средняя точность (answers.is_correct)
  • топ сложных пар (агрегация ошибок по pair_id)
  • перекос A/B (частоты choice и связь с human_position)
  • Следующий практический шаг после этой статьи обычно такой:

  • зафиксировать схему таблиц и миграции
  • реализовать заполнение pairs (контент)
  • поднять BI и собрать первые графики
  • 5. Хранение результатов: пользователи, сессии, ответы, подсчёт ошибок

    Хранение результатов: пользователи, сессии, ответы, подсчёт ошибок

    Зачем нам отдельная статья про хранение результатов

    В предыдущих материалах курса мы:

  • описали сценарии и обязательные события (показы раундов, ответы, завершение сессии)
  • выбрали архитектуру (Telegram Bot API → бэкенд → PostgreSQL → дашборд)
  • определили, как устроены пары текстов и почему важна рандомизация A/B
  • разобрали логику бота: команды, состояния, кнопки, обработка ответов
  • Теперь нужно превратить эти идеи в структуру данных, которая:

  • не теряет ответы пользователей
  • защищена от повторных кликов и «старых кнопок»
  • позволяет считать метрики простыми SQL-запросами
  • хорошо подключается к BI-дашборду (например, Metabase)
  • Ключевая мысль: если модель хранения спроектирована правильно, аналитика станет следствием, а не отдельным проектом.

    Принципы хранения для нашего бота

    Перед схемой зафиксируем несколько практических принципов.

  • Минимум персональных данных
  • - нам достаточно telegram_user_id и технических временных меток - не нужно сохранять имя, username и тем более сообщения пользователя
  • Нормализация ключевых сущностей
  • - сессии, раунды и ответы лучше хранить в отдельных таблицах - так проще строить отчёты и поддерживать целостность
  • Идемпотентность ответов
  • - повторный клик по кнопке не должен создавать второй ответ - это решается ограничениями в БД и транзакционной логикой
  • Аудитируемость
  • - важно уметь восстановить, что именно было показано пользователю (какая пара, какая перестановка A/B)

    Документация по возможностям PostgreSQL (ограничения, индексы, транзакции): PostgreSQL Documentation.

    Какие сущности мы храним

    Ниже — минимальный набор таблиц для MVP с аналитикой.

  • users — кто проходит тест
  • pairs — контент (пары текстов) и метаданные (мы описывали это в статье про контент)
  • sessions — одно прохождение теста (например, 10 раундов подряд)
  • rounds — конкретные раунды в рамках сессии (какая пара, какая позиция человека, порядок)
  • answers — ответ пользователя на конкретный раунд
  • events — опциональная таблица для произвольных событий (удобно для расширений)
  • !Схема связей между таблицами и ключевыми полями

    Почему human_position хранится в rounds, а не в pairs

    Одна и та же пара текстов может показываться разным пользователям с разной перестановкой:

  • в одном раунде человеческий текст в A
  • в другом раунде человеческий текст в B
  • Если вы запишете «где человек» в самой паре (pairs), вы потеряете возможность:

  • честно рандомизировать A/B
  • считать перекос выбора A/B
  • объяснить, почему конкретный ответ был правильным или нет
  • Поэтому правило:

  • pairs хранит контент (human/ai тексты)
  • rounds хранит как именно эта пара была показана в конкретном раунде
  • Рекомендуемая схема таблиц (PostgreSQL)

    Ниже — один из самых практичных вариантов для MVP. Он не единственный, но хорошо масштабируется и легко подключается к дашборду.

    Таблица пользователей

    Храним только то, что необходимо.

  • telegram_user_id — идентификатор пользователя в Telegram
  • first_seen_at — когда пользователь впервые взаимодействовал с ботом
  • Документация по идентификаторам и апдейтам Telegram: Telegram Bot API.

    Таблица пар текстов

    Эта таблица у вас уже появляется из проектирования контента.

    Таблица сессий

    Сессия — это «одно прохождение».

    Зачем хранить answered_rounds и correct_answers, если они считаются из answers?

  • для дашборда и быстрых выборок это удобно
  • но важно помнить: это кэш, который должен обновляться аккуратно (обычно в транзакции при записи ответа)
  • Таблица раундов

    Раунд — это «показ пары в рамках конкретной сессии».

  • round_index нужен, чтобы показывать «Раунд 3 из 10»
  • shown_at фиксирует момент, когда раунд реально показали пользователю
  • answered_at фиксирует момент ответа
  • Таблица ответов

    Ответ отделяем от раунда, чтобы:

  • обеспечить «0 или 1 ответ на раунд» через уникальность
  • хранить поля, которые относятся именно к действию пользователя
  • Ключевой момент защиты от повторных кликов:

  • round_id UNIQUE гарантирует, что второй ответ на тот же раунд не вставится
  • Таблица событий (опционально)

    Она полезна, если вы хотите логировать дополнительные действия без изменения основной схемы.

    Пример: start, session_created, round_shown, answer_submitted, session_finished.

    Как писать данные при прохождении теста

    Ниже — практический «путь записи» для вашего бэкенда.

    Когда пользователь нажал /start

  • Найти пользователя по telegram_user_id
  • Если не найден — создать строку в users
  • Записать событие start (если используете events)
  • Важно: создание пользователя удобно делать как upsert, чтобы повторный /start не создавал дубликаты.

    Когда пользователь выбрал режим (5 или 10 раундов)

  • Создать sessions со статусом in_progress
  • Подобрать planned_rounds активных пар
  • Создать строки в rounds (обычно сразу пачкой)
  • Показать первый раунд
  • Практический совет: если вы делаете «создать все раунды заранее», вам проще:

  • гарантировать нужное число раундов
  • исключать повторы в рамках сессии
  • строить аналитику по «планировали vs ответили»
  • Когда вы показываете раунд пользователю

  • Сформировать текст A и B с учётом human_position
  • Обновить rounds.shown_at = now() (если ещё не выставлено)
  • Записать событие round_shown
  • Зачем обновлять shown_at?

  • чтобы считать время ответа
  • чтобы отличать «раунд создан» от «раунд реально показан»
  • Когда пользователь ответил на раунд

    Это самый критичный момент: здесь чаще всего появляются ошибки данных.

    Правильный подход:

  • В транзакции получить раунд и проверить, что он относится к активной сессии пользователя
  • Проверить, что ответа ещё нет
  • Посчитать правильность: choice == human_position
  • Вставить строку в answers
  • Обновить rounds.answered_at
  • Обновить агрегаты в sessions (answered_rounds, correct_answers)
  • Минимальная идея расчёта правильности:

  • если human_position = 'A', то правильный ответ — «A — человек»
  • если human_position = 'B', то правильный ответ — «B — человек»
  • Подсчёт ошибок и точности

    Метрики на уровне сессии

    Для результата пользователю обычно достаточно трёх чисел:

  • сколько ответил
  • сколько верных
  • сколько ошибок
  • Ошибки можно считать без отдельного поля:

    Где:

  • — число отвеченных раундов в сессии
  • — число правильных ответов в сессии
  • — число ошибок
  • Точность (accuracy) — доля правильных ответов:

    Где:

  • — точность (от 0 до 1)
  • — число правильных
  • — число отвеченных
  • Практическое правило:

  • если , точность не считаем (или считаем как 0, но это нужно явно решить в продукте)
  • Метрика сложности пары (ошибочность)

    Для дашборда «сложные/лёгкие пары» удобно считать долю ошибок по конкретной паре:

    Где:

  • — сколько раз пользователи ошиблись на этой паре
  • — сколько раз на эту пару вообще ответили
  • Чем выше , тем пара сложнее для людей.

    Примеры SQL-запросов для дашборда

    Ниже — примеры, которые обычно становятся первыми графиками в BI.

    Сессии по дням

    Средняя точность по дням

    Тут мы используем поля-кэши в sessions.

    Самые сложные пары

    Здесь HAVING count(*) >= 20 — простая защита от выводов по слишком малым данным.

    Перекос выбора A/B

    Чтобы анализировать перекос глубже, обычно сравнивают выбор с human_position.

    Время ответа: как считать и зачем оно нужно

    Самый надёжный способ хранения времени ответа:

  • сохранять rounds.shown_at при показе
  • сохранять rounds.answered_at при ответе
  • считать разницу уже в SQL или в приложении
  • Пример расчёта в SQL (в миллисекундах):

    Зачем это метрике и продукту:

  • отличать «угадываю наугад» (очень быстрые ответы) от «думаю»
  • искать пары, где люди стабильно тратят больше времени
  • Типовые ошибки в хранении и как их избежать

    Ошибка: ответ записывается, но непонятно, что именно показывали

    Решение:

  • хранить pair_id и human_position в rounds
  • не полагаться на то, что «в паре human всегда слева»
  • Ошибка: пользователь кликает кнопку два раза, а в БД два ответа

    Решение:

  • answers.round_id UNIQUE
  • обработка в транзакции: «если ответ уже существует, второй раз ничего не делаем»
  • Ошибка: пользователь нажал кнопку от старого сообщения

    Решение:

  • в callback_data передавать round_id
  • при обработке проверять, что раунд принадлежит активной сессии пользователя и ещё не отвечен
  • Ошибка: метрики считаются “криво”, потому что часть сессий отменена

    Решение:

  • хранить sessions.status и различать finished и cancelled
  • в дашборде явно фильтровать, какие статусы вы анализируете
  • Как эта схема помогает следующему шагу курса

    После того как у вас есть:

  • корректные таблицы users, sessions, rounds, answers
  • ограничения целостности и индексы
  • понятные SQL-агрегации
  • вы можете практически без дополнительных разработок:

  • подключить BI-инструмент к PostgreSQL
  • собрать дашборд: сессии по дням, точность, сложность пар, перекос A/B
  • начать улучшать контент на основе данных
  • Следующий логичный шаг в проекте: поднять инструмент аналитики (например, Metabase) и собрать первые визуализации на SQL-запросах, которые вы теперь понимаете и контролируете.

    6. Админка и аналитика: отчёты, сегменты, графики, выгрузки

    Админка и аналитика: отчёты, сегменты, графики, выгрузки

    Зачем в этом проекте «админка»

    Мы уже сделали ключевое: бот ведёт пользователя по тесту, а база хранит users, sessions, rounds, answers и (опционально) events. Теперь задача — превратить эти записи в управляемый продукт.

    Админка и аналитика нужны, чтобы вы могли:

  • понимать, сколько людей реально проходит тест и где они отваливаются
  • находить «плохие» пары текстов (слишком лёгкие, слишком палевные, некорректные)
  • сегментировать результаты (темы, жанры, длина, язык, режимы)
  • выгружать данные для отчётов и дальнейших исследований
  • Важно: в рамках курса под «админкой» мы обычно понимаем дашборд в BI-инструменте, а не отдельный самописный интерфейс. Это быстрее, дешевле и достаточно для MVP.

    Выбор инструмента: BI поверх PostgreSQL

    Самый практичный вариант для учебного проекта — подключить BI напрямую к PostgreSQL.

    Подходящие инструменты:

  • Metabase — быстрый старт, простой интерфейс
  • Apache Superset — мощная кастомизация, сложнее настройка
  • Grafana — удобна для тайм-серий, но менее дружелюбна к табличной аналитике продукта
  • Дальше будем ориентироваться на Metabase, потому что он чаще всего даёт лучший баланс «время до результата».

    Как выглядит поток данных для аналитики

    Смысл аналитического контура простой: бот пишет факты в PostgreSQL, а BI читает и визуализирует.

    !Общая схема: от ответов пользователей до дашборда

    Ключевое правило: дашборд должен строиться на устойчивых таблицах и полях, которые вы уже спроектировали в статье про хранение результатов.

    Подготовка базы к аналитике: витрины и представления

    Если строить графики прямо по «сырым» таблицам, вы быстро столкнётесь с повторяющимися джойнами и разными трактовками метрик. Поэтому хорошая практика даже для MVP — сделать несколько SQL-представлений (views), которые станут витринами.

    Витрина сессий

    Цель: одна строка = одна сессия, со статусом, режимом и итогом.

    Что здесь важно:

  • accuracy удобно иметь как вычисляемое поле для графиков
  • status позволяет честно исключать cancelled из «качества контента», но учитывать их в воронке
  • Витрина ответов (уровень раунда)

    Цель: одна строка = один ответ на конкретную пару в конкретной сессии.

    Что здесь важно:

  • human_position и choice нужны для анализа перекоса A/B
  • response_time_ms помогает отличить «осознанные» ответы от случайных кликов
  • Витрина контента (пары + метаданные)

    Цель: легко сегментировать ответы по topic, genre, language, difficulty_label.

    Если вы не хотите показывать тексты в BI (например, чтобы ограничить доступ), не включайте human_text и ai_text в витрину.

    Что должно быть на дашборде администратора

    Удобно мыслить дашбордами как наборами ответов на конкретные вопросы.

    Дашборд продукта

    Он отвечает на вопрос: бот вообще жив и им пользуются?

    Рекомендуемые блоки:

  • сессии по дням
  • уникальные пользователи по дням
  • доля завершённых сессий
  • средняя точность по дням
  • распределение точности (сколько сессий с 0–20%, 20–40% и так далее)
  • Пример запросов.

    Сессии по дням:

    Уникальные пользователи по дням:

    Дашборд воронки

    Он отвечает на вопрос: где отваливаются?

    Варианты «ступеней» воронки зависят от того, какие события вы логируете. Если вы пока не используете events, воронку можно приблизить через статусы и факты ответов:

  • сессия создана
  • есть хотя бы 1 ответ
  • сессия завершена (finished)
  • Пример:

    Дашборд качества контента

    Он отвечает на вопрос: какие пары работают хорошо, а какие ломают тест?

    Ключевые отчёты:

  • самые сложные пары (высокая доля ошибок)
  • самые лёгкие пары (низкая доля ошибок)
  • пары с подозрительно высокой скоростью ответов
  • качество по темам, жанрам, длинам, языкам
  • Топ самых сложных пар:

    Средняя точность по теме:

    Дашборд перекоса A/B

    Он отвечает на вопрос: не угадывают ли люди по позиции, а не по смыслу?

    Минимальные проверки:

  • как часто выбирают A и B
  • как часто выбор совпадает с human_position
  • Распределение выбора:

    Матрица «где был человек» vs «что выбрали»:

    Если вы видите, что пользователи сильно чаще жмут A независимо от human_position, это UX-проблема или эффект оформления, а не качество AI.

    Сегментация: как задавать «срезы», чтобы выводы были честными

    Сегментация нужна, чтобы сравнивать «похожесть AI на человека» не в среднем по больнице, а по управляемым условиям.

    Сегменты по контенту

    Это базовые сегменты из статьи про разметку пар:

  • language
  • topic
  • genre
  • length_bucket
  • difficulty_label (предварительная сложность)
  • Практическое правило: не делайте выводов по сегменту, где мало данных. В запросах удобно задавать минимальный порог через HAVING count(*) >= N.

    Сегменты по поведению пользователя

    Их можно собрать даже без персональных данных:

  • новые vs возвращающиеся
  • быстрые vs медленные ответы
  • короткие vs полные прохождения
  • Пример: «новый пользователь» — тот, у кого это первая сессия.

    Пример: сегмент по времени ответа (грубая классификация).

    Интерпретация:

  • если в сегменте fast точность близка к 50%, значит много случайных кликов
  • если slow даёт заметно выше точность, значит люди реально читают и думают
  • Какие графики и таблицы использовать

    Ниже — практичная «карта соответствий» между вопросом и визуализацией.

    | Вопрос | Лучший вид | По какой витрине строить | |---|---|---| | Сколько сессий каждый день | линейный график (time series) | v_sessions | | Сколько уникальных пользователей | линейный график | v_sessions | | Какая конверсия в завершение | столбцы или таблица по дням | v_sessions | | Где контент сложнее | столбчатый рейтинг | v_answers + v_pairs | | Есть ли перекос A/B | таблица 2×2 или stacked bar | v_answers | | Люди отвечают быстро или долго | гистограмма | v_answers |

    !Пример структуры админ-дашборда

    Выгрузки и регулярные отчёты

    Админке часто нужен не только экран, но и «выгрузить и отправить».

    Выгрузки из BI

    В Metabase можно выгружать результаты вопросов и таблиц (в зависимости от настроек и прав доступа). Самые полезные форматы для продукта:

  • CSV для анализа в Excel/Google Sheets
  • XLSX для отчётов
  • Практичные выгрузки для нашего проекта:

  • список пар с их error_rate, количеством ответов и метками (для решения «что отключить/переписать»)
  • сырые ответы по выбранному сегменту (например, только topic = travel)
  • список сессий с низкой точностью и очень быстрыми ответами (поиск «рандом-кликов»)
  • Регулярные отчёты

    Полезная управленческая привычка: настроить регулярную отправку краткого отчёта (например, раз в неделю):

  • сколько было сессий
  • сколько было уникальных пользователей
  • средняя точность
  • топ-5 самых сложных пар
  • Это дисциплинирует продуктовую работу: вы видите динамику, а не разовые цифры.

    Доступы и безопасность: минимальные правила

    Аналитика легко превращается в утечку данных, если не ограничить доступ.

    Роли

    Рекомендуемый минимум:

  • admin: полный доступ к дашбордам
  • viewer: только просмотр готовых дашбордов
  • Если вы храните тексты в BI, выделите отдельную роль для контент-менеджмента и не давайте её всем.

    Отдельный пользователь БД только на чтение

    Практика для продакшена и полезная привычка для учебного проекта:

  • создайте отдельного пользователя PostgreSQL
  • выдайте ему права только SELECT на витрины или нужные таблицы
  • Это снижает риск случайного удаления данных из BI-инструмента.

    Приватность

    Мы изначально проектировали систему так, чтобы персональные данные были минимальными. Поддерживайте это правило:

  • не добавляйте в аналитику поля, которые не нужны для метрик
  • не храните лишнее (username, имя) без явной причины
  • Контроль качества данных: что проверять до красивых графиков

    Два простых принципа:

  • лучше иметь меньше графиков, но на корректных данных
  • любые «странные» значения должны легко диагностироваться SQL-запросом
  • Минимальные проверки:

  • доля раундов без shown_at (раунд создан, но не показан)
  • доля ответов без response_time_ms (нет времени показа или ответа)
  • количество сессий со статусом finished, но answered_rounds = 0 (признак бага в логике)
  • Пример проверки отсутствующих временных меток:

    Как это связывается с развитием продукта

    Когда у вас есть дашборд, начинается циклическая продуктовая работа:

  • Смотреть метрики воронки и вовлечённости
  • Находить проблемные места (отвалы, перекос A/B)
  • Чистить и улучшать контент по отчётам качества пар
  • Добавлять новые пары и метки
  • Повторять, сравнивая динамику по неделям
  • Таким образом аналитика становится не «красивыми графиками», а инструментом улучшения теста и накопления качественных данных для исследования.

    Что дальше

    После того как вы:

  • подняли BI и подключили его к PostgreSQL
  • сделали витрины v_sessions, v_answers, v_pairs
  • собрали базовые дашборды
  • следующий шаг, который обычно сильно усиливает проект: управление контентом как процесс.

    Практически это означает:

  • регламент модерации пар
  • правила отключения и замены пар на основе метрик
  • версионирование контента и фиксация источника (source_type)
  • Если вы хотите превратить учебный проект в настоящий продукт, именно этот этап чаще всего даёт максимальный рост качества теста и доверия к результатам.

    7. Качество и запуск: тестирование, защита от накруток, деплой и мониторинг

    Качество и запуск: тестирование, защита от накруток, деплой и мониторинг

    Зачем этот этап нужен после бота, базы и дашборда

    К этому моменту у вас уже есть:

  • логика бота (команды, состояния, кнопки, обработка ответов)
  • модель данных (users, sessions, rounds, answers, опционально events)
  • аналитика и дашборд на BI (например, Metabase)
  • Теперь задача меняется: сделать так, чтобы система стабильно работала в реальном мире. Для этого нужны четыре практических блока:

  • тестирование, чтобы не ломать ключевые сценарии
  • защита от накруток, чтобы статистика не превращалась в шум
  • деплой, чтобы бот был доступен всегда, а обновления проходили предсказуемо
  • мониторинг, чтобы вы узнавали о проблемах раньше пользователей
  • !Общая карта: где находятся тестирование, деплой и мониторинг относительно бота и аналитики

    Тестирование: что именно проверять, чтобы проект считался качественным

    Тестирование в этом проекте нужно не ради «процента покрытия», а чтобы гарантировать две вещи:

  • пользователь всегда может пройти тест без логических тупиков
  • данные для аналитики записываются корректно и консистентно
  • Что тестировать в первую очередь

    Ниже список проверок, которые дают максимальный эффект для MVP.

  • Сценарий прохождения: старт → выбор режима → N раундов → итог.
  • Идемпотентность ответа: повторный клик по кнопке не создаёт второй ответ.
  • Защита от «старых кнопок»: нажатие на кнопку из прошлого раунда не должно менять текущий прогресс.
  • Корректность рандомизации A/B: человек иногда в A, иногда в B.
  • Запись времени: shown_at и answered_at ставятся в нужные моменты.
  • Завершение сессии: статус finished выставляется только когда раунды действительно пройдены.
  • Уровни тестов: как не перегрузить себя

    Удобно разделить тесты на три уровня.

  • Юнит-тесты: проверяют функции и бизнес-логику без настоящей базы и Telegram.
  • Интеграционные тесты: проверяют работу с PostgreSQL (ограничения, транзакции, индексы, миграции).
  • Сквозные проверки: имитируют реальный сценарий пользователя на уровне «вызвали обработчики последовательно».
  • Практичные инструменты для Python-стека:

  • pytest для тестов
  • Alembic для миграций
  • PostgreSQL как источник правды по ограничениям и транзакциям
  • Критичные тест-кейсы для данных и аналитики

    Эти тесты напрямую защищают ваш будущий дашборд.

  • Один раунд — один ответ
  • - ожидаемое поведение: при повторном нажатии вторая запись в answers не появляется - техническая опора: уникальность answers.round_id и транзакционная запись
  • Ответ всегда связан с контентом
  • - ожидаемое поведение: по любому ответу можно восстановить pair_id и human_position через rounds
  • Время ответа считается
  • - ожидаемое поведение: если раунд показан и отвечен, разница answered_at - shown_at не NULL
  • Сессии не «рисуются» как завершённые
  • - ожидаемое поведение: status = 'finished' не встречается вместе с answered_rounds = 0

    Ручное тестирование перед запуском

    Даже при автоматических тестах сделайте короткий ручной прогон.

  • пройти тест в режиме 5 и 10 раундов
  • нажать кнопку ответа дважды
  • нажать кнопку ответа из предыдущего раунда
  • прервать сессию командой /cancel и начать новую
  • проверить, что в БД появились ожидаемые строки и временные метки
  • Если вы используете webhook, отдельно проверьте, что обновления реально доходят до вашего сервиса (ошибки webhook могут быть незаметны без мониторинга).

    Документация Telegram по webhook:

  • SetWebhook
  • Защита от накруток: как сохранить честную статистику

    Накрутка в таком проекте бывает двух типов:

  • случайная: человек быстро кликает наугад или запускает много сессий подряд
  • намеренная: кто-то хочет испортить исследование или «пробить» топы
  • Вам важно не «запретить всё», а снизить влияние шума на метрики.

    Какие атаки и искажения реально возможны

  • Спам-сессии: один пользователь создаёт сотни прохождений.
  • Спам-ответы: быстрые клики по кнопкам (включая повторные клики).
  • Повторение контента: пользователь специально ищет знакомые пары и поднимает точность.
  • Перекос выбора: пользователь всегда жмёт «A», не читая.
  • Важно понимать ограничение: Telegram Bot API не даёт вам IP-адрес пользователя как стабильный идентификатор, поэтому базовая антифрод-логика строится вокруг telegram_user_id и поведенческих сигналов.

    Защита на уровне базы и бизнес-логики

    Это «железобетонный минимум», который почти не зависит от UI.

  • Идемпотентность ответов
  • - уникальность answers.round_id - обработчик ответа в транзакции: если ответ уже есть, ничего не меняем
  • Проверка принадлежности
  • - round должен принадлежать активной сессии этого пользователя - сессия должна быть со статусом in_progress
  • Проверка актуальности
  • - нельзя отвечать на раунд, если он уже answered_at заполнен - нельзя отвечать после завершения сессии

    Эти проверки защищают от повторных кликов и «старых кнопок», а также от попыток подставлять чужие round_id.

    Rate limit и охлаждение: простая антиспам-мера

    Самая практичная защита от «засорения» данных:

  • ограничить создание сессий, например не чаще одной в X секунд для одного telegram_user_id
  • ограничить частоту ответов, если видите, что ответы идут «нереалистично быстро»
  • Где хранить счётчики:

  • в PostgreSQL (быстрее сделать, но нагрузка выше)
  • в отдельном кэше, если он у вас есть (например, Redis)
  • Если вы ограничиваетесь PostgreSQL, можно начать с простого правила на уровне приложения: перед созданием новой сессии проверять последнюю created_at для пользователя.

    Детектирование накрутки через аналитику

    Даже если вы не блокируете пользователя, вы можете помечать и исключать «подозрительные» ответы из продуктовых выводов.

    Признаки, которые особенно полезны в вашем проекте:

  • аномально маленькое время ответа (response_time_ms)
  • слишком много сессий за короткий период у одного пользователя
  • слишком много одинаковых паттернов выбора (например, почти всегда A)
  • Практический подход: в дашборде сделать отдельный отчёт качества данных и фильтры, которые позволяют исключать подозрительные ответы.

    Пример простого правила для анализа (как идея, не как «единственно верная истина»):

  • считать «быстрым» ответом всё, что меньше 800–1200 мс
  • если у пользователя большая доля таких ответов, его сессии пометить как шум
  • Повторы контента и защита выборки

    Чтобы пользователь не «учил» пары:

  • не показывайте одну и ту же пару одному пользователю в пределах нескольких последних сессий
  • храните факт показа пары через rounds и выбирайте новые пары с учётом истории
  • Это одновременно улучшает игровой опыт и делает метрики сложности пар более честными.

    Деплой: как запустить бота и аналитику так, чтобы обновления не были стрессом

    В учебном проекте почти всегда побеждает связка:

  • Docker для сборки и одинаковой среды
  • Docker Compose для запуска сервисов
  • webhook в продакшене
  • Ссылки:

  • Docker
  • Docker Compose
  • Рекомендуемая схема на сервере

    Минимальный набор сервисов:

  • bot-service
  • postgres
  • metabase (или другой BI)
  • reverse proxy для HTTPS и проксирования webhook
  • !Схема размещения сервисов и сетевых потоков на сервере

    HTTPS и webhook

    Webhook требует публичного HTTPS-адреса.

    Два типовых подхода:

  • Nginx как reverse proxy + сертификаты Let's Encrypt
  • Caddy, который часто упрощает получение и продление сертификатов
  • Ссылки:

  • Nginx
  • Caddy
  • Let’s Encrypt
  • После того как HTTPS готов, вы настраиваете webhook на URL вашего бота.

    Документация:

  • SetWebhook
  • Миграции базы как часть деплоя

    Если вы меняете схему БД (а вы почти неизбежно будете её менять), нужно правило:

  • миграции выполняются перед запуском новой версии приложения
  • На практике это можно сделать так:

  • отдельная команда в CI/CD
  • или отдельный контейнер/entrypoint шаг, который запускает Alembic
  • Важно: миграции должны быть версионированы и воспроизводимы, иначе вы получите ситуацию «у меня локально работает, а на сервере нет».

    Секреты и конфигурация

    Минимальный набор секретов:

  • TELEGRAM_TOKEN
  • DATABASE_URL
  • (если используете) DSN для ошибок (например, Sentry)
  • Правила:

  • не хранить секреты в репозитории
  • использовать переменные окружения и отдельный .env на сервере
  • Резервные копии

    Если вы собираете результаты и строите отчёты, бэкап PostgreSQL обязателен.

    Минимально разумный режим:

  • ежедневный pg_dump
  • хранение копий вне сервера (или хотя бы в отдельном томе/хранилище)
  • Официальная документация:

  • pg_dump
  • Мониторинг: как понять, что бот жив, а данные не испортились

    Мониторинг в этом проекте делится на три слоя:

  • мониторинг доступности (бот отвечает, webhook работает)
  • мониторинг ошибок приложения (исключения, падения)
  • мониторинг качества данных (метрики и витрины не «поехали»)
  • Логи: что писать, чтобы потом можно было расследовать инциденты

    Логи должны отвечать на вопрос: что случилось с конкретной сессией пользователя.

    Рекомендации:

  • писать структурированные логи (хотя бы ключ=значение)
  • включать идентификаторы: telegram_user_id, session_id, round_id
  • логировать основные переходы: создание сессии, показ раунда, запись ответа, завершение
  • отделять уровень info (нормальные события) от error (ошибки)
  • Важно: не логируйте TELEGRAM_TOKEN и не храните в логах чувствительные данные.

    Трекинг ошибок

    Чтобы не читать логи вручную, подключают сервис сбора ошибок.

    Популярный вариант:

  • Sentry
  • Что он даст:

  • группировку исключений
  • стек-трейсы
  • алерты по всплеску ошибок
  • Метрики и алерты

    Даже для MVP полезно иметь несколько простых метрик:

  • число входящих обновлений в минуту
  • число созданных сессий в минуту
  • доля ошибок обработки
  • задержка ответа (примерно)
  • Если вы хотите «классический» стек метрик:

  • Prometheus для сбора
  • Grafana для дашборда
  • !Карта наблюдаемости: логи, ошибки, метрики, проверки данных

    Если не хотите поднимать Prometheus, можно начать проще:

  • health endpoint в приложении
  • внешний мониторинг доступности URL (любой сервис, который пингует ваш endpoint)
  • Мониторинг качества данных

    Это отличительная особенность проекта с аналитикой: даже если бот «жив», данные могут быть испорчены логической ошибкой.

    Минимальные ежедневные проверки (SQL или запросы в BI):

  • доля раундов без shown_at
  • доля ответов без answered_at (если ответ записан)
  • сессии finished, у которых answered_rounds не совпадает с количеством answers
  • резкий рост доли выбора A (возможная UX-проблема)
  • Хорошая практика:

  • сделать отдельную страницу в админ-дашборде «качество данных»
  • поставить простое правило: если метрика выходит за пределы, вы проверяете релиз
  • План запуска: как выкатывать, не рискуя всем сразу

    Пре-запусковой чек-лист

  • бот проходит ручной прогон основных сценариев
  • в базе есть хотя бы 30–50 активных пар
  • рандомизация A/B включена и отражается в rounds.human_position
  • BI подключён пользователем БД только на чтение
  • настроены бэкапы PostgreSQL
  • подключён сбор ошибок (или хотя бы понятные логи)
  • Мягкий запуск

  • сначала дайте бот небольшой группе
  • соберите первые ответы
  • проверьте отчёты перекоса A/B и времени ответа
  • отключите пары, которые «палятся» или ломают смысл теста
  • После запуска: ритм улучшений

    Правильный цикл после запуска выглядит так:

  • вы выпускаете изменения небольшими порциями
  • проверяете, не изменилась ли воронка и распределения ответов
  • улучшаете контент на основе отчётов сложности пар
  • Смысл: качество проекта определяется не «одним релизом», а тем, насколько быстро вы видите проблемы и исправляете их, не разрушая данные.

    Итог: что означает «готово к продакшену» для учебного продукта

    Ваш бот готов к запуску, если выполняются условия:

  • ключевые сценарии протестированы (авто или ручными чек-листами)
  • ответы записываются идемпотентно, «старые кнопки» не портят данные
  • есть базовая защита от спама (хотя бы cooldown на создание сессий)
  • деплой воспроизводим (Docker Compose, миграции, секреты)
  • вы видите ошибки и можете доказать корректность данных через витрины и проверки
  • После этого проект становится настоящим продуктом: вы можете привлекать пользователей, собирать статистику и улучшать тест Тьюринга итеративно, не теряя доверия к аналитике.