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-полей + фронт).
Author
Owner

Ночной замер на стенде perf/ (29.10, масштаб 1, D0 без grow, CPU 1x, arm-ka-* остановлены)

Сборки: «до» — test 2a28748; A — b612178 (этот PR); B — c859ca1 (#242). Одна БД perf (дамп D0), миграций у A/B нет, API перезапускался на каждом коммите.

Корректность (условие мержа) — выполнено

  • compare_responses.py dump --scale 1 на base и A, затем compare base A --mode exact: совпало 56 из 56 (все листы всех owner, FORM_2/AHR c sections, своды 1 FORM_1..4/MAIN, свод 2 GO/RF, сводные plan/fact/detail, отчёты и summary проектов), код 0.
  • Для справки B: compare base B --mode strip-nulls — 56 из 56 (и A→B strip-nulls — 56 из 56).

Время: analysis/api_split.py --scale 1 --repeats 10 (версия скрипта из B, с wire_bytes; та же на всех трёх сборках)

запрос base total, мс A total (A/base) B total (B/base) base / A / B X-DB, мс base / A / B размер, МБ base / A / B на проводе, МБ python_share base/A/B
лист FORM_2/AHR (1154 строк ответа) 367 305 (0.83) 299 (0.82) 307 / 282 / 284 4.29 / 4.76 / 1.55 0.33 / 0.35 / 0.21 0.16 / 0.08 / 0.05
лист FORM_4/CAP (110 строк ответа) 579 571 (0.99) 564 (0.97) 562 / 560 / 556 0.44 / 0.49 / 0.19 0.05 / 0.05 / 0.03 0.03 / 0.02 / 0.01
лист FORM_4/AHR (110 строк ответа) 602 587 (0.98) 582 (0.97) 584 / 578 / 575 0.44 / 0.49 / 0.20 0.05 / 0.05 / 0.04 0.03 / 0.02 / 0.01
отчёт проекта LIMIT (1364 строк ответа) 337 296 (0.88) 293 (0.87) 302 / 280 / 277 1.48 / 1.65 / 1.65 0.16 / 0.17 / 0.17 0.1 / 0.05 / 0.05
свод 1 MAIN (133 строк ответа) 253 248 (0.98) 245 (0.97) 238 / 239 / 236 0.13 / 0.15 / 0.15 0.05 / 0.05 / 0.05 0.06 / 0.04 / 0.03
свод 2 GO (все организации) (5617 строк ответа) 1019 684 (0.67) 562 (0.55) 674 / 595 / 524 30.91 / 33.66 / 6.91 2.43 / 2.58 / 1.49 0.34 / 0.13 / 0.07
свод 2 RF (все организации) (8597 строк ответа) 1537 1088 (0.71) 864 (0.56) 1018 / 938 / 814 47.23 / 51.44 / 10.49 3.50 / 3.73 / 2.12 0.34 / 0.14 / 0.06
сводные проектов (747 строк ответа) 323 308 (0.96) 307 (0.95) 292 / 299 / 293 0.49 / 0.56 / 0.56 0.11 / 0.11 / 0.11 0.1 / 0.03 / 0.04

Вердикт A (порог «A ≤ 1,05 × до по всем запросам») — выполнен: максимум 0,99 (FORM_4/CAP), свод 2 GO 0,67, RF 0,71, FORM_2/AHR 0,83. Распакованный размер A больше на 9–11 % (ожидаемо, ": "), на проводе +2–7 %.

Файлы: scratchpad/night/1848/ (dump-base, dump-A, compare-A-exact.txt, split-base|A|B/api-split.{md,json}).

🤖 Generated with Claude Code

## Ночной замер на стенде perf/ (29.10, масштаб 1, D0 без grow, CPU 1x, arm-ka-* остановлены) **Сборки:** «до» — `test` 2a28748; A — b612178 (этот PR); B — c859ca1 (#242). Одна БД `perf` (дамп D0), миграций у A/B нет, API перезапускался на каждом коммите. ### Корректность (условие мержа) — выполнено - `compare_responses.py dump --scale 1` на base и A, затем `compare base A --mode exact`: **совпало 56 из 56** (все листы всех owner, FORM_2/AHR c sections, своды 1 FORM_1..4/MAIN, свод 2 GO/RF, сводные plan/fact/detail, отчёты и summary проектов), код 0. - Для справки B: `compare base B --mode strip-nulls` — 56 из 56 (и A→B strip-nulls — 56 из 56). ### Время: `analysis/api_split.py --scale 1 --repeats 10` (версия скрипта из B, с wire_bytes; та же на всех трёх сборках) | запрос | base total, мс | A total (A/base) | B total (B/base) | base / A / B X-DB, мс | base / A / B размер, МБ | base / A / B на проводе, МБ | python_share base/A/B | |---|---|---|---|---|---|---|---| | лист FORM_2/AHR (1154 строк ответа) | 367 | 305 (0.83) | 299 (0.82) | 307 / 282 / 284 | 4.29 / 4.76 / 1.55 | 0.33 / 0.35 / 0.21 | 0.16 / 0.08 / 0.05 | | лист FORM_4/CAP (110 строк ответа) | 579 | 571 (0.99) | 564 (0.97) | 562 / 560 / 556 | 0.44 / 0.49 / 0.19 | 0.05 / 0.05 / 0.03 | 0.03 / 0.02 / 0.01 | | лист FORM_4/AHR (110 строк ответа) | 602 | 587 (0.98) | 582 (0.97) | 584 / 578 / 575 | 0.44 / 0.49 / 0.20 | 0.05 / 0.05 / 0.04 | 0.03 / 0.02 / 0.01 | | отчёт проекта LIMIT (1364 строк ответа) | 337 | 296 (0.88) | 293 (0.87) | 302 / 280 / 277 | 1.48 / 1.65 / 1.65 | 0.16 / 0.17 / 0.17 | 0.1 / 0.05 / 0.05 | | свод 1 MAIN (133 строк ответа) | 253 | 248 (0.98) | 245 (0.97) | 238 / 239 / 236 | 0.13 / 0.15 / 0.15 | 0.05 / 0.05 / 0.05 | 0.06 / 0.04 / 0.03 | | свод 2 GO (все организации) (5617 строк ответа) | 1019 | 684 (0.67) | 562 (0.55) | 674 / 595 / 524 | 30.91 / 33.66 / 6.91 | 2.43 / 2.58 / 1.49 | 0.34 / 0.13 / 0.07 | | свод 2 RF (все организации) (8597 строк ответа) | 1537 | 1088 (0.71) | 864 (0.56) | 1018 / 938 / 814 | 47.23 / 51.44 / 10.49 | 3.50 / 3.73 / 2.12 | 0.34 / 0.14 / 0.06 | | сводные проектов (747 строк ответа) | 323 | 308 (0.96) | 307 (0.95) | 292 / 299 / 293 | 0.49 / 0.56 / 0.56 | 0.11 / 0.11 / 0.11 | 0.1 / 0.03 / 0.04 | ### Вердикт A (порог «A ≤ 1,05 × до по всем запросам») — **выполнен**: максимум 0,99 (FORM_4/CAP), свод 2 GO 0,67, RF 0,71, FORM_2/AHR 0,83. Распакованный размер A больше на 9–11 % (ожидаемо, `": "`), на проводе +2–7 %. Файлы: `scratchpad/night/1848/` (dump-base, dump-A, compare-A-exact.txt, split-base|A|B/api-split.{md,json}). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.