91 KiB
Backend Endpoints Competence Map Analytics
Назначение: спецификация backend-контракта для страницы
Все сервисы / Карта компетенций / Аналитическая панель.
Документ нужен как задача для backend-разработки. Цель: реализовать endpoint-ы, которые возвращают все данные аналитической панели по инновационным объединениям:
- значения фильтров;
- верхние KPI-карточки;
- состав инновационных объединений;
- аналитику по ОЭЗ;
- аналитику по технопаркам;
- аналитику по промышленным кластерам;
- рейтинги субъектов РФ;
- топ отраслей;
- временную динамику;
- численность сотрудников.
Принципы контракта
- аналитика страницы должна возвращаться одним page-level endpoint-ом;
- справочники фильтров должны возвращаться отдельным endpoint-ом;
- контракт описывает серверное состояние страницы, а не конкретную визуальную реализацию UI;
- frontend может локально решать, показывать
association_typeкак select или tabs, какие иконки использовать, как располагать карточки, как рисовать donut/bar/line charts; - backend должен возвращать все данные, необходимые для всех серверно значимых состояний страницы: значения фильтров, применённые фильтры, KPI, строки таблиц, точки графиков, числители/знаменатели, ранги, id сущностей и значения для drilldown;
- все значения должны возвращаться в 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-а.
- если число на референсном экране противоречит смыслу поля, backend-контракт должен сохранять корректную семантику поля, а не повторять ошибочное или placeholder-значение макета.
REST и HTTP правила
Модель ресурсов
Endpoint-ы описывают серверные представления ресурсов, а не действия UI.
В рамках этой страницы есть два ресурса:
/api/v1/competence-map/analytics/filters/- справочники и доступные значения фильтров;/api/v1/competence-map/analytics/- аналитический snapshot страницы для выбранных фильтров.
Требования:
- не добавлять action-style endpoint-ы вида
/getAnalytics,/loadFilters,/calculateDashboard; - не дробить текущую страницу на endpoint-ы по виджетам, пока нет отдельной серверной причины: экспорт, серверная пагинация большой таблицы, drilldown, async job;
association_typeявляется query-параметром одного аналитического ресурса, а не отдельным URI-сегментом;- path должен оставаться стабильным в рамках
/api/v1; breaking changes требуют новой версии API.
HTTP методы
Для обоих endpoint-ов используется только GET.
Причина:
- запросы только читают данные;
- запросы не меняют серверное состояние;
- результат полностью определяется URI, query params, правами пользователя и актуальностью данных.
GET request body не используется. Все параметры выбора представления передаются только через query params и headers.
POST для этих экранов не нужен. Его можно вводить только для отдельных будущих сценариев:
- создание export job;
- сохранение пользовательского набора фильтров;
- тяжёлый search/report request, который не помещается в URL и имеет отдельный жизненный цикл.
Media type
Request:
Accept: application/json
Успешный response:
Content-Type: application/json; charset=utf-8
Content-Language: ru-RU
Error response:
Content-Type: application/problem+json; charset=utf-8
Query params
Для /analytics/filters/ и /analytics/ используется единый набор query params:
association_type:all | sez | technoparks | clusters, optional, defaultall;federal_district_id: string, optional;region_id: string, optional;industry_id: string, optional;integrated_structure_id: string, optional;organization_id: string, optional.
Правила:
- если фильтр не выбран, параметр лучше не передавать;
- если frontend передал пустую строку, backend должен нормализовать её в
nullвapplied_filters; - неизвестные query params должны возвращать
400 Bad Request, чтобы опечатки не игнорировались молча; - повтор одного и того же query param запрещён, если для параметра явно не описана поддержка массива;
- значения
idсравниваются как case-sensitive строки; - все применённые значения backend возвращает в
applied_filtersуже нормализованными; - если
region_idпередан вместе сfederal_district_id, backend должен проверить, что регион входит в указанный федеральный округ; - если фильтры валидны, но данных по ним нет, возвращается
200 OKс пустыми массивами; - если значение фильтра не существует или недоступно пользователю, возвращается ошибка валидации.
Canonical examples:
GET /api/v1/competence-map/analytics/filters/
GET /api/v1/competence-map/analytics/?association_type=all
GET /api/v1/competence-map/analytics/?association_type=technoparks&federal_district_id=central®ion_id=moscow-oblast
Не использовать как canonical form:
GET /api/v1/competence-map/analytics/?association_type=all&federal_district_id=®ion_id=
Такой request можно поддержать для совместимости, но backend всё равно должен вернуть пустые значения как
null в applied_filters.
Headers и трассировка
Каждый response должен содержать:
X-Request-Id: <request id>
Требования:
- если frontend передал
X-Request-Id, backend должен использовать его или вернуть связанный id; - если id не передан, backend генерирует его сам;
- этот id должен попадать в backend logs, чтобы можно было связать ошибку UI и серверный запрос;
- технические debug-поля не добавляются в JSON payload, для них используются headers и логи.
Авторизация и права доступа
Endpoint-ы должны учитывать права текущего пользователя.
Правила:
401 Unauthorizedвозвращается, если пользователь не аутентифицирован;403 Forbiddenвозвращается, если пользователь аутентифицирован, но не имеет доступа к странице или разделу;- для
401 Unauthorizedbackend должен вернутьWWW-Authenticate, если это предусмотрено текущей схемой аутентификации; - если пользователь имеет доступ к странице, но часть справочников или данных ему недоступна, backend должен отфильтровать эти данные на сервере;
filtersне должен раскрывать id сущностей, к которым у пользователя нет доступа;disabled = trueможно использовать только для значений, которые пользователь видит, но которые неприменимы при текущих фильтрах;- значение фильтра, недоступное пользователю, обрабатывается как invalid filter value и не должно раскрывать, существует ли такая сущность в системе.
Кэширование и условные GET
Данные зависят от пользователя, прав доступа и фильтров, поэтому shared caching должен быть запрещён.
Recommended headers для успешных responses:
Cache-Control: private, max-age=60
Vary: Authorization, Accept
ETag: "<user-and-query-specific-etag>"
Last-Modified: <data_actual_at as HTTP-date>
Требования:
ETagдолжен учитывать пользователя, права доступа, endpoint path, query params и версию/актуальность данных;Last-Modifiedдолжен соответствовать актуальности данных, а не времени генерации response;- backend должен поддерживать
If-None-Matchи может вернуть304 Not Modified, если представление не изменилось; - при
304 Not Modifiedтело response не возвращается; - для персональных данных нельзя использовать
Cache-Control: public; - если backend пока не готов поддерживать validators, нужно явно вернуть
Cache-Control: no-store.
Актуальность и методика расчёта
Response уже содержит два разных времени:
generated_at- когда backend сформировал текущий response;data_actual_at- на какой момент актуальны аналитические данные.
Требования:
generated_atне использовать как дату актуальности данных;data_actual_atдолжен быть единым для всех блоков response;- если разные блоки приходят из источников с разной актуальностью, backend должен либо привести их к единому согласованному snapshot-у, либо явно согласовать и задокументировать методику;
- версия методики расчёта процентов, уникальности организаций, участий и сотрудников должна быть зафиксирована в backend-документации;
- если backend поддерживает версионирование методик, response может дополнительно возвращать
calculation_methodology_version; - изменение методики, которое меняет смысл существующих полей, считается breaking change для
/api/v1.
Большие справочники и массивы
Dashboard-массивы rows, segments, points возвращаются полностью, если для блока не описана серверная
пагинация.
Для справочника organizations возможен большой объём данных. Если список может превышать безопасный размер
для одного response, backend должен выбрать один из вариантов до реализации:
- возвращать только доступные организации в пределах текущих фильтров, если объём гарантированно небольшой;
- добавить к
/analytics/filters/query paramsorganization_searchиorganization_limit; - вынести поиск организаций в отдельный endpoint справочника.
Если вводятся organization_search и organization_limit, правила должны быть такими:
organization_search: string, optional, минимум 2 символа после trim;organization_limit: integer, optional, default50, maximum100;- сортировка организаций: релевантность поиска, затем
labelпо возрастанию (ru-RU); - выбранная в
organization_idорганизация должна присутствовать вorganizations, даже если она не попала бы в первыеorganization_limitрезультатов.
Термины и правила подсчёта
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
Численность сотрудников.
Для показателей сотрудников нужно явно различать:
- сотрудников организаций ОПК;
- сотрудников организаций ОПК-резидентов;
- сотрудников всех резидентов;
- сотрудников участников кластера;
- сотрудников выбранного типа объединений.
Региональные count-поля
В рейтингах субъектов РФ count-поля должны описывать разные сущности однозначно.
Для association_type = sez:
sez_count: количество ОЭЗ в субъекте РФ после применения фильтров;opk_organizations_count: количество уникальных организаций ОПК, связанных с ОЭЗ в субъекте РФ;opk_memberships_count: количество связейорганизация ОПК x ОЭЗ, если backend показывает именно участия, а не уникальные организации.
Для association_type = technoparks:
technoparks_count: количество технопарков в субъекте РФ после применения фильтров;opk_organizations_count: количество уникальных организаций ОПК, связанных с технопарками в субъекте РФ;opk_memberships_count: количество связейорганизация ОПК x технопарк, если backend показывает участия.
Для association_type = clusters:
clusters_count: количество промышленных кластеров в субъекте РФ после применения фильтров;opk_organizations_count: количество уникальных организаций ОПК, участвующих в кластерах в субъекте РФ;opk_memberships_count: количество связейорганизация ОПК x промышленный кластер, если backend показывает участия.
Нельзя использовать sez_count, technoparks_count или clusters_count, если значение означает
количество организаций ОПК. Для организаций ОПК нужно использовать opk_organizations_count или
более точное поле с префиксом выбранного типа объединения.
rank
Позиция строки в рейтинге или топе.
Требования:
rankвозвращается backend-ом как integer>= 1;- если используется обычное порядковое ранжирование,
rankне должен дублироваться; - если бизнес-методика допускает одинаковые места при равных значениях, блок должен вернуть
ranking_method; - допустимые значения
ranking_method:ordinal,dense,competition; - если
ranking_methodне передан, frontend считает методикуordinal.
Полнота массивов
Если в блоке нет явного limit, массив rows, segments или points должен возвращаться полностью
для текущего состояния страницы и текущих фильтров.
Frontend может отображать часть данных в видимой области и скроллить контейнер локально, но backend не должен неявно обрезать массив до первых 2-3 строк только потому, что в макете виден короткий пример.
Нужно реализовать
competence-map analytics filters
GET /api/v1/competence-map/analytics/filters/
Описание:
- возвращает справочники для фильтров аналитической панели;
- учитывает права пользователя;
- учитывает текущие выбранные фильтры, если они переданы в query params;
- возвращает серверно доступные значения
association_type; - не возвращает аналитические KPI, рейтинги и графики;
- не управляет визуальным представлением фильтров: frontend может локально показывать типы объединений как select, tabs или segmented control.
Request:
GET /api/v1/competence-map/analytics/filters/?association_type=all
Query params:
association_type:all | sez | technoparks | clusters, optional, defaultall;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_countinteger>= 0;disabledboolean.
Назначение:
- является серверным источником доступных состояний аналитической страницы;
- порядок массива определяет рекомендуемый порядок типов объединений;
- если типы объединений показываются в UI как tabs, каждый tab соответствует одному
id; - при выборе tab/select frontend передаёт выбранный
idкак query paramassociation_type; associations_countможет использоваться как серверное значение для badge/count рядом с типом;disabled = trueозначает, что тип недоступен пользователю или неприменим при текущих фильтрах.
Массивы справочников
Для массивов 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
Query params:
association_type:all | sez | technoparks | clusters, optional, defaultall;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
}
Серверные состояния страницы по association_type
Страница имеет четыре серверных состояния. Они отличаются составом данных, но используют один и тот же
endpoint /api/v1/competence-map/analytics/.
association_type |
KPI | main_block.type |
region_ranking |
top_industries.mode |
trend_block |
employee_stats |
|---|---|---|---|---|---|---|
all |
4 карточки по всем объединениям | all_associations_composition |
3 метрики по типам объединений | stacked_by_association_type |
opk_participation_by_year |
null |
sez |
4 карточки по ОЭЗ | sez_types_and_priority_directions |
ОЭЗ + организации ОПК | single_association_type |
null |
сотрудники ОПК-резидентов ОЭЗ и сотрудники ОЭЗ |
technoparks |
4 карточки по технопаркам | technopark_area_distribution |
технопарки + организации ОПК | single_association_type |
null |
сотрудники ОПК-резидентов технопарков и их доля |
clusters |
4 карточки по кластерам | cluster_specializations |
кластеры + организации ОПК | single_association_type |
cluster_creation_dynamics |
null |
Требования:
association_typeв корне response должен определять структуруmain_block, применимостьtrend_blockи применимостьemployee_stats;- frontend не должен делать дополнительные аналитические запросы при переключении между этими
состояниями, кроме повторного запроса
/analytics/filters/и/analytics/; - если тип объединения вынесен в UI как tabs, выбранный tab всё равно передаётся как
query param
association_type.
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"
},
"ranking_method": "ordinal",
"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": "chelyabinsk-oblast",
"region_name": "Челябинская область",
"cluster_opk_organizations_count": 23,
"technopark_opk_organizations_count": 23,
"sez_opk_organizations_count": 46,
"total_opk_organizations_count": 92
},
{
"rank": 3,
"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": 4,
"region_id": "tatarstan",
"region_name": "Республика Татарстан",
"cluster_opk_organizations_count": 22,
"technopark_opk_organizations_count": 22,
"sez_opk_organizations_count": 31,
"total_opk_organizations_count": 75
},
{
"rank": 5,
"region_id": "samara-region",
"region_name": "Самарская область",
"cluster_opk_organizations_count": 19,
"technopark_opk_organizations_count": 19,
"sez_opk_organizations_count": 21,
"total_opk_organizations_count": 59
},
{
"rank": 6,
"region_id": "penza-region",
"region_name": "Пензенская область",
"cluster_opk_organizations_count": 17,
"technopark_opk_organizations_count": 17,
"sez_opk_organizations_count": 19,
"total_opk_organizations_count": 53
},
{
"rank": 7,
"region_id": "tomsk-region",
"region_name": "Томская область",
"cluster_opk_organizations_count": 12,
"technopark_opk_organizations_count": 12,
"sez_opk_organizations_count": 17,
"total_opk_organizations_count": 41
},
{
"rank": 8,
"region_id": "yaroslavl-region",
"region_name": "Ярославская область",
"cluster_opk_organizations_count": 10,
"technopark_opk_organizations_count": 10,
"sez_opk_organizations_count": 18,
"total_opk_organizations_count": 38
},
{
"rank": 9,
"region_id": "udmurtia",
"region_name": "Удмуртская Республика",
"cluster_opk_organizations_count": 2,
"technopark_opk_organizations_count": 2,
"sez_opk_organizations_count": 20,
"total_opk_organizations_count": 24
},
{
"rank": 10,
"region_id": "ulyanovsk-region",
"region_name": "Ульяновская область",
"cluster_opk_organizations_count": 5,
"technopark_opk_organizations_count": 5,
"sez_opk_organizations_count": 13,
"total_opk_organizations_count": 23
},
{
"rank": 11,
"region_id": "khabarovsk-krai",
"region_name": "Хабаровский край",
"cluster_opk_organizations_count": 4,
"technopark_opk_organizations_count": 4,
"sez_opk_organizations_count": 14,
"total_opk_organizations_count": 22
},
{
"rank": 12,
"region_id": "volgograd-region",
"region_name": "Волгоградская область",
"cluster_opk_organizations_count": 2,
"technopark_opk_organizations_count": 2,
"sez_opk_organizations_count": 14,
"total_opk_organizations_count": 18
},
{
"rank": 13,
"region_id": "bashkortostan",
"region_name": "Республика Башкортостан",
"cluster_opk_organizations_count": 4,
"technopark_opk_organizations_count": 4,
"sez_opk_organizations_count": 8,
"total_opk_organizations_count": 16
},
{
"rank": 14,
"region_id": "krasnodar-krai",
"region_name": "Краснодарский край",
"cluster_opk_organizations_count": 3,
"technopark_opk_organizations_count": 3,
"sez_opk_organizations_count": 10,
"total_opk_organizations_count": 16
},
{
"rank": 15,
"region_id": "belgorod-region",
"region_name": "Белгородская область",
"cluster_opk_organizations_count": 2,
"technopark_opk_organizations_count": 2,
"sez_opk_organizations_count": 7,
"total_opk_organizations_count": 11
}
]
},
"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
},
{
"industry_id": "radio-electronics",
"industry_name": "Радиоэлектронная промышленность",
"total_opk_organizations_count": 101,
"cluster_opk_organizations_count": 43,
"technopark_opk_organizations_count": 48,
"sez_opk_organizations_count": 10
},
{
"industry_id": "shipbuilding",
"industry_name": "Судостроительная промышленность",
"total_opk_organizations_count": 78,
"cluster_opk_organizations_count": 46,
"technopark_opk_organizations_count": 20,
"sez_opk_organizations_count": 12
},
{
"industry_id": "robotics",
"industry_name": "Беспилотные системы и робототехника",
"total_opk_organizations_count": 12,
"cluster_opk_organizations_count": 4,
"technopark_opk_organizations_count": 6,
"sez_opk_organizations_count": 2
}
]
},
"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",
"title": "Типы и приоритетные направления деятельности ОЭЗ",
"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"
},
"ranking_method": "ordinal",
"rows": [
{
"rank": 1,
"direction_id": "ict",
"direction_name": "Информационно-коммуникационные технологии",
"sez_count": 4
},
{
"rank": 2,
"direction_id": "aircraft-maintenance",
"direction_name": "Обслуживание и ремонт воздушных судов",
"sez_count": 3
},
{
"rank": 3,
"direction_id": "wood-processing",
"direction_name": "Лесопереработка",
"sez_count": 2
},
{
"rank": 4,
"direction_id": "logistics",
"direction_name": "Логистика",
"sez_count": 2
},
{
"rank": 5,
"direction_id": "nanotechnology-and-new-materials",
"direction_name": "Нанотехнологии и новые материалы",
"sez_count": 2
},
{
"rank": 6,
"direction_id": "titanium-products",
"direction_name": "Производство изделий из титана",
"sez_count": 1
},
{
"rank": 7,
"direction_id": "pharmaceutical-industry",
"direction_name": "Фармацевтическая промышленность",
"sez_count": 1
},
{
"rank": 8,
"direction_id": "storage-and-logistics",
"direction_name": "Хранение и логистика",
"sez_count": 1
},
{
"rank": 9,
"direction_id": "high-tech-processing-production",
"direction_name": "Высокотехнологичные обрабатывающие производства",
"sez_count": 1
},
{
"rank": 10,
"direction_id": "woodworking",
"direction_name": "Деревообработка",
"sez_count": 1
}
]
}
},
"region_ranking": {
"title": "Рейтинг субъектов РФ по участию организаций ОПК в ОЭЗ",
"sort": {
"field": "opk_organizations_count",
"direction": "desc"
},
"ranking_method": "ordinal",
"rows": [
{
"rank": 1,
"region_id": "moscow-oblast",
"region_name": "Московская область",
"sez_count": 3,
"opk_organizations_count": 41
},
{
"rank": 2,
"region_id": "moscow",
"region_name": "г. Москва",
"sez_count": 2,
"opk_organizations_count": 28
},
{
"rank": 3,
"region_id": "chelyabinsk-oblast",
"region_name": "Челябинская область",
"sez_count": 2,
"opk_organizations_count": 23
},
{
"rank": 4,
"region_id": "tatarstan",
"region_name": "Республика Татарстан",
"sez_count": 1,
"opk_organizations_count": 22
},
{
"rank": 5,
"region_id": "samara-region",
"region_name": "Самарская область",
"sez_count": 1,
"opk_organizations_count": 19
},
{
"rank": 6,
"region_id": "penza-region",
"region_name": "Пензенская область",
"sez_count": 1,
"opk_organizations_count": 17
},
{
"rank": 7,
"region_id": "tomsk-region",
"region_name": "Томская область",
"sez_count": 1,
"opk_organizations_count": 12
},
{
"rank": 8,
"region_id": "yaroslavl-region",
"region_name": "Ярославская область",
"sez_count": 1,
"opk_organizations_count": 10
},
{
"rank": 9,
"region_id": "ulyanovsk-region",
"region_name": "Ульяновская область",
"sez_count": 1,
"opk_organizations_count": 5
},
{
"rank": 10,
"region_id": "khabarovsk-krai",
"region_name": "Хабаровский край",
"sez_count": 1,
"opk_organizations_count": 4
},
{
"rank": 11,
"region_id": "bashkortostan",
"region_name": "Республика Башкортостан",
"sez_count": 1,
"opk_organizations_count": 4
},
{
"rank": 12,
"region_id": "krasnodar-krai",
"region_name": "Краснодарский край",
"sez_count": 1,
"opk_organizations_count": 3
},
{
"rank": 13,
"region_id": "belgorod-region",
"region_name": "Белгородская область",
"sez_count": 1,
"opk_organizations_count": 2
}
]
},
"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
},
{
"industry_id": "ammunition-and-special-chemistry",
"industry_name": "Промышленность боеприпасов и спецхимии",
"opk_organizations_count": 110
},
{
"industry_id": "radio-electronics",
"industry_name": "Радиоэлектронная промышленность",
"opk_organizations_count": 101
},
{
"industry_id": "shipbuilding",
"industry_name": "Судостроительная промышленность",
"opk_organizations_count": 78
},
{
"industry_id": "robotics",
"industry_name": "Беспилотные системы и робототехника",
"opk_organizations_count": 12
}
]
},
"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",
"area_min_value": 150,
"area_max_value": 200,
"area_unit": "ha",
"technoparks_count": 12
},
{
"area_segment": "medium",
"label": "средние",
"area_range_label": "50-150",
"area_min_value": 50,
"area_max_value": 150,
"area_unit": "ha",
"technoparks_count": 14
},
{
"area_segment": "small",
"label": "мелкие",
"area_range_label": "0-50",
"area_min_value": 0,
"area_max_value": 50,
"area_unit": "ha",
"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"
},
{
"rank": 3,
"technopark_id": "technopark-003",
"technopark_name": "Технопарк Южный Урал",
"region_id": "chelyabinsk-oblast",
"region_name": "Челябинская область",
"area_value": 23,
"area_unit": "ha"
},
{
"rank": 4,
"technopark_id": "technopark-004",
"technopark_name": "Технопарк Иннополис",
"region_id": "tatarstan",
"region_name": "Республика Татарстан",
"area_value": 22,
"area_unit": "ha"
},
{
"rank": 5,
"technopark_id": "technopark-005",
"technopark_name": "Технопарк Самара",
"region_id": "samara-region",
"region_name": "Самарская область",
"area_value": 19,
"area_unit": "ha"
},
{
"rank": 6,
"technopark_id": "technopark-006",
"technopark_name": "Технопарк Пенза",
"region_id": "penza-region",
"region_name": "Пензенская область",
"area_value": 17,
"area_unit": "ha"
},
{
"rank": 7,
"technopark_id": "technopark-007",
"technopark_name": "Технопарк Томск",
"region_id": "tomsk-region",
"region_name": "Томская область",
"area_value": 12,
"area_unit": "ha"
},
{
"rank": 8,
"technopark_id": "technopark-008",
"technopark_name": "Технопарк Ярославль",
"region_id": "yaroslavl-region",
"region_name": "Ярославская область",
"area_value": 10,
"area_unit": "ha"
},
{
"rank": 9,
"technopark_id": "technopark-009",
"technopark_name": "Технопарк Ульяновск",
"region_id": "ulyanovsk-region",
"region_name": "Ульяновская область",
"area_value": 5,
"area_unit": "ha"
},
{
"rank": 10,
"technopark_id": "technopark-010",
"technopark_name": "Технопарк Хабаровск",
"region_id": "khabarovsk-krai",
"region_name": "Хабаровский край",
"area_value": 4,
"area_unit": "ha"
},
{
"rank": 11,
"technopark_id": "technopark-011",
"technopark_name": "Технопарк Башкортостан",
"region_id": "bashkortostan",
"region_name": "Республика Башкортостан",
"area_value": 4,
"area_unit": "ha"
}
]
}
},
"region_ranking": {
"title": "Рейтинг субъектов РФ по участию организаций ОПК в технопарках",
"sort": {
"field": "opk_organizations_count",
"direction": "desc"
},
"ranking_method": "ordinal",
"rows": [
{
"rank": 1,
"region_id": "moscow-oblast",
"region_name": "Московская область",
"technoparks_count": 7,
"opk_organizations_count": 41
},
{
"rank": 2,
"region_id": "moscow",
"region_name": "г. Москва",
"technoparks_count": 5,
"opk_organizations_count": 28
},
{
"rank": 3,
"region_id": "chelyabinsk-oblast",
"region_name": "Челябинская область",
"technoparks_count": 4,
"opk_organizations_count": 23
},
{
"rank": 4,
"region_id": "tatarstan",
"region_name": "Республика Татарстан",
"technoparks_count": 3,
"opk_organizations_count": 22
},
{
"rank": 5,
"region_id": "samara-region",
"region_name": "Самарская область",
"technoparks_count": 3,
"opk_organizations_count": 19
},
{
"rank": 6,
"region_id": "penza-region",
"region_name": "Пензенская область",
"technoparks_count": 2,
"opk_organizations_count": 17
},
{
"rank": 7,
"region_id": "tomsk-region",
"region_name": "Томская область",
"technoparks_count": 2,
"opk_organizations_count": 12
},
{
"rank": 8,
"region_id": "yaroslavl-region",
"region_name": "Ярославская область",
"technoparks_count": 2,
"opk_organizations_count": 10
},
{
"rank": 9,
"region_id": "ulyanovsk-region",
"region_name": "Ульяновская область",
"technoparks_count": 2,
"opk_organizations_count": 5
},
{
"rank": 10,
"region_id": "khabarovsk-krai",
"region_name": "Хабаровский край",
"technoparks_count": 1,
"opk_organizations_count": 4
},
{
"rank": 11,
"region_id": "bashkortostan",
"region_name": "Республика Башкортостан",
"technoparks_count": 1,
"opk_organizations_count": 4
},
{
"rank": 12,
"region_id": "krasnodar-krai",
"region_name": "Краснодарский край",
"technoparks_count": 1,
"opk_organizations_count": 3
},
{
"rank": 13,
"region_id": "belgorod-region",
"region_name": "Белгородская область",
"technoparks_count": 1,
"opk_organizations_count": 2
},
{
"rank": 14,
"region_id": "volgograd-region",
"region_name": "Волгоградская область",
"technoparks_count": 1,
"opk_organizations_count": 2
}
]
},
"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
},
{
"industry_id": "ammunition-and-special-chemistry",
"industry_name": "Промышленность боеприпасов и спецхимии",
"opk_organizations_count": 110
},
{
"industry_id": "radio-electronics",
"industry_name": "Радиоэлектронная промышленность",
"opk_organizations_count": 101
},
{
"industry_id": "shipbuilding",
"industry_name": "Судостроительная промышленность",
"opk_organizations_count": 78
},
{
"industry_id": "robotics",
"industry_name": "Беспилотные системы и робототехника",
"opk_organizations_count": 12
}
]
},
"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"
},
"ranking_method": "ordinal",
"rows": [
{
"rank": 1,
"specialization_id": "ict",
"specialization_name": "Информационно-коммуникационные технологии",
"clusters_count": 4
},
{
"rank": 2,
"specialization_id": "aircraft-maintenance",
"specialization_name": "Обслуживание и ремонт воздушных судов",
"clusters_count": 3
},
{
"rank": 3,
"specialization_id": "wood-processing",
"specialization_name": "Лесопереработка",
"clusters_count": 2
},
{
"rank": 4,
"specialization_id": "logistics",
"specialization_name": "Логистика",
"clusters_count": 2
},
{
"rank": 5,
"specialization_id": "nanotechnology-and-new-materials",
"specialization_name": "Нанотехнологии и новые материалы",
"clusters_count": 2
},
{
"rank": 6,
"specialization_id": "titanium-products",
"specialization_name": "Производство изделий из титана",
"clusters_count": 1
},
{
"rank": 7,
"specialization_id": "pharmaceutical-industry",
"specialization_name": "Фармацевтическая промышленность",
"clusters_count": 1
},
{
"rank": 8,
"specialization_id": "storage-and-logistics",
"specialization_name": "Хранение и логистика",
"clusters_count": 1
},
{
"rank": 9,
"specialization_id": "high-tech-processing-production",
"specialization_name": "Высокотехнологичные обрабатывающие производства",
"clusters_count": 1
},
{
"rank": 10,
"specialization_id": "woodworking",
"specialization_name": "Деревообработка",
"clusters_count": 1
}
]
}
},
"region_ranking": {
"title": "Рейтинг субъектов РФ по участию организаций ОПК в кластерах",
"sort": {
"field": "opk_organizations_count",
"direction": "desc"
},
"ranking_method": "ordinal",
"rows": [
{
"rank": 1,
"region_id": "moscow-oblast",
"region_name": "Московская область",
"clusters_count": 20,
"opk_organizations_count": 41
},
{
"rank": 2,
"region_id": "moscow",
"region_name": "г. Москва",
"clusters_count": 15,
"opk_organizations_count": 28
},
{
"rank": 3,
"region_id": "chelyabinsk-oblast",
"region_name": "Челябинская область",
"clusters_count": 12,
"opk_organizations_count": 23
},
{
"rank": 4,
"region_id": "tatarstan",
"region_name": "Республика Татарстан",
"clusters_count": 10,
"opk_organizations_count": 22
},
{
"rank": 5,
"region_id": "samara-region",
"region_name": "Самарская область",
"clusters_count": 9,
"opk_organizations_count": 19
},
{
"rank": 6,
"region_id": "penza-region",
"region_name": "Пензенская область",
"clusters_count": 8,
"opk_organizations_count": 17
},
{
"rank": 7,
"region_id": "tomsk-region",
"region_name": "Томская область",
"clusters_count": 7,
"opk_organizations_count": 12
},
{
"rank": 8,
"region_id": "yaroslavl-region",
"region_name": "Ярославская область",
"clusters_count": 6,
"opk_organizations_count": 10
},
{
"rank": 9,
"region_id": "ulyanovsk-region",
"region_name": "Ульяновская область",
"clusters_count": 5,
"opk_organizations_count": 5
},
{
"rank": 10,
"region_id": "khabarovsk-krai",
"region_name": "Хабаровский край",
"clusters_count": 4,
"opk_organizations_count": 4
},
{
"rank": 11,
"region_id": "bashkortostan",
"region_name": "Республика Башкортостан",
"clusters_count": 4,
"opk_organizations_count": 4
},
{
"rank": 12,
"region_id": "krasnodar-krai",
"region_name": "Краснодарский край",
"clusters_count": 3,
"opk_organizations_count": 3
},
{
"rank": 13,
"region_id": "belgorod-region",
"region_name": "Белгородская область",
"clusters_count": 2,
"opk_organizations_count": 2
},
{
"rank": 14,
"region_id": "volgograd-region",
"region_name": "Волгоградская область",
"clusters_count": 2,
"opk_organizations_count": 2
},
{
"rank": 15,
"region_id": "udmurtia",
"region_name": "Удмуртская Республика",
"clusters_count": 2,
"opk_organizations_count": 2
}
]
},
"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
},
{
"industry_id": "ammunition-and-special-chemistry",
"industry_name": "Промышленность боеприпасов и спецхимии",
"opk_organizations_count": 110
},
{
"industry_id": "radio-electronics",
"industry_name": "Радиоэлектронная промышленность",
"opk_organizations_count": 101
},
{
"industry_id": "shipbuilding",
"industry_name": "Судостроительная промышленность",
"opk_organizations_count": 78
},
{
"industry_id": "robotics",
"industry_name": "Беспилотные системы и робототехника",
"opk_organizations_count": 12
}
]
},
"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; - массивы внутри блока должны быть отсортированы.
main_block.type = all_associations_composition
Используется для association_type = all.
Обязательные поля:
total_associations_count;segments.
Требования к segments:
- каждый segment должен иметь
association_type,label,associations_count,opk_organizations_count,opk_memberships_count; - порядок
segments:sez,technoparks,clusters; - сумма
segments[*].associations_countдолжна быть равнаtotal_associations_count.
main_block.type = sez_types_and_priority_directions
Используется для association_type = sez.
Обязательные поля:
sez_types;priority_directions.
Требования к sez_types.segments:
- каждый segment должен иметь
sez_type,label,sez_count,opk_organizations_count; - сумма
segments[*].sez_countдолжна быть равнаsez_types.total_sez_count, если типы ОЭЗ взаимоисключающие; - если одна ОЭЗ может относиться к нескольким типам, backend должен явно зафиксировать методику до реализации endpoint-а.
Требования к priority_directions.rows:
- каждая строка должна иметь
rank,direction_id,direction_name,sez_count; direction_idдолжен быть стабильным id направления;rowsдолжны быть отсортированы поsort;- если направлений больше, чем помещается на экране, backend всё равно возвращает полный массив.
main_block.type = technopark_area_distribution
Используется для association_type = technoparks.
Обязательные поля:
total_technoparks_count;area_segments;technoparks.
Требования к area_segments:
- каждый segment должен иметь
area_segment,label,area_range_label,area_min_value,area_max_value,area_unit,technoparks_count; area_min_valueиarea_max_valueвозвращаются как number илиnullдля открытого диапазона;area_unitдолжен быть стабильным кодом единицы измерения, напримерha;- сумма
area_segments[*].technoparks_countдолжна быть равнаtotal_technoparks_count, если сегменты площадей взаимоисключающие.
Требования к technoparks.rows:
- каждая строка должна иметь
rank,technopark_id,technopark_name,region_id,region_name,area_value,area_unit; technopark_idдолжен быть стабильным id для drilldown;area_valueвозвращается как number;rowsдолжны быть отсортированы поtechnoparks.sort.
main_block.type = cluster_specializations
Используется для association_type = clusters.
Обязательные поля:
specializations.
Требования к specializations.rows:
- каждая строка должна иметь
rank,specialization_id,specialization_name,clusters_count; specialization_idдолжен быть стабильным id специализации;clusters_countвозвращается как integer>= 0;rowsдолжны быть отсортированы поspecializations.sort;- если специализаций больше, чем помещается на экране, backend всё равно возвращает полный массив.
Блок region_ranking
Поля:
title;sort;ranking_method;rows.
Требования:
titleобязательный;sort.fieldобязательный;sort.directionдолжен бытьasc | desc;ranking_methodoptional, defaultordinal;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по возрастанию;yearinteger;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 порядок:
seztechnoparksclusters
region_ranking.rows
Порядок сортировки по умолчанию:
- основной показатель по убыванию;
- при равенстве -
opk_organizations_countпо убыванию; - при равенстве -
region_nameпо возрастанию (ru-RU).
Если в блоке передан ranking_method = dense или competition, одинаковые rank допустимы
только для строк с равным основным показателем.
top_industries.rows
Порядок сортировки:
- основной показатель по убыванию;
- при равенстве -
industry_nameпо возрастанию (ru-RU).
trend_block.points
Порядок сортировки:
yearпо возрастанию.
Проверки консистентности
Backend должен гарантировать:
association_typeв корне response совпадает сapplied_filters.association_type;kpis[*].valueсогласованы с соответствующими блоками аналитики;- сумма
main_block.segments[*].associations_countдляallравнаtotal_associations_count; - сумма
main_block.sez_types.segments[*].sez_countдляsezравнаmain_block.sez_types.total_sez_count, если типы ОЭЗ взаимоисключающие; - сумма
main_block.area_segments[*].technoparks_countдляtechnoparksравнаmain_block.total_technoparks_count, если сегменты площадей взаимоисключающие; top_industries.max_valueне меньше максимального значения вtop_industries.rows;rankв рейтингах не дублируется, еслиranking_method = ordinal;- все
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": "northwestern",
"region_id": "nenets-autonomous-okrug",
"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
}
Статус-коды и ошибки
Status code matrix
| Status | Когда возвращается |
|---|---|
200 OK |
Успешный ответ, включая валидные фильтры без данных. |
304 Not Modified |
Conditional GET по If-None-Match или If-Modified-Since, если представление не изменилось. |
400 Bad Request |
Некорректный query param, неизвестный query param, повтор параметра, неверный формат значения. |
401 Unauthorized |
Пользователь не аутентифицирован. |
403 Forbidden |
Пользователь аутентифицирован, но не имеет доступа к странице или разделу. |
429 Too Many Requests |
Превышен rate limit. |
500 Internal Server Error |
Непредвиденная ошибка backend-а. |
503 Service Unavailable |
Временная недоступность сервиса или источника аналитических данных. |
Для collection/report endpoint-ов не использовать 404 Not Found, если path существует, но по валидным фильтрам
нет данных. В этом случае нужен 200 OK с пустыми массивами.
Problem Details
Все ошибки возвращаются в формате application/problem+json.
Обязательные поля:
type: стабильный URI типа ошибки;title: короткое человекочитаемое название;status: HTTP status code;detail: конкретное описание ошибки;instance: path и query текущего request-а;code: стабильный backend-код ошибки для frontend-логики;request_id: id запроса изX-Request-Id.
Frontend не должен парсить title или detail для бизнес-логики. Для логики используется только code
и дополнительные машинные поля.
Ошибка валидации query params
Для некорректного association_type:
HTTP status:
400 Bad Request
Response:
{
"type": "https://api.example.ru/problems/invalid-association-type",
"title": "Invalid association_type",
"status": 400,
"detail": "Unsupported association_type: parks",
"instance": "/api/v1/competence-map/analytics/?association_type=parks",
"code": "invalid_association_type",
"request_id": "req-01JYX4M2K7Y6WQ9RJ8T2V7VY5R",
"received_value": "parks",
"allowed_values": ["all", "sez", "technoparks", "clusters"]
}
Для несуществующего фильтра:
HTTP status:
400 Bad Request
Response:
{
"type": "https://api.example.ru/problems/invalid-filter-value",
"title": "Invalid filter value",
"status": 400,
"detail": "Unknown or unavailable filter value for region_id",
"instance": "/api/v1/competence-map/analytics/?association_type=clusters®ion_id=unknown",
"code": "invalid_filter_value",
"request_id": "req-01JYX4M2K7Y6WQ9RJ8T2V7VY5R",
"field": "region_id",
"received_value": "unknown"
}
Для неизвестного query param:
HTTP status:
400 Bad Request
Response:
{
"type": "https://api.example.ru/problems/unknown-query-param",
"title": "Unknown query parameter",
"status": 400,
"detail": "Unknown query parameter: district",
"instance": "/api/v1/competence-map/analytics/?district=central",
"code": "unknown_query_param",
"request_id": "req-01JYX4M2K7Y6WQ9RJ8T2V7VY5R",
"field": "district",
"allowed_params": [
"association_type",
"federal_district_id",
"region_id",
"industry_id",
"integrated_structure_id",
"organization_id"
]
}
Ошибки доступа и инфраструктуры
Для 401, 403, 429, 500, 503 используется тот же формат application/problem+json.
Пример 403 Forbidden:
{
"type": "https://api.example.ru/problems/forbidden",
"title": "Forbidden",
"status": 403,
"detail": "User does not have access to competence map analytics",
"instance": "/api/v1/competence-map/analytics/",
"code": "forbidden",
"request_id": "req-01JYX4M2K7Y6WQ9RJ8T2V7VY5R"
}
Для 429 Too Many Requests backend должен вернуть header:
Retry-After: <seconds>
Ожидаемая схема запросов
При загрузке страницы:
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®ion_id=moscow-oblast
GET /api/v1/competence-map/analytics/?association_type=technoparks&federal_district_id=central®ion_id=moscow-oblast
Если тип объединения отображается в интерфейсе как tabs, схема запросов не меняется.
Tab является только UI-представлением association_type.
Не требуется реализовывать отдельные endpoint-ы вида:
GET /api/v1/competence-map/analytics/sez/
GET /api/v1/competence-map/analytics/technoparks/
GET /api/v1/competence-map/analytics/clusters/
Если в будущем появятся отдельные действия, например экспорт, полноэкранная таблица с серверной
пагинацией или drilldown по строке, для них можно добавить отдельные endpoint-ы. Для текущих состояний
страницы из референсных экранов достаточно /analytics/filters/ и /analytics/.