Andrey Epifancev 3c5487c458 docs(openspec): aurora-1848-sheet-json — пороги, порядок PR, окружение тестов
Порог: свод 2 GO −30 %, лист FORM_2/AHR −10 % и меньший размер; пороги
не условие мержа, корректность (сравнение exact/strip-nulls) — жёсткое.
Решения контроллера: план в две фазы, ветка B стеком на A, archive после
merge B, база сравнения, замеры контроллером, pytest на PG16.

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

57 lines
7.6 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: тяжёлые GET листов и сводов — готовый JSON без pydantic-модели на строку и без null-полей
Jira: AURORA-1848 (эпик AURORA-1834). Gitea: #233. Источник цифр: `perf/REPORT.md`, разделы М9 и М10, и `perf/report-data/20261026-s1/analysis/api-split.md` (масштаб 1, медиана 10 повторов, напрямую на :8000).
## Why
Тяжёлые GET (лист формы, свод 1, свод 2) отдают тысячи строк, в каждой сотни полей, большинство из них `null`. Ответ проходит через Python 4–5 раз: драйвер разбирает jsonb в dict (`json.loads` внутри окна `X-DB-Time-Ms`), роутер строит `SheetResponse` на каждую строку (`api/src/api/v1/forms.py:27-35`, `summary.py:29-37`), FastAPI валидирует `response_model`, сериализует в JSON, затем `GZipMiddleware` сжимает (`api/src/main.py:248`).
| запрос (масштаб 1) | полное, мс | `X-DB-Time-Ms` | остаток | доля остатка | размер |
|---|---|---|---|---|---|
| свод 2 GO, 5 563 строки | 1 057 | 718 | 339 | 32 % | 30,6 МБ |
| свод 2 RF, 9 497 строк | 1 808 | 1 229 | 578 | 32 % | 52,0 МБ |
| лист FORM_2/AHR, 2 092 строки | 813 | 726 | 87 | 11 % | 7,6 МБ |
| отчёт проекта LIMIT, 1 364 строки | 343 | 307 | 36 | 11 % | 1,5 МБ |
Сами данные уже собраны в Postgres как jsonb. Разбирать их в Python, чтобы тут же собрать обратно, — лишняя работа. Явные `null` раздувают ответ и время разбора в браузере: на масштабе 2 свод 2 — 123–195 МБ.
## What Changes
Два PR, B стеком на A (согласовано 28.10).
**PR A — готовый JSON из Postgres, ответ по смыслу тот же.**
- `GET /api/v1/form/{form_id}/sheet/{sheet}`, `GET /api/v1/svod/1/{sheet}/{year}`, `GET /api/v1/svod/2/{sheet}/{year}` отдают тело, собранное из текста строк, который вернул Postgres (`json_build_object(...)::text`). Нет pydantic-модели на строку и нет валидации `response_model`. Обёртка `{"success","message","result","count"}` прежняя.
- Отдельным коммитом то же для GET отчётов проектов в `api/src/api/v1/projects.py`, которые объявляют `BaseListResponse[SheetResponse]`.
- После разбора JSON значения те же, что и раньше. Байты могут отличаться: пробелы и запись чисел (`1.50` вместо `1.5`).
- Заголовок `X-DB-Time-Ms` сохраняется на всех этих ответах. `response_model` остаётся в декораторах ради OpenAPI.
- `perf/`: скрипт сравнения ответов до и после, плюс размер ответа на проводе в `api_split.py`.
**PR B — без null-полей в `data` + правка фронта.**
- В ответах GET листа, свода 1 и свода 2 у объектов в `data` нет полей со значением `null`, на любой глубине (`jsonb_strip_nulls`). **BREAKING** для потребителя, который отличает «ключ есть и равен null» от «ключа нет». Единственный такой потребитель — наш фронт. Выгрузка Excel и WebSocket идут своими путями и не меняются.
- Фронт: входящая правка соавтора (WebSocket `cell_updated`, эхо PATCH) применяется и к ключу, которого нет в загруженной строке. В AURORA-1838 (PR #223) `updateCells` уже вставляет отсутствующий ключ. B проверяет, что этого достаточно, и закрепляет поведение тестом.
**Критерии приёмки (согласовано 28.10).** Свод 2 GO — полное время ниже минимум на 30 % после B. Лист FORM_2/AHR — минимум на 10 %, и распакованный размер меньше. Пороги не условие мержа: PR вливается по зелёным тестам и доказанной корректности (сравнение ответов на стенде), цифры — в PR, недобор — в follow-up.
**Что НЕ меняется**
- Ответы на правку — PATCH `/cell`, `/cells`, POST/DELETE `/line`, WebSocket `cell_updated` и правки FORM_3 — по-прежнему отдают `null` явно: очистка ячейки должна дойти до соавторов.
- Выгрузка Excel и WebSocket читают данные через прежние методы репозиториев и сервисов. Эти методы остаются без изменений.
- SQL-функции и миграции. Новой миграции нет.
- Отчёты проектов в PR B: в них `null` остаются.
- Сжатие (`GZipMiddleware`, уровень 1), постраничная загрузка (М10).
## Capabilities
### New Capabilities
- `sheet-read-api`: контракт ответа тяжёлых GET (лист, своды 1 и 2, отчёты проектов) — обёртка, строки, отсутствие null-полей, заголовок `X-DB-Time-Ms`, схема OpenAPI, неизменность путей записи и выгрузки, критерии производительности.
- `realtime-sheet-updates`: как фронт применяет входящие правки соавторов к строкам, загруженным без null-полей, и как показывает отсутствующие значения.
### Modified Capabilities
Нет: `openspec/specs/` пуст.
## Impact
- Бэк: `api/src/api/v1/forms.py`, `summary.py`, `projects.py` (только GET), `api/src/repository/sheet_repository.py`, `summary_repository.py`, `project_repository.py`, `api/src/services/sheet_service.py`, `summary_service.py`, `project_service.py` (новые методы рядом со старыми), новый хелпер ответа в `api/src/api/v1/`, тесты в `api/tests/unit` и `api/tests/integration`.
- Фронт (PR B): `web/src/components/RealtimeTable/utils/cellUtils.js` (если #223 не покрывает всё), тесты `cellUtils.test.js`.
- `perf/`: `analysis/api_split.py` (новое поле), новый `analysis/compare_responses.py`. В PR B — потребители GET в `perf/perfkit`, если они обращаются к ключам, которые стали отсутствовать.
- Связь с задачами: AURORA-1838 (PR #223) — B без неё не мержится. AURORA-1844/1850 — контракт строк ответа на правку с явными `null`, этот change его не трогает. AURORA-1846 (PR #232) меняет содержимое `data` FORM_4, этот change — упаковку; общий участок — фикстура `db_session` в `api/tests/integration/conftest.py`. AURORA-1847 — SQL листа FORM_2/AHR: дальнейшее ускорение листа сверх порога этого change (−10 % и меньший размер) — там.