Skip to content

saurongit/dispatch-core

Repository files navigation

Dispatch Core

Русский | 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.

Запуск через Docker Compose

  1. Скопируйте .env.example в .env и замените все значения change-me.
  2. Запустите базу и 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 отложены. Текущий срез — архитектурное основание, но ещё не самостоятельный продукт.

Документация

Лицензия

Dispatch Core распространяется по лицензии GNU Affero General Public License v3.0 only. Если модифицированная версия предоставляется пользователям как сетевой сервис, AGPL требует дать этим пользователям доступ к соответствующему исходному коду. Для условий коммерческого использования без AGPL следует связаться с владельцем репозитория.

About

Self-hosted dispatch core for field teams: FastAPI, PostgreSQL, Telegram/MAX, durable inbox/outbox and GPS tracking.

Topics

Resources

License

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages