# Интеграция «Заказ съёмки» (Альтум) ↔ 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):** > ⚠️ **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 прямо сейчас):** ```bash curl https://ordinis.k8s.265.nstart.cloud/api/v1/spacecraft/records?lang=ru-RU ``` ```json [ { "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 года КА вывели из эксплуатации, но мы должны помнить какой был на момент заказа»): ```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, })