Сервис планирования маршрутов выездных инженеров: PostgreSQL + FastAPI + Vue/Leaflet, self-hosted Valhalla для дорожной сети и R5 для общественного транспорта.
./deploy/local.sh # поднять всё локально одной командой
./deploy/deploy.sh # развернуть или обновить сервер (настройки — deploy/deploy.env)
Развёртывание описано в deploy/README.md: локально — deploy/LOCAL.md, на сервере с доменом и сертификатом — deploy/SERVER.md. Для разработки можно поднимать compose напрямую:
docker compose --profile transit up
| Сервис | Порт | Что это |
|---|---|---|
frontend |
5173 | Рабочее место диспетчера: заявки, бригады, планы и их пересчёт |
mobile |
5175 | Приложение бригады: маршрут дня, отметки «выехали / на месте / выполнено» |
backend |
8000 | API, GET /api/v1/health/db для проверки |
postgres |
5432 | БД |
valhalla |
8002 | Маршрутизация по данным OpenStreetMap |
r5 |
8003 | Общественный транспорт по OSM и локальному GTFS |
Первый запуск долгий: Valhalla скачивает экстракт OSM по ЦФО (~877 МБ), после чего R5 вырезает Москву и область и строит транспортный граф. Дальнейшие старты используют данные из Docker volumes.
Вход: первый администратор — admin, пароль из переменной ADMIN_PASSWORD
(cp .env.example .env, см. комментарии в файле). Не задана — будет admin, о чём бэкенд
предупреждает в журнале; сменить можно и в «Справочники» -> «Пользователи». Там же администратор заводит диспетчеров: диспетчер привязан к офису
и видит только его заявки, бригады и планы. Бригады офиса — в «Справочники» → «Бригады»
(и администратору, и диспетчеру): там же бригаде задают логин и пароль для мобильного
приложения (http://localhost:5175).
- Схема БД, миграции (накат —
db/apply_migrations.sh) и допущения при наполнении — db/README.md. - Структура бэкенда, расчёт расстояний и замеренные особенности — backend/README.md.
- Фронтенд — frontend/README.md.
- Пилотный сборщик GTFS и сервис R5 (автобус + метро) — transit/README.md.
- Приложение бригады — mobile/README.md.
Шесть исходных CSV в Обезличивание/ проверяются без подключения к БД:
python scripts/audit_anonymized.pyКоманда сверяет синтетические заявки с контрольными строка к строке по типу, окну, району и адресу без номера квартиры; проверяет даты, повторы номеров и наличие адреса офиса. Отчёт содержит только агрегаты и номера проблемных строк, без адресов и номеров заявок. Ненулевой код выхода означает ошибку структуры, а повторы номеров в контрольном файле показываются отдельно: контрольное распределение нельзя загружать как входной план. Оно нужно для последующего сравнения результатов.
В исходных файлах нет координат, смен, навыков и транспорта бригад. Перед проверкой на полном сценарии потребуется подготовить эти данные и отдельно зафиксировать правила геокодирования адресов. Все исходные окна относятся к 17 августа 2026 года. В текущем GTFS метро покрывает этот день; автобусные шаблоны доступны с 1 августа 2026 года, но до сентябрьских снимков они являются ретроспективной оценкой, а не точным историческим расписанием. Это ограничение учитываем при сравнении с контролем.
-
Диспетчер строит и утверждает план дня. Невлезшие заявки «Новыми» без решения не остаются: при утверждении второй расчёт с раскрытыми окнами сразу предлагает их клиентам время, диспетчер отмечает ответ по каждой (согласен, перенести на другой день, отменить, не дозвонились), и день считается заново — новым черновиком со ссылкой «с решениями из №…». Его смотрят и утверждают отдельно. Перенесённая заявка войдёт в план своего дня. Пока по невлезшим нет решений, сервер черновик не утвердит.
-
Бригада входит в приложение (
mobile) и видит свой маршрут; по ходу дня отмечает «Выехали» (заявка «В пути»), «На месте» («В работе»), «Выполнено» или «Не выполнить» с причиной. Статус заявки и время отметки уходят на сервер, следующую заявку не начать, пока не закрыта прошлая. -
Диспетчер на карте плана в режиме «Факт» видит, где бригады, что выполнено и кто отстаёт; у плана появляется «!» — если заявки сняли, пришли новые или бригада не успевает к окну.
-
«Пересчитать с текущего момента»: сначала пробный расчёт показывает заявки, на которые уже не успеваем, — по каждой выбирают новое окно или отмену; затем выполненное и начатое остаётся за бригадами, бригады стартуют оттуда, где они сейчас, остальное раскладывается заново. Утверждённый пересчёт заменяет план — бригады сразу видят новый маршрут; заявки, которым не нашлось места, возвращаются в «Новые»: сдвинуть окно или отменить.
Пересчёт считается не «прямо сейчас», а на выезд через
REPLAN_LEAD_MINUTES(15 минут) — запас на сам расчёт и обзвон клиентов — и в этот момент вступает в силу сам, без кнопки: в списке планов видно «вступит в силу в 12:15 · через 7 мин». Эти минуты день идёт своим чередом: бригады едут по действующему плану, а выезд открыт туда, куда ведёт и пересчёт (на остальные заявки — «Ждите нового плана»). Выехала бригада раньше по новому маршруту, доехала, закрыла заявку — всё в порядке.Если к этому моменту появились новые вводные — заявка, которой пересчёт не видел, отмена, бригада выбилась из плана или уехала вопреки расчёту, — пересчёт в силу не вступает: он помечается недействительным с причиной, у действующего плана загорается «!» с этим текстом, бригады продолжают ехать по нему, и день считают заново — уже с новыми вводными. Утвердить пересчёт можно и руками, раньше срока; сам расчёт система не перезапускает.
В таблицах «Заявки» и «Исполнители» первая колонка — отметки: галочка в строке, Shift-клик отмечает диапазон, а галочка в строке фильтров отмечает все отобранные строки (отбор и есть отбор — поэтому она там же, где фильтры). Отмеченные строки подсвечены, над таблицей появляется панель «Отмечено: N · Изменить · Удалить».
- Изменить — окно, где каждое поле включается своей галочкой: меняется только то, что отмечено, остальное у каждой строки остаётся своим. Заявки: статус, перенос на другой день (время окна сохраняется), окно, работа на месте, тип работ, приоритет, транспорт. Смены: перенос на день, часы смены, транспорт, навыки, выезд из офиса. Статус — не поле, а переход: он доступен и у заявки, которая уже в плане, а список переходов появляется, только когда статус у всех отмеченных один. Остальные поля правятся целиком или никак: если хоть одну заявку менять нельзя («В плане», «Выполнена») или бригада после переноса окажется в двух сменах сразу, сервер объяснит и не тронет ничего.
- Удалить — удаляется всё, что можно, а по остальному приходит список причин: «Удалено: 18. Не удалось удалить: 2 — заявка №12 в плане…». Одна занятая строка не отменяет удаление остальных.
- На карте отмеченные видно сразу: они в синем кольце, в легенде — «отмечено: N» (и сколько из них скрыто фильтрами). Остальные точки остаются как есть — по ним видно обстановку вокруг выбранных. При переходе на карту страница сама прокручивается к ней, сколько бы плашек ни висело сверху. Панель действий на карте та же, что над таблицей. Сами точки окрашены по статусу заявки — теми же цветами, что плашки в таблице; авария — красная обводка точки. По клику на точку открывается карточка, из неё — «Открыть в плане».
- Сбросить в строке фильтров — одна кнопка на всё, чем сужен список: снимает и фильтры, и отметки.
Заявке может требоваться оборудование — роутер, ТВ-приставка. Сколько бригада увезёт, зависит от транспорта: справочник «Справочники» → «Оборудование», таблица «Сколько увозит бригада» (пары «транспорт × оборудование», db/init/051).
Выдача считается по плану дня. На вкладке планов у открытого плана есть кнопка «Оборудование»: по заявкам бригады видно, сколько ей нужно (x0), и предлагается
выдать = min(сколько увезёт её транспорт, x0 + запас)
Запас — общая настройка «Запас оборудования, шт» в «Система» → «Состояние»: он нужен на брак и на заявки, которые появятся днём. Числа диспетчер правит и утверждает; больше предела транспорта сервер не примет. Утверждение записывает запас смен, и дальше пересчёты считают уже от него: то, что бригада израсходовала или везёт к заявке, зафиксированной на момент выезда T+15, из остатка вычитается.
План дня можно посчитать тремя способами, выбор — в окне расчёта:
- cuOpt — оптимизация на видеокарте NVIDIA;
- OR-Tools — та же постановка задачи (приоритеты, ярусы, окна, цена бригады, вес пробега), но поиск на процессоре: работает там, где видеокарты нет;
- Базовый — контрольный алгоритм ТЗ: первый подходящий исполнитель, без перестановок.
У cuOpt и OR-Tools общий вход и общий выход, поэтому дальше всё одинаково: проверка расписания общественного транспорта по R5, сохранение плана и маршруты. Планы разных решателей сравниваются на вкладке «Планы» → «Сравнение планов».
Матрица общественного транспорта и проверка расписания по каждому плечу — самые долгие шаги
расчёта. Ответы R5 хранятся в базе (travel_cache) по паре точек и времени суток выезда и
переиспользуются: утверждение черновика, пробный пересчёт → пересчёт и повторные расчёты
берут их оттуда, а числа в расчёте остаются такими же, как без кеша. Записи старше 7 дней
удаляются раз в сутки, при смене расписания GTFS — все; сбросить вручную можно в «Система» →
«Состояние». Подробно — docs/algoCachV1.md.
Там же, в «Система» → «Состояние», есть очистка перед показом: она удаляет всё наработанное — заявки и факты, планы с маршрутами и назначениями, журнал расчётов, отметки бригад и смены — оставляя справочники и учётки. Доступна только администратору и подтверждается словом «УДАЛИТЬ»: отменить очистку нельзя.