Files
mdm-ordinis/docs/integration/altum-imaging-order.md
T
Zimin A.N. 930df5b888 feat(api): Swagger UI + docs/integration для Альтума и других consumer'ов
Springdoc-openapi генерирует OpenAPI 3 spec из аннотаций контроллеров —
интеграторам не нужно поддерживать markdown-ТЗ синхронно с кодом.

- /swagger-ui.html (UI), /v3/api-docs (JSON), /v3/api-docs.yaml.
- OpenApiConfig: title/description/contact/servers (staging+prod+local),
  bearerAuth security scheme (Keycloak JWT).
- SecurityConfig: permitAll на swagger paths.

Документация:
- docs/integration/altum-imaging-order.md — ТЗ интеграции «Заказ съёмки».
  Согласованы: m2m service account через Keycloak, BFF в altum-backend
  (FastAPI), 4 справочника со схемами, cascading select через
  instrument.supported_*_codes, snapshot strategy для аудита.
- docs/tech/api-integration.md — общий гайд для интеграторов: auth flow,
  endpoints, кэш-стратегии, bitemporal модель, current dictionaries,
  cross-refs, best practices.

Скрипты codegen openapi-generator-cli работают off /v3/api-docs.
2026-05-04 15:45:09 +03:00

250 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Интеграция «Заказ съёмки» (Альтум) ↔ 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 (`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,
})
<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 `nstart` с ролью
`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 может проследить «когда оператор
закрыл КА»).