# Интеграция «Заказ съёмки» (Альтум) ↔ 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, })