# Backend Issues Competence Map Analytics Current Data Назначение: список проблем текущей реализации backend-данных для страницы `Все сервисы / Карта компетенций / Аналитическая панель`. Документ нужен как задача для backend-разработки. Цель: привести фактический response тестового backend-а к контракту из `docs/info/backend-endpoints-competence-map-analytics-from-layouts.md` и `openapi.json`. Тестовый endpoint: ```text GET https://anal-back.dev.nii-ecos.ru/api/v1/competence-map/analytics/ ``` Проблемы ниже относятся к page-level endpoint-у: ```text GET /api/v1/competence-map/analytics/?association_type=all GET /api/v1/competence-map/analytics/?association_type=sez GET /api/v1/competence-map/analytics/?association_type=technoparks GET /api/v1/competence-map/analytics/?association_type=clusters ``` Новые endpoint-ы для этих исправлений не нужны. Исправлять нужно наполнение существующего response. --- ## Общие требования к исправлению - backend должен возвращать реальные бизнес-названия сущностей, а не технические placeholder-строки; - frontend не должен подменять `Субъект РФ 11`, `Отрасль 24` и подобные значения локальными маппингами; - если сущность имеет `id`, backend должен резолвить человекочитаемое имя на сервере; - если данные по применимому блоку есть в источниках, backend должен вернуть строки блока, а не пустой массив; - если показатель применим для выбранного `association_type`, backend должен вернуть его в контрактном поле; - `0` является валидным числом и должен возвращаться как `0`, а не как `null`, пустая строка или отсутствие поля; - пустой массив допустим только когда данных действительно нет после применения фильтров; - `null` допустим только когда блок или показатель неприменим к выбранному состоянию страницы. --- ## Проблема 1. Placeholder-названия субъектов РФ и отраслей ### Где видно На странице в блоках рейтинга и топа отраслей отображаются значения вида: ```text Субъект РФ 11 Субъект РФ 59 Субъект РФ 65 Отрасль 24 Отрасль 127 Отрасль 31 ``` Это не реальные названия субъектов РФ и отраслей. Такие значения нельзя отдавать в production response. ### Затронутые поля `region_ranking.rows[]`: ```json { "rank": 1, "region_id": "11", "region_name": "Субъект РФ 11" } ``` Должно быть: ```json { "rank": 1, "region_id": "11", "region_name": "<реальное название субъекта РФ из справочника>" } ``` `top_industries.rows[]`: ```json { "industry_id": "24", "industry_name": "Отрасль 24" } ``` Должно быть: ```json { "industry_id": "24", "industry_name": "<реальное название отрасли из справочника>" } ``` ### Требования Для `region_ranking.rows[]`: - `region_id` должен оставаться стабильным backend-id субъекта РФ; - `region_name` должен быть реальным названием субъекта РФ; - `region_name` не должен генерироваться как `Субъект РФ ${id}`; - если субъект не найден в справочнике, это ошибка данных, которую нужно логировать на backend-е; - frontend должен получать уже готовое отображаемое имя. Для `top_industries.rows[]`: - `industry_id` должен оставаться стабильным backend-id отрасли; - `industry_name` должен быть реальным названием отрасли; - `industry_name` не должен генерироваться как `Отрасль ${id}`; - если отрасль не найдена в справочнике, это ошибка данных, которую нужно логировать на backend-е; - frontend должен получать уже готовое отображаемое имя. ### Где исправлять Исправление должно затронуть все состояния страницы: ```text association_type=all association_type=sez association_type=technoparks association_type=clusters ``` Поля: - `region_ranking.rows[].region_name`; - `top_industries.rows[].industry_name`; - при необходимости тот же источник справочников для `/analytics/filters/`, если там используются те же placeholder-значения в `regions[].label` или `industries[].label`. ### Acceptance criteria Response не должен содержать placeholder-паттерны: ```text region_ranking.rows[].region_name != "Субъект РФ " top_industries.rows[].industry_name != "Отрасль " ``` Для каждого `association_type` нужно проверить: ```text GET /api/v1/competence-map/analytics/?association_type=all GET /api/v1/competence-map/analytics/?association_type=sez GET /api/v1/competence-map/analytics/?association_type=technoparks GET /api/v1/competence-map/analytics/?association_type=clusters ``` И убедиться, что: - все строки `region_ranking.rows[]` имеют заполненный `region_name`; - все строки `top_industries.rows[]` имеют заполненный `industry_name`; - значения являются реальными названиями, а не техническими fallback-строками. --- ## Проблема 2. Не приходят строки таблицы приоритетных направлений ОЭЗ ### Где видно В состоянии `association_type=sez` блок `Типы ОЭЗ и приоритетные направления деятельности` отображает donut с типами ОЭЗ, но таблица `Приоритетные направления деятельности ОЭЗ` пустая и показывает `Нет данных`. При этом по контракту этот блок должен состоять из двух частей: - `main_block.sez_types` - типы ОЭЗ; - `main_block.priority_directions` - таблица приоритетных направлений ОЭЗ. ### Затронутый endpoint ```text GET /api/v1/competence-map/analytics/?association_type=sez ``` ### Затронутые поля ```json { "association_type": "sez", "main_block": { "type": "sez_types_and_priority_directions", "sez_types": {}, "priority_directions": { "title": "Приоритетные направления деятельности ОЭЗ", "sort": { "field": "sez_count", "direction": "desc" }, "ranking_method": "ordinal", "rows": [] } } } ``` Сейчас `rows` фактически пустой или не содержит нужных данных. ### Требуемый формат `main_block.priority_directions.rows[]` должен возвращать строки в формате: ```json { "rank": 1, "direction_id": "ict", "direction_name": "Информационно-коммуникационные технологии", "sez_count": 4 } ``` Полный блок должен сохранять структуру: ```json { "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 } ] } } ``` ### Требования - `main_block.type` для `association_type=sez` должен быть `sez_types_and_priority_directions`; - `main_block.priority_directions` должен присутствовать вместе с `main_block.sez_types`; - `priority_directions.rows` должен быть массивом; - строки должны быть отсортированы на backend-е согласно `sort`; - `rank` должен быть рассчитан backend-ом и начинаться с `1`; - `direction_id` должен быть стабильным id направления; - `direction_name` должен быть реальным названием направления, а не placeholder; - `sez_count` должен быть числом `>= 0`; - пустой `rows: []` допустим только если после применения фильтров действительно нет ни одного направления. ### Acceptance criteria Для запроса без дополнительных фильтров: ```text GET /api/v1/competence-map/analytics/?association_type=sez ``` Ожидается: - `main_block.priority_directions.rows.length > 0`; - каждая строка имеет `rank`, `direction_id`, `direction_name`, `sez_count`; - `direction_name` заполнен реальным названием; - `sez_count` является числом; - таблица на frontend-е перестает показывать `Нет данных`, если данные в источнике есть. --- ## Проблема 3. Не приходят KPI-карточки численности сотрудников для ОЭЗ и технопарков ### Где видно На вкладках `ОЭЗ` и `Технопарки` есть блок `Численность сотрудников`. Сейчас карточки отображаются с прочерками, потому что backend не возвращает данные в `employee_stats`. Этот блок является серверным состоянием страницы: frontend не может рассчитать эти значения локально. ### Затронутые endpoint-ы ```text GET /api/v1/competence-map/analytics/?association_type=sez GET /api/v1/competence-map/analytics/?association_type=technoparks ``` ### ОЭЗ. Требуемые поля Для `association_type=sez` backend должен возвращать: ```json { "association_type": "sez", "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 } ] } } ``` Обязательные метрики для ОЭЗ: | `code` | Назначение | `value_type` | |---|---|---| | `opk_resident_employees_count` | численность сотрудников организаций ОПК-резидентов ОЭЗ | `integer` | | `sez_employees_count` | общая численность сотрудников ОЭЗ | `integer` | ### Технопарки. Требуемые поля Для `association_type=technoparks` backend должен возвращать: ```json { "association_type": "technoparks", "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 } ] } } ``` Обязательные метрики для технопарков: | `code` | Назначение | `value_type` | |---|---|---| | `opk_resident_employees_count` | численность сотрудников организаций ОПК-резидентов технопарков | `integer` | | `opk_resident_employees_share_percent` | доля сотрудников организаций ОПК-резидентов технопарков | `percent` | ### Требования - `employee_stats` для `association_type=sez` должен быть объектом, а не `null`; - `employee_stats` для `association_type=technoparks` должен быть объектом, а не `null`; - `employee_stats.items` должен содержать все обязательные метрики для выбранного типа; - `items[].code` должен быть стабильным; - `items[].label` должен быть готовым отображаемым названием; - `items[].value` должен быть числом; - для процентных метрик `numerator` и `denominator` обязательны, если знаменатель известен; - `denominator` у процентной метрики не должен быть `0`; - `0` нужно возвращать как `0`, если показатель реально равен нулю; - не возвращать `employee_stats: null` для применимого блока только потому, что расчет ещё не реализован. ### Когда `employee_stats` может быть `null` `employee_stats: null` допустим для состояний, где блок сотрудников не применяется: ```text association_type=all association_type=clusters ``` Для `sez` и `technoparks` блок применим и должен быть заполнен. ### Acceptance criteria Для запроса: ```text GET /api/v1/competence-map/analytics/?association_type=sez ``` Ожидается: - `employee_stats !== null`; - `employee_stats.items.length >= 2`; - есть item с `code = "opk_resident_employees_count"`; - есть item с `code = "sez_employees_count"`; - оба `value` являются числами. Для запроса: ```text GET /api/v1/competence-map/analytics/?association_type=technoparks ``` Ожидается: - `employee_stats !== null`; - `employee_stats.items.length >= 2`; - есть item с `code = "opk_resident_employees_count"`; - есть item с `code = "opk_resident_employees_share_percent"`; - оба `value` являются числами; - для `opk_resident_employees_share_percent` заполнены `numerator` и `denominator`. --- ## Сводка исправлений по backend response | Проблема | Endpoint | Поля | Что нужно сделать | |---|---|---|---| | Placeholder субъектов РФ | `/analytics/` для всех `association_type` | `region_ranking.rows[].region_name` | возвращать реальные названия субъектов РФ | | Placeholder отраслей | `/analytics/` для всех `association_type` | `top_industries.rows[].industry_name` | возвращать реальные названия отраслей | | Пустая таблица направлений ОЭЗ | `/analytics/?association_type=sez` | `main_block.priority_directions.rows[]` | вернуть строки направлений ОЭЗ | | Нет карточек сотрудников ОЭЗ | `/analytics/?association_type=sez` | `employee_stats.items[]` | вернуть две KPI-метрики сотрудников | | Нет карточек сотрудников технопарков | `/analytics/?association_type=technoparks` | `employee_stats.items[]` | вернуть две KPI-метрики сотрудников | --- ## Минимальный backend checklist Перед отдачей исправления нужно проверить: ```text GET /api/v1/competence-map/analytics/?association_type=all GET /api/v1/competence-map/analytics/?association_type=sez GET /api/v1/competence-map/analytics/?association_type=technoparks GET /api/v1/competence-map/analytics/?association_type=clusters ``` Проверки: - `region_ranking.rows[].region_name` не содержит `Субъект РФ`; - `top_industries.rows[].industry_name` не содержит `Отрасль `; - для `association_type=sez` заполнен `main_block.priority_directions.rows`; - для `association_type=sez` заполнен `employee_stats.items`; - для `association_type=technoparks` заполнен `employee_stats.items`; - все числовые поля возвращаются числами, а не строками; - пустые массивы используются только когда данных действительно нет по выбранным фильтрам; - response соответствует `openapi.json` и контракту `docs/info/backend-endpoints-competence-map-analytics-from-layouts.md`.