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

13 KiB
Raw Blame History

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.