Files
dc-observatio/docs/бэклог-pcp-tgu-ops-ui.md
T
Дмитрий Соловьев 3c94815ffc fix(slots-service): #48 by-request отдаёт бронь в базовом cycle-фрейме
GET booking/by-request/{id} возвращал tn/tk/revolution из базового
cycle-фрейма без сдвига (BookedSlotEntity.toViewDTO), тогда как все
остальные пути (toDetails, allByInterval→shiftedToCycle,
resolveBookedSlotStartTime) сдвигают на cycle*durationCalc и
cycle*cycleRevs. Для броней с cycle > 0 дата уходила на cycle*durationCalc
суток раньше расчётной (cycle=0 совпадал, поэтому баг прятался; #42
починил только ключ sat:slotNum:cycle, не время).

Сдвиг перенесён в SlotService.bookedByReqId через новый
toShiftedViewDTO(satellite); КА резолвится из каталога, нет в каталоге —
базовый фрейм без падения списка. Небезопасный базовый билдер в сущности
удалён. Контур/координаты не сдвигаются (наземный след повторяется
каждый цикл), симметрично shiftedToCycle.

Регрессия в SlotServiceCancelTest: сдвиг tn/tk/витка на cycle брони и
fallback без КА. Бэклог #48 помечен сделанным.
2026-06-15 17:22:25 +03:00

44 KiB
Raw Blame History

Бэклог pcp-tgu-ops-ui

Список задач по сервису pcp-tgu-ops-ui (и связанным бэкендам). Ведётся вручную и с помощью Claude: по просьбе — отмечаем выполненное и добавляем новые задачи.

Обозначения статуса:

  • [ ] — не начато
  • [~] — в работе
  • [x] — выполнено (переносится в раздел «Выполнено» с пометкой)

У каждой задачи есть стабильный номер #N — он не меняется при правках списка, на него удобно ссылаться. Новые задачи получают следующий свободный номер.

Задачи #12–#25 заведены по результатам аудита. Подробности, обоснование и рекомендации по каждой — в аудит-проекта-2026-06-06.md (ссылки на коды находок B/F/R/C).

Обновлено: 2026-06-15


Активные задачи

Карта и отображение

  • #1 — Отображать трассы полос исходя из возможности съёмки КА. Для оптики показывать только участки, попадающие в допустимый угол Солнца.
  • #2 — При клике в область, где пересекается несколько полос обзора, выводить список этих объектов на правой панели; при выборе элемента — выделять соответствующий объект на карте. Сделано: на вкладке «Редактор» (где рисуются полосы обзора) клик собирает полосы обзора отдельно от прочих объектов — если под точкой нет иного интерактивного объекта (заявки/маршрута/сброса/станции), а полос обзора несколько (наложение соседних витков), они отдаются списком в правую панель (MapPickPanel), как у прочих наложений; одиночная полоса — сразу карточка. Выбор/раскрытие элемента подсвечивает полосу на карте (selectedObjectIdmapFocus.objectId). Полоса обзора получила карточку (КА + виток): новые поля revolution/spacecraftId у swath-полигона, проброс в Tgu2DMapSelection, ветка swath в MapInfoPanel. Клик по заявке/маршруту поверх полос не засоряется полосами (полосы — только когда другого объекта под точкой нет). Файлы: Tgu2DMapView.handleEditorClick, mapTypes.ts, mapSelectionTypes.ts, TguEditorTab (сборка сцены), EditorPanels.MapInfoPanel. Доработка (2026-06-11): когда под точкой есть наложение ≥2 интерактивных объектов (напр. маршрут + заявка), в список выбора теперь добавляются и лежащие под кликом полосы обзора, чтобы их данные тоже были видны. Список упорядочен по приоритету (editorHitRank): маршрут съёмки → заявка → полоса обзора (маркеры/станции — перед маршрутом, сбросы — между заявкой и полосой). Одиночный клик по маршруту/заявке по-прежнему сразу выбирает объект (без списка), иначе любой клик в редакторе превращался бы в список из-за сплошного покрытия полосами.
  • #3 — Показ забронированных слотов на вкладках «Карта» и «Редактор». Включает подгрузку данных по забронированным слотам с бэкенда (фетч), а не только отрисовку уже имеющихся. На вкладке «Редактор» они должны быть другого цвета и выделяться.

Группировка

  • #36 — Заполнение бортового накопителя на вкладке «Группировка». Сейчас (MVP) считается на фронте из объёмов маршрутов плана (volumeMb): старт 0 на начало интервала плана, +vol на конце съёмки, vol на конце сброса, ёмкость борта — модельные 500 ГБ (ONBOARD_CAPACITY_MB в features/constellation/realData.ts). Довести до реального источника: брать ёмкость борта из НСИ платформы (не хардкод), а заполнение — из телеметрии/бортового учёта, когда такой канал появится; уточнить модель «съёмка копит / сброс опустошает» (частичные сбросы, очерёдность).

Редактор

  • #4 — Показывать на вкладке «Редактор» только те заявки, которые может удовлетворить аппарат (например, для оптики не показывать заявку, для которой доступна только локация).
  • #5 — Дать возможность превратить план в статусе PLANNED (построенный цепочкой от последнего LAID_IN) в черновик и вручную наполнить его включениями — до того, как он будет автоматически выдан по таймеру.
  • #38 — Правка SLOTS-маршрута «телепортирует» его (исчезает с карты). При «Редактировать» редактор пересобирает контур через survey-route/build, передавая сохранённый timeStart. Для маршрутов source=MANUAL это корректно (их timeStart = время построения, контур воспроизводится). Для source=SLOTS timeStart/виток/контур посчитаны другим конвейером и не согласованы: напр. режим 174 хранит timeStart=2026-06-07T01:51:38, виток 152, контур над Чёрным морем (центр 45.42, 39.40), но в 01:51:38 спутник по flight-line над Индией (12–16° с.ш., виток 150) — пересборка даёт контур над Индией (10.73, 77.06), исходный прячется → маршрут «пропадает» из вида. Корень — та же баллистическая рассинхронизация, что и [#37]. Решение (2026-06-08): чиним только на бэкенде — согласовать timeStart/виток/контур у SLOTS-режимов, чтобы survey-route/build воспроизводил их. UI-страховку (не прятать исходный контур / блокировать правку SLOTS) сознательно НЕ делаем — симптом остаётся до бэкенд-фикса.

Комплексный план

  • #6 — Пересчитывать комплексный план при бронировании новых слотов или при поступлении новой заявки.
  • #44 — Добавить на вкладку «Комплексный план» просмотр тепловой карты (приоритеты/покрытие ячеек). Источник — priority-map сервиса заданий (importance ячеек) и/или статистика покрытия прогона; отрисовать как heatmap-слой поверх карты. Сделано: чекбокс «Тепловая карта» в тулбаре; фоновый слой-заливка ячеек по важности (cool→hot) под контурами плана/заявок; источник GET /api/pcp-request/v1/cells/priority-map, не интерактивен (в spatial-индекс не попадает). cellsApi.ts + drawHeatmap.ts + слой heatmap в Tgu2DMapView. Клик по объекту (ячейка/включение/заявка) показывает его данные в правом инспекторе ComplexInfoPanel (по аналогии с редактором; при наложении — список под точкой клика с раскрытием).

Бэкенд / инфраструктура

  • #7 — Перевести сервис станций на ordinis (по примеру получения аппаратов в сервисе tgu).
  • #47 — Перевести сервисы с Jackson 2 на Jackson 3 и убрать двойной стек. Под Spring Boot 4.0.2 MVC уже десериализует входящий JSON через Jackson 3 (tools.jackson.*), а весь код монорепы написан под Jackson 2 (com.fasterxml.jackson, versions.jackson_databind=2.18.2); в pcp-request-service для внутреннего парсинга держится отдельный Jackson 2-бин (JacksonConfig). Из-за этой рассинхронизации словили баг: Jackson 3 по умолчанию включает FAIL_ON_NULL_FOR_PRIMITIVES, и явный null на примитивном Double coverageRequiredPercent ронял создание заявки (контракт-тесты не ловили — они на Jackson 2). Срочности/EOL-риска нет, это техдолг и консистентность.
    • Сейчас, дёшево (часы): системно снять класс багов «null на примитиве» — выставить spring.jackson.deserialization.fail-on-null-for-primitives=false в config-repo request-service (проверить, что свойство применяется к Jackson 3-бину; если нет — через Jackson3ObjectMapperBuilderCustomizer). Не латать каждое поле по очереди.
    • Среднесрок, по сервису: мигрировать по одному, пилот — pcp-request-service (~7 файлов main, ~0.5–1 день). Аннотации не трогаемjackson-annotations в Jackson 3 остались в пакете com.fasterxml.jackson.annotation (у нас это основная масса: @JsonIgnoreProperties ×36, @JsonProperty ×16, @JsonInclude ×15…). Менять только databind-поверхность: ObjectMapper ×23, TypeReference ×8, JsonNode ×5, jacksonObjectMapper() ×12 → пакеты tools.jackson.databind/core/module.kotlin; в Jackson 3 JsonProcessingException стала unchecked. Дропнуть Jackson 2-зависимости и Jackson 2-бин JacksonConfig.
    • Главный риск — Kafka: KafkaConsumerConfig/сериализаторы завязаны на com.fasterxml ObjectMapper. Перед стартом — спайк (~полдня): умеет ли spring-kafka в нашем Boot 4 Jackson 3-сериализаторы. Если нет — Kafka-слой мигрируем отдельно, HTTP/внутренний JSON переводим независимо.
    • Масштаб (src/main): 67 файлов в 10 сервисах — pcp-dynamic-plan-service 22, pcp-ui-service 16 (выводится из эксплуатации, ср. #40 — можно не мигрировать), pcp-request-service 7, slots-service/pcp-tgu-service по 5, остальные 1–4. Грубая оценка всей репы — ~6–10 человеко-дней, основной риск — Kafka. Big-bang не нужен: шаг 1 закрывает причину бага, полную миграцию вести фоново сервис за сервисом.
  • #45 — Собрать native-образ сервиса (Kotlin/Native, kompile) и контейнер на его основе. Цель — быстрый старт и меньший footprint в сравнении с JVM-образом. Уточнить: какой сервис(ы) переводим первым, совместимость с рефлексией/AOT-конфигом (reachability metadata), сборку в CI и базовый образ контейнера.
  • #37 — Рассинхрон номеров витков между баллистическими источниками. Для одного и того же КА/времени flight-line (трасса, /api/tgu-planning/spacecraft/{norad}/flight-line) и survey-route/build (контур, pcp-mission-planing) отдают разный номер витка: на Emissio 1 (norad 2) 2026-06-06 12:5012:55 трасса помечена витком 142, а контур/сохранённые режимы — витком 144 (по широте ~74° с.ш. согласованы, расходится только нумерация). В редакторе карта фильтрует трассу/полосу обзора по видимому витку (buildEditorMapScenevisibleRevolutions), поэтому при сужении селектора до «виток N» точки трассы с другим номером выпадают и пропадает орбитальный контекст. Свести нумерацию витков к единому источнику (или маппинг на границе), чтобы трасса и маршрут согласовывались по витку.

Проверка сервисов

  • #27 — Проверить сервис получения паспорта маршрута по отдельности.
  • #28 — Проверить связь сервиса заданий и сервиса получения паспорта маршрута между собой.

Бэкенд / вывод pcp-ui-service из эксплуатации

  • [~] #40 — Перевести все запросы фронта с pcp-ui-service на источники и вывести сервис из эксплуатации. pcp-ui-service — наследие старого server-rendered UI; новый фронт ходит к доменным сервисам через gateway, и pcp-ui-service остаётся в маршрутизации только как catch-all для двух поверхностей: tguApi.ts/api/tgu-planning/* (+ flight-line) и editorApi.ts/api/current-plans/missions/{id}/modes. Карта запросов, источники, реальные пути и пошаговый план — в план-перевода-с-pcp-ui-service.md. Ключевые моменты: (1) перенести во фронт UX-перевод ошибок решения по плану (сейчас живёт в TguPlanningService); (2) decision/confirm-layin идут в тест-контроллер tgu (/test/tgu/*, гейт tgu.test-controller.enabled) — поведение не меняем; (3) DTO проксируются как есть, схема фронта не ломается; (4) внешних потребителей у сервиса нет (проверено grep), но перед удалением кода свериться по мёртвым поверхностям (bookings/catalog/rva/com-plan/…). После переезда — снять ui-catch-all из config-repo/pcp-gateway-service.yaml, убрать сервис из docker-compose/helm/CI/config-repo.
    • Шаги 1–7: репойнт фронта на источники (tguApi.ts/api/pcp-tgu + /test/tgu/*; flight-line → ballisticsApi.ts; editorApi.ts modes → /api/pcp-mission) и снятие ui-catch-all из gateway. Перевод ошибок уже жил во фронте (errorMessage в tguApi.ts). Typecheck чист.
    • Шаг 8: вывод сервиса из эксплуатации (compose/helm/CI/config-repo/реестр pcp.services.ui) и удаление кода — отложено до проверки репойнта на живом стенде. Пока сервис задеплоен, но без маршрутов.

Слоты (вкладка «Слоты»)

  • #41 — Запрос слотового покрытия по заявке. Сделано (2026-06-11): добавлена отдельная вкладка «Слоты» (вместо размещения на «Заявке»/«Редакторе» — операции со слотами объёмные, их собрали в одном рабочем месте). Оператор выбирает заявку, интервал и фильтры (ветвь витка, признак «Покрытие») и жмёт «Рассчитать слоты» → слоты-кандидаты от slots-service напрямую через gateway (GET /api/pcp-slots/api/slots/request-cover, минуя умирающий pcp-ui-service, ср. #40). Результат — таблица (КА, виток/ветвь, слот/цикл, время, крен, статус) и отрисовка контуров на 2D-карте поверх контура заявки; клик по строке центрирует карту и подсвечивает контур. Новый роут gateway id: slots (/api/pcp-slots/**, StripPrefix=2 → slots-service). Файлы: config-repo/pcp-gateway-service.yaml, api/slotsApi.ts, model/slotTypes.ts, features/tgu-slots/TguSlotsTab.tsx, регистрация вкладки в TguActiveTab/TguToolbar/TguPlanningPage, стили .tgu-slots-*.
  • #42 — Бронирование слотов по заявке. Сделано (2026-06-11): на вкладке «Слоты» чекбоксами выбираются слоты → «Забронировать» (пакетно, POST /api/slots/booking/request с {requestId, slots:[{satelliteId, slotNum, cycle}]}) и «Снять бронь» (DELETE …/booking/request?requestId=). После операции перечитываются брони заявки (GET …/booking/by-request/{id}), забронированные слоты получают бейдж «бронь» в таблице и отдельный цвет (слой complexPlan) на карте. Связано с показом забронированных слотов (#3) и пересчётом комплексного плана при бронировании (#6) — последнее пока на бэкенде не триггерится. Уточнить при необходимости идемпотентность повторной брони.
  • Фильтр отображения слотов по суткам (2026-06-11): на вкладке «Слоты» добавлен ползунок-фильтр «Сутки» — тот же двуручейный селектор, что «Витки» на «Редакторе» (классы map-pass-selector*), наложенный на карту. Слоты группируются по локальным суткам начала (tn); выбранный диапазон суток сужает и таблицу, и сцену карты (кандидаты), «Выбрать все» работает по видимым слотам. Новый расчёт раскрывает фильтр на весь диапазон. Клик по слоту на карте теперь не только подсвечивает строку, но и подскроливает её в зону видимости таблицы. Файлы: features/tgu-slots/SlotDaySelector.tsx (новый), features/tgu-slots/TguSlotsTab.tsx.
    • Доп. (2026-06-11): подсветка активной строки сделана явной (заливка + левый акцентный бар + bold). Скролл к строке — ручной scrollBy по getBoundingClientRect с центрированием и плавной анимацией: scrollIntoView({block:"nearest"}) оказался ненадёжен (выбирал не тот скролл-предок / не двигался). Строка ищется по стабильному data-slot-id, а не по ref (тот при «переезде» между строками мог занулиться). Подсветку ведут только клики по слоту; клик по контуру заявки её не сбрасывает, клик по пустой карте — снимает.
  • Выбор заявки на карте (вместо списка) (2026-06-11): на вкладке «Слоты» убран выпадающий список заявок — он не масштабируется на тысячи заявок. Базово все заявки интервала отображаются на карте; клик по заявке (или по наложению нескольких — список с раскрытием) открывает плавающую панель-инспектор справа над картой (общий MapObjectInspector) с данными заявки и кнопкой «Выбрать». До выбора в тулбаре вместо списка — имя выбранной заявки или «—», а левая панель показывает фильтр «Заявки на интервал» (С/По + «Загрузить»/«Сегодня»/«7 дней»/«30 дней», как на «Карте») и подсказку «Кликните заявку на карте». После выбора заявки появляется кнопка «Сброс» справа над картой — возвращает окно в состояние «заявка не выбрана, слоты не показаны». Файлы: features/tgu-slots/TguSlotsTab.tsx, features/tgu-map-2d/MapObjectInspector.tsx (опц. onSelectRequest → кнопка «Выбрать» в карточке заявки), стили .tgu-slots-request*/.tgu-slots-reset/.tgu-slots-inspector/.tgu-slots-requests-panel.
  • Бронь не отражалась в UI + явная подсветка строки (2026-06-11): «не работает кнопка бронь» — диагностировано: POST booking/request бронирует корректно и в ответе BookedSlotDTO несёт верный cycle, но GET booking/by-request/{id} отдаёт базовый слот в другом cycle-фрейме (bookedSlot.slot.toDTO без сдвига на bookedSlot.cycle), из-за чего ключ sat:slotNum:cycle не совпадал с рассчитанной строкой → бейдж «бронь» не появлялся, на карте — лишний полигон. Фронт-обход: после брони отмечаем забронированными ровно те рассчитанные слоты (с контуром), что вернул POST; by-request на горячем пути не перечитываем; cancel чистит локально. Заодно подсветка активной строки слота сделана явной (фон + левый акцентный бар + bold). Root-fix (на бэке, отдельно): slots-service SlotService.bookedByReqId/bookedById должны возвращать слот, сдвинутый на bookedSlot.cycle (как resolveBookedSlotStartTime/перечисление в request-cover) — иначе пред-существующие брони не коррелируют с расчётом. Файлы: features/tgu-slots/TguSlotsTab.tsx, стили .tgu-slots-table tbody tr.is-active.
  • #43 — Уточнение статуса слота. На вкладке «Слоты» уже показывается базовый статус слота (state: свободен/бронь/занят) и бейдж брони по заявке. Осталось: подтягивать/обновлять актуальный статус занятости по заложенному плану (слоты занимаются по LAID_IN, а не по ACCEPT, ср. #10) и согласовать его источник со slots-service (real-time/после закладки), а также прокинуть статус на вкладки «Карта»/«Редактор» (#3).
  • #48 — (баг, бэкенд) Слоты бронируются «не на те даты». Исправлено 2026-06-15: BookedSlotEntity.toViewDTO() (единственный билдер за GET …/booking/by-request/{id}) отдавал tn/tk/revolution из базового cycle-фрейма без сдвига, тогда как все остальные пути (toDetails, allByIntervalshiftedToCycle, resolveBookedSlotStartTime) сдвигают на cycle*durationCalc. Сдвиг перенесён в SlotService.bookedByReqId (новый toShiftedViewDTO(satellite), КА резолвится из каталога; нет в каталоге → базовый фрейм без падения), базовый билдер в сущности удалён. Регрессия: SlotServiceCancelTest (cycle=2 → +32 сут / +486 витков; fallback без КА). POST booking/request сохраняет только ключ (bookedSlotId/sat/slotNum/cycle), tn/tk не пересчитывает — корректно.
    Исходный отчёт: При бронировании набора слотов фактическое время брони расходится с тем, что оператор видел/выбирал в таблице расчёта. Корень — та же рассинхронизация cycle-фрейма, что разобрана в задаче «Бронь не отражалась в UI» (#42): GET …/booking/by-request/{id} отдаёт слот в базовом cycle-фрейме (bookedSlot.slot.toDTO без сдвига на bookedSlot.cycle), тогда как request-cover перечисляет слоты со сдвигом — поэтому время/сутки брони, восстановленные из by-request, не совпадают с рассчитанным слотом. Фронт сейчас обходит это, отмечая бронь по ответу POST (там cycle верный) и не перечитывая by-request на горячем пути, но это лишь маскирует симптом: пред-существующие брони и брони после перезагрузки страницы по-прежнему могут показываться/трактоваться с неверной датой. Root-fix на бэке: slots-service SlotService.bookedByReqId/bookedById должны возвращать слот, сдвинутый на bookedSlot.cycle (как resolveBookedSlotStartTime / перечисление в request-cover), чтобы время брони было согласовано с расчётом. Проверить также, что POST booking/request сохраняет именно те tn/tk, что соответствуют выбранному слоту, а не пересчитывает их в базовом фрейме.

Аутентификация и роли

  • #46 — Вывести роли на морду и связать их с аутентификацией. На фронте показывать роль(и) пользователя и разграничивать доступ к действиям/вкладкам по роли; источник ролей — аутентификация (токен/headless-auth, @nstart/auth). Уточнить: какие роли есть, где они приходят (claims токена / профиль), как маппятся на UI-разрешения (что скрывать/блокировать), и нужен ли серверный enforcement на стороне сервисов, а не только UI-гейтинг.

Баги

  • #8 — Ошибка при попытке принять план на вкладке «Планы».
  • #39 — Авторасчёт плана выдаёт нереализуемые маршруты съёмки с креном > 45° (напр. survey-211, Emissio 1, крен 46.8°). Такой маршрут нельзя построить вручную: фронт зажимает MAX_ROUTE_ROLL_DEG = 45 (TguEditorTab.tsx), а баллистика/геометрия маршрута и фильтр видимости (МПЛ, abs(kren) <= 45°) определены только до 45°. Диагностика (2026-06-08): корень — данные каталога, не логика. slots-service строит крен слота как центр угловой полосы из каталога (SlotService.kt:903-905, gamma=(begin+end)/2) и предел 45° не учитывает; полосы берутся напрямую из satellite_slot_angle. В сиде каталога (pcp-satellite-catalog-service/.../V1__initial.sql) у оптических Emissio (id 1-6, 22) собственный профиль полос уходит до 50.42°, и полоса №17 [46.08°, 47.51°] даёт центр ровно 46.8°. Версия «посчитали по логике локации» проверена и НЕ подтвердилась: в slots-service нет ветвления по типу КА (оптика/локация) — все КА строятся одним TestSatelliteImpl, солвер выбирается стратегией запроса (GREEDY/CONTINUOUS), type_code не используется. Reflexio (локация) наоборот имеет узкие полосы ~17-25°. Решение (вернуться позже): либо обрезать/убрать в каталоге оптические полосы за 45° (поз. №16-20 у Emissio), либо добавить в slots-service зажим/отсев слотов по рабочему пределу крена КА. Истинный предел Emissio подтвердить у владельца каталога/баллистиков перед правкой.

Аудит — надёжность и бэкенд

Источник и рекомендации: аудит-проекта-2026-06-06.md.

  • #12 — (B1, высокий) Camunda job-воркеры в FlowService молча глотают исключения (catch { logger.warn(ex.message) }) при autoComplete=false: задача не завершается и не падает, BPMN-процесс зависает без инцидента. Завершать через newFailCommand/проброс наверх, логировать error(msg, ex).
  • #13 — (B2, средний) Снять симуляционную заглушку Thread.sleep(5000) («имитация подтверждения плана») в FlowService.sendPlan при появлении реального канала подтверждений.
  • #14 — (B3, средний) Привести в порядок несогласованные пакеты-копипасты (ballistics→pcp_request_service, complex-mission→pcp_satellites_service, ui-service и slots-service делят slots_service, request→org.nstart.dep265, tgu→без .pcp.). Отдельной задачей из-за широкого охвата.
  • #15 — (B4, низкий) Опечатка «srpring» в pcp-srpring-boot-admin-server растиражирована по каталогу, пакету, CI, docker-compose, gradle. Чинить вместе с #14.
  • #16 — (B5, низкий) ~9 мест logger.warn/error(ex.message) теряют stack trace — заменить на logger.error("...", ex).

Аудит — фронтенд

Источник и рекомендации: аудит-проекта-2026-06-06.md.

  • #17 — (F1, средний) Вынести общий src/api/http.ts (fetchJson + extractErrorText + маппинг ошибок): extractErrorText продублирован в 5 клиентах, fetch-обвязка повторяется ~22 раза. Заодно устранить расхождение приоритета полей ошибки (F5).
  • #18 — (F2, низкий) Перевести нетипизированный островок features/constellation/* (.jsx/.js, allowJs) на TS или добавить декларации типов на границе.
  • #19 — (F3, низкий) Удалить мёртвый barrel features/constellation/index.js (нигде не импортируется).
  • #20 — (F4, низкий) Декомпозировать God-компонент TguEditorTab.tsx (1727 строк): вынести хуки (useDraftHistory, useRouteBuilder), разнести панели.

Аудит — гигиена репозитория и конфиги

Источник и рекомендации: аудит-проекта-2026-06-06.md.

  • #21 — (R1, средний) Убрать из git крупные дампы в корне (pcp_requests_converted_create_requests.json ~228 КБ, response_заданияКубернетс.json ~157 КБ, response_заданияЕмельянов.json ~66 КБ) — перенести в src/test/resources/docs или удалить с добавлением в .gitignore.
  • #22 — (R2, низкий) Удалить посторонние артефакты в корне: node_modules/ + пустой package-lock.json, obj_terra.zip; при желании добавить /package-lock.json в .gitignore.
  • #23 — (R3, низкий) Прогнать ./gradlew clean / удалить 26 устаревших каталогов bin/ (задваивают исходники, шумят при grep).
  • #24 — (C1, средний) Унифицировать креды БД в config-repo: привести все сервисы к переопределяемому password: ${DB_PASSWORD:...} (как в tgu-service), убрать зашитые password: password.
  • #25 — (C2, низкий) Сузить CORS для прода в pcp-ui-service (allowedOriginPatterns("*") + allowCredentials) до конкретных origin; держать в курсе отсутствие CORS у напрямую вызываемых сервисов (работает только через прокси/nginx).

Аудит — сервис заданий (pcp-request-service)

Источник и рекомендации: аудит-сервис-заданий.md (коды находок RS1–RS7).

  • #30 — (RS2, высокий) Матчинг засчитывает покрытие по любому геометрическому пересечению, игнорируя совместимость аппарата (survey_type, крен, разрешение, угол Солнца). Добавить фильтр совместимости маршрут↔заявка перед зачётом покрытия; тип сенсора в паспорте маршрута согласовать в рамках #28. Отложено до #28 (решение, 2026-06-06): RoutePassportDto не несёт явный surveyType — контракт фиксируется в рамках аудита связи сервисов #28, тогда же делаем фильтр.
  • #33 — (RS5, низкий) Площади считаются планарно в градусах (JTS area на EPSG:4326) — искажение для крупных/высокоширотных заявок. Перейти на геодезию (ST_Area(geography) или равновеликая проекция), когда вырастут требования к точности. Отложено по решению аудита: покрытие считается отношением площадей (планар ≈ геодезия для обычных AOI), а переключение area() затрагивает matching и точечные ассерты тестов — вводим при росте требований к точности.

Выполнено

  • #34 — (RS6) Проброс требуемого процента покрытия из API + разведение терминов. Готово: в CreateRequestDto добавлено опциональное поле coverageRequiredPercent (0 < x ≤ 100, по умолчанию 100), оно пробрасывается в сущность и используется как порог COMPLETED; захардкоженная константа убрана. В KDoc/коде разведены «прогресс покрытия» (coverageRequiredPercent) и «важность заявки» (importance, карта приоритетов ячеек). Тесты: RequestServiceTest, RequestApiDtoContractTest.
  • #31 — (RS3) DLQ и error-handler для Kafka-листенера маршрутов. Готово: KafkaConsumerConfigDefaultErrorHandler + DeadLetterPublishingRecoverer в топик pcp.route.georeference-dlq.v1, экспоненциальный bounded retry, контрактные ошибки (JsonProcessingException/ConstraintViolationException) → DLQ без ретраев, диагностические заголовки. Настройки app.kafka.consumer.route.*, активируется при заданном spring.kafka.bootstrap-servers. Тест KafkaConsumerConfigTest.
  • #35 — (RS7) Уведомление geoportal о финале заявки. Готово: единый топик pcp.request.status.v1 (заменил orphan pcp.request.completed.v1) с терминальным статусом в payload (COMPLETED/EXPIRED). Эмиссия из двух мест через тот же transactional outbox: RouteMatchingService (по покрытию) и RequestExpiryService (по сроку, #29). Сервис RequestCompletedOutboxServiceRequestStatusOutboxService, eventType REQUEST_TERMINAL_STATUS. Outbox получил backoff: колонки attempts/next_attempt_at, удвоение initial→max, предел max-attempts (терминальный FAILED). Конфиг pcp.outbox.*. Тесты: OutboxPublisherServiceTest, OutboxEventRepositoryJpaTest, RequestStatusOutboxServiceTest, RequestExpiryServiceTest, RouteMatchingServiceTest. Док: интеракции обновлены. Известный мелкий зазор: заявка, созданная уже просроченной, события не шлёт (вырожденный кейс).
  • #29 — (RS1) Планировщик истечения заявок. Готово: RequestExpiryService (@Scheduled, FOR UPDATE SKIP LOCKED, батчи с верхним лимитом) переводит просроченные ACCEPTED/ACTIVE в EXPIRED; несколько инстансов безопасны. refreshStatus теперь вызывается и при создании заявки — статус приёма отражает окно (ACCEPTED/ACTIVE/EXPIRED), а не всегда ACCEPTED. Конфиг pcp.request.expiry.* в config-repo. Тесты: RequestExpiryServiceTest (юнит) + RequestRepositoryJpaTest (H2). Разблокирует EXPIRED-эмиссию в #35.
  • #32 — (RS4) Удалить мёртвый конфиг сервиса заданий. Готово: из config-repo/pcp-request-service.yaml убраны app.request.cleanup-cron/cleanup-retention-days и неиспользуемый топик app.kafka.topics.request (pcp.request.in.v1); кода под ними не было, @Value-привязок нет.
  • #26 — Проверить сервис заданий (pcp-request-service) по отдельности. Готово: аудит в аудит-сервис-заданий.md — карта взаимодействий, учёт покрытия, жизненный цикл, мультиинстанс; находки RS1–RS7 (RS1/RS2 — высокий приоритет).
  • #9 — Расширить вправо левую панель информации на вкладке «Редактор» (цифры у границы смотрелись плохо). Готово: commit 69dfd80, рейл 15.5rem → 17rem.
  • #10 — Проставлять статусы слотов только после того, как план заложен на КА. Готово: commit 69dfd80, слоты занимаются по LAID_IN, а не по ACCEPT.
  • #11 — Запретить нажимать «Подтвердить закладку», пока начало закладки в будущем. Готово: commit 69dfd80.

Добавил вручную:

  • убрать кнопки + - квадрат с вкладки редактор и карта с карты ибо ничего не делают Решено иначе (2026-06-11): кнопки не убрали, а починили — они не срабатывали из-за setPointerCapture на контейнере карты в onPointerDown (перехватывал жест, и клик по кнопке внутри карты не доходил). Добавлен гард: нажатие в области .tgu-2d-map__zoom не запускает панорамирование/захват, плюс stopPropagation на кнопках. Теперь «+/−» (зум) и «□» (вернуть обзор) работают на всех вкладках с картой.
  • Линейка на карте (2026-06-11): инструмент измерения расстояния на всех вкладках с картой (Карта, Заявки, Комплексный план, Редактор, Слоты — общий Tgu2DMapView). Иконка-линейка в правом нижнем углу рядом с «+/−/□», включается/выключается кликом (тумблер). В активном режиме клик по карте ставит вершины замера, рисуется пунктирная ломаная с подписями накопленного расстояния по дуге большого круга (haversine), в бейдже — итоговая длина; «Сброс» очищает замер, повторный клик по иконке выключает. Реализовано целиком в Tgu2DMapView (состояние rulerActive/rulerPoints), стили .tgu-2d-map__ruler*.