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

Программные интерфейсы geoscan (REST, gRPC, схемы, выгрузки)

Этот раздел описывает публичный HTTP-контур gateway сервиса планирования полётных заданий БВС: как поставить расчёт в очередь, дождаться результата, выгрузить KML/GeoJSON и проверить план валидатором. Жюри и заказчик найдут здесь сквозной сценарий «сценарий → план → файл» за несколько минут. Разработчик — ссылки на proto, JSON Schema и реальные примеры curl к стенду https://aerozveno.ff. Оператор — коды ошибок, поллинг/SSE и что делать, если план в failed или выгрузка отдаёт 409.

Источник правды — код в src/backend/gateway, src/backend/planner, src/backend/validator, src/backend/schema/*.json. Симулятор (simulate/**) — отдельный продукт: интерфейсы симулятора.

Смежные разделы: архитектура, доменная логика плана, эксплуатация.


Границы системы

flowchart TB
  subgraph clients [Клиенты]
    UI[Веб-UI / оператор]
    CLI[Скрипты curl / CI]
  end
  subgraph gateway [Gateway NestJS + Fastify]
    REST["/api/* REST"]
    OPS["/healthz /readyz /status /metrics"]
    SPA[Статика SPA]
  end
  subgraph internal [Внутренние сервисы tailnet]
    PG[(PostgreSQL gateway)]
    PL[Planner gRPC :5001]
    VL[Validator gRPC :5002]
  end
  UI --> REST
  CLI --> REST
  REST --> PG
  REST --> PL
  REST --> VL
  OPS --> PL
  OPS --> VL
  REST --> SPA

Gateway — единственная точка входа с TLS для внешних клиентов. Планировщик и валидатор по gRPC доступны только из кластера; между сервисами передаётся заголовок x-service-api-key (см. src/backend/planner/planner/rpc/server.py:30-36, src/backend/validator/validator/rpc/server.py:26-32). Пользовательской аутентификации на REST нет: AuthGuard всегда пропускает запрос (src/backend/gateway/src/common/guards/auth.guard.ts:7-10).


Базовый URL и инструменты

Параметр Значение
Стенд (проверено 2026-09-17) https://aerozveno.ff
TLS --cacert ff-ca/ff-ca.crt (корень внутреннего CA)
OpenAPI / Swagger UI GET /api/openapi.json, UI: /api/docs (src/backend/gateway/src/main.ts:84-92)
Лимит тела запроса 26 214 400 байт (src/backend/gateway/src/config/config.schema.ts:3-4)
Rate limit 100 запросов / мин на IP (src/backend/gateway/src/main.ts:71-74)

Проверка доступности:

curl --cacert ff-ca/ff-ca.crt https://aerozveno.ff/healthz

Пример ответа (живой стенд):

{"status":"ok"}

Сквозной сценарий: расчёт плана

Поток данных

sequenceDiagram
  participant C as Клиент
  participant G as Gateway
  participant DB as PostgreSQL
  participant P as Planner gRPC
  participant V as Validator gRPC

  C->>G: POST /api/plans (сценарий)
  G->>DB: INSERT status=queued
  G-->>C: 202 planId + Location
  Note over C,G: альтернатива: GET /api/plans/{id}/events (SSE)

  loop Поллинг или SSE
    C->>G: GET /api/plans/{id} или SSE
    G-->>C: queued / running / completed
  end

  G->>P: Plan (stream PlanEvent)
  P-->>G: progress… + PlanResult
  G->>DB: status=completed, plan JSON

  C->>G: GET /api/plans/{id}/export/kml
  G->>P: ExportPlan
  P-->>G: bytes + Content-Type
  G-->>C: 200 attachment

  C->>G: POST /api/validate {scenario, plan}
  G->>V: Validate
  V-->>G: отчёт JSON
  G-->>C: 200 { report: … }

Диспетчер очереди в gateway забирает задачи и вызывает PlannerGrpcClient.runPlan (src/backend/gateway/src/plans/plan-dispatcher.service.ts:55-134). Дедлайн RPC = time_limit_s задачи + 30 с запаса (plan-dispatcher.service.ts:19, 99-100; job — runJob с 84).

Живой пример (s01-simple)

Полный цикл с planId из ответа текущего POST (без чужих UUID) — в rest-reference.md § «Сквозной сценарий». Кратко:

  1. POST /api/plans202, в теле planId, в заголовке Location: /api/plans/{uuid}.
  2. GET /api/plans/{planId} в цикле или GET …/events (SSE) на том же planId.
  3. При status: completedGET …/export/kml (и другие fmt).

Прогон 2026-09-17 (вечер): inline-сценарий из src/backend/scenarios/s01-simple.jsonplanId=2acdc05c-9e9b-430e-9993-81853bd31718, поллинг до completed, KML 38 633 байт. Только scenario_id: s01-simple на том же стенде даёт тот же порядок размера KML (38 633 байт) — см. rest-reference.md.

Поля конверта POST: scenario или scenario_id, objective (критерий), seed, time_limit_s или solver.time_limit_s (src/backend/gateway/src/plans/plans.service.ts:240-298). Умолчание лимита солвера на gateway — PLAN_DEFAULT_TIME_LIMIT_S (30 с в схеме env: config.schema.ts:30).

В completed в теле появляются metrics (обёртка gateway, snake_case) и вложенный документ plan (JSON Schema плана, snake_case). Ключи metrics на GET: makespan_s, total_flight_time_s, total_distance_m, uav_used, sorties, coverage_fraction, objective, solver (src/backend/gateway/src/plans/plan-metrics.ts:15-35).

Реализация SSE: text/event-stream, поля id / data, терминальные стадии completed и failed (src/backend/gateway/src/plans/plans.controller.ts:107-184). Поддерживается Last-Event-ID для догрузки (plans.controller.ts:127-134). Стадии прогресса приходят из планировщика (geometry, coverage, export, …) и дополняются gateway-событиями queued / completed / failed.

Подробная таблица всех REST-ручек и curl на каждую ручку — в rest-reference.md.


Жизненный цикл задания на расчёт

stateDiagram-v2
  [*] --> queued: POST /api/plans 202
  queued --> running: dispatcher claimNext
  running --> completed: PlanResult + markCompleted
  running --> failed: gRPC/INTERNAL error
  completed --> [*]
  failed --> [*]
  completed --> queued: POST .../replan 202

Статусы хранятся в репозитории планов: queued | running | completed | failed (src/backend/gateway/src/plans/plans.repository.ts:6-7). Пока статус не completed, поле plan в GET отсутствует; выгрузка /export/* возвращает 409 PLAN_NOT_READY (проверено на живом стенде).


Каталог REST (кратко)

Метод Путь Назначение
GET /healthz Liveness
GET /readyz Готовность (planner/validator/БД)
GET /status Версия, commit, uptime
GET /metrics Prometheus text
GET /api/fleet Каталог БВС (fleet.yaml)
GET /api/payloads Каталог нагрузок
GET/POST /api/scenarios Список / сохранение сценария
GET /api/scenarios/{id} Документ сценария
POST /api/plans Постановка расчёта (202)
GET /api/plans/{id} Статус или готовый план
GET /api/plans/{id}/events SSE прогресса
GET /api/plans/{id}/export/{fmt} KML, GeoJSON, plan, waypoints
POST /api/plans/{id}/replan Пересчёт без выпавших бортов (202)
POST /api/validate Отчёт валидатора (200)

Полные тела, коды ошибок и примеры — rest-reference.md.


gRPC между gateway, planner и validator

Контракты в src/backend/proto/geoscan/:

Сервис RPC Назначение
PlannerService Planstream PlanEvent Расчёт; в потоке PlanProgress и финальный PlanResult
PlannerService ExportPlan Бинарная выгрузка (KML, GeoJSON, …)
ValidatorService Validate Отчёт по паре сценарий+план

Сообщения PlanRequest, метрики, нарушения и форматы экспорта — в grpc-schemas-exports.md.


JSON Schema сценария и плана

Файл Назначение
src/backend/schema/scenario.schema.json Вход POST /api/scenarios; вложенный scenario в POST /api/plans
src/backend/schema/plan.schema.json Документ плана в GET /api/plans/{id} при completed
src/backend/schema/units.json Суффиксы единиц в proto (SR-RPC-07)

Обязательные поля, единицы, поведение при нарушении схемы — grpc-schemas-exports.md.

Диаграмма связей документов:

erDiagram
  SCENARIO ||--o{ SURVEY_TASK : tasks
  SCENARIO ||--o{ UAV : fleet
  SCENARIO ||--|| AIRSPACE : airspace
  SCENARIO ||--|| WIND : weather
  SCENARIO ||--|| OBJECTIVE : objective
  SCENARIO ||--|| SOLVER_PARAMS : solver
  PLAN ||--o{ MISSION : missions
  MISSION ||--o{ SORTIE : sorties
  SORTIE ||--o{ PHASE : phases
  PLAN ||--o{ UNASSIGNED : unassigned
  SCENARIO {
    int schema_version
    string scenario_id
    string crs
  }
  PLAN {
    int schema_version
    string scenario_id
    datetime generated_at
  }

Форматы выгрузки

fmt в URL Content-Type Содержимое
kml application/vnd.google-earth.kml+xml Документ(ы) KML 2.2: слои сценария + маршруты по фазам
geojson application/geo+json FeatureCollection: слои сценария + LineString фаз с высотой
plan QGC plan (бинар/JSON планировщика) export_qgc_plan
waypoints текстовые waypoints export_waypoints

Реализация экспорта в планировщике: src/backend/planner/planner/export/__init__.py:10-25. Gateway проксирует через gRPC ExportPlan (plans-export.service.ts:31-71).

Открытие: KML — Google Earth / QGIS «Добавить векторный слой»; GeoJSON — QGIS, geojson.io, MapLibre в UI.

Детали структуры KML/GeoJSON — grpc-schemas-exports.md.


Обработка ошибок (обзор)

flowchart TD
  A[HTTP запрос] --> B{Валидация схемы?}
  B -->|POST scenarios| C[422 SCHEMA_VALIDATION_FAILED]
  B -->|POST plans + scenario| D[400 SCHEMA_VALIDATION_FAILED]
  B -->|OK| E{Ресурс найден?}
  E -->|нет| F[404 PLAN_NOT_FOUND / SCENARIO_NOT_FOUND]
  E -->|да| G{Готовность плана?}
  G -->|export не completed| H[409 PLAN_NOT_READY]
  G -->|очередь полна| I[429 QUEUE_FULL + Retry-After]
  G -->|gRPC сбой| J[5xx UPSTREAM_* / mapped code]
  G -->|OK| K[2xx]

Единый JSON ошибки: { "errorCode", "message", "errors"? , "details"? } (src/backend/gateway/src/common/filters/domain-exception.filter.ts:19-112). Реестр кодов: src/backend/shared/src/errors/codes.ts:2-21.


Известные расхождения и ограничения

Файла 96-acceptance/00-verdict.md в этой ветке нет; ниже — проверяемые факты из кода и стенда.

Тема Факт Где смотреть
POST /api/validate на s01 На стенде 2026-09-17 (вечер): admitted: true, fails [], предупреждения G1-05, G2-04; G3-04 в CLI-статусе — «план допущен» (96-status/00-final-status.md:69-72) Живой curl в rest-reference.md; риск G3-04 — 96-status/00-final-status.md:81-83, 98-fixes/energy-model.md
Валидация objective в POST /api/plans Произвольная строка objective уходит в планировщик; схема сценария не проверяется при scenario_id plans.service.ts:366-371, scenario-schema.pipe.ts:37-40
Именование в REST Ответ 202: planId (camelCase); документ плана — snake_case plans.service.ts:210-215 vs plan.schema.json
gRPC ValidateRequest HTTP {scenario, plan} → два буфера (parse-validate-body.ts:7-12); иначе fallback — всё в plan_json, парсер convert.py:43-56 validator.client.ts:64-67, validate.service.ts:25-27
Воздушное пространство vs маршрут Транзитные перелёты обходят НФЗ (route_avoiding_nfz, nfz_route.py); граница allowed в маршрутизации не режет граф — только замер валидатором G1-02 и metrics.py См. contract-gaps.md; сводка приёмки: 96-status/00-final-status.md; архив вердикта — ветка accept/geoscan-final (git show accept/geoscan-final:docs/task5/96-acceptance/00-verdict.md)
Фронт ↔ API Таблица исправленных/оставшихся расхождений UI docs/task5/95-deploy/frontend-on-aerozveno.md:190-209

Полный список — contract-gaps.md.


Для оператора: типовые ситуации

Симптом Что проверить Действие
202, но план долго queued GET /readyz, метрики geoscan_plan_jobs Дождаться или смотреть логи planner; при 503 — зависимости
failed с error.details Тело GET /api/plans/{id} Часто неосуществимый GSD/парк; сценарий s08 — эталон (test_gateway_api.py:164-184)
409 PLAN_NOT_READY на export Статус не completed Поллинг/SSE до completed
422 на сохранение сценария Поле errors[] Сверить с scenario.schema.json (например turnaround_time_min, не _s)
Idempotency-Replayed: true Повтор POST с тем же ключом Норма; тот же planId (plans.controller.ts:62-72)

Деплой и диагностика стенда — 07-operate.


Дочерние страницы