Стата «Заявки» из воронки: запись лидов при конверсии
Воронка перестала писать в 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:
@@ -729,6 +729,7 @@ class VKBotApplication:
|
||||
self._baza_started(user_id)
|
||||
db.set_profile_field(user_id, "source", "Основное меню")
|
||||
db.add_metka(user_id, "source_main_menu")
|
||||
db.set_profile_field(user_id, "lead_recorded", False) # новый заход = новая возможная заявка
|
||||
p = db.get_profile(user_id)
|
||||
if any(p.get(f) for f in ("role", "age", "game", "level")):
|
||||
self.send_message(
|
||||
@@ -954,12 +955,26 @@ class VKBotApplication:
|
||||
self._baza_to_manager(user_id, reason="Нужен совет по формату",
|
||||
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"):
|
||||
"""Показывает ссылку на оплату/запись + кнопки «проблема с оплатой» / «вопрос менеджеру»."""
|
||||
link = self._utm(self._link(link_key), campaign)
|
||||
db.set_profile_field(user_id, "tariff", tariff_label)
|
||||
db.add_metka(user_id, "clicked_payment")
|
||||
db.add_metka(user_id, "lead_hot")
|
||||
self._baza_record_lead(user_id)
|
||||
db.update_user_state(user_id, "BAZA_PAY")
|
||||
kb = VkKeyboard(inline=True)
|
||||
kb.add_openlink_button("Перейти к оплате 💳", link)
|
||||
@@ -1001,6 +1016,7 @@ class VKBotApplication:
|
||||
db.set_profile_field(user_id, "keyword", "лето")
|
||||
db.set_profile_field(user_id, "interest_summer", True)
|
||||
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")
|
||||
self.send_message(
|
||||
user_id,
|
||||
@@ -1179,6 +1195,7 @@ class VKBotApplication:
|
||||
db.add_metka(user_id, "manager_requested")
|
||||
from datetime import datetime
|
||||
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)
|
||||
self._notify_operators("💬 Запрос менеджеру\n\n" + card)
|
||||
try:
|
||||
@@ -1413,9 +1430,6 @@ class VKBotApplication:
|
||||
ls = db.get_lead_stats()
|
||||
ds = db.get_dialogue_stats()
|
||||
|
||||
total = ls["total"]
|
||||
marketing_pct = round(ls["marketing_yes"] / total * 100) if total else 0
|
||||
|
||||
service_lines = ""
|
||||
if ls["by_service"]:
|
||||
for svc_name, cnt in ls["by_service"]:
|
||||
@@ -1425,19 +1439,15 @@ class VKBotApplication:
|
||||
|
||||
msg = (
|
||||
"📊 Статистика Кибершколы БАЗА\n\n"
|
||||
"🗂 Заявки (всего): {total}\n"
|
||||
"🗂 Заявки из бота (всего): {total}\n"
|
||||
" • сегодня: {today}\n"
|
||||
" • за 7 дней: {last7}\n"
|
||||
" • из бота: {from_bot}\n\n"
|
||||
"📨 Согласны на рассылку: {mk_yes} из {total} ({mk_pct}%)\n\n"
|
||||
"🎮 По направлениям:\n{services}\n"
|
||||
" • за 7 дней: {last7}\n\n"
|
||||
"🎯 По выбранному тарифу/формату:\n{services}\n"
|
||||
"💬 Диалоги:\n"
|
||||
" • уникальных пользователей: {uniq}\n"
|
||||
" • сообщений от клиентов: {umsg}"
|
||||
).format(
|
||||
total=total, today=ls["today"], last7=ls["last7"],
|
||||
from_bot=ls["from_bot"],
|
||||
mk_yes=ls["marketing_yes"], mk_pct=marketing_pct,
|
||||
total=ls["total"], today=ls["today"], last7=ls["last7"],
|
||||
services=service_lines, uniq=ds["unique_users"], umsg=ds["user_messages"]
|
||||
)
|
||||
self.send_message(user_id, msg)
|
||||
|
||||
@@ -62,6 +62,18 @@ def test_baza_other_game_goes_to_manager(app):
|
||||
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):
|
||||
"""§15: уже сохранённые ответы не переспрашиваются."""
|
||||
u = 70020
|
||||
|
||||
Reference in New Issue
Block a user