# Пример 03 · Запись ребёнка — защитное условие в шаге уточнения > Связано с [`GRAPH_ARCHITECTURE_v3.md`](../architecture/GRAPH_ARCHITECTURE_v3.md), разделы 3.2, 3.3. Демонстрирует **защитное условие** (guard) внутри шага `qualify` ветки `new_booking`. Когда модель определяет, что пациент — ребёнок (`is_child=true`), линейный путь машины состояний прерывается: переход `qualify → present` блокируется до тех пор, пока не будут собраны данные **законного представителя** (legal representative). Также показано, как эта особенность отражается на финальном шаге `close` — там добавляются юридические оговорки из вики. ## О чём этот пример Мама записывает 8-летнего сына к ЛОР-врачу. Ситуация юридически регулируется: согласно ФЗ № 323-ФЗ «Об основах охраны здоровья граждан в РФ» приём ребёнка возможен только с участием законного представителя, и клиника обязана зафиксировать его данные на этапе записи. Скрипт записи в вике формулирует это как обязательный сбор ФИО и контактного телефона законного представителя. В терминах архитектуры это означает: внутри шага `qualify` есть **под-состояние** (sub-state) `qualify.legal_rep`, активирующееся при `is_child=true`. Модель не может вернуть `state_after: "present"`, пока в слотах не заполнены `legal_rep_full_name` и `legal_rep_phone` — валидатор перехода в коде это проверит и отклонит ход, если переход неправильный. ## Начальное состояние ```json { "thread_id": "T-9003", "current_intent": null, "current_step": null, "slots": {}, "handoff_count": 0 } ``` --- ## Реплика 1 · «Здравствуйте, хочу записать ребёнка к лору» **Решение роутера:** `new_booking`. Упоминание «ребёнка» само по себе не сигнал эскалации — это норма для записи. **Активная ветка / шаг:** `new_booking` / `intro`. **Ретривер:** не вызывается. **Вызовы инструментов:** нет. **Структурированный ответ модели:** ```json { "reply": "Здравствуйте! Помогу записать ребёнка. Подскажите, как могу к вам обращаться, и как зовут ребёнка?", "state_after": "intro", "slots_updated": { "service_mention": "ЛОР", "is_child": true } } ``` Заметим: `is_child=true` модель устанавливает уже здесь, на основании слова «ребёнок». Это **раннее срабатывание флага** — и оно правильное, потому что определяет всю дальнейшую логику. Шаг при этом не меняется — ждём имена. **`thread_state` после хода:** ```json { "current_intent": "new_booking", "current_step": "intro", "slots": { "service_mention": "ЛОР", "is_child": true } } ``` --- ## Реплика 2 · «Меня зовут Мария, ребёнка зовут Матвей, ему 8 лет» **Решение роутера:** `new_booking`. **Активная ветка / шаг:** `new_booking` / `intro`. **Ретривер:** не вызывается. **Структурированный ответ модели:** ```json { "reply": "Очень приятно, Мария. Записать Матвея на первичный приём к ЛОР-врачу? Что именно беспокоит?", "state_after": "qualify", "slots_updated": { "patient_name": "Матвей", "patient_age": 8, "parent_first_name": "Мария" } } ``` Шаг переходит в `qualify`. Имя ребёнка — `patient_name` (это пациент, на чьё имя пойдёт запись), имя мамы временно фиксируем как `parent_first_name` — на следующем под-шаге его дополним до полного ФИО. **`thread_state` после хода:** ```json { "current_intent": "new_booking", "current_step": "qualify", "slots": { "service_mention": "ЛОР", "is_child": true, "patient_name": "Матвей", "patient_age": 8, "parent_first_name": "Мария" } } ``` --- ## Реплика 3 · «У него уши болят и плохо слышит на одно ухо» **Решение роутера:** `new_booking`. Жалоба упомянута, но в контексте записи — не самостоятельный медицинский вопрос. **Активная ветка / шаг:** `new_booking` / `qualify`. **Срабатывает guard** — при `is_child=true` обычный переход `qualify → present` заблокирован. Машина состояний ждёт под-состояние `qualify.legal_rep`. В промпте шага явно написано: «если `is_child=true` и не заполнены `legal_rep_full_name` + `legal_rep_phone` — соблюдай скрипт сбора данных представителя из вики». **Дополнительно срабатывает второй guard** — упоминание «плохо слышит на одно ухо» это сигнал на сурдолога. Из v3 раздел 3.2: «жалоба на слух без обследования у сурдолога → на шаге `present` предложить сурдолога». Этот guard сработает позже, когда дойдём до `present`. **Ретривер:** срез шага `qualify` — `wiki_sources: ['/wiki/services/**', '/wiki/doctors/**', '/wiki/legal/**']` (последний раздел добавлен для шага под-состояния `qualify.legal_rep`). - top-k: 3. - Достались чанки: - `legal/child-patient-rep-script.md` (score 0.93) — скрипт сбора данных законного представителя из вики. - `legal/fz-323-rep.md` (score 0.78) — выдержка из ФЗ-323 для случаев «зачем вам это нужно». - `services/первичный-приём-лор.md` (score 0.62). **Вызовы инструментов:** нет. **Структурированный ответ модели:** ```json { "reply": "Поняла, посмотрим. Прежде чем подобрать слот — для записи ребёнка нам по правилам клиники нужно зафиксировать данные законного представителя. Уточните, пожалуйста, ваше полное ФИО и контактный телефон.", "state_after": "qualify.legal_rep", "slots_updated": { "complaint": "боль в ушах, ухудшение слуха на одно ухо" } } ``` Шаг переходит в под-состояние `qualify.legal_rep` — guard активен. Жалоба зафиксирована в слотах, но переход в `present` пока заблокирован. **`thread_state` после хода:** ```json { "current_intent": "new_booking", "current_step": "qualify.legal_rep", "slots": { "service_mention": "ЛОР", "is_child": true, "patient_name": "Матвей", "patient_age": 8, "parent_first_name": "Мария", "complaint": "боль в ушах, ухудшение слуха на одно ухо" } } ``` --- ## Реплика 4 · «А зачем вам это нужно?» Возражение пациентки. Из вики: «Если возникает вопрос — А зачем вам это нужно? — вы можете объяснить, что это стандартная практика для обеспечения безопасности и контроля доступа к медицинским услугам ребенка...». **Решение роутера:** `new_booking`. **Активная ветка / шаг:** `new_booking` / `qualify.legal_rep`. **Ретривер:** тот же срез, чанк `legal/fz-323-rep.md` уже в контексте. **Структурированный ответ модели:** ```json { "reply": "Это стандартная практика — нужна для безопасности ребёнка и для того, чтобы мы могли с вами связаться, если в ходе приёма потребуется ваше согласие на процедуру. Согласно ФЗ № 323-ФЗ мы должны иметь возможность связаться с законным представителем в любой момент. Подскажите, пожалуйста, ваше полное ФИО и телефон.", "state_after": "qualify.legal_rep", "slots_updated": {} } ``` Шаг и слоты не меняются — guard всё ещё блокирует выход. --- ## Реплика 5 · «Хорошо. Иванова Мария Сергеевна, +79991234567» **Решение роутера:** `new_booking`. **Активная ветка / шаг:** `new_booking` / `qualify.legal_rep`. **Структурированный ответ модели:** ```json { "reply": "Спасибо, Мария Сергеевна. Записываю Матвея на первичный приём к ЛОР-врачу. Подберу ближайший свободный слот?", "state_after": "present", "slots_updated": { "legal_rep_full_name": "Иванова Мария Сергеевна", "legal_rep_phone": "+79991234567" } } ``` Guard выполнен — оба обязательных слота заполнены. Валидатор переходов в коде проверяет: переход `qualify.legal_rep → present` допустим **только если** `legal_rep_full_name` и `legal_rep_phone` заполнены. Условие выполняется, переход разрешён. **`thread_state` после хода:** ```json { "current_intent": "new_booking", "current_step": "present", "slots": { "service_mention": "ЛОР", "is_child": true, "patient_name": "Матвей", "patient_age": 8, "parent_first_name": "Мария", "complaint": "боль в ушах, ухудшение слуха на одно ухо", "legal_rep_full_name": "Иванова Мария Сергеевна", "legal_rep_phone": "+79991234567" } } ``` --- ## Реплика 6 · «Да, давайте посмотрим, что есть» **Решение роутера:** `new_booking`. **Активная ветка / шаг:** `new_booking` / `present`. **Срабатывает второй guard** — на основании слота `complaint` (упоминание ухудшения слуха) промпт шага `present` должен предложить **сурдолога перед ЛОРом**. Это правило из вики и v3 раздел 3.2. **Ретривер:** срез шага — `wiki_sources: ['/wiki/services/**', '/wiki/doctors/**', '/wiki/preparation/**']`. - top-k: 3. - Достались чанки: - `services/приём-сурдолога.md` (score 0.88) — описание услуги. - `services/первичный-приём-лор.md` (score 0.74). - `doctors/общая-информация-сурдолог.md` (score 0.65). **Структурированный ответ модели:** ```json { "reply": "С учётом того, что есть жалоба на ухудшение слуха на одно ухо, я бы предложила сначала записать Матвея к нашему сурдологу — он проведёт обследование слуха, и уже с его заключением ЛОР сможет дать более точное лечение. Если вы согласны — подберу слот к сурдологу. Или, если хотите, всё равно идём сразу к ЛОРу?", "state_after": "present", "slots_updated": { "guard_surdologist_suggested": true } } ``` Шаг не меняется — ждём решение мамы. Флаг `guard_surdologist_suggested` нужен, чтобы при следующей реплике не предлагать сурдолога повторно, если она откажется. --- ## Реплика 7 · «Да, давайте сначала к сурдологу» **Решение роутера:** `new_booking`. **Активная ветка / шаг:** `new_booking` / `present` → `offer_time`. **Структурированный ответ модели:** ```json { "reply": "Хорошо. Подбираю свободные слоты к сурдологу.", "state_after": "offer_time", "slots_updated": { "service": "приём_сурдолога", "doctor_preference": null } } ``` Шаг переходит в `offer_time`. Услуга в слотах теперь — приём сурдолога, не ЛОР. --- ## Реплика 8 · `crm.get_slots()` + предложение слотов (Аналогично `01_basic_booking.md` репликам 5-6, без особенностей. Получаем 2 слота, мама выбирает один, идём в `book` → `close`.) --- ## Реплика 9 · Закрытие — финальная сводка с детскими оговорками **Активная ветка / шаг:** `new_booking` / `close`. **Срабатывает guard на шаге `close`:** при `is_child=true` промпт обязан включить специальный блок с юридическими оговорками из вики. Берётся из `legal/child-patient-final-script.md`. **Ретривер:** срез шага — `wiki_sources: ['/wiki/contacts/**', '/wiki/preparation/**', '/wiki/legal/**']`. - Достались чанки: - `legal/child-patient-final-script.md` (score 0.95) — обязательный текст про сопровождение, паспорт + свидетельство о рождении, доверенность для других сопровождающих. - `contacts/branch-klary-tsetkin-9.md` (score 0.81) — адрес, как доехать. - `preparation/ребёнок-первичный.md` (score 0.71) — что взять с собой. **Структурированный ответ модели:** ```json { "reply": "Записала Матвея на четверг 30 апреля, 11:00, к сурдологу Кузнецовой Елене Игоревне, ул. Газеты Звезда, 31-А. Стоимость приёма — 2 100 рублей. Если в ходе приёма понадобится дополнительная диагностика, она оплачивается отдельно. Подходите за 5 минут до назначенного времени, при себе паспорт одного из родителей и свидетельство о рождении ребёнка. \n\nОбращаю внимание: на приём вместе с ребёнком должен прийти его законный представитель. Если в качестве сопровождающего лица будет кто-то другой (бабушка, дедушка), у него обязательно должен быть паспорт, доверенность от законного представителя, копия свидетельства о рождении и копия паспорта представителя. Форма доверенности — на сайте клиники.\n\nПодтверждаете?", "state_after": "close", "slots_updated": { "time_chosen": "2026-04-30 11:00", "doctor": "Кузнецова Е. И.", "branch": "ул. Газеты Звезда, 31-А", "booking_id": "B-1236" } } ``` Длинная финальная реплика с юридической оговоркой из ФЗ-323 — ровно так, как написано в скрипте записи в вике для детских пациентов. --- ## Что показал этот пример - **Guard как изменение допустимых переходов.** Шаг `qualify` имеет два пути: обычный `qualify → present` (для взрослых, `is_child=false`) и через под-шаг `qualify.legal_rep → present` (для детей). Валидатор переходов в коде должен это учитывать, иначе модель сможет «прыгнуть» через `legal_rep` и переход состоится с пустыми слотами представителя. - **Раннее срабатывание флага `is_child`.** Модель установила его уже на реплике 1, до получения имени. Это важно: если бы `is_child` появился только на реплике 2, сбор данных пошёл бы в неправильном порядке (сначала жалоба, потом данные представителя — пациентка успела бы рассказать симптомы, прежде чем мы попросили бы документы). - **Несколько guard'ов в одной ветке.** На шаге `present` сработал второй guard (предложить сурдолога), на шаге `close` — третий (юридический текст для детей). Они независимы и могут срабатывать в одном диалоге. - **Ретривер на каждом шаге достаёт релевантный для guard'а контент.** На `qualify.legal_rep` — раздел `/wiki/legal/`, на `close` — финальные юридические оговорки. Без правильного `wiki_sources` на уровне шага модель должна была бы «помнить» юридический текст из системного промпта — это плохо масштабируется. - **Возражение «а зачем вам это нужно?» обрабатывается в-line, без выхода из guard'а.** На реплике 4 модель пояснила, оставаясь в `qualify.legal_rep`. Это похоже на мягкую вставку из примера 02, но внутри одного и того же шага, не между ветками. ## Что важно проверять в eval-наборе на этом примере - **Без legal_rep слотов переход `qualify.legal_rep → present` не должен срабатывать.** Тест: подать в ветку модельный ответ с `state_after: "present"` при пустых `legal_rep_full_name` или `legal_rep_phone` → валидатор должен отклонить переход, состояние остаётся `qualify.legal_rep`, в логе предупреждение. - **`is_child=true` устанавливается рано.** Тест: фраза «запишите ребёнка», без других слов → проверить, что `is_child=true` появляется в `slots_updated` уже на первой реплике. - **На шаге `close` для ребёнка ответ обязан содержать упоминание свидетельства о рождении и доверенности.** Тест: прогнать сценарий с `is_child=true` → проверить, что финальный `reply` содержит подстроки «свидетельство о рождении» и «доверенность». Это простая проверка подстрокой, не нужен LLM-as-judge. - **Guard сурдолога не срабатывает повторно.** Тест: после реплики, в которой бот предложил сурдолога, мама отказалась → проверить, что на следующей реплике бот не предлагает сурдолога снова (флаг `guard_surdologist_suggested` сделал своё дело).