Andrey Epifancev 9d68cd5e1a docs(openspec): aurora-1843 — поведение свода 2 без изменений, решения 28.10
Поиск, колоночные фильтры, варианты фильтров и «Найдено N из M» — с точной
семантикой экрана (matchesSearch, MRT equals, faceting TanStack); регистр
без зависимости от локали БД; пороги — цели, а не условие слияния;
решения контроллера (три фазы/ветки, prefetch страницы 1, окружение тестов).

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

107 lines
13 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` — поиск с точно той же семантикой, что у экрана сейчас (`matchesSearch`: те же поля с тем же перекрытием, числа в записи JavaScript, регистр не зависит от локали БД);
- `smeta_type`, `smeta_direction` и `org_name` — колоночные фильтры с семантикой фильтра таблицы экрана (`equals`: без учёта регистра и пробелов по краям);
- `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`.** Отдаёт общее число строк, число строк по поиску («Найдено N из M») и варианты трёх колоночных фильтров («Вид сметы», «Направление», «ССП») с числами строк. Варианты считаются как сейчас на экране: с учётом поиска и фильтров двух других колонок.
- **SQL.** Новая миграция:
- `DROP` прежних `v3.v_svod2_go` и `v3.v_svod2_rf` (6 аргументов) и `CREATE` с новой сигнатурой, без перегрузок;
- общий отбор строк свода 2 выносится в одну функцию, её используют и страницы, и варианты фильтров;
- функция вариантов фильтров и проекция компактной строки.
Всё совместимо с PostgreSQL 15. Отбор строк (какие строки попадают в свод) не меняется.
- **Экран свода 2.**
- Для пользователя всё остаётся таким же (решение 28.10): строки, поиск, варианты фильтров с числами, «Найдено N из M», сброс фильтров, вкладки, прокрутка. Отличаются только ожидание ответа сервера и заглушки при быстрой прокрутке.
- Таблица и колонки остаются прежними.
- После «Загрузить» приходят первые две страницы строк (вторая — заранее), остальные догружаются при прокрутке.
- В памяти держится окно из нескольких страниц вокруг видимой области, а не весь ответ. Полоса прокрутки соответствует полному числу строк.
- Поиск (debounce 300 мс уже есть) и три колоночных фильтра выполняются на сервере, варианты фильтров приходят с сервера.
- «Найдено N из M» считает, как и сейчас, только поиск.
- Свод 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` добавлены страницы, компактный вид и варианты фильтров.
- Скрипты сверки серверной выдачи со старым полным ответом на данных стенда, в том числе оракул на Node.js с дословной копией `matchesSearch` и правил фильтров и вариантов таблицы.
- Цифры замеров — цели, а не условие слияния: недобор уходит в follow-up (решение 28.10). Условие слияния — доказанная корректность.
- **Три 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`.
- Семантика поиска, колоночных фильтров, вариантов фильтров и «Найдено N из M», включая найденную странность поиска (поля `contract_summary`, одноимённые полям `contract_detail`, не ищутся) — описана в design.md как находка, не исправляется.
- Набор, порядок, ширины и форматирование колонок, единицы (руб./тыс./млн), закреплённые колонки, «Перейти к задачам». Подписи «Все ССП/РФ», «Загрузить» и «Обновить» и порядок действий пользователя (выбрать организации → «Загрузить») тоже прежние.
## 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/fixtures/js_number_text_vectors.json` (запись чисел в JavaScript, сгенерированы Node.js);
- `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/analysis/svod2_search_oracle.mjs`;
- `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.