Andrey Epifancev 6e607b1647 docs(openspec): aurora-1843-svod2-server-paging — proposal, spec, design
Свод 2: серверная постраничная загрузка (поиск, три фильтра, total,
варианты фильтров) и компактный ответ; фронт догружает строки окном
страниц; замеры памяти, времени до первых строк, байтов и шага скролла.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:27:17 +04:00

103 lines
12 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.

# Proposal: AURORA-1843 — свод 2: серверная постраничная загрузка и компактный ответ
Gitea issue #236, Jira AURORA-1843 (эпик AURORA-1834). Стек: AURORA-1835 (PR #217) → AURORA-1842 (PR #227) → эта задача.
## Why
При выборе «Все ССП/РФ» свод 2 (`/svod/2`) получает весь лист одним ответом и держит его в памяти вкладки целиком. Поиск и колоночные фильтры при этом считаются в браузере по всем строкам.
Цифры из отчёта о производительности (`perf/REPORT.md`, М2, М9, М10; масштаб 1 и 2, CPU 1×):
| | масштаб 1 | масштаб 2 |
|---|---|---|
| ответ GO, распакованный | 30,5 МБ (5 563 строки, ~5,5 КБ на строку) | 123,2 МБ |
| ответ RF, распакованный | 52,7 МБ (9 497 строк) | 195,1 МБ |
| время ответа GO / RF | 1,06 / 1,8 с | 4,5–4,8 / 7,1–7,3 с |
| доля Python в ответе (pydantic на строку, JSON, gzip) | 32 % (0,34–0,58 с) | растёт с размером |
| шаг скролла GO / RF (до 1842) | 184 / 187 мс | 251 / 214–256 мс |
| ввод в поиск, главный поток | 0,2–0,3 с | GO 187–266 мс, RF 281–325 мс |
Пока открыт маршрут `/svod/2`, состояние страницы держит ответы обеих вкладок. GO «все» и RF «все» вместе дают около 83 МБ JSON на масштабе 1 и около 318 МБ на масштабе 2, плюс объекты строк таблицы. Рост шага на масштабе 2 при том же числе видимых строк отчёт связывает со сборкой мусора на такой куче: GC занимает 17 мс на шаг против 4–5 мс на листах. Это не подтверждено, но с объёмом данных согласуется.
Задача была аналитической: «предложить вариант с оценкой». 28.10 человек решил делать сразу реализацию с максимальным эффектом. Выбраны варианты C (серверная пагинация, поиск и фильтры на сервере) и B (компактный ответ) из черновика разведки. Вариант A (только политика кэша на фронте) не нужен.
## What Changes
- **API `GET /api/v1/svod/2/{sheet}/{year}`.** У эндпоинта уже есть `org_ids`, `offset`, `limit`, `sort_by` и `sort_direction`. Добавляются необязательные параметры:
- `search` — поиск подстроки без учёта регистра по тем же полям, что ищет экран сейчас;
- `smeta_type` и `smeta_direction` — фильтры по виду сметы и направлению;
- `view=full|compact` — `compact` отдаёт только поля, которые экран показывает.
Если в запросе есть хотя бы один новый параметр, в ответе появляется поле `total` — число строк по всем условиям запроса без учёта `offset`/`limit`. `count`, как и раньше, равен длине `result`. Фильтр по организации — это сужение `org_ids` (параметр уже есть), с прежней проверкой прав.
- **Без новых параметров ответ байт-в-байт прежний.** Это нужно Excel-выгрузке RF, интеграционным тестам, `perfkit.bench` и `perfkit.gen.verify`.
- **Новый эндпоинт `GET /api/v1/svod/2/{sheet}/{year}/facets`.** Отдаёт общее число строк и варианты трёх колоночных фильтров («Вид сметы», «Направление», «ССП») для выбранного года и организаций.
- **SQL.** Новая миграция:
- `DROP` прежних `v3.v_svod2_go` и `v3.v_svod2_rf` (6 аргументов) и `CREATE` с новой сигнатурой, без перегрузок;
- общий отбор строк свода 2 выносится в одну функцию, её используют и страницы, и варианты фильтров;
- функция вариантов фильтров и проекция компактной строки.
Всё совместимо с PostgreSQL 15. Отбор строк (какие строки попадают в свод) не меняется.
- **Экран свода 2.**
- Таблица и колонки остаются прежними.
- После «Загрузить» приходит первая страница строк, остальные догружаются при прокрутке.
- В памяти держится окно из нескольких страниц вокруг видимой области, а не весь ответ. Полоса прокрутки соответствует полному числу строк.
- Поиск (debounce 300 мс уже есть) и три колоночных фильтра выполняются на сервере, варианты фильтров приходят с сервера.
- «Найдено N из M» строится из `total` и считает и поиск, и колоночные фильтры.
- Свод 2 больше не кладёт ответы в кэш `SvodContext`. Кэш остаётся только у свода 1, его поведение не меняется.
- **Стенд замеров `perf/`.**
- `scroll.spec.ts`: свод 2 учитывает строки-заглушки, добавлены метрики времени до первых строк и байтов, фильтр ждёт ответа сервера.
- Новый сценарий `svod2-memory.spec.ts` (GO «все» → RF «все» → GO, куча после GC).
- В `api_split.py`, `explain_manual.py` и `scale_compare.py` добавлены страницы, компактный вид и варианты фильтров.
- Скрипт сверки серверной выдачи со старым полным ответом на данных стенда.
- **Три PR в стеке:**
1. сервер — SQL, API, тесты;
2. фронт — догрузка, поиск, фильтры;
3. замер и сценарии `perf/`.
### Что НЕ меняется
- Excel-выгрузка свода 2 (`GET /api/v1/export/summary/2/...`) — ни контракт, ни содержимое. Кнопки выгрузки свода 2 на фронте нет, поэтому под экранные фильтры её не подгоняем (решение 28.10).
- Какие строки попадают в свод 2. Отбор по наличию данных — миграция 0049. Отбор RF по типу формы — баг O-5, он решается отдельно в AURORA-1917.
- Свод 1, сводные проектов, `SvodFilters` (общий для сводов 1 и 2), кэш `SvodContext` для свода 1.
- Сортировка свода 2: на экране она выключена (`enableSorting: false`), на сервере остаётся по умолчанию `org_name asc`.
- Набор, порядок, ширины и форматирование колонок, единицы (руб./тыс./млн), закреплённые колонки, «Перейти к задачам». Подписи «Все ССП/РФ», «Загрузить» и «Обновить» и порядок действий пользователя (выбрать организации → «Загрузить») тоже прежние.
## Capabilities
### New Capabilities
- `summary2-api`: контракт API свода 2. Постраничная выдача с `total`, серверный поиск и фильтры, компактное представление строки, варианты фильтров, права доступа, совместимость прежнего ответа, критерии размера ответа.
- `summary2-screen`: поведение экрана свода 2. Догрузка строк страницами при прокрутке, ограниченное окно строк в памяти, поиск и фильтры через сервер, «Найдено N из M», ошибки. Критерии производительности: память, время до первых строк, шаг скролла.
### Modified Capabilities
Нет: `openspec/specs/` пока пуст.
## Impact
- **Бэк:**
- `api/src/api/v1/summary.py` (`get_summary2` и новый эндпоинт вариантов фильтров);
- `api/src/services/summary_service.py`;
- `api/src/repository/summary_repository.py`;
- `api/src/domain/schemas.py` (модели ответа с `total` и вариантов фильтров);
- новая миграция `api/alembic/versions/00NN_svod2_server_paging.py` + `sql/00NN_svod2_server_paging.sql`. Номер выбирается при rebase, см. design.md.
- **Тесты бэка:**
- `api/tests/integration/test_summary_api_smoke.py` и новые интеграционные тесты со своими данными свода 2;
- `api/tests/unit/test_summary2_related_records_sql.py` (перенацелить на новую миграцию);
- unit-тесты сервиса и валидации параметров.
- **Фронт:**
- `web/src/api/summary.js`;
- `web/src/pages/Svod2Page/Svod2Page.jsx`;
- новые `web/src/pages/Svod2Page/svod2Paging.js` (чистая логика окна и параметров, с `node --test`) и хук загрузки страниц;
- `svod2Styles.js` (стиль строк-заглушек).
- **Стенд:**
- `perf/e2e/scenarios/scroll.spec.ts`;
- новый `perf/e2e/scenarios/svod2-memory.spec.ts`;
- `perf/analysis/api_split.py`, `perf/analysis/explain_manual.py`, `perf/analysis/scale_compare.py`;
- новый `perf/analysis/svod2_paging_check.py`;
- `perf/README.md`.
- **Связанные задачи:**
- **AURORA-1842** (PR #227, база стека) — виртуализация колонок и CSS-стили свода 2; от неё берётся результат шага скролла, который нельзя ухудшить.
- **AURORA-1836** (PR #222, от `test`) правит в `Svod2Page.jsx` высоту строки (2,75 rem) и overscan строк. Окно страниц опирается на фиксированную высоту строки, при rebase брать её из 1836.
- **AURORA-1917** (баг O-5: в «РФ» попадают строки ССП) уменьшит ответ RF. Её нельзя вливать между замерами «до» и «после» 1843.
- **AURORA-1848** (тяжёлые GET без pydantic на строку) правит тот же обработчик `get_summary2`, нужна координация.
- **AURORA-1831/1844/1846** заняли номер миграции 0050.