diff --git a/docs/integration/altum-imaging-order.md b/docs/integration/altum-imaging-order.md new file mode 100644 index 0000000..4f9dff2 --- /dev/null +++ b/docs/integration/altum-imaging-order.md @@ -0,0 +1,249 @@ +# Интеграция «Заказ съёмки» (Альтум) ↔ 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 ` от Keycloak (`auth.nstart.space/realms/nstart`). +- Альтум использует **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):** + +``` +GET /dictionaries + → список доступных справочников. + +GET /dictionaries/{name}/records?lang=ru&at=2026-05-04T12:00:00Z + → активные записи на момент `at` (default: now). + ?lang=ru/en — i18n поля приходят строкой на выбранном языке. + +GET /dictionaries/{name}/records/{businessKey}?lang=ru&at=... + → одна запись по businessKey. +``` + +**Пример:** + +```bash +curl https://ordinis.k8s.265.nstart.cloud/api/v1/dictionaries/spacecraft/records?lang=ru +``` + +```json +[ + { + "businessKey": "RESURS-P-3", + "validFrom": "2025-01-01T00:00:00+03:00", + "validTo": "9999-12-31T23:59:59+00:00", + "data": { + "code": "RESURS-P-3", + "name": "Ресурс-П № 3", + "norad_id": 41386, + "satellite_type": "OPTICAL", + "launched_at": "2016-03-13", + "operator": "ROSCOSMOS", + "status": "ACTIVE" + } + } +] +``` + +## 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 года КА вывели из эксплуатации, но мы должны + помнить какой был на момент заказа»): + ```json + { + "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) + +```ts +const { data: spacecrafts } = useQuery({ + queryKey: ['reference', 'spacecraft', lang], + queryFn: () => fetch(`/api/reference/spacecraft?lang=${lang}`).then(r => r.json()), + staleTime: 5 * 60_000, +}) + +