Перейти к содержанию

Архитектура и расчёт изнутри

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

Сервисы

flowchart LR
  U[Браузер оператора] -->|HTTPS| IN[Ingress<br>TLS, доступ по паролю]
  IN -->|/| FE[Фронтенд<br>nginx: shell + 3 remote]
  IN -->|/api, /healthz, /status| GW[Gateway<br>NestJS + Fastify]
  GW -->|gRPC| PL[Планировщик<br>Python, OR-Tools]
  GW -->|gRPC| VA[Валидатор<br>Python]
  GW -->|SQL| PG[(PostgreSQL<br>сценарии, планы,<br>очередь, события)]
  FE -.->|сборка интерфейса| S3[(MinIO<br>бандл фронтенда,<br>подложка карт)]
  GW -.->|по настройке| OM[Прогноз ветра<br>Open-Meteo]
  GW -.->|по настройке| LLM[Языковая модель<br>для ассистента]
  PL --- DEM[(Цифровая модель<br>рельефа)]
  VA --- DEM
Сервис Технологии Роль
Фронтенд React 19, Vite, Module Federation, Ant Design, MapLibre + deck.gl, PMTiles Оболочка shell и три подключаемых модуля: planner (сценарий, воздушное пространство, план, экспорт, отчёт), validator (отчёт валидатора, Парето), viewer3d (3D-пролёт)
Gateway TypeScript, NestJS 10 на Fastify Единственная внешняя точка входа: REST /api/*, очередь расчётов, поток прогресса (SSE), идемпотентность, экспорт, чат-ассистент, прогноз ветра
Планировщик Python, Shapely, pyproj, numpy, OR-Tools Строит план по сценарию; выгружает KML, GeoJSON, QGC .plan, путевые точки
Валидатор Python, собственная геометрия Независимо пересчитывает план: 39 проверок, метрики, вердикт
PostgreSQL PostGIS 16 Пользовательские сценарии, готовые планы, очередь заданий, события прогресса, ключи идемпотентности
MinIO S3-совместимое хранилище Только собранный интерфейс и файлы подложки; бэкенд к нему не обращается

Внутри кластера gateway ходит в планировщик (порт 5001) и валидатор (порт 5002) по gRPC с сервисным ключом в заголовке. Размер gRPC-сообщения — до 64 МиБ, тела HTTP-запроса — до 25 МБ, ограничение частоты — 100 запросов в минуту с одного адреса.

Контракты между сервисами

Сервис Вызов Назначение
PlannerService Plan(PlanRequest) → stream PlanEvent Расчёт; в потоке — события прогресса и итоговый PlanResult
PlannerService StopPlan Остановка расчёта оператором
PlannerService ExportPlan Выгрузка KML / GeoJSON / QGC .plan / путевых точек
ValidatorService Validate(ValidateRequest) → ValidateResponse Отчёт по паре «сценарий + план»

Подробно — gRPC и схемы.

Как проходит один расчёт

sequenceDiagram
  autonumber
  actor O as Оператор
  participant UI as Интерфейс
  participant GW as Gateway
  participant DB as PostgreSQL
  participant PL as Планировщик
  participant VA as Валидатор

  O->>UI: «Рассчитать»
  UI->>GW: POST /api/plans (сценарий, критерий, лимит, Idempotency-Key)
  GW->>GW: JSON-схема сценария
  GW->>DB: задание в очередь (queued)
  GW-->>UI: 202 planId
  UI->>GW: GET /api/plans/{id}/events (SSE)
  loop каждые 500 мс
    GW->>DB: взять задание (FOR UPDATE SKIP LOCKED)
  end
  GW->>PL: gRPC Plan (дедлайн = лимит + 30 с)
  PL-->>GW: стадии прогресса, затем PlanResult
  GW->>DB: план сохранён, задание done
  GW-->>UI: completed
  UI->>GW: GET /api/plans/{id}
  UI->>GW: POST /api/validate {scenario, plan}
  GW->>VA: gRPC Validate
  VA-->>UI: отчёт: 39 проверок, вердикт

Очередь

Очередь — это таблица заданий в PostgreSQL, без отдельного брокера сообщений.

Параметр Значение
Опрос очереди каждые 500 мс, захват FOR UPDATE SKIP LOCKED
Одновременных расчётов на экземпляр gateway 2 (PLAN_CONCURRENCY)
Длина очереди до 20 (PLAN_QUEUE_LIMIT); сверх — 429 QUEUE_FULL и Retry-After: 30
Пульс задания не реже раза в 10 с; задание без пульса дольше 60 с снимается с кодом SOLVER_INTERRUPTED
Идемпотентность повтор POST с тем же Idempotency-Key возвращает тот же ответ (заголовок Idempotency-Replayed: true); ключ хранится 24 ч
Статусы queued → running → completed / failed

Прогресс

Поток GET /api/plans/{id}/events (Server-Sent Events) отдаёт события со стадией, долей готовности, значением критерия и прошедшим временем; поддерживает Last-Event-ID для переподключения. Если поток оборвался, интерфейс переходит на опрос GET /api/plans/{id} каждые 500 мс.

Стадия Что происходит
queued задание ждёт в очереди
geometry разбор сценария, справочник техники, погода
coverage отбор бортов, высота съёмки, обрезка участков по пространству и зонам
routing назначение галсов бортам и порядок галсов
solving раскладка по бортам
export упаковка в вылеты, перелёты, профиль, метрики
completed / failed итог

Честно о прогрессе

Сейчас планировщик накапливает события стадий и отправляет их вместе с результатом, поэтому во время долгого расчёта интерфейс видит «Очередь», затем пачку стадий в конце. Индикатор «Идёт М:СС» при этом показывает реальное время.

Конвейер планировщика

flowchart TD
  A[1. Разбор сценария<br>схема, справочник] --> B[2. Предпроверка GSD<br>есть ли борт, способный дать GSD]
  B -->|нет| X[422 GSD_UNREACHABLE]
  B --> C[3. Отбор бортов<br>нагрузка, ветер, заряд, температура]
  C --> D[4. Высота съёмки<br>из GSD и камеры]
  D --> E[5. Обрезка участков<br>разрешённое пространство, зоны]
  E --> F[6. Угол галсов<br>ветер, развороты]
  F --> G[7. Полосы и галсы<br>шаг с учётом сноса]
  G --> H[8. Назначение галсов бортам<br>и порядок обхода]
  H --> I[9. Упаковка в вылеты<br>энергия, резерв, оборот]
  I --> J[10. Перелёты<br>обход зон, рельеф, профиль]
  J --> K[11. Метрики, площадки,<br>разведение бортов]
  K --> L{Уточнять угол?}
  L -->|да| M[12. Целые планы по<br>вариантам угла, параллельно]
  M --> N[Лучший план]
  L -->|нет| N
  N --> O[13. Проверка фактического GSD]
№ Стадия Какие параметры сценария учитываются
1 Разбор сценария весь документ; ошибки схемы → 400/422
2 Предпроверка GSD tasks[].survey_type, quality.gsd_cm, fleet[].model/payload/home, launch_sites[].elevation_m
3 Отбор бортов fleet[].enabled, battery_left_frac, payload; objective.energy_reserve; weather.wind_speed_ms, temperature_c; предел ветра модели
4 Высота съёмки quality.gsd_cm (или плотность точек, межгалсовое расстояние), фокусное расстояние и матрица камеры
5 Обрезка участков airspace.allowed, airspace.no_fly[] на высоте съёмки, objective.safety_buffer_m, zone_vertical_margin_m
6 Угол галсов tasks[].angle_deg (если задан), ветер, objective.turn_mode
7 Полосы и галсы перекрытия overlap_forward_frac, overlap_side_frac, снос ветром, зоны и граница пространства
8 Назначение и порядок objective.criterion, lambda_frac, площадки бортов
9 Упаковка в вылеты запас хода модели, energy_reserve, температура, battery_left_frac, оборот и запасные АКБ площадок
10 Перелёты и профиль зоны, ветер, рельеф, полосы и глиссада (при включённом расчётном профиле)
11 Метрики и площадки service_slots и запасные АКБ площадок, разведение бортов (separation в запросе)
12 Уточнение угла критерий, lambda_frac, лимит времени

Детали каждой стадии и все числа — в Правилах и допущениях расчёта.

Параллельный перебор угла галсов

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

Параметр Значение
Число процессов PLANNER_ANGLE_WORKERS, по умолчанию min(число ядер, 8); на стенде — 8
Когда включается режим PLANNER_ANGLE_SEARCH=full, не пересчёт после выбытия борта, есть участки без заданного угла, участков не больше 4

Остальные пороги перебора (число кандидатов, минимальный выигрыш, остановка, доля лимита времени) — в разделе Лимит времени расчёта.

Хранение данных

Таблица PostgreSQL Что хранит
scenarios пользовательские сценарии с версиями и источником (мастер, импорт, демо)
plans готовые планы: критерий, лимит, зерно, статус решателя, документ плана, метрики, отпечаток
plan_jobs очередь расчётов: статус, стадия, прогресс, лучший критерий, ошибка, пульс, запрос на остановку
plan_events события прогресса для потока SSE
idempotency_keys ответы на повторные запросы с тем же ключом (24 ч)

Демо-сценарии в базе не лежат: gateway читает их из файлов и объединяет со списком пользовательских. Справочник бортов и нагрузок подключается файлами fleet.yaml и payloads.yaml. Отчёт валидатора сейчас в базу не сохраняется: интерфейс запрашивает его после каждого расчёта.

Развёртывание

Стенд работает в Kubernetes (k3s): по одному экземпляру каждого сервиса, PostgreSQL и MinIO — с постоянными томами. Образы помечаются 8-символьным хешем коммита. Цифровая модель рельефа подключается к планировщику и валидатору каталогом (PLANNER_DEM_DIR, VALIDATOR_DEM_DIR).

Сервис Ресурсы: запрос → предел
Gateway 0,1 CPU / 256 МиБ → 1 CPU / 1 ГиБ
Планировщик 2 CPU / 1 ГиБ → 16 CPU / 8 ГиБ
Валидатор 0,1 CPU / 256 МиБ → 1 CPU / 2 ГиБ

Локальный запуск — Эксплуатация.