Files
anal-front/docs/info/backend-endpoints-competence-map-analytics-from-layouts.md
2026-06-19 17:15:05 +03:00

1356 lines
42 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.
# Backend Endpoints Competence Map Analytics
Назначение: спецификация backend-контракта для страницы
`Все сервисы / Карта компетенций / Аналитическая панель`.
Документ нужен как задача для backend-разработки.
Цель: реализовать endpoint-ы, которые возвращают все данные аналитической панели
по инновационным объединениям:
- значения фильтров;
- верхние KPI-карточки;
- состав инновационных объединений;
- аналитику по ОЭЗ;
- аналитику по технопаркам;
- аналитику по промышленным кластерам;
- рейтинги субъектов РФ;
- топ отраслей;
- временную динамику;
- численность сотрудников.
## Принципы контракта
- аналитика страницы должна возвращаться одним page-level endpoint-ом;
- справочники фильтров должны возвращаться отдельным endpoint-ом;
- все значения должны возвращаться в raw-формате: number, string, boolean, null;
- проценты должны возвращаться числом `0..100`, а не долей `0..1`;
- даты должны возвращаться в ISO 8601 с timezone;
- каждый объект, который может использоваться для drilldown или ссылки, должен иметь стабильный `id`;
- все массивы должны возвращаться уже отсортированными;
- все KPI должны иметь стабильный `code`;
- все проценты должны иметь `numerator` и `denominator`, если знаменатель известен;
- `0` должен возвращаться как валидное значение, а не как `null`, строка или `—`;
- `null` допустим только для реально отсутствующего или неприменимого значения;
- backend должен обеспечивать консистентность KPI, таблиц и графиков внутри одного response.
Важное ограничение по числам в примерах:
- числовые значения в JSON-примерах взяты из референсных экранов и нужны для описания формы контракта;
- реальные знаменатели процентов должны быть рассчитаны по утверждённой бизнес-методике;
- если методика конкретного процента не утверждена, её нужно зафиксировать до реализации endpoint-а.
---
## Термины и правила подсчёта
### `association_type`
Тип инновационного объединения.
Допустимые значения:
- `all`: все типы объединений;
- `sez`: особые экономические зоны;
- `technoparks`: технопарки;
- `clusters`: промышленные кластеры.
### `associations_count`
Количество инновационных объединений выбранного типа после применения фильтров.
Примеры:
- для `sez` это количество ОЭЗ;
- для `technoparks` это количество технопарков;
- для `clusters` это количество промышленных кластеров;
- для `all` это сумма объединений всех типов.
### `opk_organizations_count`
Количество уникальных организаций ОПК после применения фильтров.
Одна организация ОПК может входить в несколько объединений. Поэтому это поле не должно
считаться как простая сумма участий.
### `opk_memberships_count`
Количество связей `организация ОПК x инновационное объединение`.
Это поле может быть больше `opk_organizations_count`.
### `residents_count`
Количество резидентов.
Используется для ОЭЗ и технопарков, если источник данных различает резидентов и участников.
### `participants_count`
Количество участников.
Используется для промышленных кластеров.
### `employees_count`
Численность сотрудников.
Для показателей сотрудников нужно явно различать:
- сотрудников организаций ОПК;
- сотрудников организаций ОПК-резидентов;
- сотрудников всех резидентов;
- сотрудников участников кластера;
- сотрудников выбранного типа объединений.
---
## Нужно реализовать
## `competence-map analytics filters`
### `GET /api/v1/competence-map/analytics/filters/`
Описание:
- возвращает справочники для фильтров аналитической панели;
- учитывает права пользователя;
- учитывает текущие выбранные фильтры, если они переданы в query params;
- не возвращает аналитические KPI, рейтинги и графики.
Request:
```text
GET /api/v1/competence-map/analytics/filters/?association_type=all&federal_district_id=&region_id=&industry_id=&integrated_structure_id=&organization_id=
```
Query params:
- `association_type`: `all | sez | technoparks | clusters`, optional, default `all`;
- `federal_district_id`: string, optional;
- `region_id`: string, optional;
- `industry_id`: string, optional;
- `integrated_structure_id`: string, optional;
- `organization_id`: string, optional.
Response:
```json
{
"generated_at": "2026-06-19T15:20:00+03:00",
"applied_filters": {
"association_type": "all",
"federal_district_id": null,
"region_id": null,
"industry_id": null,
"integrated_structure_id": null,
"organization_id": null
},
"association_types": [
{
"id": "all",
"label": "все",
"associations_count": 176,
"disabled": false
},
{
"id": "sez",
"label": "ОЭЗ",
"associations_count": 17,
"disabled": false
},
{
"id": "technoparks",
"label": "технопарки",
"associations_count": 35,
"disabled": false
},
{
"id": "clusters",
"label": "кластеры",
"associations_count": 124,
"disabled": false
}
],
"federal_districts": [
{
"id": "central",
"label": "Центральный федеральный округ",
"associations_count": 58,
"disabled": false
}
],
"regions": [
{
"id": "moscow-oblast",
"federal_district_id": "central",
"label": "Московская область",
"associations_count": 41,
"disabled": false
}
],
"industries": [
{
"id": "aviation",
"label": "Авиационная промышленность",
"opk_organizations_count": 180,
"disabled": false
}
],
"integrated_structures": [
{
"id": "rostec",
"label": "ГК Ростех",
"opk_organizations_count": 42,
"disabled": false
}
],
"organizations": [
{
"id": "org-001",
"label": "АО Пример",
"inn": "7700000000",
"ogrn": "1027700000000",
"association_memberships_count": 1,
"disabled": false
}
]
}
```
### Требования к `analytics/filters/`
#### Поле `generated_at`
Требования:
- ISO 8601 с timezone;
- обязательное поле;
- единое время генерации для всего payload.
#### Блок `applied_filters`
Требования:
- возвращает фактически применённые фильтры;
- все отсутствующие значения возвращаются как `null`;
- `association_type` всегда заполнен.
#### Массив `association_types`
Требования:
- содержит все доступные пользователю типы объединений;
- `id` должен быть одним из `all`, `sez`, `technoparks`, `clusters`;
- `label` обязательный;
- `associations_count` integer `>= 0`;
- `disabled` boolean.
#### Массивы справочников
Для массивов `federal_districts`, `regions`, `industries`, `integrated_structures`, `organizations`:
- `id` обязательный;
- `label` обязательный;
- `disabled` обязательный;
- count-поля должны быть integer `>= 0`;
- если по текущим фильтрам список пустой, возвращается пустой массив.
---
## `competence-map analytics`
### `GET /api/v1/competence-map/analytics/`
Описание:
- возвращает всю аналитику страницы для выбранного `association_type`;
- применяет все переданные фильтры;
- возвращает KPI, основной аналитический блок, рейтинги, топ отраслей, временную динамику и дополнительные показатели;
- не требует дополнительных аналитических endpoint-ов для отдельных виджетов.
Request:
```text
GET /api/v1/competence-map/analytics/?association_type=all&federal_district_id=&region_id=&industry_id=&integrated_structure_id=&organization_id=
```
Query params:
- `association_type`: `all | sez | technoparks | clusters`, optional, default `all`;
- `federal_district_id`: string, optional;
- `region_id`: string, optional;
- `industry_id`: string, optional;
- `integrated_structure_id`: string, optional;
- `organization_id`: string, optional.
Общий response envelope:
```json
{
"generated_at": "2026-06-19T15:20:00+03:00",
"data_actual_at": "2026-06-19T00:00:00+03:00",
"association_type": "all",
"applied_filters": {
"association_type": "all",
"federal_district_id": null,
"region_id": null,
"industry_id": null,
"integrated_structure_id": null,
"organization_id": null
},
"kpis": [],
"main_block": {},
"region_ranking": {},
"top_industries": {},
"trend_block": null,
"employee_stats": null
}
```
---
## Response для `association_type=all`
```json
{
"generated_at": "2026-06-19T15:20:00+03:00",
"data_actual_at": "2026-06-19T00:00:00+03:00",
"association_type": "all",
"applied_filters": {
"association_type": "all",
"federal_district_id": null,
"region_id": null,
"industry_id": null,
"integrated_structure_id": null,
"organization_id": null
},
"kpis": [
{
"code": "innovation_associations_count",
"label": "Количество инновационных объединений",
"value": 176,
"value_type": "integer",
"numerator": 176,
"denominator": null
},
{
"code": "opk_organizations_count",
"label": "Количество организаций ОПК",
"value": 605,
"value_type": "integer",
"numerator": 605,
"denominator": null
},
{
"code": "opk_organizations_in_associations_share_percent",
"label": "Составляют организации ОПК в инновационных объединениях",
"value": 20.0,
"value_type": "percent",
"numerator": 605,
"denominator": 3025
},
{
"code": "associations_with_opk_share_percent",
"label": "Процент инновационных объединений, в которых участвуют организации ОПК",
"value": 42.0,
"value_type": "percent",
"numerator": 176,
"denominator": 419
}
],
"main_block": {
"type": "all_associations_composition",
"title": "Организации ОПК в инновационных объединениях",
"total_associations_count": 176,
"segments": [
{
"association_type": "sez",
"label": "особые экономические зоны",
"associations_count": 17,
"opk_organizations_count": 64,
"opk_memberships_count": 64
},
{
"association_type": "technoparks",
"label": "технопарки",
"associations_count": 35,
"opk_organizations_count": 66,
"opk_memberships_count": 66
},
{
"association_type": "clusters",
"label": "промышленные кластеры",
"associations_count": 124,
"opk_organizations_count": 517,
"opk_memberships_count": 517
}
]
},
"region_ranking": {
"title": "Рейтинг субъектов РФ по участию организаций ОПК в инновационных объединениях",
"sort": {
"field": "total_opk_organizations_count",
"direction": "desc"
},
"rows": [
{
"rank": 1,
"region_id": "moscow-oblast",
"region_name": "Московская область",
"cluster_opk_organizations_count": 41,
"technopark_opk_organizations_count": 41,
"sez_opk_organizations_count": 63,
"total_opk_organizations_count": 145
},
{
"rank": 2,
"region_id": "moscow",
"region_name": "г. Москва",
"cluster_opk_organizations_count": 28,
"technopark_opk_organizations_count": 28,
"sez_opk_organizations_count": 32,
"total_opk_organizations_count": 88
},
{
"rank": 3,
"region_id": "chelyabinsk-oblast",
"region_name": "Челябинская область",
"cluster_opk_organizations_count": 23,
"technopark_opk_organizations_count": 23,
"sez_opk_organizations_count": 46,
"total_opk_organizations_count": 92
}
]
},
"top_industries": {
"title": "Топ отраслей по количеству организаций ОПК, участвующих в инновационных объединениях",
"mode": "stacked_by_association_type",
"association_type": null,
"max_value": 180,
"rows": [
{
"industry_id": "aviation",
"industry_name": "Авиационная промышленность",
"total_opk_organizations_count": 180,
"cluster_opk_organizations_count": 84,
"technopark_opk_organizations_count": 82,
"sez_opk_organizations_count": 14
},
{
"industry_id": "conventional-weapons",
"industry_name": "Промышленность обычных вооружений",
"total_opk_organizations_count": 124,
"cluster_opk_organizations_count": 72,
"technopark_opk_organizations_count": 38,
"sez_opk_organizations_count": 14
},
{
"industry_id": "ammunition-and-special-chemistry",
"industry_name": "Промышленность боеприпасов и спецхимии",
"total_opk_organizations_count": 110,
"cluster_opk_organizations_count": 45,
"technopark_opk_organizations_count": 55,
"sez_opk_organizations_count": 10
}
]
},
"trend_block": {
"type": "opk_participation_by_year",
"title": "Участие организаций ОПК в инновационных объединениях",
"points": [
{
"year": 2014,
"cluster_opk_organizations_count": 24,
"technopark_opk_organizations_count": 7,
"sez_opk_organizations_count": 5,
"total_opk_organizations_count": 36
},
{
"year": 2016,
"cluster_opk_organizations_count": 42,
"technopark_opk_organizations_count": 9,
"sez_opk_organizations_count": 6,
"total_opk_organizations_count": 57
},
{
"year": 2018,
"cluster_opk_organizations_count": 54,
"technopark_opk_organizations_count": 11,
"sez_opk_organizations_count": 8,
"total_opk_organizations_count": 73
},
{
"year": 2020,
"cluster_opk_organizations_count": 58,
"technopark_opk_organizations_count": 16,
"sez_opk_organizations_count": 10,
"total_opk_organizations_count": 84
},
{
"year": 2022,
"cluster_opk_organizations_count": 66,
"technopark_opk_organizations_count": 18,
"sez_opk_organizations_count": 12,
"total_opk_organizations_count": 96
},
{
"year": 2024,
"cluster_opk_organizations_count": 80,
"technopark_opk_organizations_count": 22,
"sez_opk_organizations_count": 14,
"total_opk_organizations_count": 116
}
]
},
"employee_stats": null
}
```
---
## Response для `association_type=sez`
```json
{
"generated_at": "2026-06-19T15:20:00+03:00",
"data_actual_at": "2026-06-19T00:00:00+03:00",
"association_type": "sez",
"applied_filters": {
"association_type": "sez",
"federal_district_id": null,
"region_id": null,
"industry_id": null,
"integrated_structure_id": null,
"organization_id": null
},
"kpis": [
{
"code": "sez_count",
"label": "Количество ОЭЗ",
"value": 17,
"value_type": "integer",
"numerator": 17,
"denominator": null
},
{
"code": "sez_with_opk_residents_share_percent",
"label": "Процент ОЭЗ, в числе резидентов которых есть организации ОПК",
"value": 32.0,
"value_type": "percent",
"numerator": 17,
"denominator": 53
},
{
"code": "opk_resident_organizations_count",
"label": "Количество организаций ОПК-резидентов ОЭЗ",
"value": 64,
"value_type": "integer",
"numerator": 64,
"denominator": null
},
{
"code": "opk_organizations_in_sez_share_percent",
"label": "Составляют организации ОПК в ОЭЗ",
"value": 15.0,
"value_type": "percent",
"numerator": 64,
"denominator": 427
}
],
"main_block": {
"type": "sez_types_and_priority_directions",
"sez_types": {
"title": "Типы ОЭЗ",
"total_sez_count": 17,
"segments": [
{
"sez_type": "industrial_production",
"label": "промышленно-производственные ОЭЗ",
"sez_count": 8,
"opk_organizations_count": 31
},
{
"sez_type": "technology_implementation",
"label": "технико-внедренческие ОЭЗ",
"sez_count": 7,
"opk_organizations_count": 27
},
{
"sez_type": "port",
"label": "портовые ОЭЗ",
"sez_count": 2,
"opk_organizations_count": 6
}
]
},
"priority_directions": {
"title": "Приоритетные направления деятельности ОЭЗ",
"sort": {
"field": "sez_count",
"direction": "desc"
},
"rows": [
{
"rank": 1,
"direction_id": "aircraft-maintenance",
"direction_name": "Обслуживание и ремонт воздушных судов",
"sez_count": 3
},
{
"rank": 2,
"direction_id": "ict",
"direction_name": "Информационно-коммуникационные технологии",
"sez_count": 4
},
{
"rank": 3,
"direction_id": "wood-processing",
"direction_name": "Лесопереработка",
"sez_count": 2
}
]
}
},
"region_ranking": {
"title": "Рейтинг субъектов РФ по участию организаций ОПК в ОЭЗ",
"sort": {
"field": "opk_organizations_count",
"direction": "desc"
},
"rows": [
{
"rank": 1,
"region_id": "moscow-oblast",
"region_name": "Московская область",
"sez_count": 41,
"opk_organizations_count": 41
},
{
"rank": 2,
"region_id": "moscow",
"region_name": "г. Москва",
"sez_count": 28,
"opk_organizations_count": 28
}
]
},
"top_industries": {
"title": "Топ отраслей по количеству организаций ОПК, участвующих в ОЭЗ",
"mode": "single_association_type",
"association_type": "sez",
"max_value": 180,
"rows": [
{
"industry_id": "aviation",
"industry_name": "Авиационная промышленность",
"opk_organizations_count": 180
},
{
"industry_id": "conventional-weapons",
"industry_name": "Промышленность обычных вооружений",
"opk_organizations_count": 124
}
]
},
"trend_block": null,
"employee_stats": {
"items": [
{
"code": "opk_resident_employees_count",
"label": "Численность сотрудников организаций ОПК-резидентов ОЭЗ",
"value": 1678,
"value_type": "integer",
"numerator": 1678,
"denominator": null
},
{
"code": "sez_employees_count",
"label": "Численность сотрудников ОЭЗ",
"value": 5235,
"value_type": "integer",
"numerator": 5235,
"denominator": null
}
]
}
}
```
---
## Response для `association_type=technoparks`
```json
{
"generated_at": "2026-06-19T15:20:00+03:00",
"data_actual_at": "2026-06-19T00:00:00+03:00",
"association_type": "technoparks",
"applied_filters": {
"association_type": "technoparks",
"federal_district_id": null,
"region_id": null,
"industry_id": null,
"integrated_structure_id": null,
"organization_id": null
},
"kpis": [
{
"code": "technoparks_with_opk_residents_count",
"label": "Количество технопарков, в числе резидентов которых есть организации ОПК",
"value": 35,
"value_type": "integer",
"numerator": 35,
"denominator": null
},
{
"code": "opk_organizations_in_technoparks_share_percent",
"label": "Доля организаций ОПК в технопарках",
"value": 10.0,
"value_type": "percent",
"numerator": 66,
"denominator": 660
},
{
"code": "opk_organizations_in_technoparks_count",
"label": "Количество организаций ОПК в технопарках",
"value": 66,
"value_type": "integer",
"numerator": 66,
"denominator": null
},
{
"code": "technoparks_with_opk_residents_share_percent",
"label": "Доля технопарков, в числе резидентов которых есть организации ОПК",
"value": 21.0,
"value_type": "percent",
"numerator": 35,
"denominator": 167
}
],
"main_block": {
"type": "technopark_area_distribution",
"title": "Распределение технопарков по площадям",
"total_technoparks_count": 35,
"area_segments": [
{
"area_segment": "large",
"label": "крупные",
"area_range_label": "150-200",
"technoparks_count": 12
},
{
"area_segment": "medium",
"label": "средние",
"area_range_label": "50-150",
"technoparks_count": 14
},
{
"area_segment": "small",
"label": "мелкие",
"area_range_label": "0-50",
"technoparks_count": 9
}
],
"technoparks": {
"sort": {
"field": "area_value",
"direction": "desc"
},
"rows": [
{
"rank": 1,
"technopark_id": "technopark-001",
"technopark_name": "Индустриальный технопарк Пример",
"region_id": "moscow-oblast",
"region_name": "Московская область",
"area_value": 41,
"area_unit": "ha"
},
{
"rank": 2,
"technopark_id": "technopark-002",
"technopark_name": "Технопарк Развитие",
"region_id": "moscow",
"region_name": "г. Москва",
"area_value": 28,
"area_unit": "ha"
}
]
}
},
"region_ranking": {
"title": "Рейтинг субъектов РФ по участию организаций ОПК в технопарках",
"sort": {
"field": "opk_organizations_count",
"direction": "desc"
},
"rows": [
{
"rank": 1,
"region_id": "moscow-oblast",
"region_name": "Московская область",
"technoparks_count": 41,
"opk_organizations_count": 41
},
{
"rank": 2,
"region_id": "moscow",
"region_name": "г. Москва",
"technoparks_count": 28,
"opk_organizations_count": 28
}
]
},
"top_industries": {
"title": "Топ отраслей по количеству организаций ОПК, участвующих в технопарках",
"mode": "single_association_type",
"association_type": "technoparks",
"max_value": 180,
"rows": [
{
"industry_id": "aviation",
"industry_name": "Авиационная промышленность",
"opk_organizations_count": 180
},
{
"industry_id": "conventional-weapons",
"industry_name": "Промышленность обычных вооружений",
"opk_organizations_count": 124
}
]
},
"trend_block": null,
"employee_stats": {
"items": [
{
"code": "opk_resident_employees_count",
"label": "Численность сотрудников организаций ОПК-резидентов технопарков",
"value": 1678,
"value_type": "integer",
"numerator": 1678,
"denominator": null
},
{
"code": "opk_resident_employees_share_percent",
"label": "Доля сотрудников организаций ОПК-резидентов технопарков",
"value": 35.0,
"value_type": "percent",
"numerator": 1678,
"denominator": 4794
}
]
}
}
```
---
## Response для `association_type=clusters`
```json
{
"generated_at": "2026-06-19T15:20:00+03:00",
"data_actual_at": "2026-06-19T00:00:00+03:00",
"association_type": "clusters",
"applied_filters": {
"association_type": "clusters",
"federal_district_id": null,
"region_id": null,
"industry_id": null,
"integrated_structure_id": null,
"organization_id": null
},
"kpis": [
{
"code": "clusters_with_opk_participants_count",
"label": "Количество кластеров, в числе участников которых есть организации ОПК",
"value": 124,
"value_type": "integer",
"numerator": 124,
"denominator": null
},
{
"code": "clusters_with_opk_participants_share_percent",
"label": "Процент кластеров, в числе участников которых есть организации ОПК",
"value": 62.0,
"value_type": "percent",
"numerator": 124,
"denominator": 200
},
{
"code": "opk_organizations_in_clusters_count",
"label": "Количество организаций ОПК, участвующих в кластерах",
"value": 517,
"value_type": "integer",
"numerator": 517,
"denominator": null
},
{
"code": "opk_organizations_in_clusters_share_percent",
"label": "Составляют организации ОПК в кластерах",
"value": 15.0,
"value_type": "percent",
"numerator": 517,
"denominator": 3447
}
],
"main_block": {
"type": "cluster_specializations",
"title": "Специализации промышленных кластеров",
"specializations": {
"sort": {
"field": "clusters_count",
"direction": "desc"
},
"rows": [
{
"rank": 1,
"specialization_id": "aircraft-maintenance",
"specialization_name": "Обслуживание и ремонт воздушных судов",
"clusters_count": 3
},
{
"rank": 2,
"specialization_id": "ict",
"specialization_name": "Информационно-коммуникационные технологии",
"clusters_count": 4
},
{
"rank": 3,
"specialization_id": "wood-processing",
"specialization_name": "Лесопереработка",
"clusters_count": 2
}
]
}
},
"region_ranking": {
"title": "Рейтинг субъектов РФ по участию организаций ОПК в кластерах",
"sort": {
"field": "opk_organizations_count",
"direction": "desc"
},
"rows": [
{
"rank": 1,
"region_id": "moscow-oblast",
"region_name": "Московская область",
"clusters_count": 41,
"opk_organizations_count": 41
},
{
"rank": 2,
"region_id": "moscow",
"region_name": "г. Москва",
"clusters_count": 28,
"opk_organizations_count": 28
}
]
},
"top_industries": {
"title": "Топ отраслей по количеству организаций ОПК, участвующих в промышленных кластерах",
"mode": "single_association_type",
"association_type": "clusters",
"max_value": 180,
"rows": [
{
"industry_id": "aviation",
"industry_name": "Авиационная промышленность",
"opk_organizations_count": 180
},
{
"industry_id": "conventional-weapons",
"industry_name": "Промышленность обычных вооружений",
"opk_organizations_count": 124
}
]
},
"trend_block": {
"type": "cluster_creation_dynamics",
"title": "Динамика создания промышленных кластеров 2020-2024 гг.",
"points": [
{
"year": 2020,
"clusters_count": 15,
"growth_percent": null
},
{
"year": 2021,
"clusters_count": 24,
"growth_percent": 60.0
},
{
"year": 2022,
"clusters_count": 47,
"growth_percent": 95.8
},
{
"year": 2023,
"clusters_count": 66,
"growth_percent": 40.4
},
{
"year": 2024,
"clusters_count": 124,
"growth_percent": 87.9
}
]
},
"employee_stats": null
}
```
---
## Требования к общим полям analytics response
### Поле `generated_at`
Требования:
- ISO 8601 с timezone;
- обязательное;
- показывает время формирования response.
### Поле `data_actual_at`
Требования:
- ISO 8601 с timezone;
- обязательное;
- показывает дату и время актуальности аналитических данных.
### Поле `association_type`
Требования:
- обязательное;
- должно совпадать с фактически применённым режимом;
- допустимые значения: `all`, `sez`, `technoparks`, `clusters`.
### Блок `applied_filters`
Требования:
- обязательный;
- содержит нормализованные значения применённых фильтров;
- отсутствующие фильтры возвращаются как `null`;
- `association_type` всегда заполнен.
### Массив `kpis`
Каждый объект массива - одна KPI-метрика.
Поля:
- `code`: стабильный системный идентификатор метрики;
- `label`: человекочитаемое название метрики;
- `value`: raw numeric value;
- `value_type`: `integer | percent | decimal`;
- `numerator`: числитель, если применимо;
- `denominator`: знаменатель, если применимо.
Требования:
- все поля, кроме `numerator` и `denominator`, обязательны;
- `value` должен быть number, не string;
- `0` допустим как валидное значение;
- для `value_type = percent` значение возвращается в диапазоне `0..100`;
- для percent-метрик `numerator` и `denominator` обязательны, если знаменатель известен;
- порядок массива должен соответствовать порядку карточек на странице.
### Блок `main_block`
Полиморфный блок. Структура зависит от `association_type`.
Допустимые `type`:
- `all_associations_composition`;
- `sez_types_and_priority_directions`;
- `technopark_area_distribution`;
- `cluster_specializations`.
Требования:
- `type` обязательный;
- `title` обязательный;
- структура должна соответствовать текущему `association_type`;
- массивы внутри блока должны быть отсортированы.
### Блок `region_ranking`
Поля:
- `title`;
- `sort`;
- `rows`.
Требования:
- `title` обязательный;
- `sort.field` обязательный;
- `sort.direction` должен быть `asc | desc`;
- `rows` может быть пустым массивом;
- `rank` возвращается в каждой строке;
- `region_id` обязательный;
- `region_name` обязательный;
- count-поля должны иметь однозначные имена.
Нельзя использовать неоднозначное поле:
```json
{
"sez_count": 63
}
```
если значение означает количество организаций ОПК.
Нужно использовать одно из точных полей:
```json
{
"sez_opk_organizations_count": 63
}
```
или:
```json
{
"sez_opk_memberships_count": 63
}
```
### Блок `top_industries`
Поля:
- `title`;
- `mode`;
- `association_type`;
- `max_value`;
- `rows`.
Требования:
- `mode` должен быть `stacked_by_association_type | single_association_type`;
- для `association_type = all` должен использоваться `mode = stacked_by_association_type`;
- для `association_type = sez | technoparks | clusters` должен использоваться `mode = single_association_type`;
- `max_value` должен быть number `>= 0`;
- `max_value` должен быть больше или равен максимальному значению в `rows`;
- `rows` должны быть отсортированы по убыванию основного значения.
### Блок `trend_block`
Может быть `null`.
Допустимые `type`:
- `opk_participation_by_year`;
- `cluster_creation_dynamics`.
Требования:
- если блок не применим для режима, возвращается `null`;
- `points` должны быть отсортированы по `year` по возрастанию;
- `year` integer;
- `growth_percent` может быть `null` для первого года;
- проценты роста возвращаются в диапазоне `0..100+`, если рост больше 100% возможен.
### Блок `employee_stats`
Может быть `null`.
Поля:
- `items`.
Требования:
- если блок не применим для режима, возвращается `null`;
- каждый item должен иметь `code`, `label`, `value`, `value_type`;
- для percent-метрик должны быть `numerator` и `denominator`, если знаменатель известен;
- подписи должны соответствовать текущему `association_type`.
---
## Порядок массивов
### `kpis`
Порядок должен соответствовать порядку карточек на странице.
### `main_block.segments`
Для `all_associations_composition` порядок:
1. `sez`
2. `technoparks`
3. `clusters`
### `region_ranking.rows`
Порядок сортировки по умолчанию:
1. основной показатель по убыванию;
2. при равенстве - `opk_organizations_count` по убыванию;
3. при равенстве - `region_name` по возрастанию (`ru-RU`).
### `top_industries.rows`
Порядок сортировки:
1. основной показатель по убыванию;
2. при равенстве - `industry_name` по возрастанию (`ru-RU`).
### `trend_block.points`
Порядок сортировки:
1. `year` по возрастанию.
---
## Проверки консистентности
Backend должен гарантировать:
- `association_type` в корне response совпадает с `applied_filters.association_type`;
- `kpis[*].value` согласованы с соответствующими блоками аналитики;
- сумма `main_block.segments[*].associations_count` для `all` равна `total_associations_count`;
- `top_industries.max_value` не меньше максимального значения в `top_industries.rows`;
- `rank` в рейтингах не дублируется, если не используется специальная tie-ranking методика;
- все `id` стабильны между запросами;
- все count-поля возвращаются как integer `>= 0`;
- все percent-поля возвращаются как number;
- пустые данные возвращаются пустыми массивами, а не отсутствующими полями.
---
## Успешный пустой ответ
Если по фильтрам данных нет, возвращается `200 OK`.
Пример:
```json
{
"generated_at": "2026-06-19T15:20:00+03:00",
"data_actual_at": "2026-06-19T00:00:00+03:00",
"association_type": "clusters",
"applied_filters": {
"association_type": "clusters",
"federal_district_id": "unknown",
"region_id": null,
"industry_id": null,
"integrated_structure_id": null,
"organization_id": null
},
"kpis": [],
"main_block": {
"type": "cluster_specializations",
"title": "Специализации промышленных кластеров",
"specializations": {
"sort": {
"field": "clusters_count",
"direction": "desc"
},
"rows": []
}
},
"region_ranking": {
"title": "Рейтинг субъектов РФ по участию организаций ОПК в кластерах",
"sort": {
"field": "opk_organizations_count",
"direction": "desc"
},
"rows": []
},
"top_industries": {
"title": "Топ отраслей по количеству организаций ОПК, участвующих в промышленных кластерах",
"mode": "single_association_type",
"association_type": "clusters",
"max_value": 0,
"rows": []
},
"trend_block": null,
"employee_stats": null
}
```
---
## Ошибка валидации query params
Для некорректного `association_type`:
HTTP status:
```text
400 Bad Request
```
Response:
```json
{
"error": {
"code": "invalid_association_type",
"message": "Unsupported association_type",
"details": {
"received_value": "parks",
"allowed_values": ["all", "sez", "technoparks", "clusters"]
}
}
}
```
Для несуществующего фильтра:
HTTP status:
```text
400 Bad Request
```
Response:
```json
{
"error": {
"code": "invalid_filter_value",
"message": "Unknown filter value",
"details": {
"field": "region_id",
"received_value": "unknown"
}
}
}
```
---
## Ожидаемая схема запросов
При загрузке страницы:
```text
GET /api/v1/competence-map/analytics/filters/
GET /api/v1/competence-map/analytics/?association_type=all
```
При смене типа объединения:
```text
GET /api/v1/competence-map/analytics/filters/?association_type=clusters
GET /api/v1/competence-map/analytics/?association_type=clusters
```
При применении нескольких фильтров:
```text
GET /api/v1/competence-map/analytics/filters/?association_type=technoparks&federal_district_id=central&region_id=moscow-oblast
GET /api/v1/competence-map/analytics/?association_type=technoparks&federal_district_id=central&region_id=moscow-oblast
```