# pcp-tgu-ops-ui — карта возможностей и источников данных Дата анализа: 2026-05-31. Документ фиксирует, что сейчас умеет `pcp-tgu-ops-ui`: что отображает, чем управляет, какие backend endpoints вызывает и откуда реально приходят данные. Анализ выполнен по исходному коду frontend и по execution path backend endpoints до service/repository/external source. ## Кратко `pcp-tgu-ops-ui` — отдельная Vite/React SPA для Mission Ops/TGU. Приложение рендерит `TguPlanningPage` и имеет три вкладки: - `Планы` — timeline persisted TGU-планов по КА. - `Карта 2D` — карта с полигонами заявок на съемку. - `Редактор` — локальный редактор включений КА с черновиком, проверками и заглушкой сохранения. Реально изменять состояние backend UI умеет только через отправку решения по плану: `ACCEPTED` или `REJECTED`. Редакторские правки пока не сохраняются во внешний сервис. ## Общая схема ```text Browser | v pcp-tgu-ops-ui SPA | |-- /api/tgu-planning/* | | | v | pcp-ui-service :7008 | | | v | pcp-tgu-service :7011 | | | |-- pcp_tgu DB | | | `-- Ordinis NSI spacecraft classifier | |-- /api/current-plans/* | | | v | pcp-ui-service :7008 | | | v | pcp-mission-planing-service :7010 | | | `-- mission planning DB | |-- /api/pcp-request/v1/* | | | v | pcp-request-service :7005 | | | `-- request DB | `-- https://*.basemaps.cartocdn.com | `-- CARTO basemap tiles ``` Dev proxy: - `/api/pcp-request` -> `http://localhost:7005`, prefix `/api/pcp-request` stripped. - `/api` -> `http://localhost:7008`. Production nginx: - `/api/pcp-request/` -> `http://host.docker.internal:7005/`. - `/api/` -> `http://host.docker.internal:7008/api/`. ## Endpoint inventory | UI endpoint | Метод | Использование в UI | Backend execution path | Источник | |---|---:|---|---|---| | `/api/tgu-planning/platforms` | GET | список КА, подписи, поиск, редактор | `pcp-ui-service` `TguPlanningRestController.platforms()` -> `TguPlanningService.platforms()` -> `pcp-tgu-service /api/platforms` -> `PlatformService.loadPlatforms()` | in-memory cache, Ordinis NSI | | `/api/tgu-planning/plans?historyDays=N` | GET | загрузка всех TGU-планов для timeline/map/editor context | `pcp-ui-service` -> `pcp-tgu-service /api/plans` -> `PlanQueryService.getAllPlans()` -> `PlannedPlanRepository.findAllByOrderByStartTime()` | `planned_plan` | | `/api/tgu-planning/spacecraft/{spacecraftId}/plans?historyDays=N` | GET | есть в API-клиенте, текущая страница в основном грузит все планы | `pcp-ui-service` -> `pcp-tgu-service /api/plans/{spacecraftId}` -> `PlanQueryService.getPlansBySpacecraftId()` | `planned_plan` | | `/api/tgu-planning/plans/{planId}/decision?decision=...&reason=...` | POST | кнопки принять/отклонить для `WAITING_DECISION` | `pcp-ui-service` -> `pcp-tgu-service /test/tgu/plans/{planId}/decision` -> `TestPlanDecisionService.generateDecision()` -> `HandlePlanDecisionUseCase.handle()` | `planned_plan`, `plan_issue_attempt`, `spacecraft_planning_state`, `processed_plan_decision_event` | | `/api/pcp-request/v1/requests/map?...` | GET | полигоны заявок на карте 2D и в редакторе | напрямую в `pcp-request-service /v1/requests/map` -> `RequestController.listRequestsForMap()` -> `RequestService.listRequestsForMap()` -> `RequestRepository.findRequests()` | `requests` | | `/api/current-plans/missions/{missionId}/modes` | GET | контуры существующих включений в редакторе | `pcp-ui-service` -> `pcp-mission-planing-service /api/missions/{missionId}/modes` -> `MissionPlaningService.modesByMission()` | `missions`, `plans`, `modes`, `survey_modes`, `drop_modes`, `drop_mode_survey`, `mode_booking` | ## Вкладка "Планы" ### Что отображает - Список КА: `name`, `NORAD`, `mission`, `status`. - Количество планов по КА в выбранном интервале. - Индикатор проблемных статусов: `REJECTED`, `EXPIRED`, `START_AMBIGUOUS`. - Timeline persisted TGU-планов по КА. - Стрелки между последовательными планами одного КА. - Линию текущего времени, если `now` попадает в выбранный диапазон. - Детали выбранного плана: - `planId`; - `spacecraftId`; - КА; - `startTime`; - `endTime`; - duration; - `kppId`; - `status`. ### Чем управляет - Фильтр диапазона дат. - Быстрые диапазоны: 24 часа, 3 суток, 7 суток. - Refresh данных. - Поиск по КА. - Выбор конкретного КА. - Выбор плана. - Отправка решения `ACCEPTED`/`REJECTED`, но только для плана в статусе `WAITING_DECISION`. ### Execution path решения по плану ```text User | v TguPlanDetails | |-- Принять -> decision=ACCEPTED, reason=UI_TEST_ACCEPTED | `-- Отклонить -> decision=REJECTED, reason from prompt | v sendPlanDecision() | v POST /api/tgu-planning/plans/{planId}/decision | v pcp-ui-service | v TguPlanningService.sendDecision() | v POST pcp-tgu-service /test/tgu/plans/{planId}/decision | v TestPlanDecisionService.generateDecision() | |-- checks plan exists |-- checks spacecraft_planning_state exists |-- checks activePlanId == planId |-- checks activeAttemptId exists and belongs to plan |-- checks attempt status STARTING/STARTED | v HandlePlanDecisionUseCase.handle() | |-- insert processed_plan_decision_event, dedup by eventId |-- lock plan_issue_attempt by attemptId |-- lock planned_plan by planId |-- lock/create spacecraft_planning_state | |-- ACCEPTED: | |-- attempt=ACCEPTED | |-- plan=ACCEPTED | |-- currentBasePlanId=planId | |-- activePlanId/activeAttemptId cleared | |-- enqueue plan_recalculation_job | `-- enqueue accepted_plan_slots_update_job | `-- REJECTED: |-- attempt=REJECTED |-- activePlanId/activeAttemptId cleared |-- if enough lead time: plan=PLANNED, issueNotBefore=now `-- else: plan=EXPIRED, promote next plan or enqueue rebuild ``` Важный caveat: UI отправляет решение через test-only controller `pcp-tgu-service /test/tgu/...`. Если `tgu.test-controller.enabled=false`, эта команда перестанет работать. ## Вкладка "Карта 2D" ### Что отображает - CARTO dark basemap tiles. - Lat/lon grid поверх тайлов. - Полигоны заявок на съемку из `pcp-request-service`. - Цвета заявок по `surveyType`: - `OPTICS`; - `RSA`; - `COMBINED`. - Hover tooltip по полигону. - Выбор полигона заявки на карте. - Детальную панель с выбранным планом и выбранным объектом карты. ### Чем управляет - Панорамирование карты drag-ом. - Zoom колесом и кнопками `+`/`-`. - Reset view. - Включение/выключение слоев. - Загрузка заявок за отдельный интервал. - Быстрые интервалы заявок: сегодня, 7 дней, 30 дней. - Скрытие/показ КА в sidebar. ### Что сейчас заявлено в UI, но не имеет данных В главной карте 2D есть переключатели слоев: - трасса КА; - полоса обзора; - работы плана; - станции; - маркеры КА; - заявки на съемку. Но текущий `buildTgu2DMapScene()` наполняет только `request` polygons. Для остальных слоев рисовалки существуют, но данные в scene не передаются. UI показывает предупреждение: `Данные слоя недоступны в текущем API.` ### Execution path карты 2D ```text User | v Tgu2DMapTab | |-- selectedPlan / range / layers / hiddenSpacecraftIds | |-- load requests | | | v | fetchRequestsForMap() | | | |-- page size = 500 | |-- max pages = 10 | |-- max loaded requests = 5000 | |-- sort = beginDateTime,asc | | | v | GET /api/pcp-request/v1/requests/map | | | v | pcp-request-service | | | v | RequestController.listRequestsForMap() | | | v | RequestService.listRequestsForMap() | | | v | RequestRepository.findRequests() | | | |-- status filter, optional | |-- surveyType filter, optional | |-- kpp filter, supported by backend but not passed by current UI | |-- highPriorityTransmit filter, supported by backend but not passed by current UI | |-- beginFrom/beginTo/endFrom/endTo | |-- exclude DELETED by default | | | v | requests table | v buildTgu2DMapScene() | |-- parse WKT from request.geometry |-- supports POLYGON and MULTIPOLYGON |-- build MapPolygon objects with layer=requests | v Tgu2DMapView | |-- drawTiles() | | | `-- https://{a|b|c|d}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png | |-- drawBaseMap() | | | `-- 30 degree lat/lon grid | |-- drawRequests() | | | `-- request polygons on canvas | |-- createPolygonSpatialIndex() |-- hitTestPolygons() | v Canvas + hover tooltip + selected object details ``` ### Фильтр заявок на карте Для выбранного интервала `[from, to]` UI передает: ```text endFrom = from beginTo = to ``` То есть выбираются заявки, которые пересекают интервал карты. ## Вкладка "Редактор" ### Что отображает - Выбранный КА и выбранный TGU-план. - Черновик включений: - `shoot` — съемка; - `downlink` — сброс. - Timeline черновика. - Окна видимости цели/станции. - Синтетическую трассу КА на карте редактора. - Полигоны заявок из `pcp-request-service`. - Контуры `SURVEY` из `pcp-mission-planing-service`, если endpoint `/current-plans/missions/{id}/modes` вернул режимы. - Счетчики: - всего включений; - съемок; - сбросов; - конфликтов. ### Что умеет делать локально - Добавить съемку через клик точки на карте. - Добавить сброс по станции и окну связи. - Перетаскивать включения по timeline. - Менять начало/конец включения drag resize. - Менять время через stepper. - Дублировать включение. - Удалять включение. - Undo/redo. - Проверять пересечения включений по времени. - Проверять видимость цели/станции по синтетической орбитальной модели `passes.ts`. - Показывать контекст других КА в rail, но сейчас `contextLanes.works` пустой. ### Что не умеет сохранять Кнопка `Применить` вызывает `saveEditedPlan()`, но функция является заглушкой: ```text Сохранение правок плана во внешний сервис еще не реализовано в backend. ``` Backend endpoint для сохранения отредактированного набора включений отсутствует. ### Execution path редактора ```text User | v TguEditorTab | |-- selectedSpacecraftId / selectedPlan from TguPlanningPage | |-- initialize draft | | | v | seedDraft(selectedPlan) | | | |-- if selectedPlan has embedded works: | | `-- use embedded works | | | `-- else: | `-- create fallback downlink work: | id = {planId}:plan-window | kind = downlink | start/end = selected plan start/end | stationId = selectedPlan.kppId | |-- load request context | | | v | fetchRequestsForMap() | | | v | GET /api/pcp-request/v1/requests/map | | | v | pcp-request-service | | | v | requests table | |-- load plan modes | | | v | fetchPlanModes(selectedPlan.planId) | | | v | GET /api/current-plans/missions/{selectedPlan.planId}/modes | | | v | pcp-ui-service | | | v | pcp-mission-planing-service | | | v | MissionPlaningService.modesByMission() | | | |-- check mission exists | |-- find modes by mission id | |-- map SurveyModeEntity -> SurveyModeResponseDTO | |-- map DropModeEntity -> DropModeResponseDTO | | | v | mission planning DB | |-- editor map scene | | | |-- groundTrack(spacecraftId) from frontend synthetic orbit | |-- station markers only if station lat/lon exists | |-- plan mode SURVEY contourWkt -> planWorks polygons | `-- request.geometry WKT -> request polygons | |-- local draft operations | | | |-- addShoot() | |-- addDownlink() | |-- updateWork() | |-- deleteWork() | |-- duplicateWork() | |-- undo() | |-- redo() | |-- overlaps() | `-- checkVisibility() | `-- apply draft | v saveEditedPlan() | v stub result only, no backend mutation ``` ### Важные caveats редактора - `fetchPlanModes()` использует `selectedPlan.planId` как `missionId` для `/api/current-plans/missions/{missionId}/modes`. - Если UUID TGU-плана не совпадает с UUID mission в `pcp-mission-planing-service`, endpoint вернет 404, а редактор просто останется без режимов плана. - Станции строятся из `selectedPlan.kppId`, но координаты станции из backend не загружаются. Поэтому проверка видимости downlink часто возвращает конфликт `Нет координат станции для проверки видимости`. - Орбитальная модель в редакторе frontend-only и синтетическая: параметры выводятся из hash `spacecraftId`, а не из реальной баллистики/TLE. ## Capability matrix | Область | Сейчас умеет | Ограничение | |---|---|---| | Платформы КА | читать из TGU/NSI, показывать name/NORAD/mission/status | при падении NSI `pcp-tgu-service` может использовать stale cache, если есть | | TGU-планы | читать persisted plans, строить timeline | UI грузит все планы и фильтрует на клиенте | | Решение по плану | отправить `ACCEPTED`/`REJECTED` для `WAITING_DECISION` | через test-only controller; нужен active plan + active attempt | | Карта заявок | загрузить и показать WKT-полигоны заявок | до 5000 заявок, без server-side фильтров kpp/highPriority в UI | | Слои карты | UI-переключатели есть | кроме заявок, основные слои главной карты не получают данных | | Редактор включений | локальный draft, drag/resize, add/delete/duplicate, undo/redo, checks | сохранения в backend нет | | Режимы плана в редакторе | пытается читать `/current-plans/missions/{id}/modes` | возможен mismatch `planId` vs `missionId` | | Станции | может показывать station markers только при наличии lat/lon | lat/lon станций сейчас не загружаются | | Тайлы карты | CARTO CDN | внешний network dependency браузера | ## Проверенные исходники Frontend: - `services/pcp-tgu-ops-ui/src/App.tsx` - `services/pcp-tgu-ops-ui/src/api/tguApi.ts` - `services/pcp-tgu-ops-ui/src/api/requestApi.ts` - `services/pcp-tgu-ops-ui/src/features/tgu-planning/*` - `services/pcp-tgu-ops-ui/src/features/tgu-map-2d/*` - `services/pcp-tgu-ops-ui/src/features/tgu-editor/*` - `services/pcp-tgu-ops-ui/vite.config.ts` - `services/pcp-tgu-ops-ui/nginx.conf` Backend/BFF: - `services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/controller/TguPlanningController.kt` - `services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/service/TguPlanningService.kt` - `services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/controller/CurrentPlansController.kt` - `services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/service/MissionService.kt` TGU: - `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/controller/PlatformController.kt` - `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/controller/PlanController.kt` - `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/controller/TestTguController.kt` - `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/service/PlatformService.kt` - `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/service/PlanQueryService.kt` - `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/service/TestPlanDecisionService.kt` - `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/service/HandlePlanDecisionUseCase.kt` - `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/repository/*` Requests: - `services/pcp-request-service/src/main/kotlin/org/nstart/dep265/requestservice/controller/RequestController.kt` - `services/pcp-request-service/src/main/kotlin/org/nstart/dep265/requestservice/service/RequestService.kt` - `services/pcp-request-service/src/main/kotlin/org/nstart/dep265/requestservice/repository/RequestRepository.kt` - `services/pcp-request-service/src/main/kotlin/org/nstart/dep265/requestservice/dto/RequestApiDtos.kt` Mission planning: - `services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/controller/MissionController.kt` - `services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/service/MissionPlaningService.kt` - `services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/repository/*` - `libs/pcp-types-lib/src/main/kotlin/space/nstart/pcp/pcp_types_lib/dto/satellite/mission/*`