Все кейсы

Кейс 01 · AI-assisted development · MCP Tutu · Tourvisor · VK

TurBot: подбор туров в VK и Telegram

Бот собирает параметры поездки и передаёт менеджеру структурированную заявку. Интеграция с Tourvisor Search API реализована и работала при активной подписке: бот запрашивал пакетные туры, показывал варианты и сохранял выбранное предложение. Код и тесты интеграции сохранены как часть проекта.

74 строки

MCP-клиент Tutu вместо тяжёлого SDK

~28 000 ₽

на столько завышалась семейная заявка, пока дети шли как взрослые

1 720 тестов

диалоги, Tourvisor, webhook security, CRM и сетевые ошибки

Коротко · если читать некогда

Четыре строки вместо всей страницы

  • Интеграция Tourvisor работала в полном цикле. VK-бот отправлял параметры в Search API, показывал пакетные туры и сохранял выбранное предложение для менеджера. Сейчас сценарий зависит от активной подписки провайдера.
  • Дети перестали быть взрослыми. Семейный расчёт завышался примерно на 28 000 ₽. Нашёл живой менеджер, а не 74 теста.
  • AI помогает быстрее, но не считает цены. Им разбираю код, сценарии и ошибки; при активной подписке фактическая стоимость приходила из Tourvisor и MCP Tutu.
  • Два транспорта работают по ситуации. VK принимает события через Callback API, Telegram использует polling там, где провайдер режет webhook.

Дальше — то же самое, но с числами и адресами, которые можно открыть и перепроверить. Если верите на слово, дальше можно не читать; но тогда непонятно, зачем вам инженер.

Контекст · почему всё горело

Свободная переписка — враг нормальной заявки

Клиент пишет как умеет: «хотим в Египет где-то в мае ну или июне человек 3–4 бюджет так». Менеджеру нужны поля и телефон — внятно, а не «где-то в чате». Ручной перевод «из чата в таблицу» — олимпийский вид спорта с высоким шансом потерять детали.

Бот не заменяет менеджера (пока) — он собирает и проверяет данные, пока заявка не превратилась в квест «кто, куда и на сколько человек». А вот на вопрос «сколько будет стоить» первая версия отвечала текстом от языковой модели: складно, уверенно и мимо. Дальше — как эти цифры заменили на настоящие и что по дороге сломалось.

Путь заявки (без лирики)

VK / Telegram → Callback / polling → FSM-диалог → Tourvisor API → Валидация → Выбор тура → CRM

Решение · что сделал

Пошаговый сценарий вместо «напишите всё одним сообщением»

Стек простой и злой: Python + Flask. Бот ведёт клиента по шагам, проверяет телефон, даты, число туристов и бюджет — и только потом сохраняет заявку. Свободный полёт мысли оставили для чата с друзьями.

  • FSM и навигация. Откуда вылет, направление, даты, состав туристов по возрастным группам, бюджет, телефон. Есть «Назад» и «Отмена» — потому что люди передумают, это норма.
  • Хранение и админка. SQLite: сессии и заявки. Админ получает уведомления, а не медитирует в чате.
  • МоиДокументы.Туризм (CRM). Заявка уходит в фоновом режиме: внешний API не держит пользователя в заложниках «секундочку…».
  • Контактные данные. Согласие перед телефоном, /privacy, /delete — данные для заявки, без «навсегда в чате». Политику отдаёт сам бот, ссылка в тексте согласия подставляется автоматически.
Один сценарий — Telegram и VK. CRM получает одинаковые поля, а не два диалекта «как получилось».

Цены · интеграция по MCP

Бот перестал выдумывать рейсы

Теперь на вопрос о стоимости бот идёт в MCP-сервер Tutu (mcp.tutu.ru) и приносит настоящие рейсы: цена, перевозчик, время вылета. Четыре вещи, которые пришлось решить по дороге.

  • Официальный SDK не пригодился. Сервер оказался stateless JSON-RPC поверх обычного POST: ни авторизации, ни хендшейка. SDK притащил бы anyio, httpx и pydantic ради одного запроса. Клиент — 74 строки в shared/tutu.py поверх уже подключённого requests. Проверяется за десять секунд: в requirements.txt лежат Flask, requests, groq, gunicorn, python-dotenv — и всё.
  • Страна — это не город. Воронка собирает страны, а Tutu ищет по городам и резолвит страну в столицу. Египет превращался в Каир: 36 152 ₽ против 24 539 ₽ до Хургады на тех же датах. Завёл карту «страна → курорт».
  • Дубли по тарифным группам. Tutu возвращает по строке на каждую тарифную группу, и один и тот же рейс приходил трижды с одинаковой ценой. Схлопываю по цене, перевозчику и времени вылета.
  • Ссылку «купить» клиент не получает. Только ориентир по цене и поиск. Кнопка оплаты увела бы продажу мимо агентства — checkout остаётся менеджеру.
Самым сложным в этой интеграции оказался не протокол, а решение не давать клиенту кнопку «купить».
🌴 АПРЕЛЬ тур живой прогон · цены из Tutu.ru по MCP 🛫 Откуда вылетаете? Нужно, чтобы посчитать перелёт — цена сильно зависит от города. 🏙 Москва 📅 Когда планируете поездку? Выберите вариант кнопкой или напишите свои даты (например: 15-22 июня). 15-22 сентября 2026 📅 Понял: 15–22 сентября 👥 Сколько человек поедет? Кнопка или число от 1 до 50. 2 🧒 Дети до 12 лет едут? У них свой тариф — без этого расчёт будет завышен. 2 👶 Есть малыши до 2 лет? Они летят без отдельного места и по особому тарифу. 1 ⋯ бюджет, способ связи и телефон — ещё три шага ⋯ ✅ Заявка принята! Менеджер «АПРЕЛЬ тур» свяжется с вами в ближайшее время. 📍 Направление: Египет 🛫 Откуда: Москва 📅 Даты: 15-22 сентября 2026 👥 Состав: 2 взр. + 2 реб. (2–11) + 1 млад. (до 2) 💰 Бюджет: 100000₽ 📞 Связь: +79211234567 Спасибо, что выбрали нас 🌺 🔔 Новая заявка (Telegram) Личное уведомление администратору От: Роман (@roman) ID: 424242 📍 Египет 🛫 Откуда: Москва 📅 15-22 сентября 2026 👥 2 взр. + 2 реб. (2–11) + 1 млад. (до 2) 💰 100000₽ 📞 Связь: +79211234567 Нажмите «✍️ Ответить» ниже — или /send 424242 ✈️ Ориентир по перелёту Москва → Хургада • 181 910 ₽ · Pegasus Airlines · 15 сен, 15:05 · 13 ч 45 мин • 188 778 ₽ · Pegasus Airlines, AJet · 15 сен, 15:05 · 13 ч 55 мин • 191 950 ₽ · Pegasus Airlines · 15 сен, 06:10 · 13 ч 45 мин Это цены на билеты по данным Tutu.ru — ориентир, чтобы вы понимали порядок сумм. Менеджер посчитает пакет с отелем и трансфером — обычно выходит выгоднее, чем собирать по частям. 🔗 Посмотреть все варианты 💼 По заявке от Роман 📊 Рынок сейчас: от 181 910 ₽ · Москва → Хургада · 2026-09-15 · Pegasus Airlines 🔗 Все варианты
Живой прогон. Вопросы про детей и младенцев — те самые, что починили расчёт. Под срезом бот присылает реальные цены из Tutu и прямым текстом объясняет, что это ориентир, а пакет посчитает менеджер.

Расчёт · нашёл человек, а не тесты

Дошкольникам считали взрослый тариф

Воронка спрашивала «сколько человек поедет» и отправляла это число в Tutu как количество взрослых. Семье с двумя детьми и младенцем выходило пять взрослых билетов. Ни один тест этого не поймал — они проверяли, что запрос уходит и ответ разбирается, а не то, что в запросе правильные люди. Нашёл живой менеджер, сверив выдачу с сайтом.

Замер на живом сервере, Москва → Анталья, одни и те же даты:

  • 2 взрослых — 58 694 ₽
  • 2 взрослых, 2 ребёнка и младенец — 118 933 ₽
  • те же пятеро, посчитанные как взрослые — около 146 700 ₽

Разница — примерно 28 000 ₽ на заявку, и всегда в сторону завышения. Теперь групп три: взрослые от 12 лет, дети 2–11 и младенцы до 2. На скриншоте выше видно, как бот спрашивает про них отдельно и объясняет зачем.

Побочная находка. Пока проверял пересчёт, семейный запрос «туда-обратно» занял 22.8 секунды при таймауте в 12. То есть именно семейные поиски — самые дорогие и самые важные для агентства — молча отваливались, и клиент вместо цен видел заглушку. Таймаут поднят до 30 секунд.

Сеть · вебхук, которого не было

Сервис здоров, а Telegram до него не доходит

После переезда бот замолчал. Симптом обманчивый: /health отдаёт 200, процесс живой, сертификат валидный, конфиг nginx правильный. При этом getWebhookInfo показывает Connection timed out и растущую очередь необработанных апдейтов.

Решающая деталь нашлась в логах nginx: от Telegram не пришло ни одного запроса. Не 403, не 500 — вообще ничего. Значит дело не в приложении и не в сертификате: пакеты не доезжают до сервера.

Проверка по адресам подтвердила: один адрес из пула Telegram закрыт, другой открыт. Хостинг фильтрует диапазон в обе стороны.

Ни один конфиг на моей стороне не чинит маршрут, которым владеет провайдер. Чинится сменой транспорта.

Отсюда BOT_MODE=polling: бот сам ходит за апдейтами, и достаточно исходящего канала. Вебхук остался значением по умолчанию — оба транспорта сходятся в один dispatch_update(), так что логика не раздвоилась. Какой режим работает сейчас, видно в /health: поле bot_mode.

Мелочи, которые дорого стоят

Три бага не там, где болело

  • Токен в системном журнале. urllib3 при обрыве соединения пишет в лог полный URL, а Telegram держит токен прямо в пути. Один сетевой сбой — и credential лежит в journald, откуда переезжает в вырезки логов и тикеты. Фильтр на корневых хендлерах режет секрет до записи.
  • Пустая переменная убивала процесс. int(os.getenv("X", "0")) берёт значение по умолчанию, только если переменной нет. Если она есть и пустая, int("") бросает исключение прямо на импорте, и наружу торчит голый exit 3 без единой строчки диагностики. Поправлено в 17 местах; отдельный тест грепает оба модуля, чтобы шаблон не вернулся.
  • Два лида с одного клиента. Двойной тап по кнопке подтверждения создавал две заявки — и менеджер звонил человеку дважды. Гонка закрыта.

Инфраструктура · переезд и данные

С Render на свой сервер в Москве

На бесплатном Render было три проблемы, и ни одна не про удобство. Холодный старт 45+ секунд, замерено дважды: клиент писал боту и ждал почти минуту. Эфемерный диск стирал базу при каждом деплое. Данные лежали в США — для российского ИП, собирающего телефоны клиентов, это отдельный разговор с 152-ФЗ.

Сейчас: свой VPS в Москве, nginx с сертификатом Let’s Encrypt, ночной бэкап базы. /health отвечает за доли секунды вместо сорока пяти — можно открыть и засечь.

Рабочий Telegram-бот использует основной backend; демонстрационный режим в текущем развёртывании выключен. Отправка менеджеру требует отдельного подтверждения. Для проверки интерфейса без реальной заявки можно пройти анкету, проверить параметры и отменить её.

Стек

  • Python
  • Flask
  • Telegram / VK
  • MCP / JSON-RPC
  • Tutu.ru
  • SQLite
  • CRM
  • pytest
  • Docker
  • nginx / VPS

Telegram Serverless · архив прототипа

Анкета в Telegram без отдельного сервера для демо

В октябре 2026 года отдельно проверена JavaScript-версия сценария для Telegram Serverless: обработчик сообщения или кнопки, временное хранение ответов в SQLite, проверка и редактирование анкеты. Ниже — результат того прогона и ссылка на зафиксированную версию кода.

Это исторический пример, а не действующий публичный демо-бот. Сейчас @apreltour_bot — рабочий бот турагентства с отдельным подтверждением отправки менеджеру. Для знакомства остановитесь на проверке параметров или отмените анкету; не отправляйте тестовую заявку.

Что проверили 10 октября 2026 года

  1. Анкета: направление, город вылета, даты, ночи, состав туристов, бюджет и тестовый контакт.
  2. Редактирование: число ночей изменено с 7 на 10; дата возвращения пересчитана, остальные ответы сохранены.
  3. Завершение: прототип подтвердил, что анкета никуда не отправлена; проверена очистка данных командой /delete.
Реальный ответ Telegram-бота: демо завершено, Турция из Москвы, 10–20 ноября 2026 года, 10 ночей, 2 взрослых; телефон скрыт маской, анкета никуда не отправлена.
Реальный прогон 10 октября 2026 года. Число ночей изменено с 7 на 10, дата возвращения — с 17 на 20 ноября. В этом архивном прогоне использован тестовый телефон. Снимок не описывает текущий сценарий рабочего бота.
  • Проверка данных: календарные даты, допустимое число ночей, возраст детей и бюджет. После изменения ночей пересчитывается дата возвращения.
  • Защита состояния: повторные обновления и старые кнопки не должны повторно менять анкету; предусмотрено восстановление после ошибки отправки.
  • Приватность: телефон маскируется до записи в базу. /delete очищает анкету; черновик истекает через 24 часа бездействия, просроченные записи удаляются при следующем обращении к боту.
  • Проверки: 16 автоматических тестов, проверка SDK в облаке и полный сценарий в Telegram 10 октября 2026 года: валидация, редактирование, завершение и очистка.

В архивном прототипе использовались вымышленные данные, без отправки менеджеру или в CRM, бронирования и оплаты. Отдельно проверялся запрос рейсов Tutu: тогда сервис не вернул цены, и прототип показал недоступность поиска. Этот результат не подтверждает текущую доступность тарифов.

Serverless-прототип сохранён в коде отдельно от рабочего Telegram/VK-бота и Mini App. Проверка VPS ниже относится к основному backend.

Код, тесты и инструкция развёртывания ↗ · Изменения в PR #317 ↗

Проверка пользовательских сценариев · октябрь 2026

От параметров поездки до результатов поиска

В Telegram Desktop проверены бот и Mini App: точный бюджет 270 123 ₽, минимальный бюджет «от», возрасты детей, возврат, редактирование и отмена. Поиск проверен на реальных предложениях без оформления заявки, бронирования или оплаты.

  • Данные: бюджет на всю поездку, даты, ночи и возрасты детей сохраняются при переходе из Mini App в чат.
  • Сбои: ошибка поиска отделена от завершённого поиска без подходящих туров; повтор сохранения проверен после прерывания соединения.
  • Границы проверки: живые сценарии на физических мобильных устройствах и финальная доставка реальной заявки менеджеру этим прогоном не подтверждены.

Живой код

Вот он. Не презентация.

Оба мессенджера, FSM, Tourvisor, MCP-клиент, CRM, защита webhook и деплой. Регресс от 16 октября 2026 года: 1 720 тестов прошли, 6 пропущены. Результат прогона в GitHub Actions ↗.

  • Открыть VK-бота — пройти доступный клиентский сценарий; автоматическая подборка Tourvisor требует активной подписки API.
  • bot.r0meo1.ru/health — состояние живого сервиса и ревизия реально запущенного кода.
  • bot.r0meo1.ru/privacy — та самая политика, которую бот отдаёт по ссылке из согласия.
GET bot.r0meo1.ru/health запрашиваю…

Этот запрос уходит из вашего браузера в момент чтения — значения не записаны в страницу. Если бот недоступен, блок честно об этом скажет.

r0meo-1/turbot-arhangelsk Telegram/VK бот · Tourvisor · CRM · Python · Flask
Python Flask Tourvisor VK Callback API Telegram API 1 720 тестов

Финал · что можно проверить

Не «я сделал бота», а два адреса и репозиторий

Основные утверждения можно проверить: клиентский путь — в VK, состояние сервиса — в /health, реализацию и тесты — в публичном репозитории. Старые решения не спрятаны: история Tutu/MCP показывает, как проект менялся вместе с задачей.