docs: полный состав строк ББ/ОФР в README и CLAUDE.md (#80)

README: достройка GET, адресация PATCH по line_code, строка UNKNOWN_CELL.
CLAUDE.md: абзац про справочник строк. design.md: рулинги R-9…R-11.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-09 12:14:53 +04:00
parent 2d21a416d6
commit 5c330eb400
3 changed files with 12 additions and 5 deletions

View File

@ -98,7 +98,10 @@
документа. **Порядок блокировок «файл → документ»**, шапка (`meta`) читается ПОД блокировкой с `refresh=True` — иначе в сессии
остаётся устаревший акцепт. Подтверждение формы (`services/documents._check_statement`) требует период (`PERIOD_REQUIRED`) и
пройденные/акцептованные жёсткие проверки (`CHECKS_NOT_PASSED`, `detail.failures`); мягкие (в т. ч. межформенная 1370↔2400)
не блокируют. Комментарии к документу — `services/documents.py` (`comments`/`add_comment`, `comment(object_type='document')`,
не блокируют. Полный состав формы — `domain/statement_lines.py` по справочнику `api/rules/statement_lines.yaml` (редакции `66n`/`fsbu4`
из шаблонов банка; копия словаря scan-gateway с ним не совпадает): `GET …/statement` достраивает строки и ячейки с
`id = null`, `PATCH` по `line_code` создаёт строку (`line_code_original_value = NULL` — «не из источника»); пустые
созданные строки не участвуют в §7 (`statement_amounts`). Решения — `openspec/changes/statement-template-lines/design.md`. Комментарии к документу — `services/documents.py` (`comments`/`add_comment`, `comment(object_type='document')`,
без аудита; `comments_count` в карточках). `GET /files/{id}/content` — `services/primary_files.content` +
`ObjectStorage.stream` (поток из S3, первая порция до заголовков → `FILE_CONTENT_NOT_FOUND`; сессию тело потока не держит;
имя — RFC 5987, `X-Content-Type-Options: nosniff`; освобождение потока — `common/streaming.py`: `ClosingStreamingResponse` +

View File

@ -568,7 +568,7 @@ S3 (storage); без S3 — предупреждение в логе, цикл
(`organization_name`, `okud`, `okpo`, …); `lines[].values[]` — суммы строкой, `is_modified`, `original_amount`; `checks` —
`{failed, soft_failed, cross_form, accepted}` (нарушение — `{column_index, relation, expected, actual}`, у межформенной
проверки `column_index = -1`). Не форма (ОСВ, расшифровка, ББ/ОФР без данных формы — созданные до Среза 3b) — 409 `DOCUMENT_NOT_STATEMENT`.
Подтверждённая форма читается.
Подтверждённая форма читается. Форма достраивается по справочнику строк редакции (`api/rules/statement_lines.yaml`): недостающие строки (`id = null`, `origin = template`) и ячейки (`id = null`); в БД не пишется.
- **`PATCH /documents/{id}/statement`** — `{header?: {inn?, report_date?, units?}, values?: [{line_id, column_id, amount}]}`
(до 5000 ячеек; `amount: null` очищает ячейку; не более двух знаков; `units` — непустая строка после обрезки пробелов до 50 символов,
пробельная строка — 422, `null` — «не менять»; `inn: null` — 422 `INVALID_INN`, ИНН не очищается). Правится только неподтверждённая форма
@ -577,6 +577,7 @@ S3 (storage); без S3 — предупреждение в логе, цикл
одна ячейка дважды — 422 `DUPLICATE_CELL`, ИНН не из 10 или 12 цифр — 422 `INVALID_INN`. Один запрос — одна транзакция и одно
событие `RECOGNIZED_DATA_EDIT` с `before/after`; возврат ячейки к исходному значению снимает `is_modified`; правка ИНН
пересчитывает `counterparty_check`, даты отчёта — период документа; любая правка сбрасывает акцепт и пересчитывает статус.
`values[]` адресует ячейку `line_id` или `line_code` (ровно одно): ячейка, которой нет, создаётся при первой непустой правке; строка по `line_code` — только код справочника редакции, которого нет в форме.
Запрос без изменений — 200 без аудита. Ответ — та же форма, что у `GET`.
- **`POST /documents/{id}/statement/checks/accept`** — `{comment}` (1–2000 символов): акцепт нарушенных жёстких проверок
(`checks_accepted_by/at/comment`). Нарушений нет — 409 `NOTHING_TO_ACCEPT`. Акцепт сбрасывается любой правкой формы (в `before_value` события аудита сброса остаётся комментарий сброшенного акцепта).
@ -855,7 +856,7 @@ email без учёта регистра проверяет только сер
| `FILE_CONTENT_NOT_FOUND` | 404 | `GET /files/{id}/content`: файл есть, объекта нет в S3 (истёк или пропал) |
| `DOCUMENT_NOT_STATEMENT` | 409 | `…/statement`: документ — не форма ББ/ОФР (ОСВ, расшифровка, ББ/ОФР без данных формы — созданные до Среза 3b) |
| `DOCUMENT_READONLY` | 409 | правка/акцепт формы: форма уже `CONFIRMED` (исправление — новым файлом) |
| `UNKNOWN_CELL` | 422 | `PATCH …/statement`: `line_id`/`column_id` не принадлежат этой форме |
| UNKNOWN_CELL | 422 | PATCH …/statement: line_id/column_id не этой формы; line_code уже есть в форме (передайте line_id), нет в справочнике редакции или редакция не определена |
| `DUPLICATE_CELL` | 422 | `PATCH …/statement`: одна ячейка указана в запросе дважды |
| `NOTHING_TO_ACCEPT` | 409 | акцепт: непройденных жёстких проверок нет (в том числе уже акцептованы) |
| `PERIOD_REQUIRED` | 409 | подтверждение формы: период не определён — поправьте дату отчёта |

View File

@ -71,7 +71,7 @@ pydantic: коды — четыре цифры, без повторов внут
есть: справочник только добавляет, ничего не убирает и не переименовывает.
### D-3. Достройка ответа
`complete_form(form_type, edition, columns, lines) -> lines'`:
Достройка (по факту — `_lines_out` в `schemas/statements.py`, R-9; логика та же):
- для каждой строки из БД — недостающие ячейки по всем `columns` (`id = null`, `amount = null`, `string_value = null`,
`confidence = null`, `is_recognized = null`, `is_modified = false`, `original_amount = null`, `modified_* = null`);
- при `edition ≠ None` — строки справочника, кода которых нет среди строк формы: `id = null`, `line_code`, `line_name` из
@ -113,7 +113,7 @@ pydantic: коды — четыре цифры, без повторов внут
`is_recognized = NULL` и `confidence = NULL` — «без сомнений», правленные — `is_modified`.
### D-6. Фронт (arm-web, PR в `test`)
`statementGrid`: ключ строки — `line.id ?? "code:" + line_code`, ключ ячейки — `rowKey:column_id`; `draftToPatch`
`statementGrid`: ключ строки — `line.id ?? "code-" + line_code` (R-10), ключ ячейки — `rowKey:column_id`; `draftToPatch`
отправляет `line_id` или `line_code` по ключу. Достроенная пустая ячейка (`id = null`) без подсветки «требует проверки»
(`is_recognized = null`), введённое значение подсвечивается как правка. `formLayout` уже раскладывает строки по разделам —
меняется только то, что разделы теперь всегда полные. **Строки «БАЛАНС» (1600 по активу, 1700 по пассиву) выделяются
@ -146,3 +146,6 @@ pydantic: коды — четыре цифры, без повторов внут
| R-6 | 09.10.2026 | Ячейка, которой нет у существующей строки, создаётся при правке по `line_id` | Та же проблема, что с отсутствующей строкой; отдельного API не требует | — |
| R-7 | 09.10.2026 | Цвет строк «БАЛАНС» — светло-фиолетовый `#f9f0ff` + жирный 700; подсветка ячейки перекрывает фон строки; ОФР не выделяется | Владелец просил выделить баланс по активу и пассиву, цвет не задал; в макете [41] строки «БАЛАНС» не видно; фиолетовый не занят: серый — разделы, синий — «исправлено», оранжевый — «требует проверки» | Цвет не понравится — правка одной константы |
| R-8 | 09.10.2026 | `has_lines` (условие D-6 «в форме есть строки») учитывает и строки, созданные правкой | Форму, у которой источник не дал строк (не найдена колонка кодов), аналитик может заполнить вручную — это его данные, как любая правка; статус всё равно требует пройденных проверок и завершённого задания | Документ без строк источника станет `RECOGNIZED` после ручного заполнения — ожидаемо |
| R-9 | 09.10.2026 | Достройка формы реализована функцией `_lines_out` в `schemas/statements.py`, а не `domain/statement_lines.complete_form` из D-3 | План конкретнее, поведение то же | Рефакторинг в domain позже |
| R-10 | 09.10.2026 | Ключ достроенной строки на фронте `code-<code>`, а не `code:<code>` из D-6 | ':' разделяет rowKey и columnId в cellKey | Нет |
| R-11 | 09.10.2026 | Адресация ячеек PATCH вынесена из `_apply_cells` в `_resolve_cells` (отклонение от кода плана ради ruff PLR0912/PLW2901); поведение и порядок проверок сохранены | Линтер | Нет |