60f8a7b398
Воронка сжата с 6 шагов до 4: intro → qualify → book → close.
Спецификация: docs/OPTIMIZATION_CONVERSION_v1.md.
Цель: сравнимая с конкурентом (NEXTBOT/Александра) конверсия — ≤3 реплик
бота до запроса телефона, содержательный ответ на жалобу в первом
осмысленном сообщении.
Промпты шагов:
- intro.md — переписан. Приветствие + открытый вопрос «что беспокоит?».
Имя НЕ спрашиваем (слот name со шага снят), оно собирается на book
вместе с телефоном. Если пациент сразу написал жалобу — не зацикливаемся,
переходим в qualify.
- qualify.md — переписан. Обязательный 5-пунктовый шаблон ответа на жалобу:
эмпатия (одна фраза) → 2-3 ЛОР-гипотезы из RAG-выдержек («может быть
связано с») → специалист → услуга/цена («при необходимости назначит») →
бинарный CTA «записать?». Если в выдержках нет гипотез/цен — пункт
пропускается, не сочиняем. Если жалоба не описана (пациент сразу
«хочу записаться к ЛОРу») — пропускаем гипотезу/услугу, оставляем
эмпатию-формальность + специалист + CTA.
Три особые ситуации сохранены: ребёнок (require_legal_rep), конкретный
врач (waitlist_flag), первичная жалоба на слух (needs_surgologist_first).
- book.md — переписан. Одной репликой: подтверждение плана с
использованием {specialist}/{reason} + запрос телефона + имени (если
ещё не было в истории). При is_child=true — обращение к родителю,
legal_rep_phone используется, если уже собран.
- present.md — DEPRECATED. Файл оставлен в репо на случай отката
(вариант 1 спецификации). Внутри — заглушка «попал по ошибке —
выходи на book».
- close.md и offer_time.md не тронуты (offer_time станет актуален с
реальным календарём).
allowed_next в SEED_INTENT_STEPS:
- intro: [intro, qualify] (без изменений)
- qualify: [qualify, book] (раньше: [qualify, present])
- present: [book] (изоляция; раньше: [present, qualify, offer_time])
- offer_time: [offer_time, book] (deprecated, без изменений)
- book: [book, qualify, close] (раньше: [book, qualify, offer_time, close])
- close: [close] (без изменений)
migrate_new_booking_allowed_next_v2(session) — одноразовая миграция в
services/intent_step_service.py. При старте для каждого шага
new_booking сравнивает текущий allowed_next_json с дореформенным
значением (_PRE_SPRINT_7_6_ALLOWED_NEXT). Если совпадает — обновляет
на новое из SEED. Если оператор правил вручную — пропускает,
warning в лог. Идемпотентна (на повторных запусках ничего не делает).
Подключена в main.py lifespan после ensure_seed_guards.
Защитное условие require_legal_rep на qualify сохранено. Теперь блокирует
переход qualify → book (раньше qualify → present). Логика та же:
при is_child=true и пустых legal_rep_name/legal_rep_phone валидатор
отклоняет переход.
eval/MANUAL_CASES.md — markdown-чеклист для ручных прогонов:
- §A: 5 конверсионных кейсов (храп+уши, боль в горле, тугоухость,
насморк >месяца, звон в ушах) с чеклистом 5 пунктов на первый ответ
и проверкой ≤3 реплик до телефона.
- §B: регрессия 8 ручных сценариев из блока H Спринта 6b со ссылками
на docs/examples/*_v2.md.
SPRINTS.md: Спринт 7.6 → ✅ Закрыт по коду. Применение промптов в БД
и ручная регрессия — за оператором (через UI «Настройки → Шаги»
для каждого из 4 шагов new_booking).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
120 lines
8.8 KiB
Markdown
120 lines
8.8 KiB
Markdown
# Ручные кейсы для регрессии
|
||
|
||
Чеклист для прогонов в Песочнице. **Не автоматизирован** — это `markdown`-чеклист, по которому оператор/разработчик прогоняет сценарии руками. Полная подсистема прогона (`eval/run.py`) — Спринт 8.
|
||
|
||
Раздел A — конверсионные кейсы Спринта 7.6 (новые). Раздел B — регрессия 8 ручных сценариев из блока H Спринта 6b (должны проходить как раньше).
|
||
|
||
---
|
||
|
||
## A · Конверсионные кейсы (Спринт 7.6)
|
||
|
||
Все 5 кейсов — про оптимизацию воронки `new_booking` до 4 шагов: `intro → qualify → book → close`. Цель проверки — сжатие воронки и содержательность первого ответа.
|
||
|
||
### Что проверяем на каждом кейсе
|
||
|
||
**Структура первого ответа бота на жалобу пациента** (5-пунктовый шаблон, см. `prompts/intents/new_booking/steps/qualify.md`):
|
||
|
||
- [ ] **Эмпатия** — одна короткая фраза (одна, не больше).
|
||
- [ ] **Гипотеза** — 2–3 ЛОР-причины формулировкой «может быть связано с…», без диагноза. Если в RAG-выдержках причин нет — пункт допустимо пропустить.
|
||
- [ ] **Специалист** — рекомендация по профилю (зафиксирован в слот `specialist`).
|
||
- [ ] **Услуга и цена** — формулировкой «при необходимости назначит». Если в RAG нет — пункт пропускается.
|
||
- [ ] **CTA** — бинарный вопрос «записать?».
|
||
|
||
**Сжатие воронки:**
|
||
|
||
- [ ] До запроса телефона — **≤ 3 реплики бота** (раньше было 5–6).
|
||
- [ ] Имя на `intro` **не спрашивается** — спрашивается на `book` вместе с телефоном.
|
||
- [ ] Граф работает по `intro → qualify → book → close`. На `present` модель не попадает (в Песочнице бейдж шага не показывает `present`).
|
||
|
||
**RAG:**
|
||
|
||
- [ ] В отладочной панели «Найденные фрагменты» видно, что чанки пришли из подписанных документов ветки `new_booking`.
|
||
- [ ] Если ветка не подписана ни на один документ — гипотеза/услуга/цена пропускаются (5-пунктовый шаблон деградирует до эмпатия + специалист + CTA).
|
||
|
||
### Кейсы
|
||
|
||
#### A.1 · «Очень сильно храплю, иногда закладывает уши»
|
||
|
||
Контрольный кейс из `docs/OPTIMIZATION_CONVERSION_v1.md` §1 (сравнение с конкурентом «Александра»).
|
||
|
||
- Ожидаемый специалист: ЛОР.
|
||
- Ожидаемые гипотезы (из вики): искривление перегородки, аденоиды, ринит.
|
||
- Ожидаемая услуга: эндоскопия, 1 000 ₽ (если в подписанных документах есть).
|
||
- Слоты после `qualify`: `reason="храп + заложенность ушей"`, `specialist="ЛОР"`.
|
||
|
||
#### A.2 · «Болит горло уже неделю, не проходит»
|
||
|
||
- Ожидаемый специалист: ЛОР.
|
||
- Ожидаемые гипотезы: тонзиллит, фарингит.
|
||
- Слоты: `reason="боль в горле, неделя"`, `specialist="ЛОР"`.
|
||
|
||
#### A.3 · «Стал плохо слышать на одно ухо, и звон»
|
||
|
||
Особая ситуация 3 (`needs_surgologist_first`).
|
||
|
||
- Ожидаемое поведение: сначала уточнить «вас уже обследовал сурдолог?», при первичном — `specialist=ЛОР`, `needs_surgologist_first=true`.
|
||
- Объяснение: «обычно начинают с ЛОР-врача, при необходимости направит к сурдологу».
|
||
|
||
#### A.4 · «Насморк больше месяца, не проходит»
|
||
|
||
- Ожидаемый специалист: ЛОР.
|
||
- Ожидаемые гипотезы: хронический ринит, синусит.
|
||
|
||
#### A.5 · «Звон в ушах, какой-то непонятный»
|
||
|
||
Аналог A.3, проверка устойчивости.
|
||
|
||
- Ожидаемое поведение: уточнение «были у сурдолога?», при первичном — ЛОР с пометкой про сурдолога.
|
||
|
||
---
|
||
|
||
## B · Регрессия 8 ручных сценариев (блок H Спринта 6b)
|
||
|
||
После переписки воронки в Спринте 7.6 — все 8 сценариев должны продолжать работать. Сравниваем с разобранными примерами в `docs/examples/*_v2.md`.
|
||
|
||
### B.1 · Базовая запись к ЛОР-врачу
|
||
- См. `docs/examples/01_basic_booking_v2.md`.
|
||
- Ожидание: путь `intro → qualify → book → close` (3 реплики бота до телефона), без особых ситуаций.
|
||
|
||
### B.2 · Soft-insertion цена в середине записи
|
||
- См. `docs/examples/02_price_during_booking_v2.md` Вариант A.
|
||
- Ожидание: на короткое «а сколько стоит?» — ответ в-line, шаг не меняется, `soft_insertion_count++`.
|
||
|
||
### B.3 · Hard-handoff в `reschedule` и возврат
|
||
- См. `docs/examples/02_price_during_booking_v2.md` Вариант B (там `price_question`, для reschedule аналогично).
|
||
- Ожидание: `suspended_intent=new_booking`, после возврата — восстановление `current_step_code` и `slots`.
|
||
|
||
### B.4 · Возврат из `suspended_intent`
|
||
- Подразумевается в B.3.
|
||
- Ожидание: при возврате `handoff_count` сбрасывается в 0.
|
||
|
||
### B.5 · Упоминание хирургии → escalate с `reason=surgery`
|
||
- Пациент в любом месте говорит «у меня уже была операция, надо перенести» — должен сработать `[INTENT_CHANGE: escalate_human]` с `reason=surgery`.
|
||
|
||
### B.6 · Петля роутера → автоэскалация с `reason=routing_loop`
|
||
- Искусственно: чередовать `new_booking ↔ price_question` 4+ раза.
|
||
- Ожидание: на 4-м переключении автоматически уйти в `escalate_human` с `reason=routing_loop` без вызова LLM.
|
||
|
||
### B.7 · Запись ребёнка с защитным условием `require_legal_rep`
|
||
- См. `docs/examples/03_child_patient_guard_v2.md`.
|
||
- Ожидание: при `is_child=true` и пустых `legal_rep_*` валидатор блокирует переход `qualify → book`. **Внимание:** в Спринте 7.6 переход теперь `qualify → book` (раньше было `qualify → present`). Защитное условие должно продолжать работать на новом переходе.
|
||
|
||
### B.8 · Конкретный врач → лист ожидания
|
||
- Пациент: «хочу к доктору Иванову».
|
||
- Ожидание: `requested_doctor="Иванов"`, `waitlist_flag=true`, фраза «администратор свяжется для уточнения даты».
|
||
|
||
---
|
||
|
||
## Как прогонять
|
||
|
||
1. Открой Песочницу (`http://localhost:8000/sandbox.html`).
|
||
2. Создай новый тред для каждого кейса (чтобы счётчики `handoff_count` и `soft_insertion_count` были чистыми).
|
||
3. Веди диалог как пациент, проставляй галочки в чеклисте по факту.
|
||
4. Если что-то не так — отметь словесно, приложи скрин/реплику. Возвращаемся в код, правим, прогоняем снова.
|
||
|
||
## Что НЕ делает этот документ
|
||
|
||
- Не запускается автоматически. Для автозапуска — Спринт 8 (`eval/run.py`).
|
||
- Не покрывает все возможные граничные случаи маршрутизатора. Для них есть `eval/router_cases_*.jsonl` (тоже к Спринту 8).
|
||
- Не сравнивает с baseline по метрикам. Это всё прогоны «глазами».
|