Русский | English
Dispatch Core — самостоятельно разворачиваемое ядро диспетчерской для небольших выездных команд. Система принимает заявки, предлагает или назначает их исполнителям, фиксирует движение и не позволяет завершить работу без требуемого отчёта.
заявка -> пул/прямое назначение -> принятие -> выезд -> работа -> отчёт -> закрытие
Это не попытка скопировать все функции CRM. Dispatch Core отвечает за операционное исполнение заявки. Клиент, оператор, мастер и администратор работают в ролевых Telegram/MAX-ботах. Сайт, телефонный ввод и внешняя система могут создавать заявки, но веб-доска диспетчера намеренно не входит в продукт.
Проект показывает архитектуру серверной системы на FastAPI и PostgreSQL, рассчитанную на реальные сбои, повторы событий и конкурентную работу нескольких воркеров:
- явные доменные автоматы состояний вместо произвольной смены статусов ботами;
- курируемый пул: кнопка «Готов взять» только сохраняет интерес, а исполнителя осознанно выбирает оператор;
- опциональный режим
first_claimдля равнозначных дежурных исполнителей; - опциональный диспетчер: прямое назначение и
first_claimмогут работать без него; - изоляция организаций и внешних идентификаторов начиная с первой миграции;
- optimistic concurrency и ограничения PostgreSQL, защищающие от гонок;
- transactional outbox, долговечный inbox провайдеров и очередь исходящих сообщений;
- конкурентные потребители на
FOR UPDATE SKIP LOCKED; - ограниченные повторные попытки, exponential backoff и dead-letter состояния;
- идемпотентное создание заявок и безопасная повторная обработка callback;
- append-only история GPS в PostgreSQL;
- защита от дублирования фотографий и координат при повторной доставке события;
- polling/webhook, кнопки, геопозиция и фотоотчёты для Telegram и MAX;
- guided intake адреса тремя способами: нативная геопозиция, защищённая карта или ручной ввод с явным предупреждением о проверке точки при VPN/GPS spoofing;
- клиентская карта с последней точкой мастера и резервная непрерывная передача GPS из браузера, если нативная геолокация мессенджера недоступна;
- раздельные 256-битные capability-токены чтения и записи, которые не попадают в query string и отзываются при завершении или отмене заявки;
- контейнеры без root, read-only filesystem и файловые Docker secrets.
Исходный код и Git-история полностью отделены от частной рабочей системы, которая подсказала бизнес-процесс. В репозитории нет production-токенов, клиентских данных, закрытых шрифтов или чужого брендинга.
Это публичная reference-редакция: она содержит запускаемый вертикальный срез, достаточный для аудита архитектуры и самостоятельной разработки интеграций. Управляемое развёртывание, инфраструктурные профили клиентов, connectivity bundles, генератор сайта и будущий self-service installer не являются частью этого репозитория и могут поставляться отдельно.
- Python 3.12–3.14;
- FastAPI, Pydantic Settings и Uvicorn;
- PostgreSQL и asyncpg;
- httpx для Telegram/MAX API и proxy-aware транспорта;
- Docker и Docker Compose;
- pytest, pytest-asyncio, Coverage и Ruff;
- GitHub Actions с PostgreSQL 17.
FastAPI / сайт / клиентский бот
|
v
Telegram или MAX -> durable inbox -> команды приложения
|
v
PostgreSQL <- WorkOrder + TrackingSession
| |
| одна транзакция
| v
+---------- domain outbox
|
проектор уведомлений
|
очередь на отправку
|
Telegram / MAX
Жизненный цикл защищён доменными правилами:
SUBMITTED --публикация curated-------> POOL_OPEN --выбор оператором--> ASSIGNED
| |
| +--first claim-----------> ASSIGNED
+--прямое назначение---------------------------------------------> ASSIGNED
ASSIGNED --принять--> ACCEPTED --выехать (опционально)--> EN_ROUTE
\---------------------------> IN_PROGRESS
EN_ROUTE -------------------------------------------------> IN_PROGRESS
IN_PROGRESS --валидный отчёт------------------------------> COMPLETED
ASSIGNED --отказ--> POOL_OPEN или SUBMITTED
любое незавершённое состояние --отмена--------------------> CANCELLED
Требования к завершению задаются данными: независимо включаются минимальное число фотографий, комментарий, подпись и код клиента. Расчёта зарплат, долей оператора или исполнителя в ядре нет.
Текущий проверенный набор содержит 670 проходящих тестов, включая 44
integration-теста на отдельной PostgreSQL-базе, указанной в
TEST_DATABASE_URL.
Проверяются:
- все 256 комбинаций требований и состава отчёта;
- допустимые и запрещённые переходы жизненного цикла;
- курируемый и
first_claimпулы; - конкурентные назначения и запрет двойной занятости исполнителя;
- транзакционность inbox/outbox и восстановление зависших сообщений;
- polling cursor, дедупликация, retries и dead-letter;
- контракты Telegram и MAX;
- реальный сквозной messenger-сценарий на PostgreSQL;
- production wiring API/worker и корректное завершение процессов;
- append-only GPS и идемпотентность повторных фото/координат.
CI запускается на Python 3.12, 3.13 и 3.14 с PostgreSQL 17 и не пропускает изменения ниже установленного порога покрытия.
Требуется Python 3.12 или новее.
python -m venv .venv
.venv/bin/pip install -e '.[server,dev]'
.venv/bin/python -m dispatch_core
.venv/bin/pytestДемонстрация в памяти проводит заявку через полный курируемый сценарий без
внешних сервисов. Интеграционные тесты PostgreSQL запускаются при наличии
TEST_DATABASE_URL.
- Скопируйте
.env.exampleв.envи замените все значенияchange-me. - Запустите базу и API:
docker compose up --build -d
curl http://127.0.0.1:8080/health/readyВ базовом профиле мессенджеры намеренно выключены. Для их подключения создайте локальные файлы токенов и добавьте transport override:
mkdir -p secrets
for name in telegram_{client,operator,master,admin}_bot_token \
max_{client,staff}_bot_token; do
install -m 600 /dev/null "secrets/$name"
done
# Поместите соответствующие токены в файлы и никогда их не коммитьте.
docker compose -f compose.yaml -f compose.transports.example.yaml up --build -dВ Telegram роли разделены между клиентским, операторским, мастерским и
административным ботами. В MAX используются два физических бота: клиентский и
общий staff-бот; staff-бот при /start предлагает кнопки только для ролей,
которые реально выданы этому человеку.
В штатном сценарии администратор создаёт сотрудника, а сотрудник привязывает
свой Telegram/MAX-аккаунт одноразовым кодом. POST /v1/actors остаётся для
развёртывания и тестовых фикстур; заявки можно создавать через
POST /v1/orders. Для локального Swagger задайте
DISPATCH_ENVIRONMENT=development; в production /docs, /redoc и
/openapi.json отключены.
Для трекинга задайте внешний HTTPS-адрес в DISPATCH_PUBLIC_BASE_URL. После
кнопки «Выехал» мастер получает нативный запрос геопозиции и резервную ссылку
/track/share#…, а клиент — отдельную read-only ссылку /track#…. Секреты
остаются во fragment URL; браузер передаёт их API только в заголовках. Карта
показывает обязательную подпись OpenStreetMap. Публичные тайлы OSM не имеют SLA,
поэтому коммерческая установка должна предусмотреть заменяемого провайдера.
В клиентском intake адрес можно отправить нативной геопозицией, выбрать на
/address#… или ввести текстом. Карта сначала заблокирована для прокрутки
страницы: первый тап включает перемещение, следующий фиксирует точку и снова
блокирует карту. Одноразовая capability ссылки атомарно погашается при
сохранении. IP-адрес VPN обычно не меняет GPS, но интерфейс просит проверить
точку и выбрать карту/ручной ввод, если клиент находится не на объекте или
использует подмену геопозиции.
Подробные команды, настройка webhook и ограничения описаны в руководстве по эксплуатации.
- Webhook отвечает успехом только после записи исходного события в PostgreSQL.
- Polling batch и следующий cursor фиксируются одной транзакцией.
- Изменение агрегата и соответствующий domain event фиксируются одной транзакцией.
- Проектор одной транзакцией создаёт callback tokens, исходящие сообщения и завершает outbox event.
- Сетевой запрос выполняется вне транзакции; его успех или повтор сохраняется отдельно.
- Повторные provider events и исходящие сообщения отсеиваются стабильными уникальными ключами.
- После падения воркера захваченная запись становится доступной по stale timeout.
- Неожиданный сбой рабочего цикла приводит к ограниченному backoff, а не к окончательной остановке процесса.
Это модель at-least-once с идемпотентными границами, а не недостоверное обещание «магической exactly-once доставки» через сеть.
Готово как основание: доменное и прикладное ядро, PostgreSQL, FastAPI, Telegram/MAX adapters, долговечный messaging workflow, GPS-трекинг, IndustryPack, guided client intake, контролируемая привязка сотрудников по одноразовому коду и рабочие operator/master-меню. Оператор уже может создать, просмотреть и безопасно отключить свободного мастера; мастер видит собственные активные заявки и допустимые действия по их текущему статусу.
До первого продукта: закончить операционные карточки и назначение из меню, проверку отчёта оператором, клиентские chat/status/review сценарии и автоматическую Telegram/MAX parity. Веб-доска не планируется.
Графический установщик, локальный профиль, hybrid edge, оптимизация маршрутов, биллинг, склад, зарплаты и полноценная sales CRM отложены. Текущий срез — архитектурное основание, но ещё не самостоятельный продукт.
- Якорь продукта и parity-контракт
- Архитектура
- Эксплуатация и развёртывание
- Каталог применимости
- Связность и резервный Telegram egress
- Дорожная карта
- Политика безопасности
- Правила участия
Dispatch Core распространяется по лицензии GNU Affero General Public License v3.0 only. Если модифицированная версия предоставляется пользователям как сетевой сервис, AGPL требует дать этим пользователям доступ к соответствующему исходному коду. Для условий коммерческого использования без AGPL следует связаться с владельцем репозитория.