Files

14 KiB
Raw Permalink Blame History

Интеграция «Заказ съёмки» (Альтум) ↔ Ordinis НСИ

Status: схемы согласованы, ожидаем service-account Keycloak и сидинг. Owner со стороны Ordinis: zimin.an@nstart.space. Owner со стороны Альтума: altum-backend (FastAPI BFF).

1. Зачем

В форме «Заказ съёмки» (greenfield на стороне Альтума — поля mission / instrument / processing_level / data_format пока отсутствуют в БД и UI) поля будут читаться из Ordinis НСИ — единый источник истины. Когда оператор НСИ заводит новый КА «Ресурс-П 4» — он сразу появляется в выпадашке заказа без релиза Альтума.

Польза: новый спутник / станция / прибор появляется в заказе через 5 минут после ввода в админке Ordinis вместо релизного цикла Альтума.

2. Контракт API (Ordinis → Альтум)

Base URL:

  • staging: https://ordinis.k8s.265.nstart.cloud/api/v1
  • prod: https://ordinis.k8s.264.nstart.cloud/api/v1 (после prod stand-up)

Авторизация:

  • На staging пока выключена (ORDINIS_AUTH_REQUIRED=false), на prod включится.
  • Header Authorization: Bearer <jwt> от Keycloak (altum.nstart.cloud/auth/realms/altum).
  • Альтум использует service account (m2m client) в реалме nstart, роль ordinis-public. JWT issued через client_credentials flow, рефрешится в altum-backend перед TTL.
  • Ordinis маппит роль ordinis-public → DataScope.PUBLIC. Маппинг — см. ordinis-auth/.../ScopeContext.java.

Endpoints (read-only):

⚠️ Read-api живёт без префикса /dictionaries/ — это admin-write путь. Для чтения используйте /api/v1/{name}/records напрямую.

GET /api/v1/dictionaries
  → список доступных справочников (только метаданные + JSON Schema).

GET /api/v1/{name}/records?lang=ru-RU&at=2026-05-04T12:00:00Z
  → активные записи на момент `at` (default: now).
  ?lang=ru-RU / ?lang=en-US — i18n поля сворачиваются в строку на этом языке.
  Без ?lang — i18n остаётся объектом {ru-RU, en-US}, BFF может flatten сам.

GET /api/v1/{name}/records/{businessKey}?lang=ru-RU&at=...
  → одна запись по businessKey.

GET /api/v1/{name}/records/{businessKey}/history
  → все версии записи (закрытые + текущая).

Пример (есть на staging прямо сейчас):

curl https://ordinis.k8s.265.nstart.cloud/api/v1/spacecraft/records?lang=ru-RU
[
  {
    "id": "72a15bec-56f6-4fb6-8e85-d91a492c707e",
    "businessKey": "1999-068A",
    "data": {
      "name": "Terra (EOS AM-1)",
      "country": "US",
      "operator": "НАСА",
      "norad_id": 25994,
      "designator": "1999-068A",
      "launch_date": "1999-12-18",
      "status": "ACTIVE",
      "satellite_type_code": "OPT_MIDRES_MS",
      "spectrum_bands": ["VIS", "NIR", "SWIR", "TIR"]
    },
    "validFrom": "2026-05-03T20:49:09Z",
    "validTo": "9999-12-31T23:59:59Z"
  }
]

businessKey формат: для существующих spacecraft — COSPAR designator (например 2016-016A = Ресурс-П № 3). Для остальных справочников — code-style (RESURS_P, MSI-S2, GEOTIFF, L1A).

3. Согласованные схемы 4 справочников

Все справочники имеют общую базовую структуру: code (string, businessKey), status (enum: active/archived). Поля name/description — i18n localized. Дальше описаны специфические поля.

mission — программа / семейство КА

поле тип пример примечание
code string RESURS businessKey, x-id-source
name i18n Ресурс / Resurs для UI
description i18n Российская программа оптического ДЗЗ среднего разрешения tooltip
spacecraft_codes array of string ["RESURS-P-3","RESURS-P-4"] businessKeys из spacecraft
status enum active / archived

instrument — конкретный сенсор на борту

поле тип пример примечание
code string GEOTON-L1 businessKey, x-id-source
name i18n Геотон-Л1
spacecraft_codes array of string ["RESURS-P-3","RESURS-P-4"] many-to-many
type enum optical / sar / infrared / multispectral / hyperspectral UI-фильтр
spatial_resolution_m number 1.0 для сортировки/фильтра
swath_km number 38
spectral_bands array of string ["PAN","B","G","R","NIR"] опц.
supported_format_codes array of string ["GEOTIFF","NITF"] businessKeys из data_format
supported_level_codes array of string ["L1A","L2"] businessKeys из processing_level
status enum active / archived

Связи instrument → format/level в самом instrument'е — даёт фронту cascading select без отдельного эндпоинта совместимости.

processing_level — уровень обработки

поле тип пример
code string L1A (businessKey, x-id-source)
name i18n Радиометрически скорректированный
description i18n (детали обработки)
sort_order integer 20 (для упорядочивания L0→L1→L2)
status enum active / archived

data_format — формат файлов

поле тип пример
code string GEOTIFF (businessKey, x-id-source)
name i18n GeoTIFF
mime_type string image/tiff
file_extension string .tif
description i18n опц.
status enum active / archived

4. Что делает команда Альтум

Backend (altum-backend, FastAPI, единая точка интеграций)

  • backend/app/clients/ordinis.py — httpx-клиент с retry + cache:
    • retry 3× с exponential backoff
    • timeout 5 сек
    • кэш с TTL 5 мин per (dictionary, lang) (in-process LRU + ETag когда появится; пока без webhook → достаточно TTL)
    • на 401 — refresh service-account JWT через Keycloak
    • на 5xx/таймаут — stale cache + warn в лог. Не падать.
  • backend/app/routers/reference.py — единая точка для фронта:
    GET /api/reference/spacecraft
    GET /api/reference/spacecraft/{business_key}
    GET /api/reference/instrument
    GET /api/reference/mission
    GET /api/reference/processing_level
    GET /api/reference/data_format
    
    Каждый внутри дёргает Ordinis read-api с кэшом. Авторизация JWT-инжекта на стороне backend — фронт ничего не знает про Keycloak Ordinis.
  • Таблица orders получает новые колонки:
    spacecraft_business_key   VARCHAR(256)
    instrument_business_key   VARCHAR(256)
    processing_level_code     VARCHAR(256)
    data_format_code          VARCHAR(256)
    reference_snapshot        JSONB
    
  • Снэпшот на момент создания заказа — копия всех использованных справочных записей (для аудита: «через 2 года КА вывели из эксплуатации, но мы должны помнить какой был на момент заказа»):
    {
      "reference_snapshot": {
        "spacecraft": {"RESURS-P-3": {"version": 2, "data": {...}}},
        "instrument": {"GEOTON-L1": {"version": 1, "data": {...}}}
      },
      "captured_at": "2026-05-15T10:30:00Z"
    }
    

Frontend (TanStack Query)

const { data: spacecrafts } = useQuery({
  queryKey: ['reference', 'spacecraft', lang],
  queryFn: () => fetch(`/api/reference/spacecraft?lang=${lang}`).then(r => r.json()),
  staleTime: 5 * 60_000,
})

<Select options={spacecrafts.map(s => ({ value: s.businessKey, label: s.data.name }))} />
  • Skeleton при первой загрузке.
  • Fallback с retry-кнопкой при недоступности backend.
  • POST /api/orders — отправляет businessKey'и (не локальные id).
  • Каскадные селекты: instrument → отфильтрованы по выбранному spacecraft через instrument.spacecraft_codes; processing_level + data_format → по instrument.supported_level_codes / supported_format_codes.

TTL vs webhook

5 минут TTL достаточно для v1 — справочники меняются редко (новый КА — раз в месяцы). На v2, если потребуется realtime: подписка altum-backend через aiokafka на топик nsi.dictionary.changed (Ordinis опубликует) с payload {name, businessKey, action: created|updated|closed}. Не блокер.

CORS

Фронт ходит через altum-backend, прямой CORS не нужен. Для отладки добавим в allowlist:

  • https://altum.nstart.cloud
  • https://altum.k8s.264.nstart.cloud

5. Что делает команда Ordinis (todo)

  • Сидинг 4 справочников (mission, instrument, processing_level, data_format) согласно §3 в bundle cuod. Отдельный changelog для добавления схем + начальные записи.
  • Service account altum-backend в Keycloak realm altum с ролью ordinis-public. Передать ORDINIS_CLIENT_ID / ORDINIS_CLIENT_SECRET команде Альтума.
  • CORS allowlist для отладочных доменов Альтума на ingress.
  • Включить ORDINIS_AUTH_REQUIRED=true на staging когда altum-backend будет готов тестировать с m2m JWT.
  • Swagger URL read-api в docs/tech/api-integration.md (создать).
  • (v2) Kafka топик nsi.dictionary.changed с change events.

6. Acceptance criteria

  • Оператор НСИ заводит новый КА в Ordinis admin — через ≤5 минут он появляется в выпадашке заказа Альтума без редеплоя.
  • Оператор закрывает КА (PUT /records/{key}/close) — через ≤5 минут пропадает из выпадашки активных, но в существующих заказах (snapshot) всё видно.
  • Заказ съёмки PDF/выгрузка — берёт name КА из snapshot, не из live read-api.
  • При недоступности Ordinis Альтум продолжает работать со stale-кэшом, выводит баннер «справочники могут быть устаревшими».
  • Lang switching — оператор переключает на EN, КА называются «Resurs-P № 3».
  • Cascading select работает: выбираешь инструмент — список форматов и уровней обработки сужается.

7. Сроки

  • Ordinis: сидинг + Keycloak service account + CORS — 1 день после ответа на §5.
  • Альтум: BFF + 4 ref-эндпоинта + миграция БД (reference_snapshot) + UI с 4 каскадными селектами + тесты — 2-3 дня после готовности Ordinis.

Repo cross-refs:

  • Read-api routing: ordinis-read-api/
  • Auth/scope: ordinis-auth/.../{ScopeContext, SecurityConfig}.java
  • Bitemporal model: ordinis-domain/.../record/DictionaryRecord.java
  • Audit log: ordinis-rest-api/.../audit/AuditLogger.java (фиксирует все изменения справочников — Альтум по trace_id может проследить «когда оператор закрыл КА»).