1356 lines
42 KiB
Markdown
1356 lines
42 KiB
Markdown
# 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=®ion_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=®ion_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®ion_id=moscow-oblast
|
||
GET /api/v1/competence-map/analytics/?association_type=technoparks&federal_district_id=central®ion_id=moscow-oblast
|
||
```
|