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

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

Этот раздел — инструкция по запуску симулятора «мир БВС» (simulate/): окружение разработчика и оператора, headless-прогон полётного задания планировщика, связка HTTP API + веб-просмотрщик, чтение артефактов и первые шаги при сбоях. Заказчику и жюри достаточно §1 и §5; новому разработчику — §2–§4 и ссылки на архитектуру; оператору — пошаговые §3–§6 и diagnostics.md.

Смысл продукта и связь с планировщиком — ../01-business/README.md. REST и CLI-контракты — 04-api. Честный перечень того, чего нет в поставке (параллельный ночной batch с checkpoint, автоматический трёхзначный GO/NO-GO при низкой полноте слоёв) — ../../../LIMITATIONS.md.


1. Что вы получите на выходе

Режим Команда / URL Результат за ~1–2 мин (демо s01)
Headless sim run fixtures/plans/s01.json Каталог с телеметрией (~16 800 строк JSONL), отчёт «план vs факт», сухие кадры
API sim serve или https://aerosim.ff Те же файлы под simulate/data/runs/<uuid>/ (или PVC на стенде)
UI npm run dev в web/ или / на aerosim Экран «Прогон»: карта, 3D-сцена, таймлайн; по умолчанию mock без бэкенда

Симулятор не планирует галсы и не заменяет валидатор geoscan — он исполняет уже готовый JSON-план и пишет факт прогона в пределах реализованной физики (ветер, LOS, сухая камера и т.д.).


2. Требования и установка окружения

2.1. Зависимости

Компонент Версия / источник
Python ≥ 3.12 (simulate/pyproject.toml:10)
Менеджер окружения uv (рекомендуется) или pip install -e . из simulate/
Node.js Для UI: сборка Vite в simulate/web/ (package.json)
Корень репозитория Каталог с docs/task5/10-hardware/fleet.yaml — иначе repo_root() падает (hardware/paths.py:12-19)

2.2. Установка Python-пакета

Из корня монорепозитория:

cd simulate
uv sync

Фактический вывод установки (сессия 2026-09-17, uv 0.12.5, CPython 3.12.3):

Creating virtual environment at: .venv
Installed 31 packages in 1.84s
 + geoscan-sim==0.1.0 (from file:///.../simulate)

CLI:

.venv/bin/sim --help
Commands:
  batch       Run N scenarios × M plans; emit CSV and JSON summaries.
  identities
  mutate
  run
  serve
  validate
  verdict     Tri-state stand verdict for a completed run.

Подкоманды batch и verdict зарегистрированы в CLI; регрессия — tests/defects/test_b6_stand.py::test_sim_batch_subcommand_registered и test_sim_verdict_subcommand_emits_tri_state. Ограничения по смыслу, а не по наличию команд: последовательный sim batch без пула/checkpoint и вердикт UNRESOLVED при LAYER_COMPLETENESS_LOWLIMITATIONS.md, 04-api/cli.md.

2.3. Диаграмма: с чего начать запуск

На схеме — минимальный путь до первого успешного прогона. Для стенда без uv см. §4.2 (compose) или §4.1 (aerosim.ff).

flowchart TD
  A[Клон worktree с docs/task5/ и simulate/] --> B{uv sync в simulate/}
  B --> C[.venv/bin/sim run fixtures/plans/s01.json]
  C --> D{RC 0?}
  D -->|да| E[Читать fact_report.json и telemetry.jsonl]
  D -->|1 fatal| F[JSON code в stderr — см. diagnostics.md]
  D -->|3 internal| G[Очистить --output или новый каталог]
  B --> H[Опционально: npm ci в web/]
  H --> I[sim serve + npm run dev]
  I --> J[UI ?api=1 → live API]

Вывод: без uv sync команда sim недоступна; без пустого --output повторный прогон в тот же каталог завершится RC=3 (RunIdCollisionErrorINTERNAL_ERROR в CLI).


3. Headless-прогон сценария (CLI)

3.1. Входные данные

  • План — JSON с schema_version, scenario_id, missions[] (пример: simulate/fixtures/plans/s01.json:1-4).
  • Мир — синтетический JSON из simulate/fixtures/worlds/; флаг --world (по умолчанию m0-synthetic-flatflat_plane, алиасы в world/runtime.py:16-18).
  • Fleet / payloads — только чтение из docs/task5/10-hardware/ (hardware/paths.py:23-28).

Готовые демо-планы:

Файл Назначение (кратко)
fixtures/plans/s01.json Один sortie Gemini, ~1680 с sim-time
fixtures/plans/s04.json Несколько бортов, длиннее по sorties
fixtures/plans/s07.json «Чистый» preflight для smoke API
fixtures/plans/s06.json, s09.json Доп. кейсы регрессии

3.2. Команда прогона

cd simulate
rm -rf /tmp/sim-doc-s01   # каталог должен быть пуст для нового run_id
.venv/bin/sim run fixtures/plans/s01.json --seed 42 --output /tmp/sim-doc-s01

Проверенный фрагмент stdout (2026-09-17, wall time ~1m17s):

{
  "fact_report": "/tmp/sim-doc-s01/fact_report.json",
  "frames": "/tmp/sim-doc-s01/frames.jsonl",
  "frames_raster_count": 0,
  "frames_written": 380,
  "manifest": "/tmp/sim-doc-s01/run_manifest.json",
  "preflight_codes": [],
  "telemetry": "/tmp/sim-doc-s01/telemetry.jsonl",
  "telemetry_sha256": "e11273715f65affb7987923aeb444d85ded8c8919264af05a2cf446cf4b62371",
  "world_snapshot_id": "24e205faab997a835132abc87aeaf74d8e2760e528ec35d2c013c277af0c11a3"
}

Код выхода: 0. Список файлов:

events.jsonl          15 lines
frames.jsonl         380 lines
telemetry.jsonl    16800 lines
run_manifest.json
fact_report.json
world_manifest.json
frames/               # сайдкары сухих кадров
.journal_run_id

Логика: preflight → последовательные sorties → интегратор → журнал → fact_report → сухие кадры (mission/runner.py:187-344, journal/writer.py:406-408).

3.3. Диаграмма: последовательность прогона

sequenceDiagram
  actor Op as Оператор
  participant CLI as sim run
  participant PF as analyze_plan
  participant WR as run_plan_headless
  participant INT as HeadlessIntegrator
  participant JW as RunJournalWriter
  participant FR as fact_report

  Op->>CLI: plan.json, --output, --seed, --world
  CLI->>CLI: validate_plan_contract
  CLI->>PF: preflight
  alt fatal без violation
    PF-->>CLI: codes
    CLI-->>Op: JSON errors, RC 1
  else прогон разрешён
    CLI->>WR: run_plan_headless
    WR->>WR: WorldRuntime.from_name
    loop каждый sortie
      WR->>INT: IntegratorConfig + фазы
      INT-->>WR: RunResult
    end
    WR->>JW: telemetry.jsonl, events.jsonl, run_manifest.json
    WR->>FR: build_fact_report
    WR-->>CLI: summary dict
    CLI-->>Op: JSON summary, RC 0/2
  end

Вывод: телеметрия появляется только после успешного прохода preflight; fatal до интегратора не создаёт полного журнала.

3.4. Дополнительные CLI-команды

Команда Назначение
sim validate --reference Таблица эталонов V-1…V-14; RC 0 если все PASS (cli/__init__.py:42-50)
sim mutate --set base Матрица мутантов; RC 1 если мутант не пойман (cli/__init__.py:53-63)
sim identities Проверки V1-идентичностей (cli/__init__.py:220-227)

Хвост sim validate --reference (та же сессия):

V-14   Determinism 10× identical sha256                0            0           +0  exact match PASS
exit=0

4. Стенд: API и веб

4.1. Стенд aerosim.ff (tailnet)

Продуктовый headless-стенд симулятора разворачивается в k3s на n1 (ssh root@nubble01, kubectl без --context). DNS уже в ff-coredns: 100.66.249.6 aerosim.ff (tailnet-only, TLS от ff-ca).

Что Значение
URL https://aerosim.ff
Манифесты k3s/aerosim/ в ветке fix4/sim-deploy (по образцу k3s/aerozveno/: namespace, Certificate, Deployment+Service, Ingress)
Образ cr.yandex/crp5jbsajr22cc3jj3vr/aerosim-api:<8-char sha>
Процесс в контейнере sim serve --host 0.0.0.0 --port 8080 --runs-dir /data/runs (simulate/Dockerfile)
Сквозная инструкция docs/task5/95-deploy/aerosim.md на той же ветке (пока не влито в release/m1 — смотреть worktree w3-sim-deploy)

Проверка живости (с рабочей станции в tailnet, 2026-09-17):

curl -sS --cacert ff-ca/ff-ca.crt https://aerosim.ff/healthz
{"status":"ok","version":"0.1.0+unknown"}
curl -sS --cacert ff-ca/ff-ca.crt https://aerosim.ff/readyz
{"status":"ok"}

Публичные без ключа: /healthz, /readyz, /metrics. Все /api/v1/* на стенде требуют Authorization: Bearer … или X-Api-Key (ключ — creds-store, не коммитить в git). Контракт ручек — 04-api/http-api.md.

Перераскатка (оператор, после merge манифестов в репозиторий):

rsync -a --delete k3s/aerosim/ root@nubble01:/root/deploy-apps/aerosim/
ssh root@nubble01 'kubectl apply -k /root/deploy-apps/aerosim --server-side'
ssh root@nubble01 'kubectl -n aerosim rollout status deploy/aerosim-api'

Тег образа в k3s/aerosim/40-deployment-api.yaml — 8-символьный sha коммита; после смены тега повторить apply + rollout status.

Мониторинг: scrape job aerosim живёт в репозитории fairflow-crm (k3s/monitoring/prometheus.yml на диске n1: /root/deploy-apps/monitoring/prometheus.yml), static target aerosim-api.aerosim.svc.cluster.local:8080, labels {service: aerosim, host: n1} — см. docs/task5/95-deploy/aerosim.md (ветка fix4/sim-deploy). Раскатка monitoring: rsync на n1 + kubectl apply -k /root/deploy-apps/monitoring (docs/05-monitoring.md). Отдельного Grafana-дашборда aerosim*.json на n1 нет — смотреть target job aerosim в Prometheus UI / Grafana Explore (https://grafana.ff, креды: creds monitoring).

Известные ограничения стенда: CronJob обновления pull-secret yc-cr на n1 может получать 403 от IAM (см. отчёт deploy-пакета); без свежего secret новый образ не подтянется.

4.2. Локально и Docker Compose (офлайн)

Для разработки без кластера:

Способ Команда
uv + два процесса sim serve + npm run dev в web/ (§4.3–4.4 ниже)
Compose из корня монорепо: docker compose -f simulate/docker-compose.yml up api (simulate/Dockerfile, порт 8080, volume /data/runs)

Тесты B6 по «готовому compose-стенду» могут оставаться xfail до закрытия чеклиста стенда (tests/defects/test_b6_stand.py) — это не отменяет наличие docker-compose.yml для ручного запуска.

Ручной стенд без Docker = два процесса (или один sim serve со статикой после npm run build).

stateDiagram-v2
  [*] --> Idle: нет процессов
  Idle --> BackendUp: sim serve
  BackendUp --> BackendReady: /healthz ok
  BackendReady --> RunQueued: POST /api/v1/runs
  RunQueued --> RunRunning: worker взял задачу
  RunRunning --> RunDone: exit_status completed
  RunRunning --> RunFailed: ошибка в worker
  BackendReady --> FrontendMock: npm run dev без ?api=1
  FrontendMock --> FrontendLive: ?api=1 или VITE_SIM_API=api
  FrontendLive --> RunDone: UI читает /runs/{id}/telemetry
  RunDone --> [*]
  RunFailed --> [*]
  note right of BackendUp
    status в api_meta:
    queued → running → done|failed
    (run_store.py:145-206)
  end note

4.3. Запуск бэкенда (локально)

cd simulate
mkdir -p /tmp/sim-doc-runs
.venv/bin/sim serve --no-auth --port 8080 --runs-dir /tmp/sim-doc-runs --workers 2

Опции по коду (cli/__init__.py:166-217):

Опция По умолчанию Смысл
--port 8080 HTTP
--runs-dir simulate/data/runs Каталог прогонов (api/app.py:43-44)
--workers 2 Пул subprocess для прогонов
--no-auth выкл Без Bearer / X-Api-Key (только dev)
(без --no-auth) Ключ генерируется при старте и печатается в stderr

Проверка (тот же порт 8080, что в команде sim serve выше и в прокси Vite):

curl -sS http://127.0.0.1:8080/healthz
{"status":"ok","version":"0.1.0+a945f914"}

Суффикс +a945f914 — короткий git-sha из сборки; на другом коммите строка version будет другой.

Постановка прогона в очередь:

curl -sS -X POST http://127.0.0.1:8080/api/v1/runs \
  -H 'Content-Type: application/json' \
  -d '{"plan_ref":"s07.json","seed":42,"world_id":"m0-synthetic-flat"}'
{"run_id":"51e908a0-40f3-46a5-a968-64cd4aef764a","status":"queued"}

Опрос: GET /api/v1/runs/{run_id} — поле status до done. Длинные планы (как s01) занимают минуты wall time на одном worker.

Если собран simulate/web/dist, sim serve отдаёт статику с / (api/app.py:80-82).

4.4. Запуск фронтенда (локально)

cd simulate/web
npm ci          # первый раз
npm run dev     # Vite, прокси /api → http://127.0.0.1:8080 (vite.config.ts:11-16)

Режим API (web/src/config/runtime.ts:4-11):

Режим Как включить Данные
mock (default) обычный URL Фикстура mock-s01-42, JSONL из web/public/fixtures/
live ?api=1 или VITE_SIM_API=api Запросы к sim serve; при auth — VITE_SIM_API_KEY

Экраны: «Прогон», «Покрытие», «Эталоны», «Вердикт» (web/src/App.tsx:17-34). Экран прогона по умолчанию тянет mock run id (RunScreen.tsx:47, mock/handlers.ts:21).

npm run typecheck в этой сессии завершился без ошибок.


5. Чтение результатов

5.1. Артефакты каталога прогона

Файл Содержание Где смотреть в коде
run_manifest.json run_id, sim_time_s, scenario_id, preflight, provenance journal/writer.py:370-411
telemetry.jsonl Покадровая/пошаговая телеметрия (lon, lat, фазы, …) writer + схема contracts/log_schema.json
events.jsonl События sim (связь, contingency, …) journal/writer.py:391-404
fact_report.json План vs факт: makespan, coverage, энергия, environment mission/fact_report.py:361+
world_manifest.json Снимок мира, layer_completeness runner.py:217-219
frames.jsonl, frames/ Сухая геометрия кадров, без JPEG/TIFF runner.py:331, LIMITATIONS
frames_summary.json Агрегаты по кадрам materialize в runner

Пример ключевых чисел для s01 (тот же прогон, что в §3.2):

Поле Значение Комментарий
run_manifest.sim_time_s 1680.0 Симulated seconds
fact_report.plan_vs_fact.makespan_s.actual.value 1680.0 Совпало с планом (suspicious_plan_copy: true в отчёте — метка модели)
telemetry_sha256 e1127371… Повторяемость на одной платформе (fact_report.determinism)
frames_raster_count 0 Ожидаемо для dry_geometric

5.2. Коды preflight после прогона

Если в summary или run_manifest есть preflight_codes, процесс может вернуть RC=2 даже после записи телеметрии (exit_codes.py:36-37). Сценарий s04 специально допускает violation при прогоне — см. should_run_with_preflight (exit_codes.py:45-51).

5.3. API-обёртки

Те же файлы доступны через REST (runs.py):

  • GET /api/v1/runs/{id} — метаданные + вложенный fact_report
  • GET /api/v1/runs/{id}/telemetry?decimate=N — выборка для UI
  • GET /api/v1/runs/{id}/events, /scene_layers, /coverage, /frames
  • GET /api/v1/runs/{id}/verdict — сейчас всегда UNRESOLVED (verdict.py:26-29)

OpenAPI: /api/v1/openapi.json (api/app.py:49).


6. Диагностика при сбоях

flowchart TD
  S[Симптом] --> Q1{Где ломается?}
  Q1 -->|sim run RC 1| F1[JSON code fatal]
  F1 --> R1[Сверить с error_codes.yaml и fleet/payloads]
  Q1 -->|sim run RC 3| F2[INTERNAL_ERROR]
  F2 --> R2[Новый --output или rm каталога]
  Q1 -->|sim run RC 2| F3[VIOLATIONS после прогона]
  F3 --> R3[Читать preflight_codes + fact_report]
  Q1 -->|serve / UI| F4[401 или connection refused]
  F4 --> R4[Ключ API / порт / npm proxy 8080]
  Q1 -->|POST /runs 429| F5[RUN_QUEUE_FULL]
  F5 --> R5[Дождаться done или увеличить очередь]
  Q1 -->|verdict UNRESOLVED| F6[Ожидаемо]
  F6 --> R6[Не блокер эксплуатации — см. LIMITATIONS]
  R1 --> T[pytest -q / sim validate --reference]
  R2 --> T
  R3 --> T

Подробная таблица кодов — diagnostics.md.

Типовые действия оператора:

  1. Повторный прогон в тот же каталог — удалить --output или выбрать новый путь (иначе RC 3).
  2. «Нет scenario_id» — RC 1, код SCENARIO_MISSING (tests/test_exit_codes.py:39-47).
  3. UI пустой при live API — убедиться, что run_id существует и прогон done; включить ?api=1.
  4. Ожидание «GO/NO-GO» из коробки — не реализовано; HTTP-вердикт заглушен до B6.

7. Связанные разделы документации

Тема Путь
geoscan — эксплуатация планировщика ../../../../docs/task5/91-doc/07-operate/README.md
simulate — бизнес-контекст ../01-business/README.md
simulate — HTTP API, CLI, журнал ../04-api/README.md
deploy симулятора (aerosim) docs/task5/95-deploy/aerosim.md (ветка fix4/sim-deploy)
simulate — архитектура ../03-architecture/README.md
Ограничения поставки ../../../LIMITATIONS.md

8. Быстрый чеклист перед демо хакатона

  1. cd simulate && uv sync
  2. .venv/bin/sim run fixtures/plans/s01.json --output /tmp/demo-s01 — дождаться RC 0 (~1–2 мин)
  3. Показать fact_report.jsonplan_vs_fact.makespan_s
  4. (Опционально) .venv/bin/sim serve --no-auth + cd web && npm run devhttp://localhost:5173/?api=1
  5. Не обещать ночной 100× параллельный batch с checkpoint и автоматический трёхзначный GO/NO-GO (sim batch и sim verdict есть, но вердикт часто UNRESOLVED) — см. LIMITATIONS; compose и aerosim — для демо API/UI