Skip to content

Latest commit

 

History

22 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Dispatch Core

Русский | English

Открытый инженерный проект цифрового бюро QUBIT.

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

заявка -> пул/прямое назначение -> принятие -> выезд -> работа -> отчёт -> закрытие

Это не попытка скопировать все функции CRM. Dispatch Core отвечает за операционное исполнение заявки. Клиент, оператор, мастер и администратор работают в ролевых Telegram/MAX-ботах. Администратор там же конструирует и публикует сервис через IndustryPack. Сайт, телефонный ввод и внешняя система могут создавать заявки как дополнительные источники.

Возможности проекта

Проект описывает архитектуру серверной системы на 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;
  • полный admin-конструктор в Telegram/MAX: бренд, каталог и порядок услуг, одиночный/множественный выбор, типизированные поля и вопросы, требования к закрытию, политика распределения, названия ролей, предпросмотр, публикация и восстановление версий;
  • начатая клиентская заявка закрепляется за версией IndustryPack, поэтому публикация новой формы не меняет уже заполняемый сценарий;
  • клиентская карта с последней точкой мастера и резервная непрерывная передача GPS из браузера, если нативная геолокация мессенджера недоступна;
  • раздельные 256-битные capability-токены чтения и записи, которые не попадают в query string и отзываются при завершении или отмене заявки;
  • контейнеры без root, read-only filesystem и файловые Docker secrets;
  • heartbeat воркеров, защищённая диагностика очередей и проверяемый PostgreSQL restore-drill.

Исходный код и 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.

Модули-адаптеры (подмодули)

Помимо мессенджерных ролевых интерфейсов, репозиторий включает два независимых модуля, подключённых как git submodule. Оба — тонкие адаптеры (порты) к HTTP API ядра; доменная логика остаётся только в Python.

  • dispatch-edge — Symfony 5 микросервис высоконагруженного приёма GPS-точек от полевых устройств (без пользовательского интерфейса: только HTTP API для устройств). Проверяет авторизацию, rate-limit-ит по IP и асинхронно (Symfony Messenger) пересылает точку в ядро через POST /v1/tracking/{session}/points (Bearer executor-token).
  • dispatch-admin — Laravel админ-интерфейс оператора: список и карточка заказов, кнопки подтверждения/отклонения для статуса report_pending (report:approve / report:reject ядра).

Оба модуля работают на едином рантайме php:8.4-cli и общаются с ядром только по HTTP-контракту (admin-key для оператора, executor-token для устройств).

устройство исполнителя --GPS--> [Symfony edge] --HTTP /v1/tracking--> [ядро]
оператор (браузер)       --HTTP--> [Laravel admin] --HTTP /v1/orders--> [ядро]

Клон всей системы одной командой:

git clone --recurse-submodules https://github.com/saurongit/dispatch-core.git

Это реализация границы, которую архитектура называет «приём трекинга можно выделить без изменения модели WorkOrder»: тяжёлый приём трафика вынесен в отдельный сервис, а операторский веб-интерфейс — в отдельный адаптер.

Реализованный сквозной сценарий

                ВНЕШНИЕ ИСТОЧНИКИ КОМАНД
  сайт | клиентский бот | API
  [Symfony edge]  --GPS-точки----------------> команды приложения (FastAPI)
  [Laravel admin] --HTTP (report:approve/reject)-> команды приложения (FastAPI)
  Telegram / MAX  --durable inbox------------> команды приложения (FastAPI)
                                      |
                                      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 --валидный отчёт------------------------------> REPORT_PENDING
REPORT_PENDING --оператор принимает-----------------------> COMPLETED
REPORT_PENDING --оператор возвращает (с причиной)---------> IN_PROGRESS

ASSIGNED --отказ--> POOL_OPEN или SUBMITTED
любое незавершённое состояние --отмена--------------------> CANCELLED

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

Тесты и контроль качества

Текущий проверенный набор содержит 739 проходящих тестов, включая 57 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 или новее.

uv sync --frozen --extra server --extra dev
uv run python -m dispatch_core
uv run 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-меню. Оператор уже может создать, просмотреть и безопасно отключить свободного мастера; мастер видит собственные активные заявки и допустимые действия по их текущему статусу.

Полиглотная архитектура портов и адаптеров реализована подмодулями dispatch-edge (Symfony) и dispatch-admin (Laravel), которые общаются с ядром исключительно по HTTP API; доменная логика остаётся в Python.

До первого продукта: закончить операционные карточки и назначение из меню, проверку отчёта оператором, клиентские chat/status/review сценарии и автоматическую Telegram/MAX parity. Все ролевые рабочие места остаются в мессенджерах.

Графический установщик, локальный профиль, офисный hybrid-профиль развёртывания, оптимизация маршрутов, биллинг, склад, зарплаты и полноценная 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

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages