Несколько месяцев назад я начал делать небольшой сервис для контейнерной логистики.

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

Так появился ГрузоБот это сервис для грузоотправителей, экспедиторов и перевозчиков, работающий через мессенджер MAX.

Сейчас проект уже находится в бета, но продакшене, поэтому хочу рассказать не столько о самом сервисе, сколько о том, что оказалось под капотом и с какими задачами пришлось столкнуться при разработке.

С чего всё началось

Контейнерная логистика это на мой взгляд, достаточно специфическая область. Есть груз. Есть контейнер. Есть точка загрузки, терминал, порт, склад, направление, тип контейнера, свободная машина, иногда порожний контейнер. А большая часть оперативного поиска до сих пор происходит примерно так:

«Есть 40 HC Новороссийск — Москва, нужна машина».

Сообщение отправляется в один чат, второй, третий. Через несколько минут оно теряется среди других сообщений. И спама где кто‑то что‑то спрашивает или устроили срач. Я решил попробовать сделать другую модель. Пользователь размещает объявление один раз, после чего оно остаётся доступным в поиске до закрытия или истечения срока актуальности.

Почему бот

Можно было сразу делать отдельный веб‑сервис или мобильное приложение, но я сознательно начал с бота.

Причина банальна и проста, мне хотелось максимально сократить путь пользователя от открытия сервиса до размещения заявки.

В итоге большинство операций свелось к нескольким кнопкам и коротким пошаговым сценариям.

При этом сайт gruzbot.ru существует отдельно и выполняет роль публичной и рекламной части проекта.

Что получилось технически

Основной стек сейчас довольно простой:

Python для бизнес‑логики, aiohttp для приёма webhook, SQLite для хранения данных, FastAPI для Mini App, Nginx в качестве фронтенда и systemd для запуска сервисов.

Сам webhook сервер построен так, чтобы максимально быстро подтверждать получение события.

MAX отправляет событие, сервер валидирует запрос, создаёт асинхронную задачу обработки и сразу возвращает HTTP 200.

Долгие операции уже выполняются отдельно.

Архитектурно цепочка выглядит примерно так:

MAX
 ↓
Nginx
 ↓
aiohttp webhook
 ↓
router
 ↓
FSM / business logic
 ↓
SQLite
 ↓
MAX API

Отдельно работает FastAPI для Mini App:

MAX Mini App
 ↓
Nginx
 ↓
FastAPI
 ↓
валидация initData
 ↓
действие пользователя

Никаких микросервисов ради микросервисов здесь нет.

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

FSM оказался важнее, чем я ожидал

Большая часть работы с ботом как оказалось это не API и даже не база.

Это состояние пользователя.

Например, создание груза проходит через последовательность:

тип груза
→ ДОПОГ
→ место загрузки
→ место выгрузки
→ контейнер
→ вес
→ ставка
→ тип оплаты
→ комментарий
→ подтверждение

На каждом шаге пользователь может нажать старую кнопку, отправить неожиданное сообщение, вернуться назад или вообще закрыть диалог.

Поэтому вся логика построена вокруг FSM.

И здесь быстро выяснилось интересное: хороший бот это не набор обработчиков кнопок.

Это управление большим количеством маленьких переходов состояния.

SQLite пока оказалось достаточно

На раннем этапе я сознательно не стал поднимать PostgreSQL. Сейчас нагрузка этого просто не требует. В базе находятся пользователи, грузы, машины, отклики, сделки, подписки, аудит и служебные очереди. Главное, что я сделал почти сразу это разделил работу бота и публичного сайта.

PHP на сайте не получает прямой доступ на запись к рабочей базе.

Вместо этого отдельный локальный процесс формирует безопасный публичный JSON со свежими объявлениями.

Получилась примерно такая схема:

gruz_bot.db
 ↓
Python exporter
 ↓
orders.json
 ↓
PHP
 ↓
gruzbot.ru

Это позволяет не выдавать веб‑серверу лишние права к рабочей SQLite‑базе.

Проверка ИНН

При регистрации пользователь указывает ИНН. Далее бот пытается проверить организацию по данным ФНС. Вот тут проявился один довольно интересный баг. У человека могло быть старое закрытое ИП, а спустя несколько лет зарегистрировано новое с тем же ИНН.

Первоначальная логика могла получить информацию о прекращённой регистрации и решить, что предприниматель сейчас не действует. Пришлось переработать выбор записи и определение актуального статуса.

Это хороший пример ошибки, которую практически невозможно увидеть на тестовых данных, но очень легко встретить после запуска.

Контакты пришлось защищать отдельно

Одна из задач проекта это не превратить сервис в готовую базу телефонных номеров. Поэтому контактные данные не отображаются всем пользователям сразу. Стороны сначала взаимодействуют через объявление и отклик. После подтверждения контакты становятся доступны участникам конкретной сделки. Дополнительно существуют ограничения по частоте откликов и просмотров контактов. Я также сохраняю аудит критичных действий.

Что показал продакшен

Самое интересное началось после регистрации реальных пользователей. За первые четыре дня после запуска в сервис пришли 70 новый пользователей. Было создано 3 объявления о грузах и 13 предложений транспорта.

И тут стало очень хорошо видно, зачем вообще нужен ранний запуск.

До продакшена мне казалось, что главное это закончить функции. После запуска оказалось, что техническая часть работает стабильно, пользователи приходят, но рынок формируется неравномерно. Перевозчики подключаются гораздо быстрее грузоотправителей. То есть текущая проблема проекта находится уже не в Python. Она находится в экономике двухстороннего сервиса.

Что я перестал делать после запуска

Самое полезное решение последних дней на мой взгляд было перестать добавлять функции просто потому, что я могу их добавить. У продукта уже есть поиск, объявления, отклики, сделки, подписки, проверка организаций и административная часть. Теперь гораздо важнее дождаться реального поведения пользователей. Если люди не совершают сделку из‑за отсутствия подходящего груза, новый красивый экран ситуацию не исправит.

И это, пожалуй, одно из главных отличий разработки продукта от разработки проекта «для себя».

Дополнительный инструмент

Отдельно я сделал визуальный калькулятор загрузки контейнера. Он позволяет выбрать 20-, 40-, 40 HC, 45 HC или стандартную еврофуру 13,6 м, добавить паллеты и посмотреть схему размещения. Есть ручная расстановка, поворот грузовых мест и автоматическая укладка.

Калькулятор полностью работает в браузере и доступен бесплатно:

https://gruzbot.ru/kalkulyator-zagruzki-konteynera/

Что дальше

Сейчас я не планирую радикально менять архитектуру. Система выдерживает текущую нагрузку с огромным запасом.

А вот следующий этап гораздо менее технический: нужно получить достаточное количество грузов, первые отклики и первые реальные сделки. А уже после этого данные покажут, что действительно стоит переписывать или улучшать.

Сам проект находится здесь:

https://gruzbot.ru/

Если тема будет интересна, отдельно могу рассказать про архитектуру webhook, защиту контактов, устройство FSM или то, какие проблемы всплыли после появления первых реальных пользователей.

Комментарии (0)