AURORA-1848: тяжёлые GET — готовый JSON из Postgres без pydantic-модели на строку (A) #239

Open
aep wants to merge 13 commits from feat/233-aurora-1848-sheet-json-no-pydantic into test
Owner

Part of #233. Jira AURORA-1848 (эпик AURORA-1834). Фаза A из двух; PR B (без null-полей в data + фронт) идёт стеком на этой ветке и закроет #233.

Что сделано

  • Тяжёлые GET (лист формы, своды 1 и 2) отдают тело, собранное из текста строк Postgres (json_build_object(...)::text), без pydantic-модели на строку. Роутеры склеивают строки хелпером list_json_response, response_model остаётся в декораторе ради OpenAPI. X-DB-Time-Ms, лог db_time, окно замера прежние.
  • Отдельным коммитом (604e277) — шесть GET проектов: отчёт, rf-rollup, сводка проекта, plan/fact/detail-summary.
  • Старые методы репозиториев и сервисов (их использует выгрузка Excel) не менялись, кроме выноса FROM-части. Дифф export_service/, websocket.py, alembic/ против test пуст. Миграций нет.
  • perf/analysis/api_split.py: поле wire_bytes (размер на проводе). perf/analysis/compare_responses.py: снимок и сравнение ответов до/после (dump/compare).
  • Ответ по смыслу тот же, байты могут отличаться (пробелы, запись чисел).

Тесты

  • unit: хелпер ответа и SQL-выражение строки.
  • интеграционные: эквивалентность старому пути на 9 эндпоинтах, GET не вызывает старые методы, заголовок X-DB-Time-Ms, пустой результат, экранирование, gzip, снимок OpenAPI.
  • Полный pytest на PG16 (dfip_1848): 3 failed (те же 3 известных на test), 243 passed, 4 skipped. perf -m "not stand": 3 failed в test_run_all.py (есть и на origin/test), остальное зелёное.
  • PG 15.12 (ветка + исправление AURORA-1922): 3 failed (те же), 245 passed, 4 skipped.

Чек-лист до merge

  • compare_responses.py dump/compare --mode exact на стенде (база test против A) — обязателен
  • PG15 — проверено контроллером (см. комментарий)
  • A <= 1,05 x до по api_split.py — справочно, не условие мержа
  • ручная проверка: лист FORM_2/AHR, свод 1, свод 2 (GO/RF), отчёт проекта открываются, данные те же

Замер до/после — добавит контроллер после прогона на стенде.

Связь: AURORA-1846 (#232) — общий хунк db_session в conftest.

Rulings I made

  • R-1 (design #1): тест экранирования листа через INSERT budget_line (name с кавычкой, слэшем, переводом строки, кириллицей), а не UPDATE justification — в фикстуре у FORM_2 нет строк. Цена ошибки: слабее покрытие других полей data.
  • R-2 (design #2): фикстура svod2_plan_line вместо POST строки (на FORM_2 POST даёт 400). Цена: привязка к идентификаторам fixture.sql.
  • R-3 (design #3): rf-rollup — тест фиксации ошибки вместо эквивалентности (на test функция v3.v_form3_rf_rollup_jsonb обращается к несуществующему r.year, эндпоинт всегда 400; O-3), _add_line не реализован. Цена: успешный путь rf-rollup не покрыт.
  • R-4 (design #4): мок в test_api_split через httpx.ByteStream.
  • R-5 (design #5): dump устойчив к сетевым ошибкам, compare сравнивает не-JSON побайтно.

Находки

Маркер Суть Статус
T1.1 лишний import pytest в test_heavy_get_json.py (дословно из плана) rejected (используется с Task 3)
T1.2 docstring db_session vs тело фикстуры (дословно из плана/PR #232) rejected
T2.1 row_json_select собирает SQL f-строкой (безопасно для константных фрагментов) rejected (решение D1)
T2.2 fetch_row_texts без docstring/типизации params rejected
T4.1 свод 2: случаи offset/limit, sort могли проходить на пустых списках fixed b612178 (= I-1)
T4.2 svod2_plan_line с зашитыми org/item без комментария fixed b612178
T4.3 порядок импортов в summary.py rejected (стиль)
T4.4 лишний f-префикс в get_svod2_rows rejected (косметика)
T5.1 тест rf-rollup назван «same as old path», лишние фикстуры fixed b612178 (= M-3)
T5.2 нет характеризующих тестов «Проект не найден» для /summary и раннего [] сводных deferred
T6.1 measure(repeats=0) падает на пустой медиане (поведение прежнее) rejected
T6.2 нет теста на отсутствие content-encoding в measure deferred
T7.1 dump: одна сетевая ошибка рвала прогон, index.json в конце (Important) fixed 88c37f9
T7.2 compare падал на не-JSON теле (Important) fixed 88c37f9
T7.3 strip_response добавлял result: [] (minor) fixed 88c37f9
T7.4 dump ловит только httpx.HTTPError deferred
T7.5 first_diff различает 1 и 1.0; build_requests молча пропускает своды без листов rejected (по замыслу)
T7.6 сообщение о пропавшем файле не называет сторону deferred
I-1 финальное ревью A: сравнения свода 2 (RF, offset/limit) вакуозны fixed b612178
I-2 design.md без R-1..R-4 и неверное утверждение о покрытии rf-rollup fixed 38990dc
M-1 потеряна pydantic-валидация полей строки rejected (принято в design «Risks»; функции возвращают типизированные столбцы, json_build_object не даёт NULL)
M-2 list_json_response: 3 копии тела (join, encode, b"".join) deferred
M-3 тест rf-rollup назван неверно fixed b612178
M-4 нет проверки gzip на тяжёлом GET fixed b612178
M-5 докстринг api_split.py ссылается на пути записи fixed b612178
M-6 неиспользуемый параметр form в SheetService.get_json rejected (симметрия с get)
R-1 (design #1) тест экранирования: INSERT budget_line вместо UPDATE justification fixed (решение, b612178 сохраняет)
R-2 (design #2) фикстура svod2_plan_line вместо POST line fixed (решение)
R-3 (design #3) rf-rollup: тест фиксации ошибки; _add_line не реализован deferred (follow-up на O-3)
R-4 (design #4) мок ByteStream в test_api_split fixed (решение)
R-5 (design #5) устойчивый dump/compare fixed 88c37f9
O-1 коммит Task 1 с трейлером Haiku, исправлен на Opus fixed 0175759
O-2 базовый прогон dfip_1848: 3 известных падения, OpenAPI-снимок проходит rejected (информационно)
O-3 v3.v_form3_rf_rollup_jsonb обращается к несуществующему r.year, /rf-rollup всегда 400 на test deferred (follow-up, миграции вне объёма)
O-4 3 падения perf/tests/test_run_all.py — есть и на origin/test rejected (не наше)
O-5 в Task 7 пропущен красный шаг TDD rejected (закрыто ревью и тестами fix-раунда)

Отложенное (deferred): T5.2, T6.2, T7.4, T7.6, M-2, R-3/O-3 (rf-rollup r.year) — уйдёт в follow-up issue при закрытии.

🤖 Generated with Claude Code

Part of #233. Jira AURORA-1848 (эпик AURORA-1834). Фаза A из двух; PR B (без null-полей в data + фронт) идёт стеком на этой ветке и закроет #233. ## Что сделано - Тяжёлые GET (лист формы, своды 1 и 2) отдают тело, собранное из текста строк Postgres (`json_build_object(...)::text`), без pydantic-модели на строку. Роутеры склеивают строки хелпером `list_json_response`, `response_model` остаётся в декораторе ради OpenAPI. `X-DB-Time-Ms`, лог `db_time`, окно замера прежние. - Отдельным коммитом (604e277) — шесть GET проектов: отчёт, rf-rollup, сводка проекта, plan/fact/detail-summary. - Старые методы репозиториев и сервисов (их использует выгрузка Excel) не менялись, кроме выноса FROM-части. Дифф export_service/, websocket.py, alembic/ против test пуст. Миграций нет. - `perf/analysis/api_split.py`: поле `wire_bytes` (размер на проводе). `perf/analysis/compare_responses.py`: снимок и сравнение ответов до/после (dump/compare). - Ответ по смыслу тот же, байты могут отличаться (пробелы, запись чисел). ## Тесты - unit: хелпер ответа и SQL-выражение строки. - интеграционные: эквивалентность старому пути на 9 эндпоинтах, GET не вызывает старые методы, заголовок `X-DB-Time-Ms`, пустой результат, экранирование, gzip, снимок OpenAPI. - Полный pytest на PG16 (dfip_1848): 3 failed (те же 3 известных на test), 243 passed, 4 skipped. perf `-m "not stand"`: 3 failed в test_run_all.py (есть и на origin/test), остальное зелёное. - PG 15.12 (ветка + исправление AURORA-1922): 3 failed (те же), 245 passed, 4 skipped. ## Чек-лист до merge - [ ] `compare_responses.py dump/compare --mode exact` на стенде (база test против A) — обязателен - [x] PG15 — проверено контроллером (см. комментарий) - [ ] `A <= 1,05 x до` по `api_split.py` — справочно, не условие мержа - [ ] ручная проверка: лист FORM_2/AHR, свод 1, свод 2 (GO/RF), отчёт проекта открываются, данные те же Замер до/после — добавит контроллер после прогона на стенде. Связь: AURORA-1846 (#232) — общий хунк `db_session` в conftest. ## Rulings I made - R-1 (design #1): тест экранирования листа через INSERT budget_line (name с кавычкой, слэшем, переводом строки, кириллицей), а не UPDATE justification — в фикстуре у FORM_2 нет строк. Цена ошибки: слабее покрытие других полей data. - R-2 (design #2): фикстура svod2_plan_line вместо POST строки (на FORM_2 POST даёт 400). Цена: привязка к идентификаторам fixture.sql. - R-3 (design #3): rf-rollup — тест фиксации ошибки вместо эквивалентности (на test функция v3.v_form3_rf_rollup_jsonb обращается к несуществующему r.year, эндпоинт всегда 400; O-3), `_add_line` не реализован. Цена: успешный путь rf-rollup не покрыт. - R-4 (design #4): мок в test_api_split через httpx.ByteStream. - R-5 (design #5): dump устойчив к сетевым ошибкам, compare сравнивает не-JSON побайтно. ## Находки | Маркер | Суть | Статус | |---|---|---| | T1.1 | лишний `import pytest` в test_heavy_get_json.py (дословно из плана) | rejected (используется с Task 3) | | T1.2 | docstring db_session vs тело фикстуры (дословно из плана/PR #232) | rejected | | T2.1 | `row_json_select` собирает SQL f-строкой (безопасно для константных фрагментов) | rejected (решение D1) | | T2.2 | `fetch_row_texts` без docstring/типизации params | rejected | | T4.1 | свод 2: случаи offset/limit, sort могли проходить на пустых списках | fixed b612178 (= I-1) | | T4.2 | `svod2_plan_line` с зашитыми org/item без комментария | fixed b612178 | | T4.3 | порядок импортов в summary.py | rejected (стиль) | | T4.4 | лишний f-префикс в get_svod2_rows | rejected (косметика) | | T5.1 | тест rf-rollup назван «same as old path», лишние фикстуры | fixed b612178 (= M-3) | | T5.2 | нет характеризующих тестов «Проект не найден» для /summary и раннего [] сводных | deferred | | T6.1 | measure(repeats=0) падает на пустой медиане (поведение прежнее) | rejected | | T6.2 | нет теста на отсутствие content-encoding в measure | deferred | | T7.1 | dump: одна сетевая ошибка рвала прогон, index.json в конце (Important) | fixed 88c37f9 | | T7.2 | compare падал на не-JSON теле (Important) | fixed 88c37f9 | | T7.3 | strip_response добавлял `result: []` (minor) | fixed 88c37f9 | | T7.4 | dump ловит только httpx.HTTPError | deferred | | T7.5 | first_diff различает 1 и 1.0; build_requests молча пропускает своды без листов | rejected (по замыслу) | | T7.6 | сообщение о пропавшем файле не называет сторону | deferred | | I-1 | финальное ревью A: сравнения свода 2 (RF, offset/limit) вакуозны | fixed b612178 | | I-2 | design.md без R-1..R-4 и неверное утверждение о покрытии rf-rollup | fixed 38990dc | | M-1 | потеряна pydantic-валидация полей строки | rejected (принято в design «Risks»; функции возвращают типизированные столбцы, json_build_object не даёт NULL) | | M-2 | list_json_response: 3 копии тела (join, encode, b"".join) | deferred | | M-3 | тест rf-rollup назван неверно | fixed b612178 | | M-4 | нет проверки gzip на тяжёлом GET | fixed b612178 | | M-5 | докстринг api_split.py ссылается на пути записи | fixed b612178 | | M-6 | неиспользуемый параметр `form` в SheetService.get_json | rejected (симметрия с get) | | R-1 (design #1) | тест экранирования: INSERT budget_line вместо UPDATE justification | fixed (решение, b612178 сохраняет) | | R-2 (design #2) | фикстура svod2_plan_line вместо POST line | fixed (решение) | | R-3 (design #3) | rf-rollup: тест фиксации ошибки; `_add_line` не реализован | deferred (follow-up на O-3) | | R-4 (design #4) | мок ByteStream в test_api_split | fixed (решение) | | R-5 (design #5) | устойчивый dump/compare | fixed 88c37f9 | | O-1 | коммит Task 1 с трейлером Haiku, исправлен на Opus | fixed 0175759 | | O-2 | базовый прогон dfip_1848: 3 известных падения, OpenAPI-снимок проходит | rejected (информационно) | | O-3 | `v3.v_form3_rf_rollup_jsonb` обращается к несуществующему `r.year`, /rf-rollup всегда 400 на test | deferred (follow-up, миграции вне объёма) | | O-4 | 3 падения perf/tests/test_run_all.py — есть и на origin/test | rejected (не наше) | | O-5 | в Task 7 пропущен красный шаг TDD | rejected (закрыто ревью и тестами fix-раунда) | Отложенное (deferred): T5.2, T6.2, T7.4, T7.6, M-2, R-3/O-3 (rf-rollup r.year) — уйдёт в follow-up issue при закрытии. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 13 commits 2026-10-28 20:47:50 +03:00
Тяжёлые GET листов, сводов и отчётов проектов: готовый JSON из Postgres
без pydantic-модели на строку (PR A) и без null-полей в data листа и
сводов с правкой фронта (PR B). Требования — sheet-read-api и
realtime-sheet-updates; дизайн, риски, тесты и сценарий замера — design.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Порог: свод 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>
Две фазы: A (feat/233-aurora-1848-sheet-json-no-pydantic, PR в test) и B (feat/233-aurora-1848-sheet-json-no-pydantic-b, стеком на A). Решения планирования дописаны в design.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Добавлены list_json_response (обёртка BaseListResponse из готовых строк),
ROW_COLUMNS, row_json_select и fetch_row_texts для тяжёлых GET (AURORA-1848).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Цикл замера вынесен в measure(); добавлен wire_bytes (num_bytes_downloaded,
байты после сжатия) рядом с декодированным размером. В .md - колонка
«на проводе, МБ». Docstring: актуальные места X-DB-Time-Ms и остаток после
задач A (склейка строк, gzip, передача, распаковка; без pydantic).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Фикстура свода 2 достроена до 6 строк на GO и RF по двум орг. единицам с разными section_code;
все случаи SVOD2_CASES проверяют непустоту. Тест rf-rollup переименован (фиксирует существующий дефект),
добавлен тест gzip, в api_split.py оставлены только GET-места.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

PG 15.12 (dfip-pg15:55434): ветка b612178 + исправление AURORA-1922 (временный worktree), полный pytest: 3 failed (те же 3 известных на test), 245 passed, 4 skipped. Новых падений нет.

PG 15.12 (dfip-pg15:55434): ветка b612178 + исправление AURORA-1922 (временный worktree), полный pytest: 3 failed (те же 3 известных на test), 245 passed, 4 skipped. Новых падений нет.
Author
Owner

Стек: PR B #242 (лист и своды без null-полей + фронт).

Стек: PR B #242 (лист и своды без null-полей + фронт).
This pull request can be merged automatically.
You are not authorized to merge this pull request.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/233-aurora-1848-sheet-json-no-pydantic:feat/233-aurora-1848-sheet-json-no-pydantic
git checkout feat/233-aurora-1848-sheet-json-no-pydantic
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RSHB/DFiP_Budget_planing#239
No description provided.