openspec: архив aurora-1848-sheet-json и aurora-1850-upd-form-cells-subtree
Обе задачи влиты в test 01.11 (PR #239/#242, #244/#274), архив пропущен. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fb45e8ddca
commit
ac686991a8
43
openspec/specs/realtime-sheet-updates/spec.md
Normal file
43
openspec/specs/realtime-sheet-updates/spec.md
Normal file
@ -0,0 +1,43 @@
|
||||
# realtime-sheet-updates Specification
|
||||
|
||||
## Purpose
|
||||
Как редактируемая таблица фронта (лист формы, отчёт проекта) применяет входящие правки соавторов и эхо собственных правок к уже загруженным строкам и как она показывает значения, которых нет в строке. Цель — чтобы правки не терялись, даже если строки загружены без null-полей.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Входящая правка применяется к отсутствующему ключу
|
||||
Когда приходит обновление строк — сообщение WebSocket `cell_updated` или ответ PATCH на собственную правку — фронт SHALL записать в найденную строку каждое поле из обновления, на любой глубине. Это делается и тогда, когда ключа этого поля или содержащего его блока в загруженной строке нет. Правка SHALL NOT теряться из-за того, что при загрузке листа поле было опущено.
|
||||
|
||||
#### Scenario: Соавтор заполняет пустую ячейку
|
||||
- **WHEN** лист загружен без ключа `data.q1.<поле>` у строки (ячейка пустая), а соавтор вводит в эту ячейку значение
|
||||
- **THEN** у пользователя ячейка показывает новое значение без перезагрузки листа
|
||||
|
||||
#### Scenario: Отсутствует весь блок
|
||||
- **WHEN** в загруженной строке нет ключа блока `data.<блок>`, а приходит обновление с `data.<блок>.<поле>`
|
||||
- **THEN** в строке появляется блок с этим полем, а ячейка показывает значение
|
||||
|
||||
#### Scenario: Прочие поля строки сохраняются
|
||||
- **WHEN** приходит обновление одного поля строки
|
||||
- **THEN** остальные поля строки сохраняют прежние значения
|
||||
|
||||
### Requirement: Очистка ячейки доходит до соавторов
|
||||
Если в обновлении поле равно `null`, фронт SHALL записать `null`, и ячейка SHALL показываться пустой.
|
||||
|
||||
#### Scenario: Соавтор очищает ячейку
|
||||
- **WHEN** у пользователя ячейка показывает значение, а соавтор очищает её
|
||||
- **THEN** у пользователя ячейка становится пустой без перезагрузки листа
|
||||
|
||||
### Requirement: Отсутствующее значение показывается как null
|
||||
Если ключа нет в `data`, редактируемая таблица, свод 1 и свод 2 SHALL показывать и обрабатывать значение так же, как явный `null`. Отображение: пустая ячейка в листе, «—» в сводах. Правила подсветки ошибок валидации не срабатывают. Поиск и фильтр по колонке не находят такую ячейку по непустому запросу. При входе в редактирование поле ввода пустое. Правка, которую фронт отправляет по WebSocket, SHALL всегда содержать ключ `value`; пустое значение передаётся как `null`, а не опускается.
|
||||
|
||||
#### Scenario: Выход из пустой ячейки без ввода
|
||||
- **WHEN** пользователь входит в редактирование ячейки, ключа которой нет в строке, и выходит без ввода
|
||||
- **THEN** сообщение `cell_updated` содержит `"value": null`, как и для ячейки с явным `null`, а пользователь не получает ошибку
|
||||
|
||||
#### Scenario: Пустая ячейка свода 2
|
||||
- **WHEN** свод 2 загружен без ключа `data.q1.<поле>` у строки
|
||||
- **THEN** ячейка показывает «—», как и для `null`
|
||||
|
||||
#### Scenario: Валидация не срабатывает
|
||||
- **WHEN** у строки листа отсутствует ключ поля, к которому привязано правило валидации (например, остаток бронирования)
|
||||
- **THEN** ячейка не подсвечивается как ошибочная
|
||||
113
openspec/specs/sheet-cell-edit/spec.md
Normal file
113
openspec/specs/sheet-cell-edit/spec.md
Normal file
@ -0,0 +1,113 @@
|
||||
# sheet-cell-edit Specification
|
||||
|
||||
## Purpose
|
||||
Ответ сервера на правку ячеек листа бюджетной формы (HTTP `PATCH /form/{id}/sheet/{sheet}/cell(s)` и WebSocket `cell_updated`): какие строки в нём приходят, какими они должны быть относительно полного GET листа и сколько времени правка может стоить на сервере.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Ответ на правку содержит затронутые строки целиком
|
||||
Ответ на правку ячеек листов AHR/CAP/OPER форм FORM_1, FORM_2 и FORM_4 SHALL содержать каждую изменённую строку INPUT и все её строки-агрегаты, то есть всех предков по дереву статей до ROOT включительно. Для FORM_4 предками считаются только ROOT раздела, GROUP программы и ITEM проекта изменённой строки. Агрегат SHALL входить в ответ, даже если его значения не изменились. Ответ SHALL NOT содержать строку META и строки других ветвей дерева. Поле `data` каждой строки SHALL быть полной строкой в том же виде, что и в GET листа с теми же `direction` и `sections`: пустые значения передаются явным `null`. Если задан `sections`, ключи верхнего уровня у исключённых разделов SHALL отсутствовать в `data`.
|
||||
|
||||
#### Scenario: Правка числовой ячейки возвращает строку и её предков
|
||||
- **WHEN** исполнитель меняет `plan.q1` у строки листа FORM_2/AHR
|
||||
- **THEN** ответ содержит эту строку INPUT и по одной строке-агрегату на каждый уровень её пути в дереве статей
|
||||
- **AND** `data` каждой строки ответа равно `data` строки того же `sort_order` из последующего GET листа
|
||||
|
||||
#### Scenario: Правка нечисловой ячейки всё равно возвращает предков
|
||||
- **WHEN** исполнитель меняет `header.justification` у строки FORM_2/AHR
|
||||
- **THEN** ответ содержит строку INPUT и всех её предков, хотя их суммы не изменились
|
||||
|
||||
#### Scenario: Очистка значения
|
||||
- **WHEN** исполнитель записывает `null` в `plan.q1`
|
||||
- **THEN** в строке INPUT ответа ключ `plan.q1` присутствует со значением `null` (или 0 — так же, как в GET)
|
||||
- **AND** суммы предков в ответе уменьшены на прежнее значение ячейки
|
||||
|
||||
#### Scenario: Правка с фильтром разделов
|
||||
- **WHEN** правка выполняется с `sections=plan`
|
||||
- **THEN** ни одна строка ответа не содержит ключей разделов, отличных от `plan`, а также служебных ключей, которых нет в GET с тем же `sections`
|
||||
|
||||
### Requirement: sort_order в ответе совпадает с полным листом
|
||||
Значения `row_type`, `depth` и `sort_order` каждой строки ответа на правку SHALL совпадать со значениями этой же строки в полном GET листа с теми же параметрами, выполненном сразу после правки. `sort_order` — сквозной номер строки во всём листе, а не номер внутри ответа. Это SHALL выполняться для первой и последней строки листа, после добавления и удаления строк в листе, а также при совпадении ключа сортировки у строки INPUT и узла дерева. Порядок строк в GET при совпадении ключа SHALL быть детерминированным и одинаковым в GET и в ответе на правку.
|
||||
|
||||
#### Scenario: Первая и последняя строка листа
|
||||
- **WHEN** правится первая по порядку строка INPUT листа, а затем последняя
|
||||
- **THEN** у каждой строки обоих ответов есть строка с тем же `(row_type, depth, sort_order)` в GET листа, и её `data` совпадает
|
||||
|
||||
#### Scenario: После добавления и удаления строк
|
||||
- **WHEN** в лист добавлены строки (POST line), одна из строк в середине листа удалена (DELETE line), после чего правится строка, расположенная ниже удалённой
|
||||
- **THEN** `sort_order` каждой строки ответа равен её `sort_order` в GET листа после этих операций
|
||||
|
||||
#### Scenario: Пакет правок по разным ветвям дерева
|
||||
- **WHEN** через `PATCH /cells` одним запросом правятся строки двух разных ветвей дерева статей
|
||||
- **THEN** ответ содержит обе строки INPUT и объединение их предков без дублей, и каждая строка совпадает со строкой GET того же `sort_order`
|
||||
|
||||
#### Scenario: Направление FORM_1
|
||||
- **WHEN** правится строка FORM_1/AHR с `direction=Support`
|
||||
- **THEN** `sort_order` строк ответа равен `sort_order` в GET FORM_1/AHR с `direction=Support`
|
||||
|
||||
### Requirement: Состав ответа на листах сателлитов, AHR_LIMIT и OTCH9F
|
||||
На листах FORM_2 AHR_RENT, AHR_UTILITY и AHR_SECURITY ответ на правку SHALL содержать только изменённые строки. На AHR_LIMIT ответ SHALL содержать изменённые строки LEAF шаблона и их предков по шаблону (GROUP и SECTION). На OTCH9F ответ SHALL содержать все строки (все месяцы) того же сочетания статьи и оборудования, что и изменённая строка: остатки на начало месяца зависят от предыдущих месяцев. Для всех этих листов SHALL выполняться требование о совпадении `data` и `sort_order` с полным GET.
|
||||
|
||||
#### Scenario: Правка строки аренды
|
||||
- **WHEN** исполнитель меняет `plan.q1` у строки AHR_RENT
|
||||
- **THEN** ответ содержит ровно одну строку с `data.id` этой строки, и она совпадает со строкой GET AHR_RENT того же `sort_order`
|
||||
|
||||
#### Scenario: Правка количества в лимитах
|
||||
- **WHEN** исполнитель меняет `qty_q1` у строки LEAF листа AHR_LIMIT
|
||||
- **THEN** ответ содержит эту строку и строки её группы и раздела с пересчитанными `amount_q1`, и они совпадают с GET AHR_LIMIT
|
||||
|
||||
#### Scenario: Правка месяца в OTCH9F
|
||||
- **WHEN** исполнитель меняет поступление за месяц у строки OTCH9F
|
||||
- **THEN** ответ содержит строки всех месяцев того же оборудования той же статьи с пересчитанными остатками, и они совпадают с GET OTCH9F
|
||||
|
||||
### Requirement: Обновление АХР после правки сателлита
|
||||
После правки или добавления строки на AHR_RENT, AHR_UTILITY, AHR_SECURITY или AHR_LIMIT формы FORM_2 сервер SHALL отправить подключённым к листу AHR событие (`cell_updated` или `row_added`). Событие SHALL содержать синхронизированные строки AHR и всех их предков, включая уровень SUB_SUB_ITEM. Каждая из этих строк SHALL совпадать со строкой того же `sort_order` полного GET листа AHR. Если синхронизированных строк нет, событие SHALL NOT содержать строк AHR, то есть ответ пуст, а не весь лист. Строки AHR_LIMIT SHALL сопоставляться и со статьями AHR глубины 4 по `num_group_id` (поведение миграции 0037).
|
||||
|
||||
#### Scenario: Правка аренды обновляет АХР
|
||||
- **WHEN** исполнитель меняет `plan.q1` у строки AHR_RENT, связанной со строкой AHR
|
||||
- **THEN** строки, которые сервер готовит для события на лист AHR, совпадают со строками GET AHR того же `sort_order`, и среди них есть синхронизированная строка INPUT с новой суммой
|
||||
|
||||
#### Scenario: Источник без синхронизированных строк
|
||||
- **WHEN** запрашивается обновление АХР для строки сателлита, у которой нет связанной строки AHR
|
||||
- **THEN** набор строк для события пуст
|
||||
|
||||
#### Scenario: Лимит на статье глубины 4
|
||||
- **WHEN** правится строка AHR_LIMIT, код статьи которой совпадает с `num_group_id` статьи AHR глубины 4
|
||||
- **THEN** набор строк для события содержит синхронизированную строку AHR этой статьи
|
||||
|
||||
### Requirement: Пакет правок атомарен
|
||||
Если хотя бы одна правка в пакете отклонена (неизвестная колонка, чужая строка, нет прав, окно закрыто), SHALL NOT применяться ни одна правка пакета, и ответ SHALL быть ошибкой.
|
||||
|
||||
#### Scenario: Ошибочная колонка в пакете
|
||||
- **WHEN** `PATCH /cells` содержит одну корректную правку и одну с несуществующей колонкой
|
||||
- **THEN** ответ — ошибка 4xx, и GET листа после запроса совпадает с GET до него
|
||||
|
||||
### Requirement: GET листа не меняется
|
||||
Содержимое GET листов FORM_1 и FORM_2 (строки, `data`, `sort_order`) после изменения SHALL совпадать с содержимым до него на тех же данных. Исключение — порядок строк с совпадающим ключом сортировки: до изменения он не был определён. Время GET FORM_2/AHR SHALL NOT вырасти больше чем на 10 %.
|
||||
|
||||
#### Scenario: Снимок GET до и после миграции
|
||||
- **WHEN** на стенде профилирования снят GET всех листов FORM_1 и FORM_2 из манифеста до применения миграции и после неё
|
||||
- **THEN** ответы попарно совпадают
|
||||
|
||||
#### Scenario: Время GET после смешанной нагрузки
|
||||
- **WHEN** в одном соединении с БД чередуются правки ячеек и GET листа FORM_2/AHR
|
||||
- **THEN** среднее время GET не больше базового + 10 %
|
||||
|
||||
### Requirement: Время правки ячейки на FORM_1 и FORM_2
|
||||
Правка одной ячейки листа FORM_1 или FORM_2 SHALL NOT собирать лист целиком: на правку SHALL приходиться 0 вложенных полных сборок листа. Среднее серверное время `v3.upd_form_cell` на FORM_2/AHR SHALL быть не больше 50 мс как на листе в 1154 строки, так и на листе в 2092 строки. Требование не распространяется на FORM_4 до слияния AURORA-1846.
|
||||
|
||||
#### Scenario: Соавтор на листе масштаба 1
|
||||
- **WHEN** на стенде профилирования (масштаб 1, без grow, FORM_2/AHR 1154 строки) выполняется шаг `coauthor`
|
||||
- **THEN** в `sql/coauthor/sql_top.json` `mean_exec_time` вызова `v3.upd_form_cell` ≤ 50 мс
|
||||
- **AND** вызовов `v3.v_form_view` не верхнего уровня (`toplevel = false`) нет, а число вызовов `v3.v_form2_view(p_form_id, p_sheet, p_sections)` равно числу GET листа и не зависит от числа правок
|
||||
|
||||
#### Scenario: Соавтор на выросшем листе
|
||||
- **WHEN** после `perfkit.gen.grow` (FORM_2/AHR 2092 строки) шаг `coauthor` выполняется повторно
|
||||
- **THEN** `mean_exec_time` вызова `v3.upd_form_cell` ≤ 50 мс
|
||||
|
||||
### Requirement: Ручной ввод без очереди
|
||||
Сервер SHALL успевать подтверждать правки одного соединения с частотой ручного ввода (5 правок/с), не накапливая очередь.
|
||||
|
||||
#### Scenario: Соавтор с частотой 5 правок в секунду
|
||||
- **WHEN** бот соавтора на FORM_2/AHR отправляет 5 правок/с в течение шага `coauthor`
|
||||
- **THEN** число полученных ответов (эхо и ошибки) составляет не меньше 95 % от числа отправленных правок
|
||||
132
openspec/specs/sheet-read-api/spec.md
Normal file
132
openspec/specs/sheet-read-api/spec.md
Normal file
@ -0,0 +1,132 @@
|
||||
# sheet-read-api Specification
|
||||
|
||||
## Purpose
|
||||
Контракт ответа тяжёлых GET с табличными данными: лист формы, своды 1 и 2, отчёты и сводные проектов РФ. Здесь описано, что получает фронт: обёртка, строки, отсутствие null-полей, служебный заголовок времени БД. Описаны и неизменные пути записи и выгрузки, и пороги производительности.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Обёртка и строки ответа тяжёлых GET
|
||||
Следующие эндпоинты (далее — «тяжёлые GET») SHALL отвечать при успехе статусом 200, `Content-Type: application/json` и телом-объектом `{"success": true, "message": null, "result": [...], "count": N}`:
|
||||
- `GET /api/v1/form/{form_id}/sheet/{sheet}`,
|
||||
- `GET /api/v1/svod/1/{sheet}/{year}`,
|
||||
- `GET /api/v1/svod/2/{sheet}/{year}`,
|
||||
- `GET /api/v1/projects/{project_id}/report/{year}/{report_type}`,
|
||||
- `GET /api/v1/rf-rollup/{branch_id}/{year}`,
|
||||
- `GET /api/v1/projects/{project_id}/summary`,
|
||||
- `GET /api/v1/projects/plan-summary`, `GET /api/v1/projects/fact-summary`, `GET /api/v1/projects/detail-summary`.
|
||||
|
||||
Каждый элемент `result` SHALL быть объектом с ключами `row_type` (строка), `depth` (целое), `sort_order` (целое или null) и `data` (объект). `count` SHALL равняться числу элементов `result`. Строки SHALL идти в том порядке, в каком их возвращает SQL-функция эндпоинта. Ответ SHALL сжиматься gzip так же, как остальные ответы API: при `Accept-Encoding: gzip` и размере от 1000 байт.
|
||||
|
||||
#### Scenario: Лист формы
|
||||
- **WHEN** пользователь с доступом к форме запрашивает `GET /api/v1/form/{id}/sheet/AHR` для формы FORM_2
|
||||
- **THEN** ответ 200, тело — объект с `success: true`, `message: null`, `result` — массив строк с ключами `row_type`, `depth`, `sort_order`, `data`, первая строка — `META` с `depth = -1`, `count` равен длине `result`
|
||||
|
||||
#### Scenario: Пустой свод
|
||||
- **WHEN** пользователь без доступа ни к одной из запрошенных `org_ids` запрашивает `GET /api/v1/svod/2/GO/{year}?org_ids=...`
|
||||
- **THEN** ответ 200 с телом `{"success": true, "message": null, "result": [], "count": 0}`
|
||||
|
||||
#### Scenario: Сжатие
|
||||
- **WHEN** клиент запрашивает свод 2 с заголовком `Accept-Encoding: gzip`
|
||||
- **THEN** ответ приходит с `Content-Encoding: gzip`, а после распаковки — тот же JSON
|
||||
|
||||
### Requirement: Значения ответа совпадают с прежними
|
||||
После разбора JSON тяжёлые GET SHALL отдавать те же строки и те же значения, что отдавались до этого изменения при тех же данных, параметрах и пользователе. Единственное допустимое отличие — правило про null-поля (см. требование «Ответ листа и сводов без null-полей»). Текстовая запись чисел MAY отличаться (`1.50` вместо `1.5`), если после разбора значения равны. Порядок ключей в объектах и пробелы MAY отличаться.
|
||||
|
||||
Проверки доступа, ответы на ошибки (статус и тело) и обработка параметров (`direction`, `sections`, `org_ids`, `offset`, `limit`, `sort_by`, `sort_direction`, `branch_id`, `project_type`) SHALL остаться прежними.
|
||||
|
||||
#### Scenario: Сравнение до и после на стенде
|
||||
- **WHEN** на одних и тех же данных стенда `perf/` сняты ответы всех листов, сводов 1 и 2 и отчётов проектов по манифесту — сначала на версии до изменения, затем после PR A
|
||||
- **THEN** для каждого запроса результат `json.loads` ответа «после» равен результату `json.loads` ответа «до»
|
||||
|
||||
#### Scenario: Ошибки не изменились
|
||||
- **WHEN** запрашивается лист несуществующей формы (`GET /api/v1/form/999/sheet/AHR`) или лист свода вне допустимого списка
|
||||
- **THEN** статус и тело ответа такие же, как до изменения (404 с прежним `message`)
|
||||
|
||||
#### Scenario: Ограничение организаций в своде
|
||||
- **WHEN** пользователь с ролью EXECUTOR_DFIP запрашивает свод с `org_ids`, часть которых ему не назначена
|
||||
- **THEN** в ответе только строки назначенных ему организаций, как и до изменения
|
||||
|
||||
### Requirement: Ответ листа и сводов без null-полей
|
||||
После PR B в ответах `GET /api/v1/form/{form_id}/sheet/{sheet}`, `GET /api/v1/svod/1/{sheet}/{year}` и `GET /api/v1/svod/2/{sheet}/{year}` объект `data` каждой строки, включая `META`, SHALL NOT содержать полей со значением `null` на любой глубине вложенности объектов. Объект, у которого все поля были `null`, SHALL остаться пустым объектом `{}`, а не исчезнуть. Элементы массивов, равные `null`, SHALL остаться. Ключи верхнего уровня строки (`row_type`, `depth`, `sort_order`, `data`) SHALL присутствовать всегда. Значения, отличные от `null`, SHALL совпадать с прежними.
|
||||
|
||||
Отчёты и сводные проектов (эндпоинты `/api/v1/projects/...` и `/api/v1/rf-rollup/...`) SHALL по-прежнему отдавать `null` явно.
|
||||
|
||||
#### Scenario: Пустая ячейка листа
|
||||
- **WHEN** у строки INPUT листа FORM_2/AHR колонка `q1.<поле>` не заполнена и фронт запрашивает лист
|
||||
- **THEN** в `data.q1` строки нет ключа этого поля, остальные поля `data.q1` присутствуют со своими значениями
|
||||
|
||||
#### Scenario: Сравнение до и после на стенде
|
||||
- **WHEN** на одних и тех же данных сняты ответы всех листов и сводов 1 и 2 до изменения и после PR B
|
||||
- **THEN** ответ «после» после `json.loads` равен ответу «до», из `data` которого рекурсивно удалены поля со значением `null`
|
||||
|
||||
#### Scenario: Отчёт проекта не меняется
|
||||
- **WHEN** после PR B запрашивается `GET /api/v1/projects/{id}/report/{year}/LIMIT`
|
||||
- **THEN** поля `data` со значением `null` присутствуют, как и до изменения
|
||||
|
||||
### Requirement: Ответы на правку отдают null явно
|
||||
Ответы путей записи SHALL содержать поля со значением `null` явно, как до изменения: `PATCH /api/v1/form/{id}/sheet/{sheet}/cell`, `PATCH .../cells`, `POST .../line`, `DELETE .../line/{row_id}`, правки FORM_3 в `/api/v1/projects/...` и сообщения WebSocket (`cell_updated` и остальные с `result`). Очищенная ячейка SHALL приходить в ответе как ключ со значением `null`.
|
||||
|
||||
#### Scenario: Очистка ячейки
|
||||
- **WHEN** пользователь отправляет `PATCH /api/v1/form/{id}/sheet/{sheet}/cell` со значением `null` для заполненной ячейки
|
||||
- **THEN** в строке ответа есть ключ этой колонки со значением `null`
|
||||
|
||||
#### Scenario: Эхо соавтору
|
||||
- **WHEN** пользователь очищает ячейку через WebSocket
|
||||
- **THEN** соавторы получают `cell_updated`, где у этой колонки значение `null`, а не отсутствующий ключ
|
||||
|
||||
### Requirement: Выгрузка Excel не меняется
|
||||
Выгрузки Excel листов, сводов 1 и 2 и отчётов проектов SHALL формировать то же содержимое, что и до изменения, при тех же данных и параметрах.
|
||||
|
||||
#### Scenario: Выгрузка свода 2
|
||||
- **WHEN** после PR A и PR B выгружается свод 2 GO за год
|
||||
- **THEN** содержимое файла (ячейки и значения) совпадает с выгрузкой до изменения
|
||||
|
||||
### Requirement: Заголовок X-DB-Time-Ms
|
||||
Каждый успешный ответ тяжёлого GET SHALL содержать заголовок `X-DB-Time-Ms` — неотрицательное число миллисекунд с двумя знаками после точки. Это время от начала обработчика до получения строк из БД. Обработчик SHALL по-прежнему писать в лог запись `db_time` с тем же набором полей.
|
||||
|
||||
#### Scenario: Заголовок на ответе листа
|
||||
- **WHEN** запрашивается `GET /api/v1/form/{id}/sheet/{sheet}`
|
||||
- **THEN** в ответе есть заголовок `X-DB-Time-Ms`, его значение разбирается как число ≥ 0
|
||||
|
||||
#### Scenario: Методика api_split
|
||||
- **WHEN** после изменения запускается `perf/analysis/api_split.py --scale 1`
|
||||
- **THEN** для каждого запроса скрипт получает числовое `db_ms` (не `nan`)
|
||||
|
||||
### Requirement: Схема OpenAPI не меняется
|
||||
В схеме OpenAPI (`/openapi.json`) у тяжёлых GET схема успешного ответа SHALL остаться `BaseListResponse[SheetResponse]`, как до изменения.
|
||||
|
||||
#### Scenario: Схема ответа листа
|
||||
- **WHEN** запрашивается `/openapi.json`
|
||||
- **THEN** у `GET /api/v1/form/{form_id}/sheet/{sheet}` схема ответа 200 ссылается на ту же компоненту, что и до изменения
|
||||
|
||||
### Requirement: Время ответа тяжёлых GET
|
||||
На стенде `perf/` (масштаб 1, те же данные, напрямую на API, медиана 10 повторов после прогрева) после PR B полное время ответа SHALL снизиться относительно версии до изменения:
|
||||
- `GET /api/v1/svod/2/GO/{year}` (все организации, роль dfip) — не менее чем на 30 %;
|
||||
- `GET /api/v1/form/{id}/sheet/AHR` для FORM_2 (роль rf) — не менее чем на 10 %. Дальнейшее ускорение этого листа — предмет AURORA-1847 (SQL листа).
|
||||
|
||||
После PR A полное время ни одного запроса из набора `api_split.py` SHALL NOT вырасти больше чем на 5 % от версии до изменения. Доля Python (`(полное − X-DB-Time-Ms) / полное`) — справочная метрика, порогом она не является.
|
||||
|
||||
Пороги этого требования и требования «Размер ответа листа FORM_2/AHR уменьшается» — критерии приёмки задачи, а не условие мержа PR. PR вливается по зелёным тестам и доказанной корректности (требования «Значения ответа совпадают с прежними» и «Ответ листа и сводов без null-полей»: сравнение ответов на стенде в режимах exact и strip-nulls). Корректность — жёсткое условие. Цифры замера дописываются в описание PR, недобор порога оформляется follow-up задачей.
|
||||
|
||||
#### Scenario: Замер свода 2 GO
|
||||
- **WHEN** `perf/analysis/api_split.py --scale 1 --repeats 10` запускается на версии до изменения и после PR B на одних и тех же данных
|
||||
- **THEN** медиана `total_ms` свода 2 GO «после» не больше 0,7 × медианы «до»
|
||||
|
||||
#### Scenario: Замер листа FORM_2/AHR
|
||||
- **WHEN** тот же замер для листа FORM_2/AHR
|
||||
- **THEN** медиана `total_ms` «после» не больше 0,9 × медианы «до»
|
||||
|
||||
#### Scenario: PR A не замедляет
|
||||
- **WHEN** тот же замер на версии после PR A
|
||||
- **THEN** медиана `total_ms` каждого запроса набора не больше 1,05 × медианы «до»
|
||||
|
||||
#### Scenario: Недобор порога
|
||||
- **WHEN** тесты зелёные, сравнение ответов на стенде прошло, а замер показал недобор порога
|
||||
- **THEN** PR вливается, цифры фиксируются в описании PR, недобор оформляется follow-up задачей
|
||||
|
||||
### Requirement: Размер ответа листа FORM_2/AHR уменьшается
|
||||
После PR B распакованный размер ответа `GET /api/v1/form/{id}/sheet/AHR` для FORM_2 на стенде (масштаб 1) SHALL быть меньше, чем до изменения (критерий Jira AURORA-1848; вместе с −10 % времени — критерий по этому листу, согласовано 28.10).
|
||||
|
||||
#### Scenario: Сравнение размеров
|
||||
- **WHEN** `api_split.py --scale 1` запускается до изменения и после PR B
|
||||
- **THEN** значение `bytes` листа FORM_2/AHR «после» меньше, чем «до»
|
||||
Loading…
x
Reference in New Issue
Block a user