Files
anal-front/docs/info/backend-issues-competence-map-analytics-current-data.md
Gleb Korotkiy 67ec34d619
Some checks failed
CI/CD Pipeline / Quality Gate (push) Failing after 2m37s
CI/CD Pipeline / Build and Deploy Dev via Compose (push) Has been skipped
refactor: align frontend with FSD architecture
2026-06-22 12:33:33 +03:00

456 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 != "Субъект РФ <id>"
top_industries.rows[].industry_name != "Отрасль <id>"
```
Для каждого `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`.