Сценарии использования geoscan¶
О чём раздел и кому читать. Здесь описаны акторы, границы сервиса планирования аэрофотосъёмки (всё вне каталога simulate/) и десять сценариев использования (UC-01…UC-10) — от «один борт, один прямоугольник» до перепланирования при отказе БВС; демо-файлы s01…s10 в 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: queued → running → completed | 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.json … s10-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_acceptable → Infeasible (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: «Исключить борт» на PlanScreen → replanWithout → runApiCompute с 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/ |