Стата «Заявки» из воронки: запись лидов при конверсии

Воронка перестала писать в leads после ребрендинга — блок «Заявки» был всегда 0.
Теперь при конверсии (нажал оплату/пробное или передан менеджеру) бот фиксирует
заявку в leads (source=bot, имя из VK, услуга = тариф/формат/игра), один раз на заход
(флаг lead_recorded сбрасывается при новом старте воронки/интенсива). Разбивка «по
тарифу/формату» снова показывает реальные цифры.

Статистика почищена: убраны «из бота» (все заявки теперь из бота) и строка «согласны
на рассылку» (воронка не собирает это согласие) — не вводят в заблуждение.

Тест: test_baza_records_lead_once. pytest 34 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kenik
2026-07-23 20:27:21 +03:00
parent 72eeccdc3a
commit 7889a19744
2 changed files with 33 additions and 11 deletions
+21 -11
View File
@@ -729,6 +729,7 @@ class VKBotApplication:
self._baza_started(user_id) self._baza_started(user_id)
db.set_profile_field(user_id, "source", "Основное меню") db.set_profile_field(user_id, "source", "Основное меню")
db.add_metka(user_id, "source_main_menu") db.add_metka(user_id, "source_main_menu")
db.set_profile_field(user_id, "lead_recorded", False) # новый заход = новая возможная заявка
p = db.get_profile(user_id) p = db.get_profile(user_id)
if any(p.get(f) for f in ("role", "age", "game", "level")): if any(p.get(f) for f in ("role", "age", "game", "level")):
self.send_message( self.send_message(
@@ -954,12 +955,26 @@ class VKBotApplication:
self._baza_to_manager(user_id, reason="Нужен совет по формату", self._baza_to_manager(user_id, reason="Нужен совет по формату",
client_text="Менеджер скоро подключится к диалогу. Можете дополнить запрос здесь.") client_text="Менеджер скоро подключится к диалогу. Можете дополнить запрос здесь.")
def _baza_record_lead(self, user_id: int):
"""Фиксирует заявку в журнале leads (для статистики) — один раз на заход воронки."""
p = db.get_profile(user_id)
if p.get("lead_recorded"):
return
name = self._vk_name(user_id) or ""
service = p.get("tariff") or p.get("format") or p.get("game") or ""
try:
db.add_lead(user_id, source="bot", name=name, phone="", service=service)
db.set_profile_field(user_id, "lead_recorded", True)
except Exception as e:
logger.error(f"Не удалось записать лид в статистику: {e}")
def _baza_send_payment(self, user_id: int, link_key: str, tariff_label: str, campaign: str = "main"): def _baza_send_payment(self, user_id: int, link_key: str, tariff_label: str, campaign: str = "main"):
"""Показывает ссылку на оплату/запись + кнопки «проблема с оплатой» / «вопрос менеджеру».""" """Показывает ссылку на оплату/запись + кнопки «проблема с оплатой» / «вопрос менеджеру»."""
link = self._utm(self._link(link_key), campaign) link = self._utm(self._link(link_key), campaign)
db.set_profile_field(user_id, "tariff", tariff_label) db.set_profile_field(user_id, "tariff", tariff_label)
db.add_metka(user_id, "clicked_payment") db.add_metka(user_id, "clicked_payment")
db.add_metka(user_id, "lead_hot") db.add_metka(user_id, "lead_hot")
self._baza_record_lead(user_id)
db.update_user_state(user_id, "BAZA_PAY") db.update_user_state(user_id, "BAZA_PAY")
kb = VkKeyboard(inline=True) kb = VkKeyboard(inline=True)
kb.add_openlink_button("Перейти к оплате 💳", link) kb.add_openlink_button("Перейти к оплате 💳", link)
@@ -1001,6 +1016,7 @@ class VKBotApplication:
db.set_profile_field(user_id, "keyword", "лето") db.set_profile_field(user_id, "keyword", "лето")
db.set_profile_field(user_id, "interest_summer", True) db.set_profile_field(user_id, "interest_summer", True)
db.add_metka(user_id, "interest_summer_intensive") db.add_metka(user_id, "interest_summer_intensive")
db.set_profile_field(user_id, "lead_recorded", False) # новый заход = новая возможная заявка
db.update_user_state(user_id, "BAZA_INT_INFO") db.update_user_state(user_id, "BAZA_INT_INFO")
self.send_message( self.send_message(
user_id, user_id,
@@ -1179,6 +1195,7 @@ class VKBotApplication:
db.add_metka(user_id, "manager_requested") db.add_metka(user_id, "manager_requested")
from datetime import datetime from datetime import datetime
db.set_profile_field(user_id, "manager_at", datetime.now().strftime("%Y-%m-%d %H:%M")) db.set_profile_field(user_id, "manager_at", datetime.now().strftime("%Y-%m-%d %H:%M"))
self._baza_record_lead(user_id)
card = self._baza_manager_card(user_id, reason) card = self._baza_manager_card(user_id, reason)
self._notify_operators("💬 Запрос менеджеру\n\n" + card) self._notify_operators("💬 Запрос менеджеру\n\n" + card)
try: try:
@@ -1413,9 +1430,6 @@ class VKBotApplication:
ls = db.get_lead_stats() ls = db.get_lead_stats()
ds = db.get_dialogue_stats() ds = db.get_dialogue_stats()
total = ls["total"]
marketing_pct = round(ls["marketing_yes"] / total * 100) if total else 0
service_lines = "" service_lines = ""
if ls["by_service"]: if ls["by_service"]:
for svc_name, cnt in ls["by_service"]: for svc_name, cnt in ls["by_service"]:
@@ -1425,19 +1439,15 @@ class VKBotApplication:
msg = ( msg = (
"📊 Статистика Кибершколы БАЗА\n\n" "📊 Статистика Кибершколы БАЗА\n\n"
"🗂 Заявки (всего): {total}\n" "🗂 Заявки из бота (всего): {total}\n"
" • сегодня: {today}\n" " • сегодня: {today}\n"
" • за 7 дней: {last7}\n" " • за 7 дней: {last7}\n\n"
" • из бота: {from_bot}\n\n" "🎯 По выбранному тарифу/формату:\n{services}\n"
"📨 Согласны на рассылку: {mk_yes} из {total} ({mk_pct}%)\n\n"
"🎮 По направлениям:\n{services}\n"
"💬 Диалоги:\n" "💬 Диалоги:\n"
" • уникальных пользователей: {uniq}\n" " • уникальных пользователей: {uniq}\n"
" • сообщений от клиентов: {umsg}" " • сообщений от клиентов: {umsg}"
).format( ).format(
total=total, today=ls["today"], last7=ls["last7"], total=ls["total"], today=ls["today"], last7=ls["last7"],
from_bot=ls["from_bot"],
mk_yes=ls["marketing_yes"], mk_pct=marketing_pct,
services=service_lines, uniq=ds["unique_users"], umsg=ds["user_messages"] services=service_lines, uniq=ds["unique_users"], umsg=ds["user_messages"]
) )
self.send_message(user_id, msg) self.send_message(user_id, msg)
+12
View File
@@ -62,6 +62,18 @@ def test_baza_other_game_goes_to_manager(app):
assert "Valorant" in _prof(u)["game"] assert "Valorant" in _prof(u)["game"]
def test_baza_records_lead_once(app):
"""Воронка пишет заявку в leads при конверсии; оплата+проблема = 1 заявка (без дублей)."""
u = 70030
for m in ["Подобрать тренировку 🎯", "Для себя", "16 лет и старше", "CS2",
"Играет для себя", "Групповые тренировки", "Купить 4 тренировки"]:
app.handle_message(u, m)
app.handle_message(u, "Проблема с оплатой") # тот же заход → не должно создать вторую
s = bot.db.get_lead_stats()
assert s["total"] == 1 and s["from_bot"] == 1
assert any("Групповой" in name for name, _ in s["by_service"])
def test_baza_smart_skip_known_fields(app): def test_baza_smart_skip_known_fields(app):
"""§15: уже сохранённые ответы не переспрашиваются.""" """§15: уже сохранённые ответы не переспрашиваются."""
u = 70020 u = 70020