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

42 KiB
Raw Blame History

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:

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:

{
  "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:

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:

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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-поля должны иметь однозначные имена.

Нельзя использовать неоднозначное поле:

{
  "sez_count": 63
}

если значение означает количество организаций ОПК.

Нужно использовать одно из точных полей:

{
  "sez_opk_organizations_count": 63
}

или:

{
  "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.

Пример:

{
  "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:

400 Bad Request

Response:

{
  "error": {
    "code": "invalid_association_type",
    "message": "Unsupported association_type",
    "details": {
      "received_value": "parks",
      "allowed_values": ["all", "sez", "technoparks", "clusters"]
    }
  }
}

Для несуществующего фильтра:

HTTP status:

400 Bad Request

Response:

{
  "error": {
    "code": "invalid_filter_value",
    "message": "Unknown filter value",
    "details": {
      "field": "region_id",
      "received_value": "unknown"
    }
  }
}

Ожидаемая схема запросов

При загрузке страницы:

GET /api/v1/competence-map/analytics/filters/
GET /api/v1/competence-map/analytics/?association_type=all

При смене типа объединения:

GET /api/v1/competence-map/analytics/filters/?association_type=clusters
GET /api/v1/competence-map/analytics/?association_type=clusters

При применении нескольких фильтров:

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