Умный пропуск шагов воронки при уже сохранённых данных (§15)

- _baza_advance: воронка переходит к первому НЕзаполненному шагу, пропуская роль/
  возраст/игру/уровень, если они уже известны; формат (выбор действия) спрашивается всегда.
- На повторном входе — короткая подсказка, что известные ответы не переспрашиваются
  и их можно изменить кнопкой «Назад» (back-навигация остаётся линейной = override).
- Интенсив: известный возраст (из подбора) не переспрашивается — сразу уровень CS2.
- Данные из ветки «лето» сохраняются и переиспользуются в подборе, и наоборот.

Тесты: test_baza_smart_skip_known_fields, test_baza_intensive_skips_known_age. pytest 33 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kenik
2026-07-23 20:10:15 +03:00
parent 4095b2ec61
commit 12ce9c0807
2 changed files with 76 additions and 17 deletions
+45 -12
View File
@@ -695,17 +695,48 @@ class VKBotApplication:
return True return True
return False return False
# Шаги воронки: порядок и какое поле профиля заполняет каждый (format — без поля,
# это выбор действия, поэтому спрашивается всегда).
_FUNNEL_ORDER = ["BAZA_FORWHO", "BAZA_AGE", "BAZA_GAME", "BAZA_LEVEL", "BAZA_FORMAT"]
_FUNNEL_FIELD = {"BAZA_FORWHO": "role", "BAZA_AGE": "age", "BAZA_GAME": "game", "BAZA_LEVEL": "level"}
def _baza_ask(self, user_id: int, state: str):
"""Задаёт вопрос конкретного шага воронки и ставит соответствующее состояние."""
prompts = {
"BAZA_FORWHO": ("Для кого подбираем тренировки?", get_baza_forwho_keyboard()),
"BAZA_AGE": ("Сколько лет игроку?", get_baza_age_keyboard()),
"BAZA_GAME": ("По какой игре хотите заниматься?", get_baza_game_keyboard()),
"BAZA_LEVEL": ("Как бы вы описали текущий уровень игрока?", get_baza_level_keyboard()),
"BAZA_FORMAT": ("Какой формат рассматриваете?", get_baza_format_keyboard()),
}
msg, kb = prompts[state]
db.update_user_state(user_id, state)
self.send_message(user_id, msg, keyboard=kb)
def _baza_advance(self, user_id: int, from_state=None):
"""Переходит к следующему шагу воронки, ПРОПУСКАЯ уже заполненные поля (§15)."""
p = db.get_profile(user_id)
start = 0 if from_state is None else self._FUNNEL_ORDER.index(from_state) + 1
for state in self._FUNNEL_ORDER[start:]:
field = self._FUNNEL_FIELD.get(state)
if not field or not p.get(field):
self._baza_ask(user_id, state)
return
self._baza_ask(user_id, "BAZA_FORMAT")
def _baza_start_funnel(self, user_id: int): def _baza_start_funnel(self, user_id: int):
"""Старт воронки «Подобрать тренировку».""" """Старт воронки «Подобрать тренировку» (уже известные ответы не переспрашиваем)."""
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.update_user_state(user_id, "BAZA_FORWHO") p = db.get_profile(user_id)
if any(p.get(f) for f in ("role", "age", "game", "level")):
self.send_message( self.send_message(
user_id, user_id,
"Для кого подбираем тренировки?", "С возвращением! Что вы уже указывали, переспрашивать не буду. "
keyboard=get_baza_forwho_keyboard() "Если нужно изменить прошлый ответ — нажмите «Назад ⬅️»."
) )
self._baza_advance(user_id, None)
def _handle_baza_forwho(self, user_id: int, text: str, text_lower: str): def _handle_baza_forwho(self, user_id: int, text: str, text_lower: str):
if self._baza_nav(user_id, text_lower): if self._baza_nav(user_id, text_lower):
@@ -719,8 +750,7 @@ class VKBotApplication:
else: else:
self.send_message(user_id, "Выберите вариант на кнопках ниже 👇", keyboard=get_baza_forwho_keyboard()) self.send_message(user_id, "Выберите вариант на кнопках ниже 👇", keyboard=get_baza_forwho_keyboard())
return return
db.update_user_state(user_id, "BAZA_AGE") self._baza_advance(user_id, "BAZA_FORWHO")
self.send_message(user_id, "Сколько лет игроку?", keyboard=get_baza_age_keyboard())
def _handle_baza_age(self, user_id: int, text: str, text_lower: str): def _handle_baza_age(self, user_id: int, text: str, text_lower: str):
if self._baza_nav(user_id, text_lower): if self._baza_nav(user_id, text_lower):
@@ -750,8 +780,7 @@ class VKBotApplication:
self.send_message(user_id, "Выберите возраст на кнопках ниже 👇", keyboard=get_baza_age_keyboard()) self.send_message(user_id, "Выберите возраст на кнопках ниже 👇", keyboard=get_baza_age_keyboard())
return return
db.set_profile_field(user_id, "age", age) db.set_profile_field(user_id, "age", age)
db.update_user_state(user_id, "BAZA_GAME") self._baza_advance(user_id, "BAZA_AGE")
self.send_message(user_id, "По какой игре хотите заниматься?", keyboard=get_baza_game_keyboard())
def _handle_baza_young(self, user_id: int, text: str, text_lower: str): def _handle_baza_young(self, user_id: int, text: str, text_lower: str):
if text_lower.startswith("обсудить"): if text_lower.startswith("обсудить"):
@@ -793,8 +822,7 @@ class VKBotApplication:
return return
db.set_profile_field(user_id, "game", game) db.set_profile_field(user_id, "game", game)
db.add_metka(user_id, metka) db.add_metka(user_id, metka)
db.update_user_state(user_id, "BAZA_LEVEL") self._baza_advance(user_id, "BAZA_GAME")
self.send_message(user_id, "Как бы вы описали текущий уровень игрока?", keyboard=get_baza_level_keyboard())
def _handle_baza_game_other(self, user_id: int, text: str, text_lower: str): def _handle_baza_game_other(self, user_id: int, text: str, text_lower: str):
if self._baza_nav(user_id, text_lower): if self._baza_nav(user_id, text_lower):
@@ -827,8 +855,7 @@ class VKBotApplication:
self.send_message(user_id, "Выберите уровень на кнопках ниже 👇", keyboard=get_baza_level_keyboard()) self.send_message(user_id, "Выберите уровень на кнопках ниже 👇", keyboard=get_baza_level_keyboard())
return return
db.set_profile_field(user_id, "level", match) db.set_profile_field(user_id, "level", match)
db.update_user_state(user_id, "BAZA_FORMAT") self._baza_advance(user_id, "BAZA_LEVEL")
self.send_message(user_id, "Какой формат рассматриваете?", keyboard=get_baza_format_keyboard())
def _handle_baza_format(self, user_id: int, text: str, text_lower: str): def _handle_baza_format(self, user_id: int, text: str, text_lower: str):
if self._baza_nav(user_id, text_lower): if self._baza_nav(user_id, text_lower):
@@ -985,6 +1012,12 @@ class VKBotApplication:
if self._baza_nav(user_id, text_lower): if self._baza_nav(user_id, text_lower):
return return
if "участвовать" in text_lower or "записаться" in text_lower or "хочу" in text_lower or "да" == text_lower: if "участвовать" in text_lower or "записаться" in text_lower or "хочу" in text_lower or "да" == text_lower:
age = (db.get_profile(user_id).get("age") or "").lower()
if age and "младше 12" not in age:
# возраст уже известен из подбора — не переспрашиваем (§15)
db.update_user_state(user_id, "BAZA_INT_LEVEL")
self.send_message(user_id, "Какой сейчас уровень в CS2?", keyboard=get_baza_int_level_keyboard())
else:
db.update_user_state(user_id, "BAZA_INT_AGE") db.update_user_state(user_id, "BAZA_INT_AGE")
self.send_message(user_id, "Сколько лет игроку?", keyboard=get_baza_age_keyboard()) self.send_message(user_id, "Сколько лет игроку?", keyboard=get_baza_age_keyboard())
return return
+26
View File
@@ -62,6 +62,32 @@ def test_baza_other_game_goes_to_manager(app):
assert "Valorant" in _prof(u)["game"] assert "Valorant" in _prof(u)["game"]
def test_baza_smart_skip_known_fields(app):
"""§15: уже сохранённые ответы не переспрашиваются."""
u = 70020
bot.db.set_profile_field(u, "role", "Игрок (пишет сам)")
bot.db.set_profile_field(u, "age", "16 лет и старше")
# роль и возраст известны → воронка стартует сразу с игры
app.handle_message(u, "Подобрать тренировку 🎯")
assert _state(u) == "BAZA_GAME"
# уровень известен → после игры сразу формат (пропуская уровень)
bot.db.set_profile_field(u, "level", "Играет для себя")
app.handle_message(u, "CS2")
assert _state(u) == "BAZA_FORMAT"
# «Назад» позволяет переопределить пропущенный уровень
app.handle_message(u, "Назад ⬅️")
assert _state(u) == "BAZA_LEVEL"
def test_baza_intensive_skips_known_age(app):
"""§15: в интенсиве известный возраст не переспрашивается."""
u = 70021
bot.db.set_profile_field(u, "age", "1415 лет")
app.handle_message(u, "Летний интенсив CS2 🔥")
app.handle_message(u, "Хочу участвовать 🔥")
assert _state(u) == "BAZA_INT_LEVEL" # возраст пропущен
def test_baza_intensive_by_keyword(app): def test_baza_intensive_by_keyword(app):
"""Кодовое слово «лето» запускает интенсив; ветка доходит до оплаты.""" """Кодовое слово «лето» запускает интенсив; ветка доходит до оплаты."""
u = 70014 u = 70014