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

Сценарии использования симулятора: акторы, потоки, границы

Этот раздел описывает кто взаимодействует с симулятором «мир БВС», зачем и какими командами/API получает проверяемый результат. Текст рассчитан на три аудитории: жюри и заказчик (суть за 10 минут), новый разработчик (воспроизводимый запуск), оператор и аналитик (шаги и коды ошибок). Утверждения сверены с кодом ветки docs2/sim-usecases; там, где функции нет, это сказано явно — см. краткий перечень ограничений и LIMITATIONS.md.

Соседние разделы: зачем симулятор · архитектура · API (черновик) · эксплуатация · сценарии планировщика.


Акторы и артефакты

Актор Цель Типичный вход Что получает на выходе
Планировщик работ (человек или сервис geoscan) Проверить план до вылета JSON полётного задания (schema_version, missions[], sorties[], фазы survey/takeoff/…) fact_report.json, журнал (telemetry.jsonl, events.jsonl, frames.jsonl), коды preflight
Разработчик / CI Доказать, что физика и геометрия не «поплыли» sim validate --reference, sim identities, pytest Таблица V-1…V-14, PASS/FAIL, ненулевой RC при регрессии
Инженер качества Поймать слепые зоны валидатора sim mutate, мутанты в fixtures/mutants/ Матрица «мутант → код нарушения → detected»
Оператор стенда Прогнать план на сервере, отдать URL команде sim serve, POST /api/v1/runs run_id, статус queuedrunningdone, ссылки на артефакты через API
Исследователь / жюри Увидеть пролёт и цифры «план vs факт»; оценить план по кодам и эталонам Веб-клиент simulate/web/ (mock или VITE_SIM_API=api) 3D/2D сцена, телеметрия, таймлайн, «Покрытие» / «Эталоны»; экран «Вердикт» есть, но HTTP всегда отдаёт UNRESOLVED до B6 (api/verdict.py:26-29) — для приёмки смотреть preflight_codes и V-1…V-14

Вне границ симулятора: построение галсов и оптимизация парка (планировщик), управление реальным БВС, выгрузка в S3, ночной sim batch (не реализован).


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

На диаграмме — что входит в продукт simulate/** и что остаётся снаружи. Зависимость от планировщика односторонняя: симулятор импортирует план как файл/JSON (mission/import_plan.py:1), обратных вызовов в geoscan нет (simulate/README.md:20-22).

flowchart TB
  subgraph external [Вне simulate]
    GS[geoscan — планировщик]
    OP[Оператор поля]
    CREDS[creds / стенд k3s]
  end

  subgraph simulate [Симулятор simulate]
    CLI[CLI sim]
    API[FastAPI /api/v1]
    ENG[HeadlessIntegrator + WorldRuntime]
    VAL[validator V-1…V-14 / mutate]
    WEB[web — просмотр журнала]
    OUT[(simulate-out / data/runs)]
  end

  GS -->|mission JSON KML GeoJSON| CLI
  GS -->|plan inline или plan_ref| API
  OP -->|браузер| WEB
  CLI --> ENG
  API --> ENG
  ENG --> OUT
  VAL -.->|эталоны без плана| CLI
  WEB -->|mock fixtures или REST| API
  CREDS -.->|API key Bearer| API

Вывод: единственный обязательный «контракт» с планировщиком — формат полётного задания и каталоги fleet.yaml / payloads.yaml (совместимость нагрузки проверяется до прогона, cli/__init__.py:132-141). Всё остальное — внутренняя модель мира и журнала.


Коды выхода CLI и смысл для оператора

Реестр: cli/exit_codes.py:10-14, логика после preflight: cli/exit_codes.py:30-42, should_run_with_preflight: cli/exit_codes.py:45-51.

Код Имя Когда
0 OK Прогон завершён, список preflight_codes пуст
1 FATAL План не принят или прогон не стартовал (fatal preflight, несовместимая нагрузка — cli/__init__.py:119-122, 132-141)
2 VIOLATIONS Прогон выполнен, но есть violation/warning в preflight_codes (типично мутант с ENDURANCE_EXCEEDED)
3 INTERNAL Исключение при прогоне, обёрнутое в INTERNAL_ERROR (cli/__init__.py:143-152). В том числе: повторный sim run в тот же --output без очистки → run_id already exists (journal/writer.py:259-265), exit 3 (не FATAL)

Структурированные ошибки печатаются JSON-строками в stdout (cli/__init__.py:27-30).


Жизненный цикл прогона (HTTP)

Асинхронный прогон через API хранит метаданные в api_meta.json и переводит статусы в RunService (api/run_store.py:122-211). CLI sim run этот граф не проходит — он синхронно пишет артефакты в --output.

stateDiagram-v2
  [*] --> queued: POST /api/v1/runs 202
  queued --> running: worker стартовал
  running --> done: subprocess ok
  running --> failed: exception или ok=false
  done --> [*]
  failed --> [*]

  note right of queued
    Ответ 202 может вернуть status queued
    пока meta уже running
    run_store.py:167-170
  end note

Вывод: оператору стенда нужно опрашивать GET /api/v1/runs/{id} до done или failed (tests/test_api.py:115-124), а не полагаться только на первый ответ 202.


Сквозной поток прогона (CLI)

sequenceDiagram
  participant U as Пользователь
  participant CLI as sim run
  participant C as contracts + import_plan
  participant P as analyze_plan preflight
  participant R as run_plan_headless
  participant J as RunJournalWriter

  U->>CLI: sim run plan.json --output DIR
  CLI->>C: validate_plan_contract + load_and_validate_plan
  alt контракт невалиден
    C-->>CLI: structured_error
    CLI-->>U: exit 1
  end
  CLI->>P: analyze_plan strict_zone_frames
  alt только fatal без violation
    P-->>CLI: codes
    CLI-->>U: exit 1 без прогона
  end
  CLI->>R: integrator + world + sorties
  R->>J: telemetry events frames fact_report
  R-->>CLI: summary JSON
  CLI->>CLI: preflight_exit_code
  CLI-->>U: exit 0 или 2 + summary

Порядок в коде: cli/__init__.py:66-159, ядро прогона: mission/runner.py:187-320.


Путь исследователя (веб-клиент)

По умолчанию UI работает без бэкенда: фикстура s01, seed 42 (docs/92-api/02-user-guide.md:4-5, web/src/config/runtime.ts:3-11). Live-режим: VITE_SIM_API=api и запущенный sim serve.

journey
  title Просмотр результата прогона (исследователь)
  section Подготовка
    Установить npm зависимости: 3: Разработчик
    npm run dev на :5173: 4: Разработчик
  section Экран Прогон
    Открыть 3D и 2D карту: 5: Исследователь
    Запустить плеер 1x–30x: 5: Исследователь
    Сверить plan vs fact в панели: 4: Исследователь
  section Анализ
    Перейти в Покрытие GSD: 4: Исследователь
    Открыть Эталоны V-1…V-14: 4: Жюри
    Экран Вердикт — только UNRESOLVED: 2: Жюри
    Сверить preflight_codes и RC sim run: 4: Жюри

Маршруты навигации: web/src/App.tsx:17-34. Экран «Вердикт» в UI подключён, но трёхзначный NO_GO / NO_FINDINGS в этой ветке не вычисляется: GET /api/v1/runs/{id}/verdict и mock-фикстура web/src/fixtures/mocks/verdict.json возвращают только "verdict": "UNRESOLVED" с блокером VERDICT_ENGINE_INCOMPLETE (api/verdict.py:20-29). Жюри и заказчик для GO/NO-GO по факту опираются на коды preflight, эталоны UC-1 и артефакты прогона, а не на готовый вердикт API.


UC-1. Регрессия эталонов V-1…V-14 (разработчик / CI)

Поле Содержание
Цель Убедиться, что якорные формулы (endurance, GSD, парашют V-10, детерминизм sha256) совпадают с допусками
Предусловия Установлен пакет geoscan-sim (uv sync в simulate/), доступна команда sim
Основной поток 1. Выполнить sim validate --reference. 2. Убедиться, что все строки PASS. 3. Код выхода 0
Альтернативы sim identities — быстрый smoke по алгебраическим тождествам (cli/__init__.py:220-227)
Исключения sim validate без флага → подсказка и exit 2 (cli/__init__.py:45-47)
Результат Таблица 14 метрик; при FAIL — ненулевой RC (validator/runner.py через reference_exit_code)

Команда (выполнено в сессии документирования):

cd simulate
uv sync -q
.venv/bin/sim validate --reference

Фрагмент вывода:

V-1    Gemini endurance calm cruise                40.01           40    -0.009217          ±5% PASS
...
V-14   Determinism 10× identical sha256                0            0           +0  exact match PASS

Код выхода: 0.


UC-2. Headless-прогон эталонного плана s01 (аналитик плана)

Поле Содержание
Цель Получить факт прогона для одного sortie Gemini: журнал, сухие кадры, fact_report
Предусловия Файл fixtures/plans/s01.json, пустой или новый каталог --output
Основной поток 1. sim run fixtures/plans/s01.json --seed 42 --output <DIR>. 2. Дождаться JSON-summary в stdout. 3. Открыть fact_report.json и run_manifest.json
Альтернативы --world m0-synthetic-flat (дефолт, cli/__init__.py:70) · другой синтетический мир из каталога worlds API
Исключения Повторный запуск в тот же --output без очистки → run_id already exists, exit 3 (journal/writer.py:259-265)
Результат preflight_codes: [], exit 0; sorties обрабатываются последовательно (mission/runner.py:35-47, 235-265)

Команда:

rm -rf /tmp/sim-doc-out
cd simulate
.venv/bin/sim run fixtures/plans/s01.json --output /tmp/sim-doc-out --seed 42

Фрагмент вывода:

{
  "fact_report": "/tmp/sim-doc-out/fact_report.json",
  "frames_written": 380,
  "preflight_codes": [],
  "telemetry_sha256": "e11273715f65affb7987923aeb444d85ded8c8919264af05a2cf446cf4b62371",
  "world_snapshot_id": "24e205faab997a835132abc87aeaf74d8e2760e528ec35d2c013c277af0c11a3"
}

Код выхода: 0. Время прогона на станции документирования: ~75 с (зависит от CPU).

Проверка коллизии run_id (тот же --output, без rm):

cd simulate
.venv/bin/sim run fixtures/plans/s01.json --output /tmp/sim-doc-out --seed 42

Фрагмент вывода:

{"code": "INTERNAL_ERROR", "message": "run_id already exists: 7d77fbdf-2ebe-5e16-8c7b-3e0787886d85", "path": "fixtures/plans/s01.json", "severity": "fatal"}

Код выхода: 3 (согласовано с таблицей RC выше; отдельный код RUN_ID_COLLISION в реестре пока не выделен).


UC-3. Прогон плана с нарушением endurance (контроль качества)

Поле Содержание
Цель Показать, что симулятор не блокирует прогон при violation-кодах, но помечает их в отчёте и возвращает RC=2
Предусловия Мутант fixtures/mutants/m08_endurance_201.json (ожидание зафиксировано в tests/defects/test_a9_exit.py:13-17)
Основной поток 1. Очистить --output. 2. sim run … m08 …. 3. Прочитать JSON ENDURANCE_EXCEEDED в stdout. 4. Проверить exit 2
Альтернативы Только preflight без движка: POST /api/v1/preflight с телом {"plan":…} (api/routes/preflight.py:16-38)
Исключения Fatal-only кейс (несовместимая нагрузка) → прогон не стартует, exit 1 (tests/test_exit_codes.py:55-59)
Результат Артефакты на диске есть, preflight_codes содержит violation; CI может трактовать RC=2 как «красный» прогон

Команда:

rm -rf /tmp/sim-m08
cd simulate
.venv/bin/sim run fixtures/mutants/m08_endurance_201.json --output /tmp/sim-m08 --seed 42

Фрагмент вывода:

{"code": "ENDURANCE_EXCEEDED", "message": "Длительность вылета превышает endurance с учётом резерва", "path": "", "severity": "violation"}

Код выхода: 2. Объём телеметрии велик (длинный sortie); для оператора важнее коды, чем размер telemetry.jsonl.

Контраст — fatal до прогона:

.venv/bin/sim run fixtures/mutants/m06_ir_on_gemini.json --output /tmp/sim-m06
{"code": "PAYLOAD_INCOMPATIBLE", "message": "Нагрузка несовместима с бортом", "path": "", "severity": "fatal"}

Код выхода: 1 (cli/__init__.py:119-122, 132-141).


UC-4. Прогон через HTTP API (оператор стенда)

Поле Содержание
Цель Поставить прогон в очередь сервиса, получить run_id, дождаться done, отдать фронту данные по REST
Предусловия Запущен sim serve (порт 8080 по умолчанию, cli/__init__.py:167-168) или тестовый TestClient; для прод-стенда — API key (cli/__init__.py:171-206)
Основной поток 1. POST /api/v1/runs с plan или plan_ref. 2. Ответ 202 + run_id. 3. Poll GET /api/v1/runs/{id} до status=done. 4. GET …/telemetry, …/fact_report, …/verdict
Альтернативы plan_ref: "s01" → файл из fixtures/plans/ (api/run_store.py:83-94, tests/test_api.py:248)
Исключения Очередь полна → 429 RUN_QUEUE_FULL (api/routes/runs.py:74-79) · неизвестный мир → 404 при resolve (WorldNotFoundError)
Результат Артефакты в simulate/data/runs/<run_id>/ (дефолтный каталог, api/app.py:43-44)

Воспроизведение без долгого serve (тот же код приложения, что и в pytest):

cd simulate
.venv/bin/python -c "
from fastapi.testclient import TestClient
from geoscan_sim.api.app import create_app
import json, time
from pathlib import Path
app = create_app(auth_disabled=True)
c = TestClient(app)
plan = json.loads(Path('fixtures/plans/s01.json').read_text())
print('preflight', c.post('/api/v1/preflight', json={'plan': plan}).status_code,
      c.post('/api/v1/preflight', json={'plan': plan}).json())
r = c.post('/api/v1/runs', json={'plan': plan, 'world_id': 'plane_seed_42', 'seed': 42})
print('create', r.status_code, r.json()['run_id'], r.json()['status'])
rid = r.json()['run_id']
for _ in range(400):
    g = c.get(f'/api/v1/runs/{rid}')
    if g.json()['status'] in ('done','failed'):
        print('final', g.json()['status'], 'exit', g.json().get('exit_status'))
        break
    time.sleep(0.25)
"

Фактический вывод (сокращённо):

preflight 200 {'codes': [], 'warnings': ['OBSTACLE_LAYER_INCOMPLETE'], 'violations': {}}
create 202 93cb304c-8c75-44e1-9322-80af2f1a9d98 queued
final done exit completed

После status=done запрос вердикта (тот же TestClient, auth_disabled=True):

# внутри того же python -c после poll: c.get(f'/api/v1/runs/{rid}/verdict')

Фрагмент ответа GET /api/v1/runs/{run_id}/verdict (проверено в сессии доработки):

{
  "verdict": "UNRESOLVED",
  "layer_completeness": { "obstacles": 0.0, "zones": 0.0 },
  "blockers": [
    {
      "code": "VERDICT_ENGINE_INCOMPLETE",
      "message": "Трёхзначный вердикт заблокирован до закрытия fidelity_class, layer_completeness и V3 (чеклист §7.2)."
    }
  ]
}

HTTP 200; значение NO_GO / NO_FINDINGS API не возвращает (api/verdict.py:26-29).

Запуск реального сервера (для оператора с браузером):

cd simulate
.venv/bin/sim serve --no-auth --port 8080
# другой терминал: cd simulate/web && VITE_SIM_API=api npm run dev

Статика web/dist монтируется, если каталог собран (api/app.py:80-82).


UC-5. Мутационная матрица (инженер качества / ревьюер)

Поле Содержание
Цель Проверить, что известные дефекты планов детектируются preflight/прогоном (регрессия валидатора)
Предусловия Набор fixtures/mutants/, манифест fixtures/mutants/mutants_manifest.yaml
Основной поток 1. sim mutate. 2. Убедиться, что для каждого мутанта detected=True. 3. Exit 0
Альтернативы HTTP POST /api/v1/validate/mutants (см. tests/test_api.py:225)
Исключения Ложный PASS базового s04 в матрице → exit 1 (cli/__init__.py:58-61)
Результат Таблица кодов (NFZ_VIOLATION, COVERAGE_GAP, …)

Команда:

cd simulate
.venv/bin/sim mutate

Хвост вывода:

m25                          NFZ_VIOLATION                True       NFZ_VIOLATION
m26                          PARACHUTE_OBSTACLE_CONFLICT  True       PARACHUTE_OBSTACLE_CONFLICT

Код выхода: 0.


UC-6. Мультибортовый план s04 (планировщик парка)

Поле Содержание
Цель Прогнать план с несколькими missions/sorties подряд в одном каталоге артефактов
Предусловия fixtures/plans/s04.json, чистый --output
Основной поток sim run fixtures/plans/s04.json --seed 42 --output <DIR>
Ограничение Борта летят последовательно в sim-time, не параллельно (mission/runner.py:235-265; см. limitations-summary)
Результат Больше кадров, чем s01; exit 0 при чистом preflight (tests/test_exit_codes.py:26-36)

Команда:

rm -rf /tmp/sim-doc-s04
cd simulate
.venv/bin/sim run fixtures/plans/s04.json --output /tmp/sim-doc-s04 --seed 42

Фрагмент summary: "frames_written": 2230, "preflight_codes": [], exit 0 (~5 мин на станции документирования).


Сводка: что выбрать

Задача Сценарий Интерфейс
«Физика не сломалась?» UC-1 sim validate --reference
«Этот план из geoscan пролетит?» UC-2, UC-3 sim run
«Нужен URL и UI для команды» UC-4 sim serve + web
«Валидатор ловит мутанты?» UC-5 sim mutate
«Несколько бортов в одном JSON» UC-6 sim run s04

Чего симулятор пока не умеет (для сценариев)

Кратко: limitations-summary.md. Полный реестр: LIMITATIONS.md.

Проверка отсутствия batch:

cd simulate && .venv/bin/sim batch --help
Error: No such command 'batch'.

Карта файлов кода (точки входа)

Интерфейс Файл
CLI run / serve / validate / mutate simulate/src/geoscan_sim/cli/__init__.py
Headless-прогон simulate/src/geoscan_sim/mission/runner.py
Preflight simulate/src/geoscan_sim/mission/preflight.py
HTTP runs simulate/src/geoscan_sim/api/routes/runs.py, api/run_store.py
Веб-маршруты simulate/web/src/App.tsx