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

Сценарии использования geoscan

О чём раздел и кому читать. Здесь описаны акторы, границы сервиса планирования аэрофотосъёмки (всё вне каталога simulate/) и десять сценариев использования (UC-01…UC-10) — от «один борт, один прямоугольник» до перепланирования при отказе БВС; демо-файлы s01s10 в src/backend/scenarios/. Заказчику и жюри хватит раздела Кратко для демо и диаграмм. Разработчику — Акторы, Жизненный цикл задания и таблицу CLI. Оператору — Путь оператора и пошаговые UC с указанием экранов.

Соседние разделы: бизнес · UI-поток (черновик) · архитектура · доменная логика · API (черновик) · эксплуатация. Пробелы относительно вопросов заказчика — customer-gaps.md.


Кратко для демо

Вход Выход
JSON-сценарий: полигон заданий, парк БВС, ВПП, ПАФС/НФЗ, ветер, критерий (makespan / total_flight_time) План полётов: галсы, вылеты, метрики; выгрузка KML/GeoJSON; отчёт валидатора

Типовой путь на стенде: оператор в веб-UI (режим API) отправляет сценарий → POST /api/plans → опрос GET /api/plans/{id} или SSE → просмотр плана → POST /api/validate → экспорт. Локально без кластера тот же смысл проверяется CLI: python -m planner run + python -m validator check.


Акторы и цели

Акторы выведены из реализованных HTTP-эндпоинтов gateway, gRPC-сервисов и экранов фронтенда, а не из ТЗ «в целом».

Актор Цель Интерфейс в коде
Оператор планирования Собрать сценарий на карте, запустить расчёт, прочитать метрики, выгрузить KML/GeoJSON, при сбое — понять причину Micro-frontend apps/planner, apps/validator, apps/viewer3d, хост apps/shell; стор с веткой API в packages/state/src/create-mission-store.ts
Интегратор / скрипт Автоматизировать расчёт и валидацию без UI REST: api/plans, api/validate, api/scenarios, api/fleet — клиент packages/api-client
Сервис валидации (в составе backend) Независимо проверить пару «сценарий + план» по правилам G1–G6 POST /api/validate → gRPC validator (validate.controller.ts)
Планировщик (worker) Выполнить расчёт из очереди PlanDispatcherService → gRPC planner (plan-dispatcher.service.ts)
SRE / мониторинг Жив ли gateway, готов ли к нагрузке, метрики очереди GET /healthz, /readyz, /metrics (ops.controller.ts)

Не актор продукта сегодня: пилот БВС и НСУ в полёте — нет API исполнения и телеметрии. Пользовательской аутентификации нет: AuthGuard всегда пропускает запрос (auth.guard.ts:7-9).


Граница системы и внешние сущности

На диаграмме — geoscan как связка gateway + planner + validator + хранилище заданий; вне рамок — симулятор (simulate/), внешние органы УВД/NOTAM, реальная телеметрия.

flowchart TB
  subgraph actors [Акторы]
    OP[Оператор]
    INT[Интегратор]
    SRE[SRE]
  end

  subgraph geoscan [Система geoscan]
    GW[gateway NestJS\nREST /api/*]
    PG[(PostgreSQL\nочередь планов)]
    PLN[planner gRPC]
    VAL[validator gRPC]
    CAT[fleet.yaml / payloads.yaml]
    GW --> PG
    GW --> PLN
    GW --> VAL
    PLN --> CAT
    VAL --> CAT
  end

  subgraph outside [Вне системы]
    SIM[simulate\nотдельный продукт]
    EXT[NOTAM / СППИ / телеметрия]
    DISK[Файлы KML/GeoJSON\nу оператора]
  end

  OP -->|HTTPS UI| GW
  INT -->|REST| GW
  SRE -->|healthz / metrics| GW
  GW -->|attachment| DISK
  SIM -.->|связь по контракту плана| GW
  EXT -.->|не подключено| GW

Вывод: geoscan принимает статический сценарий и отдаёт план и отчёт; он не закрывает контур «полёт по факту» и не подтягивает живые воздушные ограничения.


Основной поток расчёта (HTTP)

sequenceDiagram
  participant U as Оператор / клиент
  participant G as gateway
  participant Q as Очередь планов
  participant P as planner gRPC
  participant V as validator gRPC

  U->>G: POST /api/plans (сценарий или scenario_id)
  G->>G: ScenarioSchemaPipe
  G->>Q: createQueued (status queued)
  G-->>U: 202 Accepted + planId + Location

  opt Прогресс
    U->>G: GET /api/plans/{id}/events (SSE)
    G-->>U: stage, progress, message
  end

  Q->>P: RunPlan (x-service-api-key)
  P-->>Q: plan_json + metrics
  Q->>Q: status completed

  U->>G: GET /api/plans/{id}
  G-->>U: plan + metrics

  U->>G: POST /api/validate {scenario, plan}
  G->>V: Validate
  V-->>G: report
  G-->>U: 200 + violations / metrics

  U->>G: GET /api/plans/{id}/export/kml
  G-->>U: файл

Реализация приёма задания: plans.controller.ts:40-60 (202 + Location). Диспетчер и терминальные стадии SSE: plan-dispatcher.service.ts:136-164 (completed / failed). Клиентский опрос с таймаутом 120 с: plan-poll.ts:18-58.


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

Статусы задания в API: queuedrunningcompleted | failed (plans.repository.ts:6).

stateDiagram-v2
  [*] --> queued: POST /api/plans\nили replan
  queued --> running: dispatcher claimNext
  running --> completed: planner вернул plan_json
  running --> failed: RpcServiceError / таймаут / нет результата
  completed --> [*]
  failed --> [*]

  note right of queued
    queuePosition в ответе GET
    при переполнении — 429
  end note

  note right of failed
    error в GET /api/plans/{id}
    SSE stage failed
  end note

Перепланирование создаёт новое задание с parent_plan_id, не откатывая родителя (plans.service.ts:141-174, toGetResponse — поле parent_plan_id).


Путь оператора (journey)

Сценарий: демо s01-simple на стенде с включённым backend (режим API по умолчанию: runtime-config.ts:13-15).

journey
  title Оператор: от демо-сценария до выгрузки
  section Сценарий
    Открыть shell, вкладка Сценарий: 5: Оператор
    Выбрать s01-simple (GET /api/scenarios/s01-simple): 4: Оператор
    При необходимости править полигон / ветер: 3: Оператор
  section Расчёт
    Нажать «Рассчитать» (POST /api/plans): 4: Оператор
    Дождаться SSE или прогресс-бара: 3: Оператор
  section Результат
    Вкладка План: галсы, Gantt, makespan ~28 мин: 5: Оператор
    Вкладка Валидатор: отчёт G1–G6, вердикт «допущен»: 5: Оператор
    Экспорт GeoJSON/KML: 4: Оператор
  section Сбой
    Очередь полна (429) или failed: 2: Оператор
    Переключить mock только для офлайн-демо без API: 2: Оператор

В режиме mock (TopBar → dataSource mock) кнопка «Рассчитать» вызывает локальный solve() в браузере (store.ts:393-419), без очереди gateway — полезно для вёрстки, но не эталон для приёмки.


Демонстрационные сценарии s01–s10

Файлы: src/backend/scenarios/s01-simple.jsons10-replan.json. Список в API: GET /api/scenarios (scenarios.controller.ts:20-24).

Команда проверки (воспроизведено 17.09.2026)

Рабочий каталог: src/backend. Зависимости: uv sync --all-packages.

cd src/backend
uv run python -m planner run \
  --scenario scenarios/s01-simple.json \
  --objective makespan \
  --out /tmp/uc-s01-simple.json
uv run python -m validator check \
  --scenario scenarios/s01-simple.json \
  --plan /tmp/uc-s01-simple.json

Фрагмент фактического вывода валидатора для s01:

сценарий s01-simple, критерий makespan, валидатор 0.1.0
...
метрики: makespan 0:28:20, налёт 0:28:20, покрытие 1.000, развороты 6%, галсов 15, вылетов 1
ВЕРДИКТ: план допущен

Пакетный прогон s01–s10 в той же сессии:

Файл planner run Вердикт валидатора Ключевые метрики (validator)
s01-simple OK допущен makespan 0:28:20, покрытие 1.000, 15 галсов, 1 вылет
s02-nfz OK допущен G1-01 = 0 м; покрытие 0.985 (warn G6-01), makespan 0:18:28, 10 галсов, 1 вылет
s03-multi-sites OK допущен makespan 0:13:01, налёт 0:38:21, 3 вылета
s04-mixed-fleet OK допущен покрытие 0.340 (warn G6-01), makespan 8:48:01
s05-mixed-survey OK допущен makespan 5:43:09, покрытие 1.000, 76 галсов, 14 вылетов (G5 без нарушений)
s06-wind OK допущен makespan 0:58:34, 15 галсов, 2 вылета; G4 по ветру 8 м/с
s07-endurance OK НЕ допущен FAIL G3-01 endurance=170.722 s
s08-infeasible-gsd OK допущен (CLI) makespan 0:26:29, 26 галсов, 1 вылет — см. UC-08a/b
s09-high-wind OK допущен makespan 0:38:56, парк 3→фактически 201
s10-replan OK допущен makespan 5:28:37, покрытие 0.345, 28 галсов, 3 вылета (база s04)

Экспорт после расчёта:

uv run python -m planner export \
  --plan /tmp/uc-s06-wind.json \
  --scenario scenarios/s06-wind.json \
  --fmt geojson \
  --out /tmp/uc-s06.geojson

Сценарии использования (UC)

Единая структура: цель → предусловия → основной поток → альтернативы → исключения → результат → связь с s0x.

UC-01. Спланировать съёмку одной области одним бортом

Цель Получить допустимый план ПАФС-съёмки с заданным GSD и метриками makespan
Предусловия В каталоге есть geoscan_gemini + geoscan_pf1b; сценарий валиден по JSON Schema
Основной поток 1) Загрузить s01-simple (1×1 км, GSD 3 см, одна ВПП). 2) Запустить расчёт (makespan). 3) Убедиться: 15 галсов, высота ~153 м (предупреждение ИВП 150 м). 4) Прогнать валидатор. 5) Экспорт GeoJSON/KML
Альтернативы Критерий total_flight_time через --objective total_flight_time; загрузка через POST /api/plans с scenario_id: "s01-simple"
Исключения Очередь полна → HTTP 429 (QueueFullError в plans.controller.ts:77-81). Невалидный JSON → 400 от ScenarioSchemaPipe
Результат coverage_frac = 1.0, makespan 0:28:20, 15 галсов, 1 вылет (прогон CLI 17.09.2026). В expected.note (s01-simple.json:94) описана целевая «honest»-модель: два вылета с оборотом 10 мин на запасной АКБ — фактический план на ветке не совпадает с этой пометкой
Сценарий s01 Да — команда выше, вердикт «план допущен»

Оператор (API): «Рассчитать» → SSE стадии до completed → на вкладке «План» makespan 0:28:20, один вылет (совпадает с CLI).


UC-02. Съёмка с бесполётной зоной (НФЗ)

Цель Построить галсы с обходом НФЗ без нарушения G1-01
Предусловия В airspace.no_fly задан полигон; борт один
Основной поток 1) Открыть s02-nfz. 2) Рассчитать план. 3) Валидатор: G1-01 = 0 м (в НФЗ не заходим); покрытие 0.985 (coverage_frac, warn G6-01), не полные 1.0
Альтернативы Нарисовать НФЗ в UI инструментом draw-nfz (store.ts — тип инструмента) и сохранить черновик через POST /api/scenarios
Исключения НФЗ делит полигон на недостижимые куски → unassigned с причиной в плане
Результат План допущен: makespan 0:18:28, 10 галсов, 1 вылет; G1-01 без нарушений (прогон 17.09.2026)
Сценарий s02 Да — scenarios/s02-nfz.json, validator «план допущен»

UC-03. Несколько взлётно-посадочных пунктов

Цель Снизить makespan за счёт привязки бортов к ближайшим ВПП
Предусловия ≥2 launch_sites, ≥2 борта в fleet
Основной поток 1) s03-multi-sites. 2) Расчёт makespan. 3) Сравнить суммарный налёт и makespan (3 вылета, makespan 0:13:01 в прогоне)
Альтернативы Replan на стенде: e2e исключает gemini-01 после расчёта s03 (test_stand_acceptance.py:183-193)
Исключения ВПП не подходит по suitable_for — борт не назначается на эту площадку
Результат План допущен, покрытие 1.0
Сценарий s03 Да

UC-04. Смешанный парк на большом полигоне

Цель Распределить работу между 201, Gemini и 801 по производительности, оптимизируя makespan
Предусловия Сценарий s04-mixed-fleet, критерий makespan, лимит решателя из solver.time_limit_s
Основной поток 1) Расчёт. 2) Проверить назначение по G5. 3) Осознать частичное покрытие при лимите времени
Альтернативы Увеличить time_limit_s в теле POST /api/plans (parseBody в plans.service.ts:275-282)
Исключения warn G6-01 при покрытии 0.34 — план всё равно допущен (прогон 17.09.2026)
Результат makespan 8:48:01, 37 галсов, 4 вылета; не полное покрытие 40 км² в отведённый лимит
Сценарий s04 Да

UC-05. Смешанные типы съёмки (RGB + ИК)

Цель Развести задания по допустимым нагрузкам (ИК только на тепловизор 801)
Предусловия Два tasks с разным survey_type / GSD
Основной поток 1) s05-mixed-survey. 2) Валидатор G5-01 без нарушений
Альтернативы Смена критерия на total_flight_time
Исключения Нет подходящей нагрузки в парке → unassigned / Infeasible на этапе планирования
Результат План допущен (прогон 17.09.2026)
Сценарий s05 Да

UC-06. Учёт ветра при выборе направления галсов

Цель Уложиться в wind_max и сократить время за счёт ориентации галсов
Предусловия weather.wind_speed_ms > 0; однородное поле на область (02-customer-assumptions.md Q-23)
Основной поток 1) s06-wind (8 м/с). 2) Проверки G4 в валидаторе. 3) Сравнить с прогоном без ветра (s01)
Альтернативы Фиксированный угол галсов — через параметры сценария / objective (см. 70-plan/02-test-scenarios.md § s06)
Исключения Поперечный ветер выше допустимого → Infeasible("CROSSWIND_EXCEEDS_AIRSPEED") в wind.py
Результат План допущен
Сценарий s06 Да

UC-07. Сильный ветер и отсев части парка

Цель Построить план только на бортах, у которых wind_max ≥ скорости ветра
Предусловия s09-high-wind, ветер 11 м/с
Основной поток 1) Расчёт. 2) Убедиться, что мультироторы не получают галсы при превышении ветра (G4)
Альтернативы
Исключения Если ни один борт не годен — пустой / невыполнимый план
Результат makespan 0:38:56, 1 вылет, покрытие 1.0 (прогон 17.09.2026)
Сценарий s09 Да

UC-08. Недостижимое качество (GSD) и граница запаса хода

Два связанных краевых случая из набора s07/s08.

UC-08a. Недостижимый GSD (s08)

Цель Система должна отказать во входе (GSD 0,5 см), а не выдать заниженный план
Ожидание спецификации expected.input_error: true в s08-infeasible-gsd.json:58
Факт CLI planner run завершается с EXIT=0; валидатор: «план допущен» (17.09.2026)
Факт HTTP/gRPC Перед расчётом вызывается ensure_scenario_acceptableInfeasible (convert.py:167-172); e2e допускает 422 или status: failed с details (test_stand_acceptance.py:196-218)
Сценарий s08 Да, но поведение зависит от пути

UC-08b. Граница endurance (s07)

Цель Нарезка на несколько вылетов с учётом перелёта и оборота
Факт Планировщик строит план, валидатор отклоняет: FAIL G3-01, endurance=170.722
Сценарий s07 Да — расхождение planner ↔ validator; см. customer-gaps.md

UC-09. Перепланирование после отказа борта

Цель Исключить выбывший БВС и пересчитать оставшиеся галсы
Предусловия Есть завершённый план (часто после s04/s10); известен planId
Основной поток 1) POST /api/plans/{id}/replan с телом { "exclude_uav_ids": ["fw-201"] } (plans.controller.ts:197-217). 2) Дождаться нового planId с parent_plan_id. 3) Валидировать новый план
Оператор В режиме API: «Исключить борт» на PlanScreenreplanWithoutrunApiCompute с replanParentPlanId (create-mission-store.ts:99-130)
Альтернативы В mock: локальное исключение без сервера (store.ts:422-426)
Исключения Нет activePlanId → ошибка клиента «Нет активного плана для replan» (create-mission-store.ts:100-104)
Ограничение Не моделируется положение бортов «в воздухе»; keep_completed в API не реализует полный смысл (см. limitations §7)
Сценарий s10 JSON — база для демо; e2e replan проверяется на s03 (test_stand_acceptance.py)

UC-10. Проверить чужой план (валидатор как арбитр)

Цель Получить отчёт G1–G6 и вердикт «допущен / не допущен»
Предусловия Документы сценария и плана в snake_case
Основной поток POST /api/validate или uv run python -m validator check --scenario … --plan …
Оператор Вкладка «Валидатор»; в API-режиме отчёт с сервера (reportFromServer в store.ts:69-70)
Исключения Несовместимые документы → 400 (клиент хранит scenarioDoc/planDoc с сервера специально для этого — комментарий в plan-poll.ts:25-28)
Сценарий Любой s01–s10

Сводка: UC ↔ s01–s10

UC s0x Роль в приёмке
UC-01 s01 Эталон арифметики GSD / галсов
UC-02 s02 НФЗ
UC-03 s03 Multi-site + e2e replan
UC-04 s04 Mixed fleet, частичное покрытие
UC-05 s05 Mixed survey
UC-06 s06 Ветер
UC-08b s07 Endurance (валидатор ловит FAIL)
UC-08a s08 Infeasible GSD (HTTP ≠ CLI)
UC-07 s09 High wind
UC-09 s10 Replan (логика + s04-база)

Что заказчик просил, но продукт не закрывает

Подробная таблица: customer-gaps.md. Кратко: нет деконфликтации бортов (Q-10), нет ограничения по радиусу связи (Q-02), нет ЛАФС/LiDAR «под ключ», KML не сертифицирован под Geoscan Planner (Q-06), replan без телеметрии (Q-29).


Связь с симулятором

Продукт simulate проверяет исполнение плана в модели мира; сценарии использования симулятора — simulate/docs/91-doc/02-usecases/README.md. Geoscan отдаёт план; симулятор потребляет его отдельным контуром.


Навигация по коду (точки входа)

Задача Где смотреть
REST планы src/backend/gateway/src/plans/plans.controller.ts
Replan src/backend/gateway/src/plans/plans.service.ts
Очередь и gRPC src/backend/gateway/src/plans/plan-dispatcher.service.ts
CLI планировщика src/backend/planner/planner/cli/__main__.py
CLI валидатора src/backend/validator/validator/cli/check.py
UI + API расчёт src/frontend/packages/state/src/create-mission-store.ts
Демо-сценарии src/backend/scenarios/