Стата «Заявки» из воронки: запись лидов при конверсии
Воронка перестала писать в 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)
|
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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user