Все кейсы

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

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

Бот собирает заявку, ищет пакетные туры через Tourvisor, показывает варианты и передаёт менеджеру выбранное предложение. AI ускоряет разбор кода, сценариев и ошибок, а фактические цены берутся из внешних сервисов: Tourvisor и MCP Tutu.

74 строки

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

~28 000 ₽

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

202 теста

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

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

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

  • Клиент получает варианты сам. VK-бот отправляет параметры в Tourvisor, показывает пакетные туры и сохраняет выбранное предложение для менеджера.
  • Дети перестали быть взрослыми. Семейный расчёт завышался примерно на 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 отвечает за доли секунды вместо сорока пяти — можно открыть и засечь.

Публичная витрина работает в демо-режиме: бот предупреждает, что это демонстрация, и хранит телефон замаскированным — +7916***4567. Собирать чужие персональные данные от лица реального ИП ради витрины незачем. У VK-бота отдельный флаг, он боевой.

Стек

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

Живой код

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

Оба мессенджера, FSM, Tourvisor, MCP-клиент, CRM, защита webhook и деплой. Сейчас в проекте 202 теста; полный прогон опубликован в GitHub Actions.

  • Открыть VK-бота — пройти клиентский сценарий и посмотреть самостоятельную подборку.
  • 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 202 теста

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

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

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