Files
RAG_helper/eval/branch_cases_price_question.jsonl
AR 15 M4 bb5e3f5eb3 feat(sprint8b): регрессия ответов веток · general_info + фикс PRAGMA foreign_keys
Параллель к 8a, но проверяем не код intent от роутера, а содержимое ответа
конкретной ветки на одиночную реплику. Старт — general_info, 46 кейсов.

Логика pass/fail (для одного кейса):
- A — RAG-секция: среди retrieved-чанков есть кусок с
  section == expected_doc_section (точное совпадение). Если поле не задано —
  пропускаем.
- B — keywords: обязательные expected_keywords встречаются в predicted_answer
  (case-insensitive). По умолчанию все; поддерживаются keywords_min: N
  и keywords_any: true. Запрещённые expected_must_not — ни одного.
- Pass = A ∧ B. Незаданные поля не проверяются.
- Кэш: (text_hash, branch_config_id) → {answer_text, retrieved_sections}.
  Привязан к версии промпта ветки. Смена версии = пустой кэш = свежий прогон.
  Правка JSONL без изменения text → pass/fail пересчитывается без LLM.

Backend:
- Таблицы eval_branch_runs / eval_branch_run_cases / eval_branch_predictions.
  Миграция m9g1f7e89j56.
- services/eval_branch_run_service.py: загрузка JSONL, фоновый прогон через
  asyncio.create_task, кэш, оценка A+B с поддержкой keywords_min/keywords_any.
- chat_service.run_branch_single_turn — изолированный single-turn без
  роутера и треда (использует существующий config_service + vectorstore + llm).
- API: POST /eval/branch-runs, GET /eval/branch-runs?intent_code=,
  GET /eval/branch-runs/{id}, GET /eval/branch-cases-with-status?intent_code=.

UI (static/regression.html):
- Селектор режима «Роутер / Ветка · general_info». Логика пикера переиспользуется
  (фильтры, диапазон, массовый выбор, счётчик «новые / в кэше»).
- Для режима «Ветка»: фильтр по coverage, колонки секция/coverage, keywords,
  частота, кэш. Drill-down прогона: ожидание, retrieved-секции, причины fail,
  полный ответ ветки.

База кейсов (eval/branch_cases_general_info.jsonl) — от пользователя, 46 кейсов
по схеме {text, intent, coverage, expected_doc_section?, expected_keywords?,
expected_must_not?, keywords_min?, keywords_any?, count?, note?}.

Связанная правка SQLite (нашли при удалении документа в этом спринте):
- db/session.py: connect-listener PRAGMA foreign_keys=ON на каждое подключение.
  Без этого ondelete=CASCADE в SQLite не enforced, и удаление документа
  оставляло подписки в intent_documents висячими (что давало пустой RAG
  и fail регрессии).
- Миграция n0h2g8f9a0k67 — одноразовая чистка существующих висячих подписок.

docs/SPRINTS.md: Спринт 8b →  Закрыт. Diff vs предыдущий прогон для веток
и кнопка «Сбросить кэш регрессии» вынесены в docs/BACKLOG.md.

Также включены обновлённые data/datasets/general_info.md и price_question.md
(рабочий материал оператора), и черновик eval/branch_cases_price_question.jsonl
для следующего захода (8b на price_question).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 01:20:59 +05:00

41 lines
26 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"text": "Сколько стоит прием", "intent": "price_question", "expected_keywords": ["1900", "2300"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "диагноз", "примите", "лечение лекарств", "назнача"], "expected_doc_section": "Приём ЛОР-врача (К. Цеткин, 9)", "coverage": "covered", "note": "Самая частотная общая формулировка (count=2). Бот должен дать вилку 1900/2300, по правилу промта — упомянуть про эндоскопию +1000 ₽."}
{"text": "Здравствуйте сколько стоит прием у Гашеевой лора?", "intent": "price_question", "expected_keywords": ["1900", "2300"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "диагноз", "примите", "лечение лекарств", "назнача", "3700", "4300"], "expected_doc_section": "Приём ЛОР-врача (К. Цеткин, 9)", "coverage": "covered", "note": "Гашеева есть в списке отоларингологов (без особой пометки). Цена 1900 (высшая) или 2300 (КМН/завотделением). В датасете отдельно категория Гашеевой не зафиксирована — допустимо дать вилку или эскалировать."}
{"text": "стоимость приема отоларинголога", "intent": "price_question", "expected_keywords": ["1900", "2300"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "диагноз", "примите", "лечение лекарств", "назнача"], "expected_doc_section": "Приём ЛОР-врача (К. Цеткин, 9)", "coverage": "covered", "note": "Без указания врача — должен быть диапазон цен."}
{"text": "Здравствуйте. Сколько стоит первичный прием к врачу Макаровой Людмиле Германовне? У меня тугоухость левого уха.", "intent": "price_question", "expected_keywords": ["1900", "2300"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "лечение", "назначу", "примите"], "expected_doc_section": "Приём ЛОР-врача (К. Цеткин, 9)", "coverage": "covered", "note": "Пациент описал симптом (тугоухость) — бот должен дать цену приёма, не ставя диагноз. Промт ветки price_question запрещает медсовет."}
{"text": "Здравствуйте. Хотела бы уточнить сколько будет стоить вторичный прием ЛОРа?", "intent": "price_question", "expected_keywords": ["оператор"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "1900", "2300"], "expected_doc_section": null, "coverage": "not_covered", "note": "Цена повторного приёма ЛОРа в датасете не зафиксирована (это явно отмечено в нижнем разделе «что нужно дополнить»). Бот должен честно эскалировать."}
{"text": "Сколько стоит прием со скидкой по направлению?", "intent": "price_question", "expected_keywords": ["950", "1150", "50%"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "Приём ЛОР-врача (К. Цеткин, 9)", "coverage": "covered", "note": "Скидка 50% по направлению на лечебную процедуру. Цена 950 (высшая) или 1150 (КМН)."}
{"text": "Здравствуйте, на 2 декабря есть свободная запись к отоларингологу Головач Светлане Вячеславовне ? И сколько будет стоить прием?", "intent": "price_question", "expected_keywords": ["1900", "2300", "оператор"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "Приём ЛОР-врача (К. Цеткин, 9)", "coverage": "partial", "note": "Двойной вопрос: цена + наличие записи на конкретную дату. Цену бот даёт; про запись — должен предложить переход в ветку записи или эскалировать."}
{"text": "Сколько стоит приём у заведующего отделением?", "intent": "price_question", "expected_keywords": ["2300"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "1900"], "expected_doc_section": "Приём ЛОР-врача (К. Цеткин, 9)", "coverage": "covered", "note": "Заведующий отделением — категория КМН/завотделением, цена 2300. Если бот даст 1900 — фейл."}
{"text": "Прайс", "intent": "price_question", "expected_keywords": ["оператор", "что", "уточн"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": null, "coverage": "partial", "note": "Однословный запрос «прайс» — слишком общий. Бот должен спросить, какая услуга интересует, или предложить эскалацию."}
{"text": "Стоимость услуг", "intent": "price_question", "expected_keywords": ["оператор", "уточн", "какая"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": null, "coverage": "partial", "note": "Тоже слишком широкий запрос — нужно уточнение."}
{"text": "Сколько стоит удалить серную пробку из уха", "intent": "price_question", "expected_keywords": ["оператор", "приём ЛОР", "дополнительно"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "550"], "expected_doc_section": null, "coverage": "not_covered", "note": "Цена 550 была только в Пирогова (Краснокамск); филиал закрыт в 2026, прайс убран из активной части датасета. Бот должен эскалировать; допустимо упомянуть, что процедура выполняется на приёме ЛОРа и оплачивается дополнительно."}
{"text": "Здравствуйте! Хотела узнать стоимость удаления аденоидов", "intent": "price_question", "expected_keywords": ["30000", "от"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "Стоимость операций (от какой суммы стартует)", "coverage": "covered", "note": "Аденотомия от 30000 . Должно прозвучать «от» точную сумму определяет хирург после осмотра."}
{"text": "Здравствуйте,сколько стоит удаление миндалин?", "intent": "price_question", "expected_keywords": ["19800", "40000", "тонзилл"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "Стоимость операций (от какой суммы стартует)", "coverage": "covered", "note": "Тонзиллотомия 19800, тонзиллэктомия от 40000. Бот может уточнить, какую именно операцию имеет в виду пациент."}
{"text": "Здравствуйте, сколько стоит УЗИ щитовидной железы и брюшной полости (комплексное)", "intent": "price_question", "expected_keywords": ["оператор"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "рублей"], "expected_doc_section": null, "coverage": "not_covered", "note": "УЗИ щитовидки и брюшной полости не профильная услуга клиники (УЗИ есть только в Пирогова, и тот закрыт). Бот должен эскалировать."}
{"text": "Стоимость септопластики", "intent": "price_question", "expected_keywords": ["30000", "от"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "Стоимость операций (от какой суммы стартует)", "coverage": "covered", "note": "Самый частотный запрос про операции (count=2). От 30000 ; со слизистой дополнительно от 16200 ."}
{"text": "Стоимость аденотомии у ребенка ?", "intent": "price_question", "expected_keywords": ["30000", "от", "оператор"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "16500", "21500"], "expected_doc_section": "Стоимость операций (от какой суммы стартует)", "coverage": "partial", "note": "Аденотомия от 30000. ВНИМАНИЕ: в датасете расхождение по цене наркоза для аденотомии (16500 vs 21500), бот не должен называть конкретную сумму наркоза только эскалировать или дать диапазон."}
{"text": "Здравствуйте подскажите пожалуйста сколько стоит темпанопластика на 1 ухо?", "intent": "price_question", "expected_keywords": ["76000", "82600"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "Стоимость операций (от какой суммы стартует)", "coverage": "covered", "note": "Тимпанопластика 76000 , 2 типа 82600 . Нужно дать обе цифры."}
{"text": "Здравствуйте. Подскажите, пожалуйста, стоимость пункции 2 пазух по гайморите?", "intent": "price_question", "expected_keywords": ["2300"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "Стоимость операций (от какой суммы стартует)", "coverage": "covered", "note": "Пункция гайморовой пазухи 2300 . Пациент про 2 пазухи допустимо упомянуть, что это за одну, итог 4600."}
{"text": "Добрый день. Сколько стоит прием Лора для ребенка? Есть ли запись на 18.11?", "intent": "price_question", "expected_keywords": ["1900", "2300"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "детский тариф"], "expected_doc_section": "Приём ЛОР-врача (К. Цеткин, 9)", "coverage": "covered", "note": "Цена приёма для ребёнка такая же, как для взрослого отдельного детского тарифа в датасете нет. Бот не должен выдумывать «детский тариф»."}
{"text": "Здравствуйте! Скажите пожалуйста сколько стоит удаление серных пробок 1 взрослый и 1 ребенок (13лет)?", "intent": "price_question", "expected_keywords": ["оператор", "приём ЛОР"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "550"], "expected_doc_section": null, "coverage": "not_covered", "note": "Цена 550 была в Пирогова; филиал закрыт. По Цеткин/Звезде в датасете нет цены процедуры бот должен эскалировать."}
{"text": "Стоимость удаления аденоидов ребенку", "intent": "price_question", "expected_keywords": ["30000", "от"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "Стоимость операций (от какой суммы стартует)", "coverage": "covered", "note": "Аденотомия от 30000 . Должно прозвучать «от»."}
{"text": "Сколько стоит удаление серной пробки?", "intent": "price_question", "expected_keywords": ["оператор", "приём ЛОР", "дополнительно"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "550"], "expected_doc_section": null, "coverage": "not_covered", "note": "Самая частотная процедура; цена 550 была в Пирогова, филиал закрыт. По Цеткин в датасете цены нет бот эскалирует. Допустимо упомянуть «процедура выполняется на приёме ЛОРа и оплачивается дополнительно»."}
{"text": "Здравствуйте, сколько стоит процедура промывание миндалин?", "intent": "price_question", "expected_keywords": ["1200", "оплачивается дополнительно"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "Лечебные процедуры (КУГН, К. Цеткин, 9)", "coverage": "covered", "note": "Промывание лакун миндалин 1200 . По правилам промта упомянуть «выполняется на приёме врача и оплачивается дополнительно»."}
{"text": "Добрый день. Сколько стоит в вашей клинике удаление серной пробки + первичная консультация?", "intent": "price_question", "expected_keywords": ["1900", "2300", "оператор"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "550"], "expected_doc_section": "Приём ЛОР-врача (К. Цеткин, 9)", "coverage": "partial", "note": "Пациент сам понимает, что нужен приём + процедура. Цену приёма (1900/2300) бот может назвать; цена самой процедуры в Цеткин не зафиксирована (550 было только в Пирогова, филиал закрыт) должен эскалировать."}
{"text": "Сурдолог стоимость", "intent": "price_question", "expected_keywords": ["5000", "комплексн"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "Приём сурдолога и комплексное обследование слуха", "coverage": "covered", "note": "Комплексное обследование слуха 5000 . Не путать с консультацией 2100."}
{"text": "Добрый день! Подскажите, пожалуйста, сколько будет стоить повторный прием сурдолога?", "intent": "price_question", "expected_keywords": ["3700"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "5000", "2100"], "expected_doc_section": "Приём сурдолога и комплексное обследование слуха", "coverage": "covered", "note": "Повторный сурдолог 3700 , около часа. Не путать с первичным."}
{"text": "Сурдолог принимает по полису ОМС?", "intent": "price_question", "expected_keywords": ["ОМС", "сурдолог", "да"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "нет, по ОМС не принимаем"], "expected_doc_section": "Приём сурдолога и комплексное обследование слуха", "coverage": "covered", "note": "Сурдолог единственный в клинике, кто принимает по ОМС. Это явно в промте и в нижнем правиле. Должен быть «да»."}
{"text": "Сколько стоит прием у аллерголога ?", "intent": "price_question", "expected_keywords": ["2400"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "Приём аллерголога-иммунолога (Г. Звезда, 31а)", "coverage": "covered", "note": "Очный приём аллерголога 2400 ."}
{"text": "Добрый вечер. Подскажите пожалуйста, стоимость аллергопроб (кожные тесты)?", "intent": "price_question", "expected_keywords": ["3600", "2000", "500"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "Стандартные диагностические процедуры", "coverage": "covered", "note": "Аллергопробы: комплекс 3600, постановка 2000, единичная 500. Хорошо если бот даёт хотя бы одну цену с пояснением."}
{"text": "Здравствуйте. Есть ли у вас детский пульмонолог?? И сколько стоит прием?", "intent": "price_question", "expected_keywords": ["2400", "оператор"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "детский тариф"], "expected_doc_section": "Приём пульмонолога (Г. Звезда, 31а)", "coverage": "partial", "note": "Пульмонолог Абыденков А. В. без явного указания «детский». Цена 2400 . Про детского можно эскалировать."}
{"text": "Здравствуйте. С какими ДМС работает клирика?", "intent": "price_question", "expected_keywords": ["Адонис", "ВСК", "СОГАЗ", "Согласие", "Росгосстрах"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "Альфа", "Ингосстрах", "Ренессанс"], "expected_doc_section": "ДМС: страховые компании, с которыми сотрудничает клиника", "coverage": "covered", "note": "Должны быть перечислены страховые из списка. Не должно быть тех, кого нет (Альфа, Ингосстрах и т.д.)."}
{"text": "вы сотрудничаете по ДМС с Альфастрахованием?", "intent": "price_question", "expected_keywords": ["нет", "не работаем", "оператор"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "да, работаем", "Альфастрахование"], "expected_doc_section": "ДМС: страховые компании, с которыми сотрудничает клиника", "coverage": "covered", "note": "Альфастрахования НЕТ в списке партнёров. Бот не должен подтверждать."}
{"text": "Добрый день! Подскажите, пожалуйста, возможна ли оплата приема ЛОР врача по ДМС?", "intent": "price_question", "expected_keywords": ["да", "гарантийн", "ДМС"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "нет, не принимаем"], "expected_doc_section": "ДМС: страховые компании, с которыми сотрудничает клиника", "coverage": "covered", "note": "Да, ДМС-приём возможен по гарантийному письму страховой. Опционально упомянуть про список страховых."}
{"text": "Добрый день! Вы работаете с ОМС?", "intent": "price_question", "expected_keywords": ["сурдолог", "ОМС"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "да, по всем направлениям"], "expected_doc_section": null, "coverage": "covered", "note": "По правилу промта: «По ОМС в данный момент ведёт приём только врач-сурдолог. Остальные направления платно или по ДМС»."}
{"text": "Добрый день ! Проводится ли в вашей клинике удаление миндалин по полису ОМС?", "intent": "price_question", "expected_keywords": ["нет", "сурдолог"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "да, по ОМС"], "expected_doc_section": null, "coverage": "covered", "note": "Операции по ОМС не проводятся. По ОМС только сурдолог."}
{"text": "Здравствуйте. Сколько стоит КТ околоносных пазух?", "intent": "price_question", "expected_keywords": ["2500", "2900", "наш", "сторонн"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле"], "expected_doc_section": "КТ-исследование ЛОР-органов (центр ЛорДент, Г. Звезда, 31а)", "coverage": "covered", "note": "Цена зависит от «наш/сторонний» и нужно ли описание. Хорошо, если бот объяснит вилку или уточнит у пациента."}
{"text": "Сколько стоит кт головного мозга ?", "intent": "price_question", "expected_keywords": ["оператор"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "рублей"], "expected_doc_section": null, "coverage": "not_covered", "note": "КТ головного мозга не профиль клиники (КТ только ЛОР-органов и стоматологии). Бот должен эскалировать."}
{"text": "Добрый день. Подскажите по qr коду можно оплатить?", "intent": "price_question", "expected_keywords": ["оператор", "СБП", "уточн"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "да, по СБП", "да, по QR"], "expected_doc_section": "Способы оплаты", "coverage": "not_covered", "note": "СБП/QR в датасете явно не указано бот должен эскалировать. Это правило прямо записано в датасете."}
{"text": "Можно картой оплатить?", "intent": "price_question", "expected_keywords": ["да", "терминал"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "нет"], "expected_doc_section": "Способы оплаты", "coverage": "covered", "note": "Терминал есть. Также наличные."}
{"text": "Доброе утро! Скажите пожалуйста, скидки есть инвалидам и пенсионерам", "intent": "price_question", "expected_keywords": ["оператор", "нет"], "expected_must_not": ["дорого", "дёшево", "дешево", "недорого", "выгоднее", "дешевле", "да, есть для пенсионеров", "10%"], "expected_doc_section": "Скидки и условия", "coverage": "covered", "note": "Системных скидок нет (только 50% по направлению). Бот должен либо сказать «нет» с предложением уточнить, либо эскалировать."}