2017 lines
83 KiB
Markdown
2017 lines
83 KiB
Markdown
# Backend Endpoints Competence Map Association Map
|
||
|
||
Назначение: спецификация backend-контракта для страниц:
|
||
|
||
- `Все сервисы / Карта компетенций / Карта кластеров` (`/new-map`);
|
||
- `Все сервисы / Карта компетенций / Карта технопарков` (`/new-map/technoparks`);
|
||
- `Все сервисы / Карта компетенций / Карта ОЭЗ` (`/new-map/sez`).
|
||
|
||
Документ нужен как задача для backend-разработки.
|
||
Цель: реализовать endpoint-ы, которые возвращают все серверное состояние новых страниц карты
|
||
по участию организаций ОПК в инновационных объединениях:
|
||
|
||
- значения фильтров;
|
||
- доступные разделы карты;
|
||
- доступные типы отображения карты;
|
||
- выбранные субъекты РФ или федеральные округа как фильтр карты;
|
||
- KPI-карточки;
|
||
- данные для раскраски и tooltip-ов карты субъектов РФ;
|
||
- данные для раскраски и tooltip-ов карты федеральных округов;
|
||
- список объединений, в числе участников которых есть организации ОПК;
|
||
- рейтинг субъектов РФ;
|
||
- счетчик и перечень организаций ОПК для табличного представления.
|
||
|
||
Документ является дополнением к
|
||
`docs/backend-endpoints-competence-map-analytics-from-layouts.md`.
|
||
Он не заменяет endpoint-ы аналитической панели `/dashboard`.
|
||
|
||
## Принципы контракта
|
||
|
||
- snapshot страницы карты должен возвращаться одним page-level endpoint-ом;
|
||
- справочники фильтров должны возвращаться отдельным endpoint-ом;
|
||
- табличный перечень организаций должен возвращаться отдельным collection endpoint-ом, потому что это
|
||
потенциально большой массив и он открывается отдельным состоянием страницы;
|
||
- контракт описывает серверное состояние страницы, а не конкретную визуальную реализацию UI;
|
||
- frontend может локально решать, показывать `association_type` как route tabs или navigation links,
|
||
какие иконки использовать, как отрисовывать SVG-карту и как скроллить таблицы;
|
||
- backend должен возвращать все данные, необходимые для всех серверно значимых состояний страницы:
|
||
фильтры, выбранные субъекты/округа, KPI, строки рейтинга, строки списка объединений, строки таблицы
|
||
организаций, id сущностей и значения для drilldown;
|
||
- все значения должны возвращаться в raw-формате: number, string, boolean, null;
|
||
- проценты должны возвращаться числом `0..100`, а не долей `0..1`;
|
||
- даты должны возвращаться в ISO 8601 с timezone;
|
||
- каждый объект, который может использоваться для drilldown или ссылки, должен иметь стабильный `id`;
|
||
- все массивы должны возвращаться уже отсортированными, если для блока задан серверный порядок;
|
||
- все KPI должны иметь стабильный `code`;
|
||
- `0` должен возвращаться как валидное значение, а не как `null`, строка или `—`;
|
||
- `null` допустим только для реально отсутствующего или неприменимого значения;
|
||
- backend должен обеспечивать консистентность KPI, карты, рейтинга, списка объединений и таблицы
|
||
организаций внутри одного набора фильтров.
|
||
|
||
Важное ограничение по числам в примерах:
|
||
|
||
- числовые значения в JSON-примерах взяты из референсных экранов и текущих mock-данных страницы;
|
||
- реальные значения должны быть рассчитаны по утвержденной backend-методике;
|
||
- если методика конкретного показателя не утверждена, ее нужно зафиксировать до реализации endpoint-а;
|
||
- если число на референсном экране противоречит смыслу поля, backend-контракт должен сохранять
|
||
корректную семантику поля, а не повторять ошибочное или placeholder-значение макета.
|
||
|
||
---
|
||
|
||
## REST и HTTP правила
|
||
|
||
### Модель ресурсов
|
||
|
||
Endpoint-ы описывают серверные представления ресурсов, а не действия UI.
|
||
|
||
В рамках этих страниц есть три ресурса:
|
||
|
||
- `/api/v1/competence-map/association-map/filters/` - справочники и доступные значения фильтров;
|
||
- `/api/v1/competence-map/association-map/` - snapshot карты для выбранных фильтров;
|
||
- `/api/v1/competence-map/association-map/organizations/` - табличный перечень организаций ОПК
|
||
для выбранных фильтров.
|
||
|
||
Требования:
|
||
|
||
- не добавлять action-style endpoint-ы вида `/getMap`, `/loadMapFilters`, `/selectRegion`;
|
||
- не использовать frontend path `/new-map` в API path;
|
||
- `association_type` является query-параметром одного ресурса карты, а не отдельным URI-сегментом;
|
||
- выбранные на карте субъекты РФ и федеральные округа являются query-параметрами snapshot-а,
|
||
а не отдельными action endpoint-ами;
|
||
- 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:
|
||
|
||
```text
|
||
Accept: application/json
|
||
```
|
||
|
||
Успешный response:
|
||
|
||
```text
|
||
Content-Type: application/json; charset=utf-8
|
||
Content-Language: ru-RU
|
||
```
|
||
|
||
Error response:
|
||
|
||
```text
|
||
Content-Type: application/problem+json; charset=utf-8
|
||
```
|
||
|
||
### Query params
|
||
|
||
Для `/association-map/filters/` и `/association-map/` используется единый набор query params:
|
||
|
||
- `association_type`: `clusters | technoparks | sez`, optional, default `clusters`;
|
||
- `map_view_mode`: `regions | federal_districts`, optional, default `regions`;
|
||
- `federal_district_id`: string, optional;
|
||
- `region_id`: string, optional;
|
||
- `federal_district_ids`: array of string, optional;
|
||
- `region_ids`: array of string, optional;
|
||
- `industry_id`: string, optional;
|
||
- `integrated_structure_id`: string, optional;
|
||
- `organization_id`: string, optional;
|
||
- `competence_id`: string, optional.
|
||
|
||
Для `/association-map/organizations/` дополнительно используются query params:
|
||
|
||
- `limit`: integer, optional, default `500`, maximum `1000`;
|
||
- `offset`: integer, optional, default `0`;
|
||
- `sort_field`: string, optional, default `serial_number`;
|
||
- `sort_direction`: `asc | desc`, optional, default `asc`.
|
||
|
||
Правила:
|
||
|
||
- если фильтр не выбран, параметр лучше не передавать;
|
||
- если frontend передал пустую строку, backend должен нормализовать ее в `null` или пустой массив
|
||
в `applied_filters`;
|
||
- неизвестные query params должны возвращать `400 Bad Request`, чтобы опечатки не игнорировались молча;
|
||
- повтор одного и того же query param запрещен, кроме массивов `region_ids` и `federal_district_ids`;
|
||
- массивы передаются в canonical form как repeated query params:
|
||
|
||
```text
|
||
region_ids=RU-MOS®ion_ids=RU-MOW
|
||
federal_district_ids=cfo&federal_district_ids=pfo
|
||
```
|
||
|
||
- значения `id` сравниваются как case-sensitive строки;
|
||
- все примененные значения backend возвращает в `applied_filters` уже нормализованными;
|
||
- `region_id` и элементы `region_ids` для карты должны соответствовать SVG-id субъектов РФ
|
||
в формате `RU-*`, например `RU-MOS`, `RU-MOW`, `RU-TA`;
|
||
- `federal_district_id` и элементы `federal_district_ids` должны соответствовать стабильным id
|
||
федеральных округов, например `cfo`, `pfo`, `ufo`, `sfo`, `dfo`, `nwfo`, `skfo`, `yufo`;
|
||
- если `region_id` передан вместе с `federal_district_id`, backend должен проверить, что регион входит
|
||
в указанный федеральный округ;
|
||
- нельзя одновременно передавать `region_id` и `region_ids`;
|
||
- нельзя одновременно передавать `federal_district_id` и `federal_district_ids`;
|
||
- нельзя одновременно передавать `region_ids` и `federal_district_ids`;
|
||
- нельзя одновременно передавать одиночные geography-фильтры (`region_id`, `federal_district_id`)
|
||
и multi-selection-фильтры карты (`region_ids`, `federal_district_ids`);
|
||
- если фильтры валидны, но данных по ним нет, возвращается `200 OK` с пустыми массивами;
|
||
- если значение фильтра не существует или недоступно пользователю, возвращается ошибка валидации.
|
||
|
||
Canonical examples:
|
||
|
||
```text
|
||
GET /api/v1/competence-map/association-map/filters/?association_type=clusters
|
||
GET /api/v1/competence-map/association-map/?association_type=clusters
|
||
GET /api/v1/competence-map/association-map/?association_type=technoparks&map_view_mode=regions®ion_ids=RU-MOS®ion_ids=RU-MOW
|
||
GET /api/v1/competence-map/association-map/?association_type=sez&map_view_mode=federal_districts&federal_district_ids=cfo&federal_district_ids=pfo
|
||
GET /api/v1/competence-map/association-map/organizations/?association_type=clusters®ion_ids=RU-MOS&limit=500&offset=0
|
||
```
|
||
|
||
Не использовать как canonical form:
|
||
|
||
```text
|
||
GET /api/v1/competence-map/association-map/?association_type=clusters®ion_id=&federal_district_id=
|
||
GET /api/v1/competence-map/association-map/?association_type=clusters®ion_id=RU-MOS®ion_ids=RU-MOW
|
||
```
|
||
|
||
Первый request можно поддержать для совместимости, но backend все равно должен вернуть пустые значения как
|
||
`null` или `[]` в `applied_filters`.
|
||
Второй request должен возвращать `400 Bad Request`, потому что одиночный фильтр и multi-selection фильтр
|
||
конфликтуют.
|
||
|
||
### Headers и трассировка
|
||
|
||
Каждый response должен содержать:
|
||
|
||
```text
|
||
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 Unauthorized` backend должен вернуть `WWW-Authenticate`, если это предусмотрено текущей схемой
|
||
аутентификации;
|
||
- если пользователь имеет доступ к странице, но часть справочников или данных ему недоступна, backend должен
|
||
отфильтровать эти данные на сервере;
|
||
- `filters` не должен раскрывать id сущностей, к которым у пользователя нет доступа;
|
||
- `disabled = true` можно использовать только для значений, которые пользователь видит, но которые неприменимы
|
||
при текущих фильтрах;
|
||
- значение фильтра, недоступное пользователю, обрабатывается как invalid filter value и не должно раскрывать,
|
||
существует ли такая сущность в системе.
|
||
|
||
### Кэширование и условные GET
|
||
|
||
Данные зависят от пользователя, прав доступа и фильтров, поэтому shared caching должен быть запрещен.
|
||
|
||
Recommended headers для успешных responses:
|
||
|
||
```text
|
||
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` должен быть единым для snapshot-а карты и таблицы организаций при одинаковых фильтрах;
|
||
- если разные блоки приходят из источников с разной актуальностью, backend должен либо привести их к единому
|
||
согласованному snapshot-у, либо явно согласовать и задокументировать методику;
|
||
- версия методики расчета уникальности организаций, участий и принадлежности к регионам должна быть зафиксирована
|
||
в backend-документации;
|
||
- если backend поддерживает версионирование методик, response может дополнительно возвращать
|
||
`calculation_methodology_version`;
|
||
- изменение методики, которое меняет смысл существующих полей, считается breaking change для `/api/v1`.
|
||
|
||
### Большие справочники и массивы
|
||
|
||
Dashboard-массивы `map.regions`, `map.federal_districts`, `association_list.rows` и
|
||
`region_ranking.rows` возвращаются полностью.
|
||
|
||
Табличный перечень организаций может быть большим, поэтому он вынесен в отдельный endpoint
|
||
`/association-map/organizations/` с `limit` и `offset`.
|
||
|
||
Требования к таблице организаций:
|
||
|
||
- `limit` default `500`;
|
||
- `limit` maximum `1000`;
|
||
- `offset` default `0`;
|
||
- `total_count` всегда возвращается независимо от `limit`;
|
||
- `unique_organizations_count` возвращается отдельно от количества строк, потому что одна организация
|
||
может иметь несколько строк участия;
|
||
- если текущий UI хочет показать таблицу без пагинации, frontend может запросить `limit=1000`;
|
||
- если `total_count > limit`, frontend должен догружать следующие страницы или включить пагинацию/виртуализацию.
|
||
|
||
Для справочника `organizations` в `/association-map/filters/` также возможен большой объем данных.
|
||
Если список может превышать безопасный размер для одного response, backend должен выбрать один из вариантов
|
||
до реализации:
|
||
|
||
- возвращать только доступные организации в пределах текущих фильтров, если объем гарантированно небольшой;
|
||
- добавить к `/association-map/filters/` query params `organization_search` и `organization_limit`;
|
||
- вынести поиск организаций в отдельный endpoint справочника.
|
||
|
||
Если вводятся `organization_search` и `organization_limit`, правила должны быть такими:
|
||
|
||
- `organization_search`: string, optional, минимум 2 символа после trim;
|
||
- `organization_limit`: integer, optional, default `50`, maximum `100`;
|
||
- сортировка организаций: релевантность поиска, затем `label` по возрастанию (`ru-RU`);
|
||
- выбранная в `organization_id` организация должна присутствовать в `organizations`, даже если она не попала бы
|
||
в первые `organization_limit` результатов.
|
||
|
||
---
|
||
|
||
## Термины и правила подсчета
|
||
|
||
### `association_type`
|
||
|
||
Тип инновационного объединения для карты.
|
||
|
||
Допустимые значения:
|
||
|
||
- `clusters`: промышленные кластеры;
|
||
- `technoparks`: технопарки;
|
||
- `sez`: особые экономические зоны.
|
||
|
||
Для страниц карты значение `all` не используется.
|
||
Объединенный режим всех типов относится к аналитической панели `/dashboard` и описан в отдельном документе.
|
||
|
||
Соответствие frontend routes:
|
||
|
||
| Route | `association_type` |
|
||
| ---------------------- | ------------------ |
|
||
| `/new-map` | `clusters` |
|
||
| `/new-map/technoparks` | `technoparks` |
|
||
| `/new-map/sez` | `sez` |
|
||
|
||
### `map_view_mode`
|
||
|
||
Тип отображения карты.
|
||
|
||
Допустимые значения:
|
||
|
||
- `regions`: карта субъектов РФ;
|
||
- `federal_districts`: карта федеральных округов.
|
||
|
||
`map_view_mode` сам по себе не меняет методику подсчета данных. Он определяет активное визуальное
|
||
представление и то, какие id frontend будет передавать при клике по карте.
|
||
|
||
### `region_id`
|
||
|
||
Идентификатор субъекта РФ для карты.
|
||
|
||
Требования:
|
||
|
||
- должен совпадать с SVG-id субъекта РФ в компоненте карты;
|
||
- формат: `RU-*`;
|
||
- примеры: `RU-MOS`, `RU-MOW`, `RU-TA`, `RU-CHE`;
|
||
- если у backend-а есть внутренний id региона, backend должен уметь сопоставить его с `map_region_id`.
|
||
|
||
### `federal_district_id`
|
||
|
||
Идентификатор федерального округа.
|
||
|
||
Требования:
|
||
|
||
- стабильный string id;
|
||
- используется в фильтрах, карте федеральных округов и связке субъектов РФ с ФО;
|
||
- рекомендуемые значения: `cfo`, `nwfo`, `yufo`, `pfo`, `ufo`, `sfo`, `dfo`, `skfo`.
|
||
|
||
### `region_ids` и `federal_district_ids`
|
||
|
||
Множественный выбор на карте.
|
||
|
||
Назначение:
|
||
|
||
- `region_ids` используется, когда пользователь выбрал один или несколько субъектов РФ на карте;
|
||
- `federal_district_ids` используется, когда пользователь выбрал один или несколько федеральных округов;
|
||
- оба массива работают как фильтр данных страницы.
|
||
|
||
Правила:
|
||
|
||
- массивы могут быть пустыми;
|
||
- пустой массив означает, что multi-selection на карте не применен;
|
||
- если передан массив, соответствующий одиночный select-фильтр должен быть пустым;
|
||
- `region_ids` и `federal_district_ids` взаимоисключающие;
|
||
- порядок id в `applied_filters` должен сохранять порядок, нормализованный backend-ом.
|
||
|
||
### `associations_count`
|
||
|
||
Количество инновационных объединений выбранного `association_type` после применения фильтров.
|
||
|
||
Примеры:
|
||
|
||
- для `clusters` это количество промышленных кластеров;
|
||
- для `technoparks` это количество технопарков;
|
||
- для `sez` это количество ОЭЗ.
|
||
|
||
### `opk_organizations_count`
|
||
|
||
Количество уникальных организаций ОПК после применения фильтров.
|
||
|
||
Одна организация ОПК может входить в несколько объединений. Поэтому это поле не должно считаться как простая
|
||
сумма строк таблицы организаций, если таблица возвращает строки участия.
|
||
|
||
### `opk_memberships_count`
|
||
|
||
Количество связей `организация ОПК x инновационное объединение`.
|
||
|
||
Это поле может быть больше `opk_organizations_count`.
|
||
Если таблица организаций возвращает строки участия, `total_count` таблицы может совпадать с
|
||
`opk_memberships_count`, но не обязан совпадать с `opk_organizations_count`.
|
||
|
||
### `organization_directory.total_count`
|
||
|
||
Количество строк в табличном перечне организаций после применения фильтров и до применения `limit`/`offset`.
|
||
|
||
Требования:
|
||
|
||
- значение используется для UI-счетчика `Найдено: N`;
|
||
- если таблица возвращает строки участия, а не уникальные организации, значение может быть больше
|
||
`unique_organizations_count`;
|
||
- семантика строки таблицы должна быть зафиксирована в поле `row_granularity`.
|
||
|
||
### `rank`
|
||
|
||
Позиция строки в рейтинге.
|
||
|
||
Требования:
|
||
|
||
- `rank` возвращается backend-ом как integer `>= 1`;
|
||
- если используется обычное порядковое ранжирование, `rank` не должен дублироваться;
|
||
- если бизнес-методика допускает одинаковые места при равных значениях, блок должен вернуть
|
||
`ranking_method`;
|
||
- допустимые значения `ranking_method`: `ordinal`, `dense`, `competition`;
|
||
- если `ranking_method` не передан, frontend считает методику `ordinal`.
|
||
|
||
### Полнота массивов
|
||
|
||
Если в блоке нет явного `limit`, массив `rows`, `regions` или `federal_districts` должен возвращаться полностью
|
||
для текущего состояния страницы и текущих фильтров.
|
||
|
||
Frontend может отображать часть данных в видимой области и скроллить контейнер локально, но backend не должен
|
||
неявно обрезать массив до первых 5-10 строк только потому, что в макете виден короткий пример.
|
||
|
||
---
|
||
|
||
## Нужно реализовать
|
||
|
||
## `competence-map association-map filters`
|
||
|
||
### `GET /api/v1/competence-map/association-map/filters/`
|
||
|
||
Описание:
|
||
|
||
- возвращает справочники для фильтров страниц карты;
|
||
- учитывает права пользователя;
|
||
- учитывает текущие выбранные фильтры, если они переданы в query params;
|
||
- возвращает серверно доступные значения `association_type`;
|
||
- возвращает доступные типы отображения карты;
|
||
- не возвращает KPI, рейтинг, список объединений и таблицу организаций;
|
||
- не управляет визуальным представлением фильтров: frontend может локально показывать типы объединений
|
||
как route navigation, tabs или segmented control.
|
||
|
||
Request:
|
||
|
||
```text
|
||
GET /api/v1/competence-map/association-map/filters/?association_type=clusters
|
||
```
|
||
|
||
Query params:
|
||
|
||
- `association_type`: `clusters | technoparks | sez`, optional, default `clusters`;
|
||
- `map_view_mode`: `regions | federal_districts`, optional, default `regions`;
|
||
- `federal_district_id`: string, optional;
|
||
- `region_id`: string, optional;
|
||
- `federal_district_ids`: array of string, optional;
|
||
- `region_ids`: array of string, optional;
|
||
- `industry_id`: string, optional;
|
||
- `integrated_structure_id`: string, optional;
|
||
- `organization_id`: string, optional;
|
||
- `competence_id`: string, optional.
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"generated_at": "2026-06-24T15:20:00+03:00",
|
||
"applied_filters": {
|
||
"association_type": "clusters",
|
||
"map_view_mode": "regions",
|
||
"federal_district_id": null,
|
||
"region_id": null,
|
||
"federal_district_ids": [],
|
||
"region_ids": [],
|
||
"industry_id": null,
|
||
"integrated_structure_id": null,
|
||
"organization_id": null,
|
||
"competence_id": null
|
||
},
|
||
"association_types": [
|
||
{
|
||
"id": "clusters",
|
||
"label": "Кластеры",
|
||
"route_path": "/new-map",
|
||
"associations_count": 124,
|
||
"disabled": false
|
||
},
|
||
{
|
||
"id": "technoparks",
|
||
"label": "Технопарки",
|
||
"route_path": "/new-map/technoparks",
|
||
"associations_count": 35,
|
||
"disabled": false
|
||
},
|
||
{
|
||
"id": "sez",
|
||
"label": "Особые экономические зоны",
|
||
"route_path": "/new-map/sez",
|
||
"associations_count": 17,
|
||
"disabled": false
|
||
}
|
||
],
|
||
"map_view_modes": [
|
||
{
|
||
"id": "regions",
|
||
"label": "Субъекты РФ",
|
||
"disabled": false
|
||
},
|
||
{
|
||
"id": "federal_districts",
|
||
"label": "Федеральные округа",
|
||
"disabled": false
|
||
}
|
||
],
|
||
"federal_districts": [
|
||
{
|
||
"id": "cfo",
|
||
"label": "Центральный ФО",
|
||
"region_ids": ["RU-BEL", "RU-MOS", "RU-MOW", "RU-YAR"],
|
||
"associations_count": 58,
|
||
"opk_organizations_count": 163,
|
||
"disabled": false
|
||
}
|
||
],
|
||
"regions": [
|
||
{
|
||
"id": "RU-MOS",
|
||
"map_region_id": "RU-MOS",
|
||
"federal_district_id": "cfo",
|
||
"label": "Московская область",
|
||
"associations_count": 17,
|
||
"opk_organizations_count": 68,
|
||
"disabled": false
|
||
}
|
||
],
|
||
"industries": [
|
||
{
|
||
"id": "radio-electronics",
|
||
"label": "Радиоэлектронная промышленность",
|
||
"opk_organizations_count": 180,
|
||
"disabled": false
|
||
}
|
||
],
|
||
"integrated_structures": [
|
||
{
|
||
"id": "rostec",
|
||
"label": "Госкорпорация Ростех",
|
||
"opk_organizations_count": 42,
|
||
"disabled": false
|
||
}
|
||
],
|
||
"organizations": [
|
||
{
|
||
"id": "org-concern-agat",
|
||
"label": "АО \"Концерн \"Моринсис - Агат\"",
|
||
"inn": "7700000000",
|
||
"ogrn": "1027700000000",
|
||
"association_memberships_count": 1,
|
||
"disabled": false
|
||
}
|
||
],
|
||
"competences": [
|
||
{
|
||
"id": "microelectronics",
|
||
"label": "Микроэлектроника",
|
||
"opk_organizations_count": 64,
|
||
"disabled": false
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### Требования к `association-map/filters/`
|
||
|
||
#### Поле `generated_at`
|
||
|
||
Требования:
|
||
|
||
- ISO 8601 с timezone;
|
||
- обязательное поле;
|
||
- единое время генерации для всего payload.
|
||
|
||
#### Блок `applied_filters`
|
||
|
||
Требования:
|
||
|
||
- возвращает фактически примененные фильтры;
|
||
- все отсутствующие одиночные значения возвращаются как `null`;
|
||
- все отсутствующие multi-selection значения возвращаются как `[]`;
|
||
- `association_type` всегда заполнен;
|
||
- `map_view_mode` всегда заполнен.
|
||
|
||
#### Массив `association_types`
|
||
|
||
Требования:
|
||
|
||
- содержит все доступные пользователю типы объединений для страниц карты;
|
||
- `id` должен быть одним из `clusters`, `technoparks`, `sez`;
|
||
- `label` обязательный;
|
||
- `route_path` optional, но если возвращается, должен соответствовать frontend route;
|
||
- `associations_count` integer `>= 0`;
|
||
- `disabled` boolean.
|
||
|
||
Назначение:
|
||
|
||
- является серверным источником доступных разделов карты;
|
||
- порядок массива определяет рекомендуемый порядок навигации;
|
||
- при выборе раздела frontend передает выбранный `id` как query param `association_type`;
|
||
- `associations_count` может использоваться как серверное значение для badge/count рядом с разделом;
|
||
- `disabled = true` означает, что тип недоступен пользователю или неприменим при текущих фильтрах.
|
||
|
||
#### Массив `map_view_modes`
|
||
|
||
Требования:
|
||
|
||
- содержит `regions` и `federal_districts`, если оба режима доступны пользователю;
|
||
- `id` обязательный;
|
||
- `label` обязательный;
|
||
- `disabled` обязательный.
|
||
|
||
#### Массивы справочников
|
||
|
||
Для массивов `federal_districts`, `regions`, `industries`, `integrated_structures`, `organizations`,
|
||
`competences`:
|
||
|
||
- `id` обязательный;
|
||
- `label` обязательный;
|
||
- `disabled` обязательный;
|
||
- count-поля должны быть integer `>= 0`;
|
||
- если по текущим фильтрам список пустой, возвращается пустой массив;
|
||
- выбранное значение должно присутствовать в справочнике, если пользователь имеет к нему доступ.
|
||
|
||
Для `regions` дополнительно:
|
||
|
||
- `map_region_id` обязательный;
|
||
- `federal_district_id` обязательный;
|
||
- `map_region_id` должен совпадать с id региона в SVG-карте субъектов РФ.
|
||
|
||
Для `federal_districts` дополнительно:
|
||
|
||
- `region_ids` обязательный;
|
||
- каждый `region_ids[*]` должен существовать в справочнике `regions`, если регион доступен пользователю.
|
||
|
||
---
|
||
|
||
## `competence-map association-map`
|
||
|
||
### `GET /api/v1/competence-map/association-map/`
|
||
|
||
Описание:
|
||
|
||
- возвращает основную аналитическую часть страницы карты для выбранного `association_type`;
|
||
- применяет все переданные фильтры;
|
||
- применяет выбранные на карте субъекты РФ или федеральные округа как фильтр;
|
||
- возвращает KPI, данные карты, список объединений, рейтинг субъектов РФ и summary таблицы организаций;
|
||
- не возвращает полный табличный перечень организаций, для него используется отдельный endpoint
|
||
`/association-map/organizations/`.
|
||
|
||
Request:
|
||
|
||
```text
|
||
GET /api/v1/competence-map/association-map/?association_type=clusters
|
||
```
|
||
|
||
Query params:
|
||
|
||
- `association_type`: `clusters | technoparks | sez`, optional, default `clusters`;
|
||
- `map_view_mode`: `regions | federal_districts`, optional, default `regions`;
|
||
- `federal_district_id`: string, optional;
|
||
- `region_id`: string, optional;
|
||
- `federal_district_ids`: array of string, optional;
|
||
- `region_ids`: array of string, optional;
|
||
- `industry_id`: string, optional;
|
||
- `integrated_structure_id`: string, optional;
|
||
- `organization_id`: string, optional;
|
||
- `competence_id`: string, optional.
|
||
|
||
Общий response envelope:
|
||
|
||
```json
|
||
{
|
||
"generated_at": "2026-06-24T15:20:00+03:00",
|
||
"data_actual_at": "2026-06-24T00:00:00+03:00",
|
||
"association_type": "clusters",
|
||
"applied_filters": {
|
||
"association_type": "clusters",
|
||
"map_view_mode": "regions",
|
||
"federal_district_id": null,
|
||
"region_id": null,
|
||
"federal_district_ids": [],
|
||
"region_ids": [],
|
||
"industry_id": null,
|
||
"integrated_structure_id": null,
|
||
"organization_id": null,
|
||
"competence_id": null
|
||
},
|
||
"page": {},
|
||
"kpis": [],
|
||
"map": {},
|
||
"association_list": {},
|
||
"region_ranking": {},
|
||
"organization_directory": {}
|
||
}
|
||
```
|
||
|
||
### Серверные состояния страницы по `association_type`
|
||
|
||
Страница имеет три серверных состояния. Они используют один и тот же endpoint
|
||
`/api/v1/competence-map/association-map/`.
|
||
|
||
| Frontend route | `association_type` | Primary KPI | Список объединений | Рейтинг |
|
||
| ---------------------- | ------------------ | ------------------- | --------------------- | ---------------------------------------------------- |
|
||
| `/new-map` | `clusters` | `clusters_count` | промышленные кластеры | субъекты РФ по участию организаций ОПК в кластерах |
|
||
| `/new-map/technoparks` | `technoparks` | `technoparks_count` | технопарки | субъекты РФ по участию организаций ОПК в технопарках |
|
||
| `/new-map/sez` | `sez` | `sez_count` | ОЭЗ | субъекты РФ по участию организаций ОПК в ОЭЗ |
|
||
|
||
Требования:
|
||
|
||
- `association_type` в корне response должен совпадать с `applied_filters.association_type`;
|
||
- frontend не должен делать отдельные endpoint-ы `/clusters`, `/technoparks`, `/sez`;
|
||
- при переходе между frontend routes меняется только `association_type` и повторно запрашиваются
|
||
`/association-map/filters/` и `/association-map/`;
|
||
- `map_view_mode` меняет активное представление карты, но не требует отдельного backend resource.
|
||
|
||
---
|
||
|
||
## Response для `association_type=clusters`
|
||
|
||
```json
|
||
{
|
||
"generated_at": "2026-06-24T15:20:00+03:00",
|
||
"data_actual_at": "2026-06-24T00:00:00+03:00",
|
||
"association_type": "clusters",
|
||
"applied_filters": {
|
||
"association_type": "clusters",
|
||
"map_view_mode": "regions",
|
||
"federal_district_id": null,
|
||
"region_id": null,
|
||
"federal_district_ids": [],
|
||
"region_ids": [],
|
||
"industry_id": null,
|
||
"integrated_structure_id": null,
|
||
"organization_id": null,
|
||
"competence_id": null
|
||
},
|
||
"page": {
|
||
"title": "Участие организаций ОПК в кластерах",
|
||
"subtitle": "Российская Федерация",
|
||
"legend_label": "Присутствие одной или более организаций ОПК в кластере",
|
||
"empty_list_label": "По текущим фильтрам кластеры не найдены."
|
||
},
|
||
"kpis": [
|
||
{
|
||
"code": "clusters_count",
|
||
"label": "Количество кластеров",
|
||
"value": 124,
|
||
"value_type": "integer",
|
||
"numerator": 124,
|
||
"denominator": null
|
||
},
|
||
{
|
||
"code": "opk_organizations_count",
|
||
"label": "Количество организаций ОПК",
|
||
"value": 517,
|
||
"value_type": "integer",
|
||
"numerator": 517,
|
||
"denominator": null
|
||
}
|
||
],
|
||
"map": {
|
||
"view_mode": "regions",
|
||
"selected_scope": null,
|
||
"selected_region_ids": [],
|
||
"selected_federal_district_ids": [],
|
||
"selected_chip_text": null,
|
||
"highlight_color": "#022456",
|
||
"regions": [
|
||
{
|
||
"region_id": "RU-MOS",
|
||
"map_region_id": "RU-MOS",
|
||
"region_name": "Московская область",
|
||
"federal_district_id": "cfo",
|
||
"associations_count": 17,
|
||
"opk_organizations_count": 68,
|
||
"has_data": true,
|
||
"is_highlighted": false,
|
||
"tooltip_label": "Московская область: 17 кластеров, 68 организаций ОПК"
|
||
},
|
||
{
|
||
"region_id": "RU-MOW",
|
||
"map_region_id": "RU-MOW",
|
||
"region_name": "Москва",
|
||
"federal_district_id": "cfo",
|
||
"associations_count": 11,
|
||
"opk_organizations_count": 34,
|
||
"has_data": true,
|
||
"is_highlighted": false,
|
||
"tooltip_label": "Москва: 11 кластеров, 34 организации ОПК"
|
||
}
|
||
],
|
||
"federal_districts": [
|
||
{
|
||
"federal_district_id": "cfo",
|
||
"federal_district_name": "Центральный ФО",
|
||
"region_ids": ["RU-MOS", "RU-MOW", "RU-YAR", "RU-BEL"],
|
||
"associations_count": 37,
|
||
"opk_organizations_count": 143,
|
||
"has_data": true,
|
||
"is_highlighted": false,
|
||
"tooltip_label": "Центральный ФО: 37 кластеров, 143 организации ОПК"
|
||
}
|
||
]
|
||
},
|
||
"association_list": {
|
||
"title": "Кластеры, в числе участников которых есть организации ОПК",
|
||
"row_metric_label": "Количество организаций ОПК",
|
||
"total_count": 124,
|
||
"rows": [
|
||
{
|
||
"association_id": "cluster-ru-mos-001",
|
||
"association_type": "clusters",
|
||
"association_name": "Радиоэлектронный кластер Подмосковья",
|
||
"region_id": "RU-MOS",
|
||
"region_name": "Московская область",
|
||
"federal_district_id": "cfo",
|
||
"opk_organizations_count": 9,
|
||
"opk_memberships_count": 9
|
||
}
|
||
]
|
||
},
|
||
"region_ranking": {
|
||
"title": "Рейтинг субъектов РФ по участию организаций ОПК в кластерах",
|
||
"sort": {
|
||
"field": "associations_count",
|
||
"direction": "desc"
|
||
},
|
||
"ranking_method": "ordinal",
|
||
"columns": [
|
||
{
|
||
"field": "associations_count",
|
||
"label": "Количество кластеров",
|
||
"value_type": "integer"
|
||
},
|
||
{
|
||
"field": "opk_organizations_count",
|
||
"label": "Количество организаций ОПК",
|
||
"value_type": "integer"
|
||
}
|
||
],
|
||
"rows": [
|
||
{
|
||
"rank": 1,
|
||
"region_id": "RU-MOS",
|
||
"region_name": "Московская область",
|
||
"federal_district_id": "cfo",
|
||
"associations_count": 17,
|
||
"opk_organizations_count": 68,
|
||
"opk_memberships_count": 68
|
||
},
|
||
{
|
||
"rank": 2,
|
||
"region_id": "RU-MOW",
|
||
"region_name": "Москва",
|
||
"federal_district_id": "cfo",
|
||
"associations_count": 11,
|
||
"opk_organizations_count": 34,
|
||
"opk_memberships_count": 34
|
||
}
|
||
]
|
||
},
|
||
"organization_directory": {
|
||
"title": "Перечень организаций ОПК",
|
||
"endpoint": "/api/v1/competence-map/association-map/organizations/",
|
||
"row_granularity": "organization_association_membership",
|
||
"total_count": 517,
|
||
"unique_organizations_count": 517
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Response для `association_type=technoparks`
|
||
|
||
```json
|
||
{
|
||
"generated_at": "2026-06-24T15:20:00+03:00",
|
||
"data_actual_at": "2026-06-24T00:00:00+03:00",
|
||
"association_type": "technoparks",
|
||
"applied_filters": {
|
||
"association_type": "technoparks",
|
||
"map_view_mode": "regions",
|
||
"federal_district_id": null,
|
||
"region_id": null,
|
||
"federal_district_ids": [],
|
||
"region_ids": [],
|
||
"industry_id": null,
|
||
"integrated_structure_id": null,
|
||
"organization_id": null,
|
||
"competence_id": null
|
||
},
|
||
"page": {
|
||
"title": "Участие организаций ОПК в технопарках",
|
||
"subtitle": "Российская Федерация",
|
||
"legend_label": "Присутствие одной или более организаций ОПК в технопарке",
|
||
"empty_list_label": "По текущим фильтрам технопарки не найдены."
|
||
},
|
||
"kpis": [
|
||
{
|
||
"code": "technoparks_count",
|
||
"label": "Количество технопарков",
|
||
"value": 35,
|
||
"value_type": "integer",
|
||
"numerator": 35,
|
||
"denominator": null
|
||
},
|
||
{
|
||
"code": "opk_organizations_count",
|
||
"label": "Количество организаций ОПК",
|
||
"value": 66,
|
||
"value_type": "integer",
|
||
"numerator": 66,
|
||
"denominator": null
|
||
}
|
||
],
|
||
"map": {
|
||
"view_mode": "regions",
|
||
"selected_scope": null,
|
||
"selected_region_ids": [],
|
||
"selected_federal_district_ids": [],
|
||
"selected_chip_text": null,
|
||
"highlight_color": "#022456",
|
||
"regions": [
|
||
{
|
||
"region_id": "RU-MOS",
|
||
"map_region_id": "RU-MOS",
|
||
"region_name": "Московская область",
|
||
"federal_district_id": "cfo",
|
||
"associations_count": 6,
|
||
"opk_organizations_count": 12,
|
||
"has_data": true,
|
||
"is_highlighted": false,
|
||
"tooltip_label": "Московская область: 6 технопарков, 12 организаций ОПК"
|
||
}
|
||
],
|
||
"federal_districts": [
|
||
{
|
||
"federal_district_id": "cfo",
|
||
"federal_district_name": "Центральный ФО",
|
||
"region_ids": ["RU-MOS", "RU-MOW", "RU-YAR", "RU-BEL"],
|
||
"associations_count": 14,
|
||
"opk_organizations_count": 28,
|
||
"has_data": true,
|
||
"is_highlighted": false,
|
||
"tooltip_label": "Центральный ФО: 14 технопарков, 28 организаций ОПК"
|
||
}
|
||
]
|
||
},
|
||
"association_list": {
|
||
"title": "Технопарки, в числе участников которых есть организации ОПК",
|
||
"row_metric_label": "Количество организаций ОПК",
|
||
"total_count": 35,
|
||
"rows": [
|
||
{
|
||
"association_id": "technopark-ru-mos-001",
|
||
"association_type": "technoparks",
|
||
"association_name": "Подмосковный технопарк высоких технологий",
|
||
"region_id": "RU-MOS",
|
||
"region_name": "Московская область",
|
||
"federal_district_id": "cfo",
|
||
"opk_organizations_count": 4,
|
||
"opk_memberships_count": 4
|
||
}
|
||
]
|
||
},
|
||
"region_ranking": {
|
||
"title": "Рейтинг субъектов РФ по участию организаций ОПК в технопарках",
|
||
"sort": {
|
||
"field": "associations_count",
|
||
"direction": "desc"
|
||
},
|
||
"ranking_method": "ordinal",
|
||
"columns": [
|
||
{
|
||
"field": "associations_count",
|
||
"label": "Количество технопарков",
|
||
"value_type": "integer"
|
||
},
|
||
{
|
||
"field": "opk_organizations_count",
|
||
"label": "Количество организаций ОПК",
|
||
"value_type": "integer"
|
||
}
|
||
],
|
||
"rows": [
|
||
{
|
||
"rank": 1,
|
||
"region_id": "RU-MOS",
|
||
"region_name": "Московская область",
|
||
"federal_district_id": "cfo",
|
||
"associations_count": 6,
|
||
"opk_organizations_count": 12,
|
||
"opk_memberships_count": 12
|
||
}
|
||
]
|
||
},
|
||
"organization_directory": {
|
||
"title": "Перечень организаций ОПК",
|
||
"endpoint": "/api/v1/competence-map/association-map/organizations/",
|
||
"row_granularity": "organization_association_membership",
|
||
"total_count": 66,
|
||
"unique_organizations_count": 66
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Response для `association_type=sez`
|
||
|
||
```json
|
||
{
|
||
"generated_at": "2026-06-24T15:20:00+03:00",
|
||
"data_actual_at": "2026-06-24T00:00:00+03:00",
|
||
"association_type": "sez",
|
||
"applied_filters": {
|
||
"association_type": "sez",
|
||
"map_view_mode": "regions",
|
||
"federal_district_id": null,
|
||
"region_id": null,
|
||
"federal_district_ids": [],
|
||
"region_ids": [],
|
||
"industry_id": null,
|
||
"integrated_structure_id": null,
|
||
"organization_id": null,
|
||
"competence_id": null
|
||
},
|
||
"page": {
|
||
"title": "Участие организаций ОПК в особых экономических зонах",
|
||
"subtitle": "Российская Федерация",
|
||
"legend_label": "Присутствие одной или более организаций ОПК в особой экономической зоне",
|
||
"empty_list_label": "По текущим фильтрам особые экономические зоны не найдены."
|
||
},
|
||
"kpis": [
|
||
{
|
||
"code": "sez_count",
|
||
"label": "Количество ОЭЗ",
|
||
"value": 17,
|
||
"value_type": "integer",
|
||
"numerator": 17,
|
||
"denominator": null
|
||
},
|
||
{
|
||
"code": "opk_organizations_count",
|
||
"label": "Количество организаций ОПК",
|
||
"value": 64,
|
||
"value_type": "integer",
|
||
"numerator": 64,
|
||
"denominator": null
|
||
}
|
||
],
|
||
"map": {
|
||
"view_mode": "regions",
|
||
"selected_scope": null,
|
||
"selected_region_ids": [],
|
||
"selected_federal_district_ids": [],
|
||
"selected_chip_text": null,
|
||
"highlight_color": "#022456",
|
||
"regions": [
|
||
{
|
||
"region_id": "RU-KGN",
|
||
"map_region_id": "RU-KGN",
|
||
"region_name": "Курганская область",
|
||
"federal_district_id": "ufo",
|
||
"associations_count": 9,
|
||
"opk_organizations_count": 9,
|
||
"has_data": true,
|
||
"is_highlighted": false,
|
||
"tooltip_label": "Курганская область: 9 ОЭЗ, 9 организаций ОПК"
|
||
}
|
||
],
|
||
"federal_districts": [
|
||
{
|
||
"federal_district_id": "ufo",
|
||
"federal_district_name": "Уральский ФО",
|
||
"region_ids": ["RU-CHE", "RU-KGN", "RU-SVE"],
|
||
"associations_count": 15,
|
||
"opk_organizations_count": 21,
|
||
"has_data": true,
|
||
"is_highlighted": false,
|
||
"tooltip_label": "Уральский ФО: 15 ОЭЗ, 21 организация ОПК"
|
||
}
|
||
]
|
||
},
|
||
"association_list": {
|
||
"title": "Особые экономические зоны, в числе участников которых есть организации ОПК",
|
||
"row_metric_label": "Количество организаций ОПК",
|
||
"total_count": 17,
|
||
"rows": [
|
||
{
|
||
"association_id": "sez-kurgan-4",
|
||
"association_type": "sez",
|
||
"association_name": "Особая экономическая зона \"Курганский 4\"",
|
||
"region_id": "RU-KGN",
|
||
"region_name": "Курганская область",
|
||
"federal_district_id": "ufo",
|
||
"opk_organizations_count": 9,
|
||
"opk_memberships_count": 9
|
||
}
|
||
]
|
||
},
|
||
"region_ranking": {
|
||
"title": "Рейтинг субъектов РФ по участию организаций ОПК в особых экономических зонах",
|
||
"sort": {
|
||
"field": "associations_count",
|
||
"direction": "desc"
|
||
},
|
||
"ranking_method": "ordinal",
|
||
"columns": [
|
||
{
|
||
"field": "associations_count",
|
||
"label": "Количество ОЭЗ",
|
||
"value_type": "integer"
|
||
},
|
||
{
|
||
"field": "opk_organizations_count",
|
||
"label": "Количество организаций ОПК",
|
||
"value_type": "integer"
|
||
}
|
||
],
|
||
"rows": [
|
||
{
|
||
"rank": 1,
|
||
"region_id": "RU-KGN",
|
||
"region_name": "Курганская область",
|
||
"federal_district_id": "ufo",
|
||
"associations_count": 9,
|
||
"opk_organizations_count": 9,
|
||
"opk_memberships_count": 9
|
||
}
|
||
]
|
||
},
|
||
"organization_directory": {
|
||
"title": "Перечень организаций ОПК",
|
||
"endpoint": "/api/v1/competence-map/association-map/organizations/",
|
||
"row_granularity": "organization_association_membership",
|
||
"total_count": 64,
|
||
"unique_organizations_count": 64
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## `competence-map association-map organizations`
|
||
|
||
### `GET /api/v1/competence-map/association-map/organizations/`
|
||
|
||
Описание:
|
||
|
||
- возвращает табличный перечень организаций ОПК для выбранного состояния карты;
|
||
- применяет те же фильтры, что `/association-map/`;
|
||
- используется для экрана, который открывается по ссылке `Перейти к перечню организаций`;
|
||
- возвращает строки таблицы, счетчик `Найдено`, pagination metadata и нормализованные примененные фильтры.
|
||
|
||
Request:
|
||
|
||
```text
|
||
GET /api/v1/competence-map/association-map/organizations/?association_type=clusters®ion_ids=RU-MOS&limit=500&offset=0
|
||
```
|
||
|
||
Query params:
|
||
|
||
- `association_type`: `clusters | technoparks | sez`, optional, default `clusters`;
|
||
- `map_view_mode`: `regions | federal_districts`, optional, default `regions`;
|
||
- `federal_district_id`: string, optional;
|
||
- `region_id`: string, optional;
|
||
- `federal_district_ids`: array of string, optional;
|
||
- `region_ids`: array of string, optional;
|
||
- `industry_id`: string, optional;
|
||
- `integrated_structure_id`: string, optional;
|
||
- `organization_id`: string, optional;
|
||
- `competence_id`: string, optional;
|
||
- `limit`: integer, optional, default `500`, maximum `1000`;
|
||
- `offset`: integer, optional, default `0`;
|
||
- `sort_field`: string, optional, default `serial_number`;
|
||
- `sort_direction`: `asc | desc`, optional, default `asc`.
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"generated_at": "2026-06-24T15:20:00+03:00",
|
||
"data_actual_at": "2026-06-24T00:00:00+03:00",
|
||
"association_type": "clusters",
|
||
"applied_filters": {
|
||
"association_type": "clusters",
|
||
"map_view_mode": "regions",
|
||
"federal_district_id": null,
|
||
"region_id": null,
|
||
"federal_district_ids": [],
|
||
"region_ids": ["RU-MOS"],
|
||
"industry_id": null,
|
||
"integrated_structure_id": null,
|
||
"organization_id": null,
|
||
"competence_id": null
|
||
},
|
||
"row_granularity": "organization_association_membership",
|
||
"total_count": 68,
|
||
"unique_organizations_count": 68,
|
||
"pagination": {
|
||
"limit": 500,
|
||
"offset": 0,
|
||
"has_next": false,
|
||
"has_previous": false
|
||
},
|
||
"sort": {
|
||
"field": "serial_number",
|
||
"direction": "asc"
|
||
},
|
||
"columns": [
|
||
{
|
||
"field": "serial_number",
|
||
"label": "№ п/п",
|
||
"value_type": "integer"
|
||
},
|
||
{
|
||
"field": "organization_name",
|
||
"label": "Наименование",
|
||
"value_type": "string"
|
||
},
|
||
{
|
||
"field": "director_full_name",
|
||
"label": "ФИО руководителя",
|
||
"value_type": "string"
|
||
},
|
||
{
|
||
"field": "official_emails",
|
||
"label": "Официальная почта",
|
||
"value_type": "string_array"
|
||
},
|
||
{
|
||
"field": "official_phones",
|
||
"label": "Официальный телефон",
|
||
"value_type": "string_array"
|
||
},
|
||
{
|
||
"field": "industry_name",
|
||
"label": "Отрасль",
|
||
"value_type": "string"
|
||
},
|
||
{
|
||
"field": "activity_type_name",
|
||
"label": "Вид деятельности",
|
||
"value_type": "string"
|
||
},
|
||
{
|
||
"field": "ownership_type_name",
|
||
"label": "Тип собственности",
|
||
"value_type": "string"
|
||
}
|
||
],
|
||
"rows": [
|
||
{
|
||
"row_id": "cluster-ru-mos-001:org-concern-agat",
|
||
"serial_number": 1,
|
||
"organization_id": "org-concern-agat",
|
||
"organization_name": "АО \"Концерн \"Моринсис - Агат\"",
|
||
"director_full_name": "Храмов М.Ю.",
|
||
"official_emails": ["info@concern-agat.ru", "gen_director@concern-agat.ru"],
|
||
"official_phones": ["603-90-05"],
|
||
"industry_id": "radio-electronics",
|
||
"industry_name": "Радиоэлектронная промышленность",
|
||
"activity_type_id": "research-organizations",
|
||
"activity_type_name": "Научные организации",
|
||
"ownership_type_id": "directly-or-indirectly-controlled",
|
||
"ownership_type_name": "Прямо или косвенно контролируемые",
|
||
"association_membership": {
|
||
"association_type": "clusters",
|
||
"association_id": "cluster-ru-mos-001",
|
||
"association_name": "Радиоэлектронный кластер Подмосковья",
|
||
"region_id": "RU-MOS",
|
||
"region_name": "Московская область",
|
||
"federal_district_id": "cfo"
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### Требования к `association-map/organizations/`
|
||
|
||
#### Поле `row_granularity`
|
||
|
||
Допустимые значения:
|
||
|
||
- `unique_organization`;
|
||
- `organization_association_membership`.
|
||
|
||
Для текущего табличного представления рекомендуется `organization_association_membership`, потому что
|
||
одна организация может участвовать в нескольких объединениях или отображаться в разных отраслевых контекстах.
|
||
|
||
Требования:
|
||
|
||
- если `row_granularity = unique_organization`, `total_count` должен быть равен
|
||
`unique_organizations_count`;
|
||
- если `row_granularity = organization_association_membership`, `total_count` может быть больше
|
||
`unique_organizations_count`;
|
||
- `row_id` должен быть стабильным id строки, а не только `organization_id`.
|
||
|
||
#### Поля организации
|
||
|
||
Требования:
|
||
|
||
- `organization_id` обязательный;
|
||
- `organization_name` обязательный;
|
||
- `director_full_name` может быть `null`, если данных нет;
|
||
- `official_emails` возвращается массивом строк, может быть пустым массивом;
|
||
- `official_phones` возвращается массивом строк, может быть пустым массивом;
|
||
- `industry_id` и `industry_name` обязательны, если строка имеет отраслевой контекст;
|
||
- `activity_type_id` и `activity_type_name` обязательны, если источник данных содержит вид деятельности;
|
||
- `ownership_type_id` и `ownership_type_name` обязательны, если источник данных содержит тип собственности;
|
||
- неизвестные значения возвращаются как `null` или пустой массив, а не как строка `-`.
|
||
|
||
#### Блок `association_membership`
|
||
|
||
Требования:
|
||
|
||
- обязателен, если `row_granularity = organization_association_membership`;
|
||
- `association_type` должен совпадать с корневым `association_type`;
|
||
- `association_id` обязательный;
|
||
- `association_name` обязательный;
|
||
- `region_id` должен быть совместим с SVG-картой;
|
||
- `federal_district_id` обязательный.
|
||
|
||
---
|
||
|
||
## Требования к общим полям association-map response
|
||
|
||
### Поле `generated_at`
|
||
|
||
Требования:
|
||
|
||
- ISO 8601 с timezone;
|
||
- обязательное;
|
||
- показывает время формирования response.
|
||
|
||
### Поле `data_actual_at`
|
||
|
||
Требования:
|
||
|
||
- ISO 8601 с timezone;
|
||
- обязательное для `/association-map/` и `/association-map/organizations/`;
|
||
- показывает дату и время актуальности данных.
|
||
|
||
### Поле `association_type`
|
||
|
||
Требования:
|
||
|
||
- обязательное;
|
||
- должно совпадать с фактически примененным режимом;
|
||
- допустимые значения: `clusters`, `technoparks`, `sez`.
|
||
|
||
### Блок `applied_filters`
|
||
|
||
Требования:
|
||
|
||
- обязательный;
|
||
- содержит нормализованные значения примененных фильтров;
|
||
- отсутствующие одиночные фильтры возвращаются как `null`;
|
||
- отсутствующие multi-selection фильтры возвращаются как `[]`;
|
||
- `association_type` и `map_view_mode` всегда заполнены.
|
||
|
||
### Блок `page`
|
||
|
||
Поля:
|
||
|
||
- `title`;
|
||
- `subtitle`;
|
||
- `legend_label`;
|
||
- `empty_list_label`.
|
||
|
||
Требования:
|
||
|
||
- все поля обязательны для `/association-map/`;
|
||
- значения должны соответствовать текущему `association_type`;
|
||
- frontend может использовать эти строки напрямую или локализовать их на своей стороне.
|
||
|
||
### Массив `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`;
|
||
- порядок массива должен соответствовать порядку карточек на странице;
|
||
- для текущих страниц карты порядок: count выбранного типа объединения, затем количество организаций ОПК.
|
||
|
||
### Блок `map`
|
||
|
||
Поля:
|
||
|
||
- `view_mode`;
|
||
- `selected_scope`;
|
||
- `selected_region_ids`;
|
||
- `selected_federal_district_ids`;
|
||
- `selected_chip_text`;
|
||
- `highlight_color`;
|
||
- `regions`;
|
||
- `federal_districts`.
|
||
|
||
Требования:
|
||
|
||
- `view_mode` должен совпадать с `applied_filters.map_view_mode`;
|
||
- `selected_scope` должен быть `regions`, `federal_districts` или `null`;
|
||
- если применен `region_id` или `region_ids`, `selected_scope = regions`;
|
||
- если применен `federal_district_id` или `federal_district_ids`, `selected_scope = federal_districts`;
|
||
- `selected_region_ids` и `selected_federal_district_ids` обязательны, даже если пустые;
|
||
- selected ids описывают эффективный выбор географии, включая одиночные select-фильтры
|
||
`region_id`/`federal_district_id` и multi-selection фильтры `region_ids`/`federal_district_ids`;
|
||
- `selected_chip_text` может быть `null`, если ничего не выбрано;
|
||
- `highlight_color` optional, но если возвращается, должен быть CSS-compatible color string;
|
||
- `regions` должен включать все доступные пользователю субъекты РФ, а не только подсвеченные;
|
||
- `federal_districts` должен включать все доступные пользователю федеральные округа, а не только подсвеченные.
|
||
|
||
### `map.regions`
|
||
|
||
Каждый объект массива - один субъект РФ.
|
||
|
||
Поля:
|
||
|
||
- `region_id`;
|
||
- `map_region_id`;
|
||
- `region_name`;
|
||
- `federal_district_id`;
|
||
- `associations_count`;
|
||
- `opk_organizations_count`;
|
||
- `has_data`;
|
||
- `is_highlighted`;
|
||
- `tooltip_label`.
|
||
|
||
Требования:
|
||
|
||
- `region_id` обязательный;
|
||
- `map_region_id` обязательный;
|
||
- `map_region_id` должен совпадать с SVG-id региона;
|
||
- `associations_count` integer `>= 0`;
|
||
- `opk_organizations_count` integer `>= 0`;
|
||
- `has_data = true`, если по текущим фильтрам в регионе есть хотя бы одно объединение выбранного типа
|
||
или хотя бы одна организация ОПК;
|
||
- `is_highlighted = true`, если регион должен быть подсвечен при текущих фильтрах/выборе;
|
||
- `tooltip_label` optional, frontend может построить подпись локально из raw-полей.
|
||
|
||
### `map.federal_districts`
|
||
|
||
Каждый объект массива - один федеральный округ.
|
||
|
||
Поля:
|
||
|
||
- `federal_district_id`;
|
||
- `federal_district_name`;
|
||
- `region_ids`;
|
||
- `associations_count`;
|
||
- `opk_organizations_count`;
|
||
- `has_data`;
|
||
- `is_highlighted`;
|
||
- `tooltip_label`.
|
||
|
||
Требования:
|
||
|
||
- `federal_district_id` обязательный;
|
||
- `region_ids` обязательный;
|
||
- `region_ids` должен содержать id субъектов РФ, входящих в ФО и доступных пользователю;
|
||
- count-поля должны быть агрегированы по регионам округа с учетом текущих фильтров;
|
||
- `is_highlighted = true`, если округ должен быть подсвечен при текущих фильтрах/выборе.
|
||
|
||
### Блок `association_list`
|
||
|
||
Поля:
|
||
|
||
- `title`;
|
||
- `row_metric_label`;
|
||
- `total_count`;
|
||
- `rows`.
|
||
|
||
Требования:
|
||
|
||
- `title` должен соответствовать текущему `association_type`;
|
||
- `row_metric_label` для текущего UI: `Количество организаций ОПК`;
|
||
- `total_count` integer `>= 0`;
|
||
- `rows` возвращаются полностью для текущих фильтров;
|
||
- строки должны быть отсортированы по `opk_organizations_count` по убыванию, затем
|
||
`association_name` по возрастанию (`ru-RU`), если не задан другой backend-порядок.
|
||
|
||
Каждая строка:
|
||
|
||
- `association_id` обязательный;
|
||
- `association_type` обязательный;
|
||
- `association_name` обязательный;
|
||
- `region_id` обязательный;
|
||
- `region_name` обязательный;
|
||
- `federal_district_id` обязательный;
|
||
- `opk_organizations_count` integer `>= 0`;
|
||
- `opk_memberships_count` integer `>= 0`.
|
||
|
||
### Блок `region_ranking`
|
||
|
||
Поля:
|
||
|
||
- `title`;
|
||
- `sort`;
|
||
- `ranking_method`;
|
||
- `columns`;
|
||
- `rows`.
|
||
|
||
Требования:
|
||
|
||
- `title` обязательный;
|
||
- `sort.field` обязательный;
|
||
- `sort.direction` должен быть `asc | desc`;
|
||
- `ranking_method` optional, default `ordinal`;
|
||
- `columns` обязательный, чтобы frontend не хардкодил `Количество кластеров` для страниц ОЭЗ/технопарков;
|
||
- `rows` может быть пустым массивом;
|
||
- `rank` возвращается в каждой строке;
|
||
- `region_id` обязательный;
|
||
- `region_name` обязательный;
|
||
- `associations_count` integer `>= 0`;
|
||
- `opk_organizations_count` integer `>= 0`.
|
||
|
||
### Блок `organization_directory`
|
||
|
||
Поля:
|
||
|
||
- `title`;
|
||
- `endpoint`;
|
||
- `row_granularity`;
|
||
- `total_count`;
|
||
- `unique_organizations_count`.
|
||
|
||
Требования:
|
||
|
||
- используется как summary для кнопки/ссылки перехода к таблице организаций;
|
||
- `endpoint` должен указывать на `/api/v1/competence-map/association-map/organizations/`;
|
||
- `total_count` должен совпадать с `total_count`, который вернет organizations endpoint при тех же фильтрах;
|
||
- `unique_organizations_count` должен совпадать с `unique_organizations_count`, который вернет organizations endpoint
|
||
при тех же фильтрах.
|
||
|
||
---
|
||
|
||
## Порядок массивов
|
||
|
||
### `association_types`
|
||
|
||
Рекомендуемый порядок:
|
||
|
||
1. `clusters`
|
||
2. `technoparks`
|
||
3. `sez`
|
||
|
||
### `map_view_modes`
|
||
|
||
Рекомендуемый порядок:
|
||
|
||
1. `regions`
|
||
2. `federal_districts`
|
||
|
||
### `map.regions`
|
||
|
||
Порядок сортировки:
|
||
|
||
1. `federal_district_id` в порядке справочника федеральных округов;
|
||
2. `region_name` по возрастанию (`ru-RU`).
|
||
|
||
### `map.federal_districts`
|
||
|
||
Порядок сортировки должен соответствовать порядку федеральных округов на backend-е.
|
||
Если такого порядка нет, использовать:
|
||
|
||
1. `cfo`
|
||
2. `nwfo`
|
||
3. `yufo`
|
||
4. `skfo`
|
||
5. `pfo`
|
||
6. `ufo`
|
||
7. `sfo`
|
||
8. `dfo`
|
||
|
||
### `association_list.rows`
|
||
|
||
Порядок сортировки:
|
||
|
||
1. `opk_organizations_count` по убыванию;
|
||
2. `association_name` по возрастанию (`ru-RU`);
|
||
3. `association_id` по возрастанию.
|
||
|
||
### `region_ranking.rows`
|
||
|
||
Порядок сортировки по умолчанию:
|
||
|
||
1. `associations_count` по убыванию;
|
||
2. при равенстве - `opk_organizations_count` по убыванию;
|
||
3. при равенстве - `region_name` по возрастанию (`ru-RU`).
|
||
|
||
Если в блоке передан `ranking_method = dense` или `competition`, одинаковые `rank` допустимы
|
||
только для строк с равным основным показателем.
|
||
|
||
### `organizations.rows`
|
||
|
||
Порядок сортировки по умолчанию:
|
||
|
||
1. `serial_number` по возрастанию.
|
||
|
||
`serial_number` должен соответствовать позиции строки после применения фильтров и сортировки,
|
||
а не позиции внутри текущей страницы `limit`/`offset`.
|
||
|
||
---
|
||
|
||
## Проверки консистентности
|
||
|
||
Backend должен гарантировать:
|
||
|
||
- `association_type` в корне response совпадает с `applied_filters.association_type`;
|
||
- `map.view_mode` совпадает с `applied_filters.map_view_mode`;
|
||
- `kpis[*].value` согласованы с соответствующими блоками карты и таблицы;
|
||
- primary KPI count выбранного типа объединения равен `association_list.total_count`;
|
||
- `organization_directory.total_count` равен `total_count` organizations endpoint-а при тех же фильтрах;
|
||
- `organization_directory.unique_organizations_count` равен `unique_organizations_count` organizations endpoint-а
|
||
при тех же фильтрах;
|
||
- `map.regions[*].region_id` существует в справочнике `regions`;
|
||
- `map.federal_districts[*].region_ids` ссылаются на существующие `map.regions[*].region_id`;
|
||
- `region_ranking.rows[*].region_id` существует в `map.regions`;
|
||
- если `selected_scope = regions`, `selected_region_ids` не пустой и `selected_federal_district_ids` пустой;
|
||
- если `selected_scope = federal_districts`, `selected_federal_district_ids` не пустой и `selected_region_ids`
|
||
пустой;
|
||
- если `selected_scope = null`, оба массива selected ids пустые;
|
||
- `rank` в рейтингах не дублируется, если `ranking_method = ordinal`;
|
||
- все `id` стабильны между запросами;
|
||
- все count-поля возвращаются как integer `>= 0`;
|
||
- пустые данные возвращаются пустыми массивами, а не отсутствующими полями.
|
||
|
||
---
|
||
|
||
## Успешный пустой ответ
|
||
|
||
Если по фильтрам данных нет, возвращается `200 OK`.
|
||
|
||
Пример:
|
||
|
||
```json
|
||
{
|
||
"generated_at": "2026-06-24T15:20:00+03:00",
|
||
"data_actual_at": "2026-06-24T00:00:00+03:00",
|
||
"association_type": "clusters",
|
||
"applied_filters": {
|
||
"association_type": "clusters",
|
||
"map_view_mode": "regions",
|
||
"federal_district_id": null,
|
||
"region_id": "RU-NEN",
|
||
"federal_district_ids": [],
|
||
"region_ids": [],
|
||
"industry_id": null,
|
||
"integrated_structure_id": null,
|
||
"organization_id": null,
|
||
"competence_id": null
|
||
},
|
||
"page": {
|
||
"title": "Участие организаций ОПК в кластерах",
|
||
"subtitle": "Российская Федерация",
|
||
"legend_label": "Присутствие одной или более организаций ОПК в кластере",
|
||
"empty_list_label": "По текущим фильтрам кластеры не найдены."
|
||
},
|
||
"kpis": [
|
||
{
|
||
"code": "clusters_count",
|
||
"label": "Количество кластеров",
|
||
"value": 0,
|
||
"value_type": "integer",
|
||
"numerator": 0,
|
||
"denominator": null
|
||
},
|
||
{
|
||
"code": "opk_organizations_count",
|
||
"label": "Количество организаций ОПК",
|
||
"value": 0,
|
||
"value_type": "integer",
|
||
"numerator": 0,
|
||
"denominator": null
|
||
}
|
||
],
|
||
"map": {
|
||
"view_mode": "regions",
|
||
"selected_scope": "regions",
|
||
"selected_region_ids": ["RU-NEN"],
|
||
"selected_federal_district_ids": [],
|
||
"selected_chip_text": "Ненецкий автономный округ: 0 кластеров",
|
||
"highlight_color": "#022456",
|
||
"regions": [
|
||
{
|
||
"region_id": "RU-NEN",
|
||
"map_region_id": "RU-NEN",
|
||
"region_name": "Ненецкий автономный округ",
|
||
"federal_district_id": "nwfo",
|
||
"associations_count": 0,
|
||
"opk_organizations_count": 0,
|
||
"has_data": false,
|
||
"is_highlighted": false,
|
||
"tooltip_label": "Ненецкий автономный округ: данных по текущим фильтрам нет"
|
||
}
|
||
],
|
||
"federal_districts": [
|
||
{
|
||
"federal_district_id": "nwfo",
|
||
"federal_district_name": "Северо-Западный ФО",
|
||
"region_ids": ["RU-NEN"],
|
||
"associations_count": 0,
|
||
"opk_organizations_count": 0,
|
||
"has_data": false,
|
||
"is_highlighted": false,
|
||
"tooltip_label": "Северо-Западный ФО: данных по текущим фильтрам нет"
|
||
}
|
||
]
|
||
},
|
||
"association_list": {
|
||
"title": "Кластеры, в числе участников которых есть организации ОПК",
|
||
"row_metric_label": "Количество организаций ОПК",
|
||
"total_count": 0,
|
||
"rows": []
|
||
},
|
||
"region_ranking": {
|
||
"title": "Рейтинг субъектов РФ по участию организаций ОПК в кластерах",
|
||
"sort": {
|
||
"field": "associations_count",
|
||
"direction": "desc"
|
||
},
|
||
"ranking_method": "ordinal",
|
||
"columns": [
|
||
{
|
||
"field": "associations_count",
|
||
"label": "Количество кластеров",
|
||
"value_type": "integer"
|
||
},
|
||
{
|
||
"field": "opk_organizations_count",
|
||
"label": "Количество организаций ОПК",
|
||
"value_type": "integer"
|
||
}
|
||
],
|
||
"rows": []
|
||
},
|
||
"organization_directory": {
|
||
"title": "Перечень организаций ОПК",
|
||
"endpoint": "/api/v1/competence-map/association-map/organizations/",
|
||
"row_granularity": "organization_association_membership",
|
||
"total_count": 0,
|
||
"unique_organizations_count": 0
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Статус-коды и ошибки
|
||
|
||
### 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:
|
||
|
||
```text
|
||
400 Bad Request
|
||
```
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"type": "https://api.example.ru/problems/invalid-association-type",
|
||
"title": "Invalid association_type",
|
||
"status": 400,
|
||
"detail": "Unsupported association_type: all",
|
||
"instance": "/api/v1/competence-map/association-map/?association_type=all",
|
||
"code": "invalid_association_type",
|
||
"request_id": "req-01JYX4M2K7Y6WQ9RJ8T2V7VY5R",
|
||
"received_value": "all",
|
||
"allowed_values": ["clusters", "technoparks", "sez"]
|
||
}
|
||
```
|
||
|
||
Для несуществующего фильтра:
|
||
|
||
HTTP status:
|
||
|
||
```text
|
||
400 Bad Request
|
||
```
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"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/association-map/?association_type=clusters®ion_id=unknown",
|
||
"code": "invalid_filter_value",
|
||
"request_id": "req-01JYX4M2K7Y6WQ9RJ8T2V7VY5R",
|
||
"field": "region_id",
|
||
"received_value": "unknown"
|
||
}
|
||
```
|
||
|
||
Для конфликтующих фильтров:
|
||
|
||
HTTP status:
|
||
|
||
```text
|
||
400 Bad Request
|
||
```
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"type": "https://api.example.ru/problems/conflicting-filters",
|
||
"title": "Conflicting filters",
|
||
"status": 400,
|
||
"detail": "region_id cannot be used together with region_ids",
|
||
"instance": "/api/v1/competence-map/association-map/?association_type=clusters®ion_id=RU-MOS®ion_ids=RU-MOW",
|
||
"code": "conflicting_filters",
|
||
"request_id": "req-01JYX4M2K7Y6WQ9RJ8T2V7VY5R",
|
||
"fields": ["region_id", "region_ids"]
|
||
}
|
||
```
|
||
|
||
Для неизвестного query param:
|
||
|
||
HTTP status:
|
||
|
||
```text
|
||
400 Bad Request
|
||
```
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"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/association-map/?district=central",
|
||
"code": "unknown_query_param",
|
||
"request_id": "req-01JYX4M2K7Y6WQ9RJ8T2V7VY5R",
|
||
"field": "district",
|
||
"allowed_params": [
|
||
"association_type",
|
||
"map_view_mode",
|
||
"federal_district_id",
|
||
"region_id",
|
||
"federal_district_ids",
|
||
"region_ids",
|
||
"industry_id",
|
||
"integrated_structure_id",
|
||
"organization_id",
|
||
"competence_id",
|
||
"limit",
|
||
"offset",
|
||
"sort_field",
|
||
"sort_direction"
|
||
]
|
||
}
|
||
```
|
||
|
||
### Ошибки доступа и инфраструктуры
|
||
|
||
Для `401`, `403`, `429`, `500`, `503` используется тот же формат `application/problem+json`.
|
||
|
||
Пример `403 Forbidden`:
|
||
|
||
```json
|
||
{
|
||
"type": "https://api.example.ru/problems/forbidden",
|
||
"title": "Forbidden",
|
||
"status": 403,
|
||
"detail": "User does not have access to competence map association map",
|
||
"instance": "/api/v1/competence-map/association-map/",
|
||
"code": "forbidden",
|
||
"request_id": "req-01JYX4M2K7Y6WQ9RJ8T2V7VY5R"
|
||
}
|
||
```
|
||
|
||
Для `429 Too Many Requests` backend должен вернуть header:
|
||
|
||
```text
|
||
Retry-After: <seconds>
|
||
```
|
||
|
||
---
|
||
|
||
## Ожидаемая схема запросов
|
||
|
||
При загрузке `/new-map`:
|
||
|
||
```text
|
||
GET /api/v1/competence-map/association-map/filters/?association_type=clusters
|
||
GET /api/v1/competence-map/association-map/?association_type=clusters
|
||
```
|
||
|
||
При загрузке `/new-map/technoparks`:
|
||
|
||
```text
|
||
GET /api/v1/competence-map/association-map/filters/?association_type=technoparks
|
||
GET /api/v1/competence-map/association-map/?association_type=technoparks
|
||
```
|
||
|
||
При загрузке `/new-map/sez`:
|
||
|
||
```text
|
||
GET /api/v1/competence-map/association-map/filters/?association_type=sez
|
||
GET /api/v1/competence-map/association-map/?association_type=sez
|
||
```
|
||
|
||
При выборе нескольких субъектов РФ на карте:
|
||
|
||
```text
|
||
GET /api/v1/competence-map/association-map/filters/?association_type=clusters&map_view_mode=regions®ion_ids=RU-MOS®ion_ids=RU-MOW
|
||
GET /api/v1/competence-map/association-map/?association_type=clusters&map_view_mode=regions®ion_ids=RU-MOS®ion_ids=RU-MOW
|
||
```
|
||
|
||
При выборе нескольких федеральных округов на карте:
|
||
|
||
```text
|
||
GET /api/v1/competence-map/association-map/filters/?association_type=sez&map_view_mode=federal_districts&federal_district_ids=cfo&federal_district_ids=pfo
|
||
GET /api/v1/competence-map/association-map/?association_type=sez&map_view_mode=federal_districts&federal_district_ids=cfo&federal_district_ids=pfo
|
||
```
|
||
|
||
При переходе к перечню организаций:
|
||
|
||
```text
|
||
GET /api/v1/competence-map/association-map/organizations/?association_type=clusters&map_view_mode=regions®ion_ids=RU-MOS®ion_ids=RU-MOW&limit=500&offset=0
|
||
```
|
||
|
||
Не требуется реализовывать отдельные endpoint-ы вида:
|
||
|
||
```text
|
||
GET /api/v1/competence-map/association-map/clusters/
|
||
GET /api/v1/competence-map/association-map/technoparks/
|
||
GET /api/v1/competence-map/association-map/sez/
|
||
GET /api/v1/competence-map/association-map/select-region/
|
||
GET /api/v1/competence-map/association-map/select-federal-district/
|
||
```
|
||
|
||
Если в будущем появятся отдельные действия, например экспорт таблицы организаций или drilldown по строке,
|
||
для них можно добавить отдельные endpoint-ы. Для текущих состояний страниц из референсных экранов достаточно
|
||
`/association-map/filters/`, `/association-map/` и `/association-map/organizations/`.
|