Эксплуатация симулятора: установка, прогон, стенд, результаты¶
Этот раздел — инструкция по запуску симулятора «мир БВС» (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_LOW — LIMITATIONS.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 (RunIdCollisionError → INTERNAL_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-flat→flat_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_reportGET /api/v1/runs/{id}/telemetry?decimate=N— выборка для UIGET /api/v1/runs/{id}/events,/scene_layers,/coverage,/framesGET /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.
Типовые действия оператора:
- Повторный прогон в тот же каталог — удалить
--outputили выбрать новый путь (иначе RC 3). - «Нет scenario_id» — RC 1, код
SCENARIO_MISSING(tests/test_exit_codes.py:39-47). - UI пустой при live API — убедиться, что
run_idсуществует и прогонdone; включить?api=1. - Ожидание «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. Быстрый чеклист перед демо хакатона¶
cd simulate && uv sync.venv/bin/sim run fixtures/plans/s01.json --output /tmp/demo-s01— дождаться RC 0 (~1–2 мин)- Показать
fact_report.json→plan_vs_fact.makespan_s - (Опционально)
.venv/bin/sim serve --no-auth+cd web && npm run dev→http://localhost:5173/?api=1 - Не обещать ночной 100× параллельный batch с checkpoint и автоматический трёхзначный GO/NO-GO (
sim batchиsim verdictесть, но вердикт частоUNRESOLVED) — см. LIMITATIONS; compose и aerosim — для демо API/UI