Этот документ описывает, какие метрики доступны в текущей версии скрипта и как ими пользоваться.
Таблица funnel_entries используется для базовой аналитики воронок.
В таблице есть уникальный индекс по (email, funnel_type, test_id), который обеспечивает идемпотентность на уровне базы данных. Это ограничение гарантирует, что:
- Один и тот же пользователь не может быть добавлен в одну и ту же воронку несколько раз для одного теста.
- Дублирующиеся записи предотвращаются даже при параллельном выполнении сервиса синхронизации.
- Сервис можно безопасно запускать несколько раз без создания дублирующихся записей.
Когда сервис пытается вставить дублирующуюся запись, нарушение ограничения базы данных обрабатывается корректно: транзакция откатывается, и записывается информационное сообщение в лог. Исключение не выбрасывается, что позволяет сервису продолжить обработку других кандидатов.
Эта гарантия идемпотентности устраняет гонки (TOCTOU - Time-Of-Check-Time-Of-Use), которые могли возникнуть при проверке существующих записей перед вставкой. Ограничение на уровне базы данных предоставляет надежный, атомарный механизм для предотвращения дубликатов.
Важные поля:
-
emailEmail пользователя.
-
funnel_typeТип воронки:
-
languageдля языковых тестов; -
non_languageдля неязыковых тестов.
-
-
user_id,test_idСвязь с тестом и пользователем в базе MODX.
-
entered_atДата и время попадания пользователя в воронку.
-
certificate_purchasedФлаг:
-
0сертификат не куплен; -
1сертификат куплен.
-
-
certificate_purchased_atДата и время покупки сертификата. Заполняется при синхронизации с таблицами MODX.
Перед применением миграции с уникальным ограничением в продакшене оператор должен проверить наличие существующих дублирующихся записей в таблице funnel_entries. Это можно сделать с помощью диагностического скрипта:
python -m scripts.find_funnel_duplicatesСкрипт запрашивает базу данных на предмет групп записей, которые имеют одинаковую комбинацию (email, funnel_type, test_id), и отображает их в читаемом табличном формате. Вывод включает:
- Дублирующуюся комбинацию email, типа воронки и ID теста
- Количество дублирующихся записей для каждой комбинации
- Минимальный и максимальный ID записей
- Самые ранние и поздние временные метки записей
Важно: Если дубликаты найдены, их необходимо очистить вручную или через MySQL-скрипты перед запуском миграции, которая добавляет уникальное ограничение. Миграция завершится ошибкой, если дублирующиеся записи существуют при применении ограничения.
Скрипт строго предназначен только для чтения и не изменяет базу данных каким-либо образом. Его безопасно запускать на продакшн-базах данных в диагностических целях.
Таблица brevo_sync_outbox хранит ожидающие и обработанные задачи для синхронизации с Brevo. Эта таблица реализует паттерн outbox, разделяя записи в базу данных и внешние вызовы API для обеспечения надежной обработки операций Brevo.
Каждая строка в outbox связана с записью воронки через поле funnel_entry_id, которое ссылается на funnel_entries.id. Таблица отслеживает статус операции, попытки повтора и информацию об ошибках для поддержки надежной доставки вызовов API Brevo.
Задачи в outbox могут иметь следующие статусы:
-
pending: Задача ожидает обработки или запланирована для повторной попытки. Задачи со статусомstatus='pending'иnext_attempt_at IS NULL OR next_attempt_at <= NOW()доступны для обработки. -
success: Задача успешно обработана. Задача больше не обрабатывается воркером. -
failed: Задача окончательно провалилась после превышения максимального количества попыток или при возникновении фатальной ошибки. Задача больше не обрабатывается воркером.
Outbox реализует явное планирование повторных попыток с использованием retry_count и next_attempt_at:
-
retry_count: Отслеживает количество попыток обработки задачи. Этот счетчик увеличивается каждый раз, когда задача завершается ошибкой. -
next_attempt_at: Поле типа DATETIME, которое указывает, когда задача должна быть повторно обработана. Это поле используется для планирования повторных попыток с экспоненциальной задержкой:- Когда задача завершается ошибкой с временной ошибкой и
retry_countвсе еще ниже максимума (по умолчанию: 5), статус задачи остаетсяpending, аnext_attempt_atустанавливается вNOW() + INTERVAL (retry_count * 5) MINUTE. - Когда задача завершается фатальной ошибкой или превышает максимальное количество попыток,
next_attempt_atустанавливается вNULL, а статус задачи устанавливается вfailed.
- Когда задача завершается ошибкой с временной ошибкой и
-
Максимальное количество попыток: По умолчанию задачи повторяются до 5 раз. После превышения максимального количества попыток задача помечается как
failedи больше не обрабатывается воркером.
Воркер обрабатывает только задачи, где status='pending' и (next_attempt_at IS NULL OR next_attempt_at <= NOW()), что гарантирует соблюдение запланированных повторных попыток и предотвращает обработку задач до запланированного времени.
Для просмотра конверсии по воронкам используется скрипт:
python -m app.report_conversions
Пример вывода:
Funnel conversion report
------------------------
language: entries=10, purchased=3, conversion=30.00%
non_language: entries=5, purchased=1, conversion=20.00%
Где:
entriesколичество пользователей, которые попали в воронку;purchasedколичество пользователей, которые купили сертификат после входа в воронку;conversionотношениеpurchased / entriesв процентах.
Можно указать период:
python -m app.report_conversions --from-date 2024-01-01 --to-date 2025-01-01
--from-dateвключительно;--to-dateисключительно.
Если указать только --from-date, будут учтены записи с этой даты и до текущего момента.
Если параметры не указаны, берутся все записи из funnel_entries.
Примеры вопросов, на которые можно ответить:
- Сколько людей попало в языковую воронку за последний месяц?
- Какова конверсия из воронки в покупку сертификата по языковым тестам?
- Как конверсия языковых тестов отличается от неязыковых?
Для более детальной аналитики можно использовать SQL-запросы к funnel_entries, комбинируя условия по email, user_id, test_id и времени.
Сейчас скрипт не изменяет содержимое писем, а только отправляет контакты в Brevo. Для расширенной аналитики через UTM-метки можно использовать следующий подход:
-
В шаблонах писем Brevo добавить UTM-метки в ссылки, например:
-
для языковой воронки:
?utm_source=testizer&utm_medium=email&utm_campaign=language_funnel -
для неязыковой:
?utm_source=testizer&utm_medium=email&utm_campaign=non_language_funnel
-
-
В системах аналитики (например, Google Analytics или аналогах) анализировать переходы по этим UTM.
-
При необходимости можно связать внешнюю аналитику с данными
funnel_entriesпо email и временным периодам.
При таком подходе структура базы и скрипта не меняется, а расширенная аналитика настраивается через шаблоны писем и внешние отчеты.
Если в будущем понадобится более детализированная аналитика, можно:
- добавить в
funnel_entriesполеsource, напримерemail_language_v1,email_non_language_v1; - записывать туда версию воронки или кампании;
- строить отчеты по комбинации
funnel_type+source.
Текущая архитектура уже готова к таким расширениям, так как:
- вход в воронку фиксируется централизованно в
funnel_entries; - покупка сертификата привязывается к тем же записям;
- отчет
app.report_conversionsможно расширять без изменения основного бизнес-кода.