feat(cuod-bundle): JSON Schema драфты для spacecraft/satellite_type/ground_station + auto-import

3 schema драфта в classpath:/bundles/cuod/:

- spacecraft.schema.json: КА с COSPAR designator, NORAD ID, multi-locale
  name/operator/mission, orbit params (type/semimajor/eccentricity/inclination/
  raan/aop/epoch), spectrum_bands (12 bands), launch/decay dates, status enum
- satellite_type.schema.json: классификация КА — code, multi-locale name/desc,
  mission_category enum (10 categories), mass_class, typical_orbit_classes,
  typical_resolution_m, typical_revisit_hours
- ground_station.schema.json: code, multi-locale name/location/operator,
  country (ISO 3166), lat/lon/elevation, antennas array (id, diameter,
  bands, polarizations, tx/rx_capable, G/T, EIRP), status

manifest.json: bundle 'cuod' v1.0.0, 3 dictionaries с supported_locales
[ru-RU, en-US] default ru-RU, scope PUBLIC

ordinis-cuod-bundle модуль:
- BundleManifest record + DictionaryEntry record (Jackson deserializer)
- CuodBundleProperties @ConfigurationProperties (auto-import flag,
  update-existing-schema flag, resource-root path)
- CuodBundleImporter @Service (proxified, @Transactional работает) — читает
  manifest + schemas, для каждого: findByName → если нет, создать definition
  + outbox event DefinitionCreated; если есть, проверить schema_version, при
  отличии и update_existing_schema=true — обновить + DefinitionUpdated outbox
- CuodBundleStartupRunner @Configuration с ApplicationRunner — триггерит
  importBundle при старте Spring Boot. Отделено от Importer чтобы Importer
  оставался proxified (@Transactional на importBundle vs lambda не работает)

ordinis-validation FIX:
- RecordValidator теперь учитывает required array родительской схемы.
  Отсутствие x-localized optional поля (например mission, operator) больше
  не считается ошибкой. Required x-localized поля по-прежнему обязательны.

E2E проверено:
- ORDINIS_BUNDLE_CUOD_AUTO_IMPORT=true → 3 справочника создались с правильными
  scope, locales, schema_version
- POST spacecraft с реальными Kanopus-V данными (orbit + spectrum_bands) → 201
- POST satellite_type с code='lower-case-bad' → 422 pattern error
- Idempotent: повторный старт пропускает existing без ошибок
This commit is contained in:
Zimin A.N.
2026-05-03 14:29:43 +03:00
parent 51fcc95818
commit ae69a7642b
10 changed files with 558 additions and 4 deletions
@@ -0,0 +1,105 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://ordinis.nstart.cloud/schemas/cuod/ground_station/1.0.0",
"title": "Ground Station",
"description": "Наземная станция приёма и/или управления КА. Геометрия (точка) пишется в отдельную колонку geometry, не дублируется в data.lat/lon.",
"type": "object",
"required": ["code", "name"],
"additionalProperties": false,
"properties": {
"code": {
"type": "string",
"description": "Уникальный код станции",
"pattern": "^[A-Z][A-Z0-9_-]{1,31}$"
},
"name": {
"type": "object",
"x-localized": true,
"patternProperties": {
"^[a-z]{2}-[A-Z]{2}$": { "type": "string", "minLength": 1 }
},
"required": ["ru-RU"]
},
"location": {
"type": "object",
"x-localized": true,
"description": "Текстовое описание местоположения (город, регион, страна)",
"patternProperties": {
"^[a-z]{2}-[A-Z]{2}$": { "type": "string" }
}
},
"country": {
"type": "string",
"description": "ISO 3166-1 alpha-2",
"pattern": "^[A-Z]{2}$"
},
"lat": {
"type": "number",
"description": "Широта (для удобства поиска без geometry; geometry — source of truth)",
"minimum": -90,
"maximum": 90
},
"lon": {
"type": "number",
"minimum": -180,
"maximum": 180
},
"elevation_m": {
"type": "number",
"description": "Высота над уровнем моря, м"
},
"elevation_min_deg": {
"type": "number",
"description": "Минимальный угол места, градусы (горизонт станции)",
"minimum": 0,
"maximum": 90
},
"antennas": {
"type": "array",
"description": "Антенные системы станции",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "diameter_m", "bands"],
"properties": {
"id": { "type": "string" },
"diameter_m": { "type": "number", "minimum": 0 },
"bands": {
"type": "array",
"items": {
"type": "string",
"enum": ["L_BAND", "S_BAND", "C_BAND", "X_BAND", "KU_BAND", "KA_BAND", "UHF", "VHF"]
},
"minItems": 1,
"uniqueItems": true
},
"polarizations": {
"type": "array",
"items": {
"type": "string",
"enum": ["LHCP", "RHCP", "H", "V"]
}
},
"tx_capable": { "type": "boolean", "default": false },
"rx_capable": { "type": "boolean", "default": true },
"g_t_db_per_k": {
"type": "number",
"description": "G/T (gain-to-noise-temperature ratio), дБ/К"
},
"eirp_dbw": { "type": "number" }
}
}
},
"operator": {
"type": "object",
"x-localized": true,
"patternProperties": {
"^[a-z]{2}-[A-Z]{2}$": { "type": "string" }
}
},
"status": {
"type": "string",
"enum": ["PLANNED", "OPERATIONAL", "MAINTENANCE", "DECOMMISSIONED"]
}
}
}
@@ -0,0 +1,34 @@
{
"bundle": "cuod",
"displayName": "ЦУОД ДЗЗ",
"description": "Bundle справочников для Центра Управления Обработкой Данных ДЗЗ. Anchor v1.",
"version": "1.0.0",
"supportedLocales": ["ru-RU", "en-US"],
"defaultLocale": "ru-RU",
"dictionaries": [
{
"name": "spacecraft",
"displayName": "Космические аппараты",
"description": "Каталог КА (космических аппаратов) ДЗЗ-миссий",
"scope": "PUBLIC",
"schemaResource": "spacecraft.schema.json",
"schemaVersion": "1.0.0"
},
{
"name": "satellite_type",
"displayName": "Типы КА",
"description": "Классификация типов космических аппаратов по миссиям и характеристикам",
"scope": "PUBLIC",
"schemaResource": "satellite_type.schema.json",
"schemaVersion": "1.0.0"
},
{
"name": "ground_station",
"displayName": "Наземные станции",
"description": "Каталог наземных пунктов приёма и управления",
"scope": "PUBLIC",
"schemaResource": "ground_station.schema.json",
"schemaVersion": "1.0.0"
}
]
}
@@ -0,0 +1,69 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://ordinis.nstart.cloud/schemas/cuod/satellite_type/1.0.0",
"title": "Satellite Type",
"description": "Тип КА — классификация по миссии и массовому классу. Используется как FK в spacecraft.satellite_type_code.",
"type": "object",
"required": ["code", "name", "mission_category"],
"additionalProperties": false,
"properties": {
"code": {
"type": "string",
"description": "Уникальный код типа",
"pattern": "^[A-Z][A-Z0-9_]{1,31}$"
},
"name": {
"type": "object",
"x-localized": true,
"patternProperties": {
"^[a-z]{2}-[A-Z]{2}$": { "type": "string", "minLength": 1 }
},
"required": ["ru-RU"]
},
"description": {
"type": "object",
"x-localized": true,
"patternProperties": {
"^[a-z]{2}-[A-Z]{2}$": { "type": "string" }
}
},
"mission_category": {
"type": "string",
"enum": [
"OPTICAL_HIRES",
"OPTICAL_MIDRES",
"OPTICAL_LOWRES",
"RADAR_SAR",
"HYPERSPECTRAL",
"THERMAL",
"METEO",
"ATMOSPHERIC",
"OCEAN",
"GEODESY"
]
},
"mass_class": {
"type": "string",
"description": "Массовый класс (по конвенциям ESA/NASA)",
"enum": ["FEMTO", "PICO", "NANO", "MICRO", "MINI", "SMALL", "MEDIUM", "LARGE"]
},
"typical_orbit_classes": {
"type": "array",
"items": {
"type": "string",
"enum": ["LEO", "MEO", "GEO", "HEO", "SSO"]
},
"uniqueItems": true
},
"typical_resolution_m": {
"type": "number",
"description": "Типичное пространственное разрешение, м",
"minimum": 0
},
"typical_revisit_hours": {
"type": "number",
"description": "Типичный период повторного покрытия, часов",
"minimum": 0
}
}
}
@@ -0,0 +1,99 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://ordinis.nstart.cloud/schemas/cuod/spacecraft/1.0.0",
"title": "Spacecraft (КА)",
"description": "Космический аппарат ДЗЗ. Идентифицируется designator'ом (международное обозначение COSPAR/NORAD ID).",
"type": "object",
"required": ["designator", "name", "satellite_type_code", "status"],
"additionalProperties": false,
"properties": {
"designator": {
"type": "string",
"description": "Международное обозначение (COSPAR designator), например 1998-067A",
"pattern": "^[0-9]{4}-[0-9]{3}[A-Z]+$"
},
"norad_id": {
"type": "integer",
"description": "NORAD catalog number",
"minimum": 1
},
"name": {
"type": "object",
"x-localized": true,
"description": "Наименование аппарата",
"patternProperties": {
"^[a-z]{2}-[A-Z]{2}$": { "type": "string", "minLength": 1 }
},
"required": ["ru-RU"]
},
"operator": {
"type": "object",
"x-localized": true,
"description": "Оператор/владелец КА",
"patternProperties": {
"^[a-z]{2}-[A-Z]{2}$": { "type": "string" }
}
},
"country": {
"type": "string",
"description": "ISO 3166-1 alpha-2 код страны-оператора",
"pattern": "^[A-Z]{2}$"
},
"satellite_type_code": {
"type": "string",
"description": "FK на satellite_type.code"
},
"mass_kg": {
"type": "number",
"description": "Масса КА на старте, кг",
"minimum": 0
},
"launch_date": {
"type": "string",
"format": "date"
},
"decay_date": {
"type": "string",
"format": "date",
"description": "Дата схода с орбиты (если применимо)"
},
"status": {
"type": "string",
"enum": ["PLANNED", "ACTIVE", "INACTIVE", "DECAYED", "LOST"]
},
"orbit": {
"type": "object",
"description": "Параметры орбиты на эпоху",
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": ["LEO", "MEO", "GEO", "HEO", "SSO", "OTHER"]
},
"semimajor_axis_km": { "type": "number", "minimum": 0 },
"eccentricity": { "type": "number", "minimum": 0, "maximum": 1 },
"inclination_deg": { "type": "number", "minimum": 0, "maximum": 180 },
"raan_deg": { "type": "number", "minimum": 0, "maximum": 360 },
"argument_of_perigee_deg": { "type": "number", "minimum": 0, "maximum": 360 },
"epoch": { "type": "string", "format": "date-time" }
}
},
"spectrum_bands": {
"type": "array",
"description": "Спектральные диапазоны полезной нагрузки",
"items": {
"type": "string",
"enum": ["VIS", "NIR", "SWIR", "MWIR", "LWIR", "TIR", "L_BAND", "S_BAND", "C_BAND", "X_BAND", "KU_BAND", "KA_BAND"]
},
"uniqueItems": true
},
"mission": {
"type": "object",
"x-localized": true,
"description": "Краткое описание миссии",
"patternProperties": {
"^[a-z]{2}-[A-Z]{2}$": { "type": "string" }
}
}
}
}