Поиск, колоночные фильтры, варианты фильтров и «Найдено N из M» — с точной семантикой экрана (matchesSearch, MRT equals, faceting TanStack); регистр без зависимости от локали БД; пороги — цели, а не условие слияния; решения контроллера (три фазы/ветки, prefetch страницы 1, окружение тестов). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
107 lines
13 KiB
Markdown
107 lines
13 KiB
Markdown
# 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.
|