Программные интерфейсы 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 § «Сквозной сценарий».
Кратко:
POST /api/plans→ 202, в телеplanId, в заголовкеLocation: /api/plans/{uuid}.GET /api/plans/{planId}в цикле илиGET …/events(SSE) на том жеplanId.- При
status: completed—GET …/export/kml(и другиеfmt).
Прогон 2026-09-17 (вечер): inline-сценарий из src/backend/scenarios/s01-simple.json → planId=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 |
Plan → stream 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.
Дочерние страницы¶
- rest-reference.md — все REST-ручки, коды ответов, живые примеры
- grpc-schemas-exports.md — proto, JSON Schema, KML/GeoJSON
- contract-gaps.md — расхождения контрактов и приёмки