Merge pull request 'Срез 3b (docs): §7/§9 по факту, контракт API форм и мелких дыр, OQ' (#49) from feat/48-slice-3b-docs into main
Reviewed-on: #49
This commit is contained in:
commit
6be6823808
@ -160,6 +160,37 @@ advisory-lock по `(ЮЛ, sha256)` (двойной клик не проскак
|
||||
- **Нагрузка и транзакции.** Переход версии после загрузки выполняется в отдельной короткой транзакции
|
||||
(`session_transaction`), сбой не отменяет принятые файлы (R-10). Чтение (`GET …/osv`, сводка, экспорт) блокировок не берёт.
|
||||
|
||||
### Формы ББ/ОФР, проверки §7 и API экрана проверки (Срез 3b)
|
||||
|
||||
Формы (`statementDocument` контракта v1.2) приходят от обоих сервисов распознавания в одном формате; ядро — единственное
|
||||
место, где они становятся строками БД и где считаются проверки §7 (самопроверку `checks` кладёт в результат только tabular-parser, scan-gateway — нет; ядро её игнорирует).
|
||||
|
||||
- **Маппинг форм — ветка `map_file`, а не отдельный цикл.** После листов ОСВ тот же вызов `map_file` создаёт документы
|
||||
`BALANCE`/`INCOME` и `financial_statement_meta/column/line/value` в той же транзакции (порядок: очистка прежних данных
|
||||
файла, включая `financial_statement_*` его документов → документы ОСВ и форм → строки → статус файла → автозамена →
|
||||
`on_documents_mapped`). Отдельный цикл делил бы файл, его статус и автозамену между двумя транзакциями. `MappingFatal` —
|
||||
только при пустом `documents`. Чистые функции — `domain/statement_mapping.py` (черновики документа, шапки, колонок,
|
||||
строк и значений; период по дате отчёта либо по самой поздней дате колонок; статус документа §9.3); правила — `05-domain-rules.md`
|
||||
§9.5 п. 2.
|
||||
- **Проверки §7 — чистые функции без БД.** `domain/statement_checks.py` (`run_checks`, `cross_form_check`) — перенос
|
||||
логики `tabular-parser/api/src/parser/statements/checks.py`; словарь — `arm-core/api/rules/statement_checks.yaml`,
|
||||
**копия** `tabular-parser/api/rules/statement_checks.yaml` с пометкой источника. Теста идентичности копий между
|
||||
репозиториями нет намеренно (урок arm-core #33: тест, читающий соседний репозиторий, ломал main). **Риск:** копии могут
|
||||
разойтись незаметно; при изменении словаря в tabular-parser правку нужно повторять в ядре вручную и наоборот. Результат не
|
||||
хранится: считается по текущим суммам на каждый `GET …/statement`, правку и подтверждение; хранится только акцепт
|
||||
(`financial_statement_meta.checks_accepted_by/at/comment`).
|
||||
- **Слои API.** `api/endpoints/files/` — `statements.py` (`GET`/`PATCH /documents/{id}/statement`, `POST …/statement/checks/accept`),
|
||||
`comments.py` (`POST/GET /documents/{id}/comments`), `read.py` (`GET /files/{id}/content`); сервис `services/statements.py`
|
||||
(чтение, правка, акцепт, пересчёт статуса), `services/documents.py` (подтверждение: `PERIOD_REQUIRED`, `CHECKS_NOT_PASSED`),
|
||||
`repositories/statements.py`; схемы — `schemas/statements.py`, `schemas/comments.py`; `GET /users/brief` — `api/endpoints/users.py`.
|
||||
Правка и акцепт берут блокировки в общем порядке «файл `FOR UPDATE` → документ `FOR NO KEY UPDATE`» и перечитывают шапку
|
||||
под блокировкой.
|
||||
- **Исходный файл** отдаётся ядром потоком из S3 (`miniopy-async`, чтение частями, без загрузки целиком в память):
|
||||
прямая ссылка на MinIO не используется, потому что в контуре клиент может не видеть хранилище, а через ядро действует
|
||||
видимость по ЮЛ. Объекта нет в хранилище (убран ретеншном S3 или пропал) — 404 `FILE_CONTENT_NOT_FOUND`; S3 не подключено — 503 `STORAGE_UNAVAILABLE`.
|
||||
- **Документы Среза 3b не меняют модель:** DBML v5.16, миграций и значений enum нет; контракт `docs/contracts/` не менялся
|
||||
(редакция формы `66n`/`fsbu4` выводится ядром из даты, OQ-145).
|
||||
|
||||
## Схема ключей S3
|
||||
|
||||
**Один бакет** (`OPENBAO__MINIO__MINIO_BUCKET` шаблона); роль объекта — первый сегмент ключа:
|
||||
|
||||
@ -455,6 +455,24 @@ variance = |amount_a − amount_b|; variance_pct = variance / min(amount_a, amou
|
||||
`1370 − 1370(начало года) = 2400` парсером не выполняется. Это сигнал о структуре и данных файла, а не замена проверок
|
||||
ядра: ядро (Срез 3) пересчитывает §7 и ведёт акцепт (`financial_statement_meta.checks_accepted_*`).
|
||||
|
||||
**Ядро (Срез 3b, реализовано).** Ядро считает §7 само, для обоих источников (Excel и OCR), по **текущим** суммам формы
|
||||
(`financial_statement_value.amount` после правок аналитика); результат не хранится, считается на лету (`GET
|
||||
/documents/{id}/statement`, подтверждение). Правила — словарь `arm-core/api/rules/statement_checks.yaml`: **копия**
|
||||
словаря tabular-parser (те же жёсткие и мягкие соотношения, допуск ±1 в единицах формы, округление 0,001 %, `abs_terms`,
|
||||
мягкая «сумма пятизначных подстрок = родитель»); тест идентичности копий между репозиториями не ставится, расхождение —
|
||||
известный риск (`03-architecture.md`). Соотношение считается по каждой колонке и только если строка-цель (левая часть) есть в форме;
|
||||
отсутствующий код и пустая сумма — 0 (ненулевые слагаемые при пустой цели дают жёсткое нарушение). Результат в ответе API: `failed` (жёсткие), `soft_failed` (мягкие, в
|
||||
том числе «в пределах округления»), `cross_form`, `accepted`.
|
||||
Межформенная `1370(отчётная дата) − 1370(начало года) = 2400` — **мягкая** и не блокирует подтверждение: пара — другой
|
||||
документ формы того же ЮЛ с тем же годом и кварталом периода (файл не `SUPERSEDED`; подтверждённый предпочтительнее, далее
|
||||
самый поздний `created_at`); считается, только если найдены пара и обе колонки (ОФР часто приходит отдельным файлом позже).
|
||||
**Акцепт** — по жёстким нарушениям, с комментарием (1–2000 символов), `POST /documents/{id}/statement/checks/accept`;
|
||||
без нарушений — 409 `NOTHING_TO_ACCEPT`. Акцепт **сбрасывается любой правкой формы** (значения или шапки): данные
|
||||
изменились, проверку нужно смотреть заново. Аудит акцепта — `RECOGNIZED_DATA_EDIT` (отдельного `audit_action` нет, новое
|
||||
значение enum потребовало бы DBML v5.17). Формулы одни для редакций `66n` и `fsbu4` (`ASSUMPTION(OQ-12, OQ-146)`); редакцию
|
||||
ядро определяет по дате периода документа и отдаёт в ответе (`edition`: `66n` при дате ≤ 2024-12-31, `fsbu4` — позже, `null`
|
||||
без даты; `ASSUMPTION(OQ-145)`).
|
||||
|
||||
Когда: после распознавания; после «Сохранить» правок; при «Подтвердить данные». Непройдено → `document.status =
|
||||
NEEDS_CHECK`; пройдено/акцептовано + нажато «Подтвердить» → `CONFIRMED` + `audit_event(DOCUMENT_CONFIRM)`.
|
||||
Отрицательные значения в формах — в скобках `(2100)` → `-2100`. Значения с `confidence < 100` подсвечиваются.
|
||||
@ -547,6 +565,17 @@ INITIATED; в Срезе 2c консолидаций ещё нет, поэтом
|
||||
равен ИНН владельца файла → `MATCH`; ИНН нет — нормализованное имя шапки совпало с именем или алиасом владельца → `MATCH`;
|
||||
иначе `MISMATCH`; пустая шапка → `UNKNOWN`. `period_check` — `UNKNOWN`: подходящесть документа версии — функция (документ, версия), а один документ входит в несколько версий (§3 п. 1, Срез 4; OQ-120).
|
||||
|
||||
**Документ формы ББ/ОФР (Срез 3b).** `RECOGNIZED` — только если одновременно: задание `done`; нет непройденных
|
||||
неакцептованных **жёстких** проверок §7; `counterparty_check ≠ MISMATCH`; определены `period_year` и `period_quarter`; у
|
||||
каждого значения либо `is_modified`, либо `is_recognized` не `false` и `confidence` равна 100 или пуста; нет предупреждений
|
||||
маппинга по документу (повтор кода, значение вне колонок, пропущенная вторая форма); есть хотя бы одна строка. Иначе
|
||||
`NEEDS_CHECK`. Статус пересчитывается при маппинге, после каждой правки формы и после акцепта (кроме `CONFIRMED`); при
|
||||
пересчёте после правки/акцепта предупреждения маппинга не учитываются — на уровне документа они не хранятся
|
||||
(`metrics.mapping_warnings` — на уровне файла). Расшифровки (`DISCLOSURE`) и прочие (`OTHER`) — один документ на тип на
|
||||
файл без данных, статус `RECOGNIZED` (OQ-23). Документ ББ/ОФР, созданный до Среза 3b и не имеющий данных формы
|
||||
(`financial_statement_meta` нет), подтверждается без условий п. 9.5.4, а `GET`/`PATCH …/statement` по нему — 409
|
||||
`DOCUMENT_NOT_STATEMENT`.
|
||||
|
||||
### 9.4 Задание (`recognition_status`): queued → published → processing → done | needs_check | error | timeout → dlq;
|
||||
replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `reply_deadline` OCR 5 мин.
|
||||
|
||||
@ -564,8 +593,25 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `
|
||||
`counterparty_id` (§3 п. 4), пишутся `recognition_job.group_schema` и `metrics.mapping_warnings`, файл → `PROCESSED`,
|
||||
выполняется автозамена. Второй лист того же файла с тем же счётом документа не создаёт (предупреждение, первый
|
||||
документ — `NEEDS_CHECK`). Повторяемые сбои (БД, S3) — до `MAPPING_MAX_ATTEMPTS` (3) попыток, затем файл `ERROR`
|
||||
(`metrics.mapping_error`); неповторяемые (ключ вне папки, нет объекта, результат другого задания, нет листов ОСВ) —
|
||||
сразу `ERROR`. Документы типов ББ/ОФР/прочее в результате игнорируются (Срез 2b/3).
|
||||
(`metrics.mapping_error`); неповторяемые (ключ вне папки, нет объекта, результат другого задания, пустой список
|
||||
`documents`) — сразу `ERROR`. **Формы (Срез 3b):** после листов ОСВ, в той же транзакции, каждый `statementDocument`
|
||||
`BALANCE`/`INCOME` (Excel и OCR) становится `document` (`osv_account_code = NULL`, `is_cumulative = NULL` — отчёт
|
||||
нарастающий, `locator` из результата) + `financial_statement_meta` (шапка: ИНН, дата отчёта, единицы, ОКУД/ОКПО/ОКОПФ/ОКФС,
|
||||
адрес; поля `*_original_value` заполняются как распознано и больше не меняются) + `financial_statement_column` +
|
||||
`financial_statement_line` (строки с кодом, `(meta_id, line_code)` уникален: повтор кода — остаётся первая строка,
|
||||
предупреждение) + `financial_statement_value` — **на каждую пару «строка × колонка» одна запись** (ячейки, которых нет
|
||||
в результате, — с пустыми суммой и уверенностью; значение с `column_index` вне колонок формы пропускается с
|
||||
предупреждением; повтор `column_index` в колонках результата или повтор пары «строка × колонка» — остаётся первое, пишется
|
||||
предупреждение, документ `NEEDS_CHECK`). `checks` результата ядро игнорирует: §7 считает само (§7). **Период формы:** дата = `header.report_date`,
|
||||
а если даты нет — **самая поздняя `period_date` колонок** (дата отчёта в OCR часто мусор; колонки надёжнее); `period_year` —
|
||||
год даты, `period_quarter` — только если дата — последний день марта, июня, сентября или декабря, иначе квартал пуст и
|
||||
предупреждение «дата … не конец квартала»; без даты год и квартал пусты. У колонки с самой поздней датой `is_current = true`.
|
||||
`counterparty_check` — как у ОСВ (§9.3). **Повтор формы:** второй `BALANCE`/`INCOME` того же типа в файле не создаётся
|
||||
(предупреждение, первый документ — `NEEDS_CHECK`, OQ-77); `DISCLOSURE`/`OTHER` — один документ на тип на файл без данных
|
||||
(OQ-23). Форма без строк (нет колонки кодов, OQ-139) создаётся с шапкой и колонками, если они есть в результате, статус
|
||||
`NEEDS_CHECK`. Файл только с формами — не ошибка; `ERROR` по результату — только при пустом `documents`. Страницы одной
|
||||
формы из разных файлов не склеиваются (OQ-140), строки без кода не хранятся (OQ-36, OQ-141, OQ-149). Статус документа формы —
|
||||
§9.3.
|
||||
3. **Автозамена.** После успешного маппинга другие файлы того же ЮЛ (не `SUPERSEDED`), содержащие документ с теми же
|
||||
`document_type`, `osv_account_code`, `period_year`, `period_quarter`, переводятся в `SUPERSEDED`
|
||||
(`superseded_by_file_id` = новый файл); `version_no` нового = максимум заменённых + 1. Заменяется и файл с `CONFIRMED`
|
||||
@ -578,15 +624,27 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `
|
||||
`FILE_REPLACE.after_value.unbound_version_ids`; новый документ привязывается только после подтверждения (§3 п. 1).
|
||||
4. **Подтверждение** (`POST /documents/{id}/confirm`, `DOCUMENT_EDIT`): `RECOGNIZED`/`NEEDS_CHECK` → `CONFIRMED`
|
||||
(`confirmed_by/at`, `audit_event(DOCUMENT_CONFIRM)`); повтор — 409 `ALREADY_CONFIRMED`; документ заменённого файла —
|
||||
409 `FILE_SUPERSEDED`; прочие статусы — 409 `DOCUMENT_NOT_CONFIRMABLE`. Правка строк и корректировки — подсрез 2d/Срез 3.
|
||||
409 `FILE_SUPERSEDED`; прочие статусы — 409 `DOCUMENT_NOT_CONFIRMABLE`. Правка строк ОСВ — подсрез 2d, корректировки — §8.
|
||||
**Форма ББ/ОФР (Срез 3b)** дополнительно: период не определён (`period_year` или `period_quarter` пуст) — 409
|
||||
`PERIOD_REQUIRED` (поправьте дату отчёта, п. 9); непройденные неакцептованные жёсткие проверки §7 — 409
|
||||
`CHECKS_NOT_PASSED` (список нарушений — в `detail.failures`: `column_index`, `relation`, `expected`, `actual`); порядок
|
||||
409: `FILE_SUPERSEDED` → `ALREADY_CONFIRMED` → `DOCUMENT_NOT_CONFIRMABLE` → `PERIOD_REQUIRED` → `CHECKS_NOT_PASSED`.
|
||||
При подтверждении пишется `financial_statement_meta.reviewed_by/at`. Несовпадение контрагента (`MISMATCH`) и низкая
|
||||
уверенность значений подтверждение **не блокируют** (как у ОСВ): защита — статус `NEEDS_CHECK` и подсветка. Мягкие проверки
|
||||
(в том числе межформенная) не блокируют. `DISCLOSURE`/`OTHER` — без условий. Привязка к версиям и переходы статуса версии —
|
||||
как у ОСВ (§3 п. 1, §9.1): ББ/ОФР за квартал версии (`Q1` — только версии за `Q1`, R-14a Среза 4).
|
||||
5. **Удаление** (`DELETE /files/{id}`, `DOCUMENT_EDIT`): запрещено при `CONFIRMED` документе
|
||||
(`FILE_HAS_CONFIRMED_DOCUMENTS`) и если файл заменил другие (`FILE_SUPERSEDES_OTHERS`); `SUPERSEDED` файл удалить
|
||||
можно. Удаляются строки, документы, задания и `stored_file`; объектам S3 ставится `expires_at = now()` (физически не
|
||||
можно. Удаляются строки, документы (вместе с данными форм `financial_statement_*` и комментариями документов), задания и `stored_file`; объектам S3 ставится `expires_at = now()` (физически не
|
||||
стираются — OQ-111); аудит `FILE_DELETE` с карточкой файла в `before_value`. **OQ-110 закрыт для Среза 4:** к версии
|
||||
привязываются только `CONFIRMED` документы, а файл с таким документом удалить нельзя — удалить файл с документом в
|
||||
версии невозможно; расширять удаление не требуется.
|
||||
6. **Аудит.** `FILE_UPLOAD`, `FILE_REPLACE`, `FILE_DELETE`, `DOCUMENT_CONFIRM` (раздел `PRIMARY_DOCUMENTS`); смены статусов
|
||||
задания/файла в обмене и в цикле маппинга `audit_event` не пишут (OQ-55).
|
||||
6. **Аудит.** `FILE_UPLOAD`, `FILE_REPLACE`, `FILE_DELETE`, `DOCUMENT_CONFIRM` (раздел `PRIMARY_DOCUMENTS`); правка формы и
|
||||
акцепт проверок — `RECOGNIZED_DATA_EDIT` (раздел `RECOGNITION`, `object_type = document`, `before/after` изменённых полей и
|
||||
ячеек); смены статусов задания/файла в обмене и в цикле маппинга `audit_event` не пишут (OQ-55). Не аудируются (Срез 3b):
|
||||
скачивание исходного файла, CSV чек-листа, комментарии к документу (`comment` сам хранит автора и время); экспорт сводной
|
||||
ОСВ в xlsx, напротив, пишет `audit_event(EXPORT)` (Срез 4) — нужна ли такая же запись для скачивания первички и CSV,
|
||||
решает OQ-153 (`ASSUMPTION(OQ-153)`: не писать).
|
||||
7. **Порядок блокировок** (против взаимоблокировок): задание → файл → документы (`FOR NO KEY UPDATE`) → advisory-lock ГК →
|
||||
версии консолидации (`FOR UPDATE`, по возрастанию `id`); автозамена дополнительно берёт advisory-lock по ЮЛ. Подробности —
|
||||
`03-architecture.md`.
|
||||
@ -594,6 +652,25 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `
|
||||
(409 `VERSION_NOT_EDITABLE`), ЮЛ — член периметра (422 `ENTITY_NOT_IN_PERIMETER`); после приёма хотя бы одного файла
|
||||
INITIATED → DOCUMENTS_PROCESSING (§9.1); сбой этого перехода не отменяет принятые файлы. Блокировка версии для загрузки не
|
||||
нужна.
|
||||
9. **Правка формы ББ/ОФР** (`PATCH /documents/{id}/statement`, `DOCUMENT_EDIT`, Срез 3b; решения владельца 07.10.2026).
|
||||
Правятся **суммы** ячеек (`values[] {line_id, column_id, amount}`) и три поля шапки: `inn` (10 или 12 цифр, иначе 422
|
||||
`INVALID_INN`, в том числе `null` — очистить ИНН нельзя; пересчитывается `counterparty_check`), `report_date` (пересчитываются `period_year`, `period_quarter`,
|
||||
`reporting_date` по правилу периода п. 2) и `units` (непустая строка до 50 символов). Код и название строки, добавление и
|
||||
удаление строк и колонок не правятся. **Только до подтверждения:** документ `CONFIRMED` — 409 `DOCUMENT_READONLY`
|
||||
(исправление подтверждённой формы — новым файлом через автозамену, п. 3); файл `SUPERSEDED` — 409 `FILE_SUPERSEDED`; не
|
||||
форма — 409 `DOCUMENT_NOT_STATEMENT`; ячейка не этого документа — 422 `UNKNOWN_CELL`. Исходное сохраняется: у значения при
|
||||
первой правке `original_amount` = прежняя сумма, `is_modified = (amount ≠ original_amount)` (возврат к исходному снимает
|
||||
признак), `modified_by/at`; у поля шапки `*_modified = true`, `*_original_value` не меняется. После правки: акцепт проверок
|
||||
сбрасывается (§7; прежний акцепт, включая комментарий, остаётся в `before_value.checks_accepted` записи аудита), проверки и статус пересчитываются (§9.3), пишется одно `audit_event(RECOGNIZED_DATA_EDIT)`; запрос
|
||||
без изменений — 200 без аудита. Автозамена при правке не запускается (только при маппинге): правка даты, давшая период
|
||||
другого файла ЮЛ, привязывает документ к версии лишь при подтверждении. Порядок блокировок — как у подтверждения (файл →
|
||||
документ).
|
||||
10. **Исходный файл и комментарии (Срез 3b).** `GET /files/{id}/content` (`DOCUMENT_VIEW`, policy по ЮЛ) отдаёт исходный
|
||||
объект из S3 потоком через ядро (в контуре MinIO для пользователя может быть недоступен, а через ядро действует
|
||||
видимость); работает и для `SUPERSEDED`; объекта нет в хранилище — 404 `FILE_CONTENT_NOT_FOUND`. Комментарии к документу —
|
||||
`POST/GET /documents/{id}/comments` (таблица `comment`, `object_type = 'document'`); добавлять можно к документу в любом
|
||||
статусе; правки и удаления нет; при удалении файла (`DELETE /files/{id}`, п. 5) комментарии его документов удаляются вместе с ними
|
||||
(связь полиморфная, FK нет — удаляет сервис).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
| OQ-20 | `company_group.crm_id` обязателен (модалка) или nullable (UC-02, DBML) | DOC-221280 | срез 1 | решено DBML: nullable + `code` |
|
||||
| OQ-21 | «Дата перевода» ЮЛ в другую ГК — помечена «??» | DOC-221280 | срез 1 | **закрыт решением владельца 05.10.2026:** дата перевода — `effective_date` ≥ сегодня, пишется в `legal_entity_history`; перевод применяется сразу; граница суток — по МСК (OQ-96, follow-up arm-core#8) |
|
||||
| OQ-22 | Ретеншн `results/` в S3 (ARCH-010) | DBML | срез 2 | допущение: как `uploads/` |
|
||||
| OQ-23 | Расшифровки (`DISCLOSURE`): разбирать или только хранить (ANL-040) | DBML | — | только хранить |
|
||||
| OQ-23 | Расшифровки (`DISCLOSURE`): разбирать или только хранить (ANL-040) | DBML | — | только хранить; **Срез 3b: допущение реализовано** — `DISCLOSURE` — один документ на тип на файл (`locator` — через запятую), без данных, статус `RECOGNIZED` (§9.5 п. 2). |
|
||||
| OQ-24 | Форма 276-ОД — состав и шаблон | FNR03 3.4 | — | вне scope |
|
||||
| OQ-25 | Состав ГСК для тестовой группы «Русский бисквит»: входит ли ОАО «ЧКФ», ООО «ОФГ», «Лидер» | фикстуры | тесты консолидации | **частично закрыт по выгрузке ЕЦП (04.10.2026):** ГК «Русский Бисквит» (`1-AZFL6BQ`, Санкт-Петербургский РФ) — 21 запись; флаг «входит в ГСК» только у АО «Русский бисквит» и ООО «Пекарня Счастливочная»; ЧКФ, ОФГ, Малечкинская птицефабрика, ТК РБ, Регион Финанс и др. — в ГК, но **не в ГСК** (срез — `fixtures/directories/ecp-export-russkiy-biskvit.xlsx`). Периметр тестовой консолидации — две компании; OQ-25 остаётся про то, верен ли состав ГСК в CRM |
|
||||
| OQ-26 | Единицы: ББ/ОФР в тыс. руб., ОСВ в руб. — правило округления при элиминации (до тыс., банковское?) | — | срез 6 | допущение: считать в рублях, округлять до тыс. руб. на выходе в `kfo_result_line` (ROUND_HALF_UP) |
|
||||
@ -41,7 +41,7 @@
|
||||
| OQ-33 | «ID ГСК/ГСЗ в CRM» в формах (`51-03`, `51-04`, `77-02`; пример «3-9CGSK01» в формате ЕЦП CRM): у `related_group` в DBML только внутренний `code` (GSK-001). Есть ли у ГСК/ГСЗ идентификатор в CRM? | макет [77-02] | срез 1, DBML | **закрыт реализацией Среза 1:** `related_group.code` генерируется системой (`GSK-NNNNNN`/`GSZ-NNNNNN`), ID из CRM не вводится и не импортируется (в выгрузке ЕЦП его нет — OQ-91) |
|
||||
| OQ-34 | «Удалить» ГСК в меню строки (`77-01`): физическое удаление или закрытие с историей? В DBML у `related_group` нет флага/истории удаления | макет [77-01] | срез 1 | **закрыт реализацией Среза 1 (ASSUMPTION подтверждено отсутствием возражений):** удаляется только пустая ГСК/ГСЗ (409 `GROUP_NOT_EMPTY`), состав сохраняется в `before_value` аудита; группа с FK из консолидаций — follow-up `GROUP_IN_USE` (arm-core#8, T8.1) |
|
||||
| OQ-35 | База процента отклонения: в таблице корректировки % считается к сумме стороны ЮЛ-1 (−79,77 % для 1 000 ↔ 202,32, `31-02-02`), в «Подтверждении КФО клиента» — к версии банка (ст. 2, `31-06`); DBML и Методика (сноска 14) — к **меньшей** сумме. Что считать правильным для `correction.deviation_percent` и `client_kfo_line.deviation_percent`? | макет [31-02] [31-06] vs DBML, Методика | срезы 5–6 | допущение: к меньшей (Методика); макет считать неточным |
|
||||
| OQ-36 | Экран проверки показывает строки формы **без кода** («в том числе: Основные средства, пригодные к использованию…», `41`); DBML хранит только строки с кодом (`financial_statement_line`), контракт требует `line_code`. Хранить ли расшифровочные строки без кода (и как их показывать)? | макет [41] vs DBML, контракт | срез 3, контракт v1.x | допущение: строки без кода не хранить и не показывать в первой очереди |
|
||||
| OQ-36 | Экран проверки показывает строки формы **без кода** («в том числе: Основные средства, пригодные к использованию…», `41`); DBML хранит только строки с кодом (`financial_statement_line`), контракт требует `line_code`. Хранить ли расшифровочные строки без кода (и как их показывать)? | макет [41] vs DBML, контракт | срез 3, контракт v1.x | допущение: строки без кода не хранить и не показывать в первой очереди; **Срез 3b: допущение реализовано** — `financial_statement_line` пишется только для строк с кодом (строк без кода в результате нет), экран их не получает. |
|
||||
| OQ-37 | Строка «Гудвилл 1105» (ФСБУ 4/2023) в распознанной форме (`41`) vs строка `kfo_result_line.line_code = 'GOODWILL'` в разделе I КФО: использовать код 1105 или служебный GOODWILL? Влияет на шаблон строк на фронте и на формулы §7 (1100 = … + 1105?) | макет [41], ФСБУ 4/2023 | срез 3, 6 | допущение: хранить код 1105 как пришёл; строка КФО «GOODWILL» — отдельная, уточнить у методологов |
|
||||
| OQ-38 | ЕХ в макете показывает и **неверифицированные** консолидации («Консолидация 2025 К4», без даты верификации, `62` [83-01]); ЕХ ТЗ 6.2 — срез пишется только после «Верифицирован». Показывать ли в ЛК ЕХ незавершённые консолидации (как «в работе») или только `ex_snapshot`? | макет [62] [83-01] vs ЕХ ТЗ 6.2 | срез 7 | допущение: только срезы `ex_snapshot`; незавершённые — в разделе «Отчётность» |
|
||||
| OQ-39 | Состав «сформированной отчётности» в ЕХ (`83-01`): КФО .xlsx, Шахматка .xlsx и «Консолидированная отчетность .pdf» с датой «Сформирован» и подписями Утвердил/Верифицировал. Нужен ли PDF-экспорт КФО и какие ещё артефакты (сводная ОСВ, корректировки, гудвилл — ЕХ ТЗ 5.2) показывать? | макет [83-01] vs ЕХ ТЗ 5.2 | срез 7 | допущение: xlsx из `payload` на лету для всех `item_type`; PDF — не делать до решения |
|
||||
@ -82,7 +82,7 @@
|
||||
| OQ-74 | `PUBLISH_RETRIES` = одна первая попытка + до 3 повторов (всего до 4) с тем же `Nats-Msg-Id`; у `scan-gateway` тип `PositiveInt` (повторы нельзя отключить настройкой), у `tabular-parser` `NonNegativeInt` (0 допустим); паузы 0,5 с и 2,0 с. Согласовать единую семантику (повторы или всего попыток) и тип? | scan-gateway: п. 1 таблицы предложений (R-13, T11.1); docs: O-25, design #36 | контракт v1.x; настройки сервисов | допущение принято: «1 + 3 повтора» (design #36), расхождение «3 против 4» обмен не ломает (дедуп по `Nats-Msg-Id`); выровнять тип — косметика конфигурации |
|
||||
| OQ-75 | `reply_deadline` проверяет только `scan-gateway` (`error TIMEOUT` до отправки в ИС и потолок ожидания ответа); `tabular-parser` обрабатывает просроченный запрос. Пример `contracts/examples/request-image.json` имеет `reply_deadline` в прошлом (`2026-10-03T12:45:00Z`) и даёт `TIMEOUT` при ручном прогоне. Единая обязанность сервисов и свежая дата примера? | scan-gateway: п. 12 таблицы предложений (O-e); docs: O-26, design #37 | контракт v1.x; срез 2 | допущение принято: срок — дело сервиса, пример — иллюстрация формы (оговорка в spec и `03-architecture.md`, design #37); обновить дату можно одним PR с пересинхронизацией копий в трёх репозиториях |
|
||||
| OQ-76 | Обе реальные фикстуры `fixtures/ocr` (`aep_5.json`, `aep_8_form4.json`) — ответы-ошибки ИС (`error.code = INVALID_DOCUMENT`, пустой `table_structure`). Успешный путь `image` (`done` / `needs_check`, файл в `results/…`, `s3_object` с `bucket_role = results`) сквозной проверкой не покрыт; в `scan-gateway` проверяется только синтетическим ответом. Нужны образцы успешных ответов Content AI. | scan-gateway: п. 3 таблицы предложений (O-1, R-10, design #32); docs: O-15 (задача 4 фазы 2), design #31 | срез 3 (OQ-13/14, нормализатор); fixtures | **частично закрыт человеком 04.10.2026:** добавлены реальные образцы — `aep_3.json` (успех: баланс 42 строки + ОФР 18 строк), `aep_3_ofr.json` (второй ответ ИС по тому же файлу, `INVALID_DOCUMENT`), `aep_6.json` (успех: баланс 122 строки + ОФР 49 строк); PR в `arm-ka-fixtures`. Успешный путь `image` на реальных ответах — Срез 3 (нормализатор); транспорт `scan-gateway` проверяется на них в Срезе 0 (склейка нескольких json, `NOT_IMPLEMENTED`) |
|
||||
| OQ-77 | §11 `05-domain-rules.md` описывает `data_ocr[]` (список), а в фикстурах `data_ocr` — объект с полями `label/organization/inn/units/date/table_structure`. Какой формат фактический на стенде ИС (связано с OQ-13, OQ-14)? | `05-domain-rules.md` §11; scan-gateway: п. 4 таблицы предложений (O-1, T3.2) | срез 3 (нормализатор) | **закрыт человеком 04.10.2026 (по `aep_3.json`, `aep_6.json`):** `data_ocr` — **объект**, один на json-файл (`label`, `organization`, `inn`, `units`, `date` — `{form_name, confidence}`; `table_structure` — **список таблиц** `{label, rows[]}`, одна таблица = один документ БС/ОФР, в одном json их может быть несколько). Успех — `error = {code: "", message: ""}` (не `null`!), ошибка — непустой `code` и пустой `table_structure`. §11 `05-domain-rules.md` исправлен. **Уточнение человека 04.10.2026:** разбиение одной формы на несколько таблиц (сегменты) в образцах не встречалось; наблюдается только несколько таблиц **разных** форм в одном json (баланс + ОФР). Нештатный случай (повтор `label`) — `NEEDS_CHECK`, правило уточнить в Срезе 3 по реальным данным |
|
||||
| OQ-77 | §11 `05-domain-rules.md` описывает `data_ocr[]` (список), а в фикстурах `data_ocr` — объект с полями `label/organization/inn/units/date/table_structure`. Какой формат фактический на стенде ИС (связано с OQ-13, OQ-14)? | `05-domain-rules.md` §11; scan-gateway: п. 4 таблицы предложений (O-1, T3.2) | срез 3 (нормализатор) | **закрыт человеком 04.10.2026 (по `aep_3.json`, `aep_6.json`):** `data_ocr` — **объект**, один на json-файл (`label`, `organization`, `inn`, `units`, `date` — `{form_name, confidence}`; `table_structure` — **список таблиц** `{label, rows[]}`, одна таблица = один документ БС/ОФР, в одном json их может быть несколько). Успех — `error = {code: "", message: ""}` (не `null`!), ошибка — непустой `code` и пустой `table_structure`. §11 `05-domain-rules.md` исправлен. **Уточнение человека 04.10.2026:** разбиение одной формы на несколько таблиц (сегменты) в образцах не встречалось; наблюдается только несколько таблиц **разных** форм в одном json (баланс + ОФР). Нештатный случай (повтор `label`) — `NEEDS_CHECK`, правило уточнить в Срезе 3 по реальным данным; **Срез 3b: допущение реализовано** — второй `BALANCE`/`INCOME` того же типа в файле не создаётся (предупреждение «вторая форма … пропущена (OQ-77)»), первый документ — `NEEDS_CHECK`; правило по реальным данным стенда по-прежнему уточнить (§9.5 п. 2). |
|
||||
| OQ-78 | Шлюз читает ровно `<output>/<job_id>/<job_id>.json` (спека); старый код читал все файлы каталога `Output/<id>/`. Какие файлы и когда реальная ИС кладёт в `Output`, в каком порядке (момент готовности)? | scan-gateway: п. 5 таблицы предложений (T8.5) | срез 3 (стенд ИС) | **закрыт человеком 04.10.2026:** ИС кладёт **все распознанные документы одновременно** в каталог `<job_id>`, по одному json на документ (пример: `aep_3.json` + `aep_3_ofr.json`); шлюз читает **все** json каталога и склеивает в один ответ (готовность — по стабилизации набора файлов, `SCAN_SETTLE_SECONDS`); ошибка одного документа не отменяет остальные (`metrics.warnings`). Реализуется в Срезе 0 (доработка PR scan-gateway #3) |
|
||||
| OQ-79 | Временный файл `.<имя>.part` лежит в `Input` (атомарная запись `.part` + rename): реальная ИС без маски может забрать недописанный файл. Уточнить у владельцев ИС маску и поведение или перенести временный файл в служебный подкаталог рядом с `Input`? | scan-gateway: п. 6 таблицы предложений (R-5, T7/T8) | срез 3 (стенд ИС) | **закрыт человеком 04.10.2026:** временный файл записи уходит из `Input` в служебный каталог рядом (`SCAN_TMP_DIR`, по умолчанию `Tmp`, на той же шаре — `rename` остаётся атомарным). Реализуется в Срезе 0 (доработка PR scan-gateway #3) |
|
||||
| OQ-80 | `make up` расходится с эталоном (scan-gateway и `dev/Makefile.common`): в `tabular-parser` нет `minio-init` — на свежем томе бакета `arm-ka` нет, `test_s3_integration` даёт `NoSuchBucket`, хотя README сервиса обещает бакет; в `arm-core` `up -d` без `--wait` и без `--project-name` (имя в compose фиксировано, безвредно). Также `check-full` в arm-core и tabular-parser — через пререквизиты (порядок при `make -j` не гарантирован), в scan-gateway — через `$(MAKE)`; `lint` arm-core включает `mypy scripts`. Подключить `include ../docs/dev/Makefile.common` в сервисах? | tabular-parser: `Makefile` (цель `up`); scan-gateway: п. 18 таблицы предложений (I-1); docs: O-1 и O-2 (задача 1 фазы 2), O-4 и O-5 (задача 2 фазы 2), `dev/checks.md`, `dev/Makefile.common` | срез 0 (процесс), все сервисы | допущение принято: эталон — scan-gateway, расхождения описаны в `dev/checks.md`, Makefile сервисов не правились (design #22, #24); выровнять — правкой Makefile сервисов (follow-up) |
|
||||
@ -145,12 +145,12 @@
|
||||
| OQ-137 | Раскладка горячих папок ИС Сканирование: scan-gateway кладёт `Input/<job_id>_<original_name>` через временный `Tmp/.<имя>.part`, ничего не удаляет; PDFWorker старого АРМ (`RSHB/armka_file_worker`, проверен в контуре) кладёт `Input/<task_id>.<ext>` через временный `<назначение>.<uuid>.tmp` рядом (ИС игнорирует `*.tmp`), читает батч `Output/<task_id>/*.json` и удаляет `Output/<task_id>/` и входящий файл после чтения. Какая раскладка актуальна? | сравнение `armka_file_worker` (README «Файлы», `clients.py`) и `scan-gateway/gateway/hotfolder.py`, `smb.py`; OQ-78, OQ-79 | scan-gateway (спека `image-recognition-gateway`, эмулятор, тесты) | **закрыт владельцем 06.10.2026: «правильно как в FileWorker»** — имя `<job_id>.<ext>`, очистка `Output/<job_id>/` и входящего файла после чтения; `Output/<job_id>/*.json` батчем и стабилизация набора (OQ-78) остаются. Реализация — arm-scan-gateway #6. **Уточнение (Срез 3a, решение владельца 06.10.2026):** вместо временного `*.tmp` рядом с назначением — `.part` в `Tmp` (в `Input` временных файлов не класть: ИС может подхватить недописанный файл, гарантии игнора `*.tmp` у нас нет; OQ-79 остаётся в силе), публикация без замены снимает `.part` сама; очистка — под флагом `SCAN_CLEANUP`, по умолчанию выключена (OQ-144) |
|
||||
| OQ-138 | Единицы «тыс. руб.» в шапке против значений в рублях с копейками в выгрузках 1С (Унагранде Компани/Групп, НПЗ) и тысяч с дробями (Межениновская): кто и где приводит единицы. Примеры: книга НПЗ (`npz/2025-Q3`) — ББ `needs_check`, `1600 = 1700` расходится на 1.94 / 9.02 / 2.48 руб.; ОФР НПЗ и ОФР Унагранде (стр. 1) — `needs_check` по 2100/2200/2300 по той же причине; полные формы по факту сходятся только у Межениновской | `fixtures/statements-xlsx/README.md`, tabular-parser design 2b D-6, D-7, решения №22–24 (R-11, R-13, R-14) | Срез 3 (маппинг форм), §8 (КФО в тыс. руб.) | **закрыт решением владельца 06.10.2026:** единица «тыс. руб.» в шапке считается верной, значения отдаются как в файле; расхождения итогов в пределах округления (до 0,001 % наибольшего числа соотношения) статус не портят — мягкие нарушения «в пределах округления» (arm-tabular-parser#20). Книга НПЗ, ОФР стр. 1 Унагранде — `done`. Принимать ли расхождение и приводить ли единицы — решает КА в ядре (акцепт проверок, Срез 3) |
|
||||
| OQ-139 | Форма без колонки кодов: сопоставление по именам строк из шаблонов 66н/ФСБУ (словарь `statement_lines.yaml`, `line_code_confidence < 100`)? Сейчас документ без строк и `needs_check` («колонка кодов не найдена») | решение владельца 06.10.2026 (вопрос 6), tabular-parser design 2b D-4 | §12 п. 2, Срез 3 | открыт: нужен реальный файл без колонки кодов; допущение — не сопоставлять по именам; **связано (Срез 3a):** то же для PDF-пути — справочник кодов по редакциям, имена из OCR при пустой редакции (OQ-146, OQ-150) |
|
||||
| OQ-140 | Страница-продолжение в отдельном файле (1С печатает ББ/ОФР постранично, один файл = страница): склейка в ядре по ЮЛ + период + тип формы; признак `is_continuation` / `form_page` в контракте (MINOR, две необязательных поля) | решение владельца 06.10.2026 (вопрос 2), design 2b D-8 | §12 п. 9, Срез 3, контракт v1.x | допущение принято: страницы разных файлов не склеиваются; страница 2 без шапки — документ с пустой шапкой и `needs_check`, «загрузите форму одним файлом» |
|
||||
| OQ-140 | Страница-продолжение в отдельном файле (1С печатает ББ/ОФР постранично, один файл = страница): склейка в ядре по ЮЛ + период + тип формы; признак `is_continuation` / `form_page` в контракте (MINOR, две необязательных поля) | решение владельца 06.10.2026 (вопрос 2), design 2b D-8 | §12 п. 9, Срез 3, контракт v1.x | допущение принято: страницы разных файлов не склеиваются; страница 2 без шапки — документ с пустой шапкой и `needs_check`, «загрузите форму одним файлом»; **Срез 3b: допущение реализовано** — страницы одной формы из разных файлов не склеиваются: каждый файл — отдельный документ со своим периодом. |
|
||||
| OQ-141 | Строка без кода, но с суммами в ОФР ФСБУ 4/2023 (НПЗ «Реализация иного имущества…»): хранить ли (OQ-36 расширяется с расшифровочных строк на строки с суммами) | `fixtures/statements-xlsx/npz`, design 2b D-6 | Срез 3, DBML `financial_statement_line` (хранит только строки с кодом) | допущение принято: строки без кода не отдаются, при наличии сумм — предупреждение «строк без кода с суммами пропущено: N» |
|
||||
| OQ-142 | Образец ФСБУ 4/2023 (`templates/ББ после 01.01.2025.xlsx`): «АКТИВ» и код 1110 в одной строке из-за объединённых ячеек; шаблоны (коды есть, сумм и шапки нет) не данные. Нужен ли их разбор вообще | `fixtures/statements-xlsx/templates`, design 2b D-8 | §12 п. 10, тесты | допущение принято: шаблоны дают `needs_check` (шапка пустая, значения `null`); разбор шаблонов как источника данных не планируется, пока владелец не скажет иначе |
|
||||
| OQ-143 | Лист продолжения с **единственной** кодовой строкой (книга НПЗ: лист ОФР с одной строкой 2400): `min_codes` = 3 → лист не распознаётся как форма и пропускается, строка 2400 теряется при `needs_check` документа. Понизить порог / принимать лист по близости к открытому документу ОФР / маркеру продолжения | tabular-parser design 2b: решение №25, O-9 | §12 п. 2, п. 9; полнота ОФР НПЗ | **реализовано в 2b (финальная волна, I-1):** лист с 1–2 кодами форм после листа формы берётся как продолжение (`min_codes_continuation = 1` в словаре) и присоединяется при совпадении числа колонок сумм и типа формы; не присоединён — `needs_check` и предупреждение; первая страница с 1–2 кодами формой не считается. Строка 2400 НПЗ в результате есть |
|
||||
| OQ-144 | Права на удаление на SMB-шаре контура (`Input` — входящие файлы и `.done`, `Output/<job_id>/`, `Tmp`): можно ли сервису `scan-gateway` удалять файлы, и не удаляет ли их сама ИС Сканирование после обработки. Пока `SCAN_CLEANUP=false` — сервис ничего не удаляет (кроме `.part`, ставшего целью публикации записью без замены), в `Input`, `Output` и `Tmp` накапливаются остатки | решение владельца 06.10.2026, change `slice-3a-ocr-normalizer` D-2, решения №26/№27 | scan-gateway (горячие папки), эксплуатация стенда | открыт: допущение — очистка выключена; dev-compose включает `SCAN_CLEANUP=true`. Спросить платформу/владельцев ИС; при положительном ответе — включить флаг на стенде |
|
||||
| OQ-145 | Редакция формы (`66n` / `fsbu4`) в контракте: нужна ли ядру редакция документа в конверте (необязательное поле `statementDocument`, кандидат MINOR 1.3), или ядро выбирает редакцию по `report_date` само, по тому же правилу §11.1 п. 10. Сейчас scan-gateway выбирает редакцию внутри и наружу её не отдаёт | change `slice-3a-ocr-normalizer`, открытый вопрос 2 | контракт `docs/contracts/` (v1.3), Срез 3b (маппинг форм, справочник кодов в ядре) | открыт: допущение — в конверт не передаётся, контракт остаётся v1.2; при положительном решении — MINOR 1.3 со ссылкой на этот OQ |
|
||||
| OQ-145 | Редакция формы (`66n` / `fsbu4`) в контракте: нужна ли ядру редакция документа в конверте (необязательное поле `statementDocument`, кандидат MINOR 1.3), или ядро выбирает редакцию по `report_date` само, по тому же правилу §11.1 п. 10. Сейчас scan-gateway выбирает редакцию внутри и наружу её не отдаёт | change `slice-3a-ocr-normalizer`, открытый вопрос 2 | контракт `docs/contracts/` (v1.3), Срез 3b (маппинг форм, справочник кодов в ядре) | открыт: допущение — в конверт не передаётся, контракт остаётся v1.2; при положительном решении — MINOR 1.3 со ссылкой на этот OQ; **Срез 3b: допущение реализовано** — контракт v1.2 не менялся; ядро определяет редакцию по дате периода документа (`66n` при дате ≤ 2024-12-31, иначе `fsbu4`, без даты — `null`) и отдаёт её в `GET /documents/{id}/statement` (поле `edition`) для выбора шаблона строк; на проверки §7 редакция не влияет (OQ-146). |
|
||||
| OQ-146 | Словари §7 (проверки форм) и §12 (справочник кодов `statement_lines.yaml`, формулы ОФР) **по редакциям** 66н / ФСБУ 4/2023 для tabular-parser (2b) и ядра (3b): один набор правил на обе редакции или раздельные; как выбирать редакцию в ядре для Excel-пути | change `slice-3a-ocr-normalizer` (справочник с редакциями только в scan-gateway), OQ-12, OQ-139 | tabular-parser `rules/*.yaml`, ядро (Срез 3b), §7, §12 п. 2 | открыт: допущение — scan-gateway ведёт свой `api/rules/statement_lines.yaml`; tabular-parser и ядро словарь по редакциям не используют; расширяет OQ-12 и OQ-139 |
|
||||
| OQ-147 | Закрывающая скобка, распознанная OCR как `}` или `]` (`(7744536}` в `aep_3.json`): дословный перенос `parse_amount` минус не ставит — сумма положительная, `is_recognized = false`. Расширить ли множество закрывающих скобок в §11.1 п. 7 после проверки на стенде | change `slice-3a-ocr-normalizer`, решение №12; владелец 06.10.2026: «пока устроит как есть» | scan-gateway `normalizer/ocr/amounts.py`, §11.1 п. 7 | открыт: допущение — перенос дословный; знак теряется, строка подсвечена `is_recognized = false`, исходный текст — в `raw/` |
|
||||
| OQ-148 | Длинное/среднее тире «—»/«–» как прочерк: дословный `parse_amount` вырезает тире как «лишний» символ → сумма `null`, но `is_recognized = false` даже при `confidence = 100` (дефис «-» даёт `true`). Лишний `needs_check` на формах с длинным тире. Считать ли прочерк любого вида распознанным | change `slice-3a-ocr-normalizer`, решение №20 | scan-gateway `amounts.py`, §11.1 п. 7, доля `done` на реальных сканах | открыт: допущение — перенос дословный; правило уточняется после прогона на стенде |
|
||||
@ -158,6 +158,8 @@
|
||||
| OQ-150 | Состав редакции `fsbu4` (ФСБУ 4/2023, отчётность с 2025 года) в `api/rules/statement_lines.yaml` scan-gateway: коды и наименования строк ББ и ОФР; источник — Приложения 3–4 к ФСБУ 4/2023 (черновой перечень кодов — шаблоны `fixtures/statements-xlsx/templates/`, OQ-142). Пока `lines: {}` | change `slice-3a-ocr-normalizer`, D-7 | scan-gateway `api/rules/`, эталоны `fixtures/ocr/expected_normalize/` для документов 2025 года, статус `done` | открыт: **данные даёт владелец**, код нормализатора не меняется; до заполнения документы 2025 года идут с именами из OCR, предупреждением «редакция fsbu4 не заполнена» и `needs_check`. Порядок заполнения — `scan-gateway/README.md` |
|
||||
| OQ-151 | Эмулятор ИС Сканирование: файл, совпавший по sha256 с PDF из `SCAN_FIXTURES_DIR`, у которого нет JSON-набора (`<имя>.json`), отвечает «нет пары» (`INVALID_DOCUMENT`) и **не** уходит на `SCAN_EMULATOR_DEFAULT`. Пробел спеки эмулятора: считать ли такой файл промахом и отдавать набор по умолчанию | change `slice-3a-ocr-normalizer`, спека `scan-emulator` | scan-gateway `scan_emulator`, dev-стенд (`dev/qa-guide.md` §6) | открыт: допущение — поведение как есть; PDF без JSON-пары в `fixtures/ocr` не класть |
|
||||
| OQ-152 | Повтор кода строки в таблице OCR: остаётся **первая** строка (паритет с Excel-путём, §12 п. 5; решение №15), но в `aep_6.json` (ОФР) код 2410 повторён, оставленная первая строка пуста, а отброшенная содержит `(649)`. Выбрать правило «первая строка с суммой» вместо «первая» (для `aep_3` оно даёт иной результат, чем для `aep_6` — нужна проверка на стенде) | change `slice-3a-ocr-normalizer`, решение №34 (R-16), финальное ревью I-2 | scan-gateway `document.py`, §11.1 п. 4, Excel-путь §12 п. 5 | открыт: допущение — первая строка; предупреждение «код N повторяется» сигнализирует аналитику, сумма отброшенной строки остаётся в `raw/` |
|
||||
| OQ-153 | Журналировать ли скачивание первички (`GET /files/{id}/content`) и CSV чек-листа (ТЗ 8.2: перечень событий журнала включает «выгрузку»)? Экспорт сводной ОСВ в xlsx пишет `audit_event(EXPORT)` (Срез 4), а скачивание исходного файла и CSV чек-листа в Срезе 3b — нет; комментарии к документу тоже не аудируются (`comment` хранит автора и время) | change `slice-3b-statements-api`, design D-10, D-11, D-13, решение №8; ТЗ 8.2 | `audit_event`, `GET /files/{id}/content`, `GET …/checklist?format=csv`, §9.5 п. 6 | допущение принято: не писать (`ASSUMPTION(OQ-153)`); при положительном решении — `audit_event(EXPORT)` с `object_type = stored_file` / `consolidation_version`, без DBML v5.17 (значение `EXPORT` в enum есть) |
|
||||
| OQ-154 | Автозамена файла работает на уровне файла (ключ документа: тип, счёт/NULL, год, квартал, нарастающий): исправленный отдельный ОФР заменит весь прежний файл-книгу «ББ + ОФР» того же периода (`SUPERSEDED`), а подтверждённый ББ из неё отвяжется от версий «в работе». Допустима ли такая замена на уровне файла, или формы одного периода нужно заменять по документам | финальное ревью Среза 3b (заметка контроллера), change `slice-3b-statements-api` | Срез 3b / 5 | допущение: замена на уровне файла (поведение ОСВ из Срезов 2c/4 распространено на формы); решение владельца не получено |
|
||||
|
||||
Служебные идентификаторы в DBML/аналитике: `ANL-nnn` — замечания аналитика к макету; `ARCH-nnn` — архитектурные решения;
|
||||
`DECISION-n` — решения по модели; `FIX-nn` — исправления дефектов v4.
|
||||
|
||||
@ -77,7 +77,7 @@ UI справочника по макету) разделён: всё, кром
|
||||
Решение владельца 05.10.2026: срез делится на четыре подсреза со своими change'ами и PR; сначала **только парсер ОСВ**.
|
||||
Порядок: 2a → 2b → 2c → 2d. DoD среза в целом (без правил под конкретный файл, без координат в коде парсера) — в конце раздела.
|
||||
|
||||
### Срез 2a — Парсер ОСВ (`tabular-parser`) — реализован, ожидает merge
|
||||
### Срез 2a — Парсер ОСВ (`tabular-parser`) — закрыт 05.10.2026 (tabular-parser #6, архив #8; docs #10, архив #15; fixtures #5)
|
||||
|
||||
Change'и: `tabular-parser/openspec/changes/slice-2a-osv-parser` (issue arm-tabular-parser#5, PR #6), сквозной
|
||||
`docs/openspec/changes/slice-2a-osv-parser` (arm-ka-docs#9), фикстуры arm-ka-fixtures#4. Копия схемы v1.1 в `arm-core` —
|
||||
@ -124,7 +124,7 @@ Change'и: `tabular-parser/openspec/changes/slice-2a-osv-parser` (issue arm-tabu
|
||||
- **Не сделано / риски:** единицы не нормализуются (OQ-138); форма без колонки кодов не разбирается (OQ-139); страницы
|
||||
в разных файлах не склеиваются (OQ-140); строки без кода не хранятся (OQ-141); после контракта v1.2 `docs/contracts/**` заморожены (копии схемы в arm-core и scan-gateway должны совпадать с ними; в tabular-parser версия закреплена тестом `api/tests/unit/test_contract_v12.py`, побайтное сравнение копий — вручную через `diff` при раскатке); косметика T1.1/T2.x уходит в следующий MINOR.
|
||||
|
||||
### Срез 2c — Загрузка файлов и маппинг результата в ядре (`arm-core`) — реализован, ожидает ревью и merge
|
||||
### Срез 2c — Загрузка файлов и маппинг результата в ядре (`arm-core`) — закрыт 06.10.2026 (arm-core #15, архив #16; docs #17, архив #18)
|
||||
|
||||
Change'и: `arm-core/openspec/changes/slice-2c-osv-ingest` (issue arm-core#14, ветка `feat/14-slice-2c-osv-ingest`), сквозной
|
||||
`docs/openspec/changes/slice-2c-osv-ingest` (ветка `feat/16-slice-2c-osv-ingest`, DBML v5.15).
|
||||
@ -173,7 +173,7 @@ Change'и: `arm-core/openspec/changes/slice-2c-osv-ingest` (issue arm-core#14,
|
||||
|
||||
### Срез 3a — scan-gateway: горячие папки и нормализатор OCR (репозиторий scan-gateway)
|
||||
|
||||
**Статус: реализован в scan-gateway, октябрь 2026; ожидает merge.** Change `slice-3a-ocr-normalizer` (arm-scan-gateway#6;
|
||||
**Статус: закрыт 07.10.2026** (scan-gateway #8, архив #10; docs #47; fixtures #13; follow-up — issue arm-scan-gateway#9). Change `slice-3a-ocr-normalizer` (arm-scan-gateway#6;
|
||||
docs#46, fixtures#12), спецификация — `scan-gateway/openspec/changes/slice-3a-ocr-normalizer/` (design D-1…D-9, «Decisions made
|
||||
without discussion» №1–№35). Реализация — по `05-domain-rules.md` §11 и §11.1.
|
||||
|
||||
@ -187,15 +187,36 @@ without discussion» №1–№35). Реализация — по `05-domain-rul
|
||||
- Эталоны нормализатора — `fixtures/ocr/expected_normalize/` (отдельный PR fixtures), `make golden-update`.
|
||||
- Новые вопросы: OQ-144…OQ-152.
|
||||
|
||||
### Срез 3b — маппинг форм, проверки, подтверждение (arm-core)
|
||||
### Срез 3b — маппинг форм, проверки, подтверждение и API до сводной ОСВ (arm-core)
|
||||
|
||||
**Статус: бэкенд и API реализованы в arm-core, октябрь 2026; UI экрана «Проверка распознавания» в срез не входит.** Объём по
|
||||
решению владельца 07.10.2026: весь API пути пользователя до сводной ОСВ включительно. Issue arm-core#36, docs#48; спецификация —
|
||||
`arm-core/openspec/changes/slice-3b-statements-api/` (design D-1…D-14, «Decisions made without discussion» №1–№22); документы
|
||||
workspace — bounded-PR в docs (без сквозного change: DBML и контракт не менялись).
|
||||
|
||||
**Сделано (по факту):**
|
||||
- Маппинг форм в ядре: `statementDocument` `BALANCE`/`INCOME` (Excel и OCR) → `document` + `financial_statement_meta/column/line/value`
|
||||
в той же транзакции `map_file`; период по дате отчёта либо по самой поздней дате колонок; `DISCLOSURE`/`OTHER` — документ без
|
||||
данных; файл только с формами — не ошибка (`05-domain-rules.md` §9.5 п. 2, `03-architecture.md`).
|
||||
- Проверки §7 в ядре (`domain/statement_checks.py`, словарь `api/rules/statement_checks.yaml` — копия словаря tabular-parser),
|
||||
межформенная проверка 1370/2400 — мягкая; акцепт жёстких нарушений с комментарием; статус документа формы (§9.3).
|
||||
- API формы: `GET`/`PATCH /documents/{id}/statement`, `POST …/statement/checks/accept`; правка значений и ИНН/даты отчёта/единиц только
|
||||
до подтверждения; подтверждение ББ/ОФР с условиями `PERIOD_REQUIRED` и `CHECKS_NOT_PASSED`; `RECOGNIZED_DATA_EDIT` с before/after.
|
||||
- Мелкие дыры пути пользователя: `GET /files/{id}/content` (исходный файл потоком), комментарии к документу и `comments_count`,
|
||||
`GET /users/brief`, `documents_count` у ЮЛ, CSV чек-листа (`…/checklist?format=csv`).
|
||||
- Контракт API — `dev/api-contract-slice-3b.md`; QA — `dev/qa-guide.md` §6.9.
|
||||
- **Не вошло:** UI; правка строк ОСВ, добавление/удаление строк формы и правка кода/названия строки; отмена подтверждения (исправление
|
||||
— новым файлом); формулы §7 и справочники по редакциям (OQ-146); аудит скачивания (OQ-153).
|
||||
- **Закрыты допущения:** OQ-23, 36, 77, 140, 145 («Срез 3b: допущение реализовано»). **Открыт новый:** OQ-153. Открытые вопросы 3b:
|
||||
OQ-146 (словари по редакциям), OQ-149, OQ-152.
|
||||
|
||||
**Исходный план среза (для справки):**
|
||||
- Маппинг: `financial_statement_meta/column/line/value` из `statementDocument` (общий для Excel и OCR); числа, скобки, confidence; ИНН/имя сверка.
|
||||
- Логические проверки §7 (чистые функции `domain/checks.py`), акцепт КА, «Подтвердить данные» → `CONFIRMED`,
|
||||
`RECOGNIZED_DATA_EDIT` с before/after.
|
||||
- Чек-лист `required_document_rule × периметр − document CONFIRMED` (API + модалка + CSV).
|
||||
- UI: экран «Проверка распознавания» (исходник слева — PDF.js, результат справа, блок «Логические ошибки»).
|
||||
- Тесты: пары `aep_N.pdf/json`; машиночитаемые PDF АО РБ 2024 как dev-путь без OCR (опционально pdfplumber).
|
||||
- Открытые вопросы для 3b: редакция формы в контракте (OQ-145), словари §7/§12 по редакциям (OQ-146), строки с нечисловым кодом (OQ-149), повтор кода (OQ-152).
|
||||
|
||||
## Срез 4 — Консолидация: карточка, версии, периметр, периоды, сводная ОСВ
|
||||
|
||||
@ -226,9 +247,9 @@ DATA_CORRECTION, истечение блокировки, `period_check`, пор
|
||||
у версий, Q1-эквивалентность, склейка Q1 версии Q1, область ручного сопоставления, рекомендация кандидата, неоднозначность
|
||||
дорезолва, формулировка сценария «1 + propagated»).
|
||||
|
||||
**Остаётся (UI и последующие срезы):** экраны `34`–`37`, `42`, `30`, `32-*`, `31-00` (другой разработчик); CSV чек-листа;
|
||||
«Добавить версию», `CONFIRM`, `EXTENDED`, гудвилл, маршрут согласования (Срезы 6–7); ББ/ОФР в своде и чек-лист ББ/ОФР по
|
||||
реальным документам — Срез 3; шахматка и ВГО — Срез 5.
|
||||
**Остаётся (UI и последующие срезы):** экраны `34`–`37`, `42`, `30`, `32-*`, `31-00`; CSV чек-листа (сделан в Срезе 3b);
|
||||
«Добавить версию», `CONFIRM`, `EXTENDED`, гудвилл, маршрут согласования (Срезы 6–7); формы ББ/ОФР в чек-листе по
|
||||
реальным документам — Срез 3b (реализован); шахматка и ВГО — Срез 5.
|
||||
|
||||
**Исходный план среза (для справки):** `consolidation` / `consolidation_version` / `consolidation_member` /
|
||||
`consolidation_document`; приведение квартальных ОСВ (§2); сводная ОСВ (§3) как запрос; признак контрагента; ручное
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
|
||||
Для ручного тестирования стенда (установка ПО, запуск, чек-лист сценариев по ролям) — [`qa-guide.md`](qa-guide.md).
|
||||
Разработчику фронта: как устроены вход и права `arm-core`, какие ручки когда дёргать, семантика 401/403/404 —
|
||||
[`frontend-auth-guide.md`](frontend-auth-guide.md); контракт API консолидации — [`api-contract-slice-4.md`](api-contract-slice-4.md).
|
||||
[`frontend-auth-guide.md`](frontend-auth-guide.md); контракт API консолидации — [`api-contract-slice-4.md`](api-contract-slice-4.md); контракт API форм ББ/ОФР и пути
|
||||
пользователя до сводной ОСВ — [`api-contract-slice-3b.md`](api-contract-slice-3b.md).
|
||||
Перенос сервисов в контур банка (снимки без истории, что вырезается, скрипт подготовки, проверки в контуре) —
|
||||
[`contour-transfer-guide.md`](contour-transfer-guide.md).
|
||||
|
||||
|
||||
167
dev/api-contract-slice-3b.md
Normal file
167
dev/api-contract-slice-3b.md
Normal file
@ -0,0 +1,167 @@
|
||||
# Контракт API Среза 3b (формы ББ/ОФР и путь пользователя до сводной ОСВ)
|
||||
|
||||
Источник — реальный код arm-core (`api/src/schemas/statements.py`, `schemas/comments.py`, `api/endpoints/files/statements.py`,
|
||||
`comments.py`, `read.py`, `users.py`; Swagger — `/docs`) и спека `arm-core/openspec/changes/slice-3b-statements-api/` (design
|
||||
D-1…D-14, «Decisions made without discussion» №1–№22). Правила предметной области — `context/05-domain-rules.md` §7, §9.3, §9.5
|
||||
(п. 2, 4, 9, 10); экраны — `context/10-user-journey.md` (пути 1–2, шаг 3.1), номера в `[…]` — снимки макета (`mockup/INDEX.md`).
|
||||
Общие правила (префикс, токен, формат ошибок, права) — как в `dev/api-contract-slice-4.md` §1; здесь — то, что добавил Срез 3b.
|
||||
|
||||
## 1. Общее
|
||||
|
||||
- Префикс `/api/v1`, `Authorization: Bearer <JWT>`. Права: чтение формы, комментариев и исходного файла — `DOCUMENT_VIEW`; правка
|
||||
формы, акцепт проверок, комментарий, подтверждение — `DOCUMENT_EDIT`; CSV чек-листа — `CONSOLIDATION_VIEW`; `GET /users/brief` —
|
||||
любой аутентифицированный пользователь, отдельного права нет. Нет права — 403 строкой; нет токена — 401.
|
||||
- Порядок проверок на маршрутах документа: право (403) → валидация схемы запроса (422 массивом pydantic, до обращения к данным) →
|
||||
видимость (404) → состояние (409) → доменные 422 (`INVALID_INN`, `UNKNOWN_CELL`, `DUPLICATE_CELL`). Документ, файл или ЮЛ вне
|
||||
области видимости неотличимы от несуществующих: **404 с кодом `NOT_FOUND`**.
|
||||
- Доменные ошибки: `{"detail": {"code": "...", "message": "на русском", ...доп. поля}}`; ветвиться клиент должен по `code`. 422
|
||||
валидации тела/параметров схемы — массив pydantic (`detail: [{loc, msg, type}]`) без `code`; доменные 422 (`INVALID_INN`,
|
||||
`UNKNOWN_CELL`, …) — с `code`. Каталог кодов — `arm-core/README.md`, «Ошибки API».
|
||||
- **Деньги — строки JSON** (`"1234.50"`, `numeric(18,2)`), не `float`: `amount`, `original_amount`, `expected`, `actual`; в запросе
|
||||
`amount` — число с не более чем двумя знаками и не более 16 цифр до запятой, либо `null` (очистить ячейку). `confidence` — строка
|
||||
(`"100.00"`), `null` у ячеек, которых нет в результате распознавания.
|
||||
- Идентификаторы — uuid-строки; даты — ISO (`2025-09-30`); время — ISO с часовым поясом.
|
||||
- Типы форм: `document_type` — `BALANCE` (ББ), `INCOME` (ОФР); `DISCLOSURE` и `OTHER` — документ без данных, `…/statement` для них
|
||||
и для `OSV` — 409 `DOCUMENT_NOT_STATEMENT`.
|
||||
- `DocumentStatus` — `RECOGNIZED`, `NEEDS_CHECK`, `CONFIRMED`, `ERROR`; `DocumentCheckResult` (`counterparty_check`) — `MATCH`,
|
||||
`MISMATCH`, `UNKNOWN`; `PeriodQuarter` — `Q1`…`Q4`.
|
||||
|
||||
## 2. Эндпоинты
|
||||
|
||||
| Метод и путь | Право | Запрос | Ответ | Ошибки | Экран |
|
||||
|---|---|---|---|---|---|
|
||||
| `GET /documents/{document_id}/statement` | VIEW | — | 200 `StatementOut` | 404 `NOT_FOUND`, 409 `DOCUMENT_NOT_STATEMENT` | [41] |
|
||||
| `PATCH /documents/{document_id}/statement` | EDIT | `StatementPatch {header?, values[]}` | 200 `StatementOut` (состояние после правки) | 404, 409 `FILE_SUPERSEDED` / `DOCUMENT_READONLY` / `DOCUMENT_NOT_CONFIRMABLE` / `DOCUMENT_NOT_STATEMENT`, 422 `INVALID_INN` / `UNKNOWN_CELL` / `DUPLICATE_CELL`, 422 (pydantic-массив) | [41-02] «Сохранить» |
|
||||
| `POST /documents/{document_id}/statement/checks/accept` | EDIT | `ChecksAcceptIn {comment}` (1–2000 символов после обрезки) | 200 `StatementOut` | 404, 409 `NOTHING_TO_ACCEPT` и те же 409, что у `PATCH`, 422 (pydantic-массив) | [41] блок «Логические ошибки» |
|
||||
| `POST /documents/{document_id}/confirm` | EDIT | — | 200 `DocumentOut` (как в Срезе 2c) | 404, 409 `FILE_SUPERSEDED` / `ALREADY_CONFIRMED` / `DOCUMENT_NOT_CONFIRMABLE` / **`PERIOD_REQUIRED`** / **`CHECKS_NOT_PASSED`** | [41] «Подтвердить данные» |
|
||||
| `POST /documents/{document_id}/comments` | EDIT | `CommentIn {text}` (1–4000 символов после обрезки пробелов) | 201 `CommentOut` | 404, 422 (pydantic-массив) | [41] «Добавить комментарий» |
|
||||
| `GET /documents/{document_id}/comments` | VIEW | — | 200 `CommentOut[]` по возрастанию `created_at`, без пагинации | 404 | [41] |
|
||||
| `GET /files/{file_id}/content` | VIEW | query `disposition` = `attachment` (по умолчанию) или `inline` | 200 поток байт файла | 404 `NOT_FOUND`, 404 `FILE_CONTENT_NOT_FOUND`, 503 `STORAGE_UNAVAILABLE`, 422 (pydantic-массив) при другом `disposition` | [41] «Исходный документ» |
|
||||
| `GET /users/brief?ids=<uuid>,<uuid>…` | любой аутентифицированный | query `ids` (обязателен, ≤ 200) | 200 `UserBriefOut[]` | 422 `TOO_MANY_IDS`, 422 `INVALID_IDS` | [39] «Загрузил» |
|
||||
| `GET /consolidations/versions/{version_id}/checklist?format=csv` | CONSOLIDATION_VIEW | query `format` = `json` (по умолчанию, `ChecklistOut`) или `csv` | 200 `text/csv; charset=utf-8` | 404 `NOT_FOUND`, 422 (pydantic-массив) при другом `format` | [37] «Скачать» |
|
||||
|
||||
Изменения в уже существующих ответах: `comments_count` (целое, `0` по умолчанию) — у документа в карточке файла (`GET
|
||||
/legal-entities/{id}/files`, `GET /files/{id}`: `documents[]`) и в `DocumentOut` (`GET /documents/{id}`); `documents_count` (целое) —
|
||||
в `LegalEntityOut` (`GET /directories/legal-entities`, `GET …/{id}`): число документов файлов ЮЛ, кроме файлов в статусе
|
||||
`SUPERSEDED`.
|
||||
|
||||
Подтверждение формы ББ/ОФР привязывает её к версиям консолидации «в работе» так же, как ОСВ (Срез 4): ББ/ОФР — отчёт на конец
|
||||
квартала и подходит версии за тот же квартал (форма за `Q1` — только версиям за `Q1`); ячейка «Бух. баланс» / «ОФР» чек-листа
|
||||
после этого — `CONFIRMED`.
|
||||
|
||||
## 3. Схемы
|
||||
|
||||
Поля без `?` присутствуют всегда; `null`-able поля помечены `| null`.
|
||||
|
||||
**`StatementOut`** `{document, edition, header, columns[], lines[], checks}`:
|
||||
- `document` — `StatementDocumentOut {id, document_type, status, period_year | null, period_quarter | null, reporting_date | null,
|
||||
counterparty_check | null, locator | null, file_id}`.
|
||||
- `edition` — `"66n"` (дата периода документа ≤ 2024-12-31), `"fsbu4"` (позже) или `null` (даты нет): для выбора шаблона строк формы;
|
||||
на проверки §7 не влияет (OQ-146).
|
||||
- `header` — `StatementHeaderOut`: `inn`, `report_date`, `units` — каждый `HeaderFieldOut {value | null, original_value | null,
|
||||
confidence | null, verified | null, modified | null}` (`confidence` есть только у ИНН; `report_date.value` — ISO-дата строкой;
|
||||
`original_value` — как распознано, при правке не меняется), плюс строки `organization_name`, `okud`, `okpo`, `okopf`, `okfs`,
|
||||
`address` (все `| null`).
|
||||
- `columns[]` — `ColumnOut {id, column_index, label | null, period_date | null, is_current | null}`; `is_current = true` у колонки с
|
||||
самой поздней датой.
|
||||
- `lines[]` — по коду строки (по возрастанию `line_code`): `LineOut {id, line_code | null, line_code_confidence | null, line_name |
|
||||
null, line_name_confidence | null, values[]}`; `values[]` — по одной записи на **каждую** колонку формы (`ValueOut {id, column_id,
|
||||
column_index, amount | null, string_value | null, confidence | null, is_recognized | null, is_modified, original_amount | null,
|
||||
modified_by | null, modified_at | null}`). `amount` — текущее значение (после правок); `original_amount` заполняется с первой
|
||||
правки; `string_value` — неразобранный текст ячейки (когда `amount` пуст).
|
||||
- `checks` — `ChecksOut {failed[], soft_failed[], cross_form | null, accepted | null}`: `FailedCheckOut {column_index, relation,
|
||||
expected, actual}` (`relation` — читаемая запись соотношения, `expected` и `actual` — суммы строками; `column_index = -1` — у
|
||||
межформенной проверки колонки нет); `accepted` — `{by, at, comment}` (в коде схема `ChecksAcceptedOut`) или `null`.
|
||||
|
||||
**`StatementPatch`** `{header?: {inn?, report_date?, units?}, values?: [{line_id, column_id, amount}]}` — частичное тело, лишние поля
|
||||
запрещены (422). `header.inn` — строка из 10 или 12 цифр (любое другое значение, включая `null`, — 422 `INVALID_INN`: ИНН очистить нельзя); `header.report_date` — дата, `null` очищает; `header.units` — непустая
|
||||
строка после обрезки пробелов, ≤ 50 (`null` — «не менять», единицы очистить нельзя; пробельная строка — 422 pydantic-массивом). Поле шапки, которого нет в теле, не меняется. `values` — до 5000 ячеек; одна ячейка дважды в запросе — 422
|
||||
`DUPLICATE_CELL`; `line_id`/`column_id` не этого документа — 422 `UNKNOWN_CELL`. **`ChecksAcceptIn`** `{comment}`.
|
||||
|
||||
**`CommentIn`** `{text}`. **`CommentOut`** `{id, text, author: {id, full_name}, created_at}`. **`UserBriefOut`** `{id, full_name,
|
||||
is_active}`.
|
||||
|
||||
**`DocumentOut`** (из Среза 2c) + `comments_count`. Ошибка `CHECKS_NOT_PASSED` несёт доп. поле `failures[] {column_index, relation,
|
||||
expected, actual}` (суммы — строками) внутри `detail`: `{"detail": {"code": "CHECKS_NOT_PASSED", "message": "...", "failures": [...]}}`.
|
||||
|
||||
## 4. Нюансы, которые легко пропустить
|
||||
|
||||
1. **Форма создаётся при маппинге, а не при загрузке.** Пока файл `UPLOADED`/`PROCESSING`, документов формы нет. Документ появляется
|
||||
в карточке файла (`documents[]`) после статуса файла `PROCESSED`; `GET …/statement` до этого — 404.
|
||||
2. **Значение на каждую ячейку.** У каждой пары «строка × колонка» есть `ValueOut` с собственным `id`, даже если в результате
|
||||
распознавания ячейки не было (тогда `amount = null`, `confidence = null`, `is_recognized = null`). Для правки не нужно создавать
|
||||
значения: передавайте `line_id` и `column_id` существующей ячейки. Строк без кода (расшифровочных, «в том числе …») в ответе нет
|
||||
(OQ-36, OQ-141, OQ-149).
|
||||
3. **Подтверждённая форма — только для чтения.** `PATCH` и `accept` по документу `CONFIRMED` — 409 `DOCUMENT_READONLY`; отмены
|
||||
подтверждения нет, исправление — новым файлом (автозамена, `05-domain-rules.md` §9.5 п. 3). По файлу `SUPERSEDED` — 409
|
||||
`FILE_SUPERSEDED`; по документу в статусе `ERROR` — 409 `DOCUMENT_NOT_CONFIRMABLE`. Комментарии при этом разрешены всегда.
|
||||
4. **Правка значения: `is_modified` снимается возвратом к исходному.** При первой правке ячейки `original_amount` = прежняя сумма;
|
||||
`is_modified = (amount ≠ original_amount)`. Верните исходную сумму — `is_modified = false`, `original_amount` остаётся. Правка
|
||||
шапки: `modified = true` у поля, `original_value` не меняется (возврат значения `modified` не снимает).
|
||||
5. **Правка даты отчёта пересчитывает период.** После `PATCH header.report_date` в `document` меняются `period_year`,
|
||||
`period_quarter` (только если дата — последний день квартала, иначе `null`), `reporting_date`; `edition` — по новой дате. Правка
|
||||
`inn` пересчитывает `counterparty_check`. Статус документа (`RECOGNIZED`/`NEEDS_CHECK`) пересчитывается после каждой правки.
|
||||
6. **Акцепт сбрасывается любой правкой формы.** После `PATCH`, который что-то изменил, `checks.accepted` снова `null`. Запрос,
|
||||
который ничего не меняет (те же значения, пустое тело), — 200 с неизменным состоянием, без записи в журнал и без сброса акцепта.
|
||||
Если акцепт был, в записи журнала `RECOGNIZED_DATA_EDIT` его прежнее значение сохраняется в `before.checks_accepted`
|
||||
(`by`, `at`, `comment`), поэтому текст сброшенного акцепта можно восстановить из аудита.
|
||||
7. **`checks.failed` не очищается акцептом.** Акцепт не скрывает нарушения: `failed` остаётся заполненным, а `accepted` непусто —
|
||||
это сигнал «нарушения приняты аналитиком». Блокируют подтверждение только нарушения **без** акцепта.
|
||||
8. **Что блокирует подтверждение** (`POST …/confirm`): период не определён — 409 `PERIOD_REQUIRED` (исправить датой отчёта);
|
||||
жёсткие нарушения без акцепта — 409 `CHECKS_NOT_PASSED` (список — в `detail.failures`). **Не блокируют:** мягкие нарушения
|
||||
(`soft_failed`, в том числе «в пределах округления»), межформенная проверка (`cross_form`), `counterparty_check = MISMATCH`,
|
||||
низкая уверенность значений. Порядок 409, если совпало несколько: `FILE_SUPERSEDED`, `ALREADY_CONFIRMED`,
|
||||
`DOCUMENT_NOT_CONFIRMABLE`, `PERIOD_REQUIRED`, `CHECKS_NOT_PASSED`. `DISCLOSURE`/`OTHER` подтверждаются без условий.
|
||||
9. **Статус документа формы** — подсказка экрану, а не замок: `RECOGNIZED` только если нет жёстких нарушений без акцепта, контрагент
|
||||
не `MISMATCH`, период определён, все значения уверенные или правленые и нет предупреждений маппинга. Формы из OCR почти всегда
|
||||
`NEEDS_CHECK` (уверенность < 100, ИНН распознан неуверенно) — это ожидаемо; подтвердить можно и из `NEEDS_CHECK`.
|
||||
10. **Межформенная проверка зависит от другого документа.** `cross_form` считается, только если у ЮЛ есть документ-пара другого
|
||||
типа формы за тот же год и квартал (файл не `SUPERSEDED`; подтверждённый предпочтительнее) и найдены обе колонки; иначе `null`.
|
||||
Появление ОФР отдельным файлом позже меняет `cross_form` у уже открытого ББ — перечитайте форму. Нарушение мягкое.
|
||||
11. **Редакция формы (`edition`) — только подсказка.** Определяется ядром по дате периода документа; для документа без даты —
|
||||
`null`. Контракт обмена с сервисами распознавания редакцию не передаёт (OQ-145).
|
||||
12. **Исходный файл.** `GET /files/{id}/content` отдаёт байты через ядро; прямой ссылки на S3 нет. Забирайте как blob с `Authorization`
|
||||
(`<a href>` и `<img src>` без заголовка не сработают). `Content-Type` — по типу файла (`application/pdf`, `image/png`,
|
||||
`image/jpeg`, для xls/xlsx/xlsb — свои MIME). `Content-Disposition`: `attachment` (по умолчанию) или `inline` (для просмотра в
|
||||
браузере) с ASCII-именем `file.<ext>` и `filename*=UTF-8''…` — исходным именем в percent-кодировке; берите имя из `filename*`. Ответ несёт
|
||||
`X-Content-Type-Options: nosniff` (браузер не угадывает тип по содержимому; тип задаёт `Content-Type`) и `Cache-Control: private,
|
||||
no-store` (не кэшируйте байты); при `disposition=inline` — ещё `Content-Security-Policy: sandbox` (активное содержимое файла в
|
||||
браузере не исполняется; проверьте, что выбранный просмотрщик PDF работает при таком заголовке). Работает и для файла `SUPERSEDED`. Объекта нет в хранилище (убран ретеншном) — 404 `FILE_CONTENT_NOT_FOUND` (не путать с `NOT_FOUND`:
|
||||
файл в БД есть, а байтов нет); хранилище не подключено — 503 `STORAGE_UNAVAILABLE`. Скачивание в журнал не пишется (OQ-153).
|
||||
13. **Комментарии.** Только добавление и чтение; правки и удаления нет. `created_at` — порядок показа (по возрастанию). `text`
|
||||
обрезается по краям, пустой после обрезки или длиннее 4000 символов — 422 в формате pydantic. Автор — пользователь запроса.
|
||||
`comments_count` в карточках — для бейджа; список перечитывается отдельным запросом.
|
||||
Удаление файла (`DELETE /files/{id}`) удаляет и комментарии его документов, и данные форм ББ/ОФР (`financial_statement_*`):
|
||||
после этого `GET …/comments` и `GET …/statement` по документам файла — 404.
|
||||
14. **Имена пользователей.** `GET /users/brief` нужен, чтобы показать «Загрузил» (`uploaded_by`) и авторов правок
|
||||
(`modified_by`, `checks.accepted.by`, `confirmed_by`) без права `USERS_ADMIN`. Отдаёт только найденные id (включая неактивных,
|
||||
`is_active = false`), упорядоченные по `id`, а не в порядке запроса; неизвестные пропускаются без ошибки; пустой `ids` — `[]`; больше 200 id — 422 `TOO_MANY_IDS`; не-UUID —
|
||||
422 `INVALID_IDS`. Собирайте id пачкой и кэшируйте.
|
||||
15. **CSV чек-листа.** `text/csv; charset=utf-8`, **BOM** в начале, разделитель `;`, переводы строк CRLF. Первая строка —
|
||||
«Организация;ИНН;<правило>…», где правило — «Бух. баланс», «ОФР», «ОСВ <счёт>» (в порядке `rules[]` чек-листа). Ячейки — подписи
|
||||
макета: `CONFIRMED` «Загружен», `PROCESSING` «Обработка», `NEEDS_CHECK` и `RECOGNIZED` «Требуется проверка», `ERROR` «Ошибка
|
||||
загрузки», `MISSING` «Отсутствует»; при пропущенных кварталах — с хвостом « (нет: Q1, Q2)». Заголовок ответа —
|
||||
`Content-Disposition: attachment; filename="checklist.csv"; filename*=UTF-8''checklist-<ГК>-<год>-<квартал>.csv` (ASCII-имя
|
||||
фиксированное; настоящее имя — в `filename*`, percent-кодировка целиком, кириллица и пробелы экранируются; берите имя оттуда).
|
||||
Забирайте как blob с `Authorization`. Правило подписи — по `document_type` правила чек-листа; ячейки с текстом (имя ЮЛ) в CSV не
|
||||
нейтрализуются от формул Excel (design №19, R-9): значение, начинающееся с `=`, `+`, `-`, `@`, Excel может принять за формулу.
|
||||
Без `format=csv` (по умолчанию `format=json`) маршрут отдаёт JSON `ChecklistOut` (Срез 4); значение `format`, кроме `json` и `csv`,
|
||||
— 422 (pydantic-массив); право — `CONSOLIDATION_VIEW`, версия вне видимости — 404 `NOT_FOUND`.
|
||||
16. **`documents_count` у ЮЛ** считается на странице списка и в карточке одним запросом; заменённые файлы (`SUPERSEDED`) не входят.
|
||||
Число документов, а не файлов: один файл с ББ и ОФР даёт 2.
|
||||
17. **Что не вошло в срез.** Правка кода и названия строки, добавление и удаление строк и колонок, отмена подтверждения, смена
|
||||
типа формы, склейка страниц одной формы из разных файлов (OQ-140), формулы §7 по редакциям (OQ-146).
|
||||
|
||||
## 5. Сквозной сценарий экрана «Проверка распознавания» [41]
|
||||
|
||||
| Шаг | Вызовы |
|
||||
|---|---|
|
||||
| Открыть документ из панели [39] | список документов — `GET /legal-entities/{id}/files` (`documents[]`: `id`, `document_type`, `status`, `comments_count`); имена загрузивших — `GET /users/brief?ids=…` |
|
||||
| Левая панель «Исходный документ» | `GET /files/{file_id}/content?disposition=inline` (blob с токеном), `file_id` — из `StatementOut.document.file_id` или карточки файла |
|
||||
| Правая панель «Результат распознавания» | `GET /documents/{id}/statement`: шапка, колонки, строки со значениями; блок «Логические ошибки» — `checks.failed` / `soft_failed` / `cross_form`; подсветка — `confidence < 100`, `is_recognized = false`, `is_modified` |
|
||||
| Переход между документами организации (выпадающий список «Документ») | тот же `GET /documents/{id}/statement` по другому `document_id` |
|
||||
| «Редактировать» → «Сохранить» [41-02] | `PATCH /documents/{id}/statement {values:[…], header:{…}}` → в ответе новое состояние формы (проверки, статус, период пересчитаны) |
|
||||
| Нарушены жёсткие проверки, данные верны | `POST /documents/{id}/statement/checks/accept {comment}` → `checks.accepted` заполнен |
|
||||
| «Подтвердить данные» | `POST /documents/{id}/confirm`; 409 `PERIOD_REQUIRED` — предложить поправить дату отчёта; 409 `CHECKS_NOT_PASSED` — показать `detail.failures` |
|
||||
| «Добавить комментарий» | `POST /documents/{id}/comments {text}`, список — `GET /documents/{id}/comments` |
|
||||
| После подтверждения — чек-лист [37] | `GET /consolidations/versions/{id}/checklist` (ячейка «Бух. баланс»/«ОФР» — `CONFIRMED`), «Скачать» — тот же маршрут с `format=csv` |
|
||||
@ -1,4 +1,4 @@
|
||||
# Гайд тестировщика: локальный стенд АРМ КА (Срезы 0–2c, 4)
|
||||
# Гайд тестировщика: локальный стенд АРМ КА (Срезы 0–2c, 3b, 4)
|
||||
|
||||
Документ для ручного тестирования текущего `main`. Путь проверен 05.10.2026 на macOS (Docker 28, compose 2.34,
|
||||
Node 24, uv 0.10): от пустого каталога до экрана администратора — около 15 минут, из них 10 — скачивание образов.
|
||||
@ -17,7 +17,7 @@ Node 24, uv 0.10): от пустого каталога до экрана адм
|
||||
- Журнал аудита в БД (`dkb.audit_event`).
|
||||
- Загрузка файлов ОСВ через API (`POST /legal-entities/{id}/files`), распознавание реальным `tabular-parser`, разбор
|
||||
результата в документы и строки ОСВ, статусы файла и документа, автозамена версий, подтверждение документа, удаление
|
||||
файла (раздел 6.6); проверка парсера на произвольных форматах ОСВ и формах ББ/ОФР — раздел 6.7 (маппинг форм в ядре — Срез 3). Всё — через Swagger/curl, UI «Первичные документы» — подсрез 2d.
|
||||
файла (раздел 6.6); проверка парсера на произвольных форматах ОСВ и формах ББ/ОФР — раздел 6.7; формы ББ/ОФР в ядре (просмотр, правка, акцепт проверок, подтверждение, комментарии, исходный файл, CSV чек-листа; Срез 3b) — раздел 6.9. Всё — через Swagger/curl, UI «Первичные документы» — подсрез 2d.
|
||||
- Распознавание PDF в `scan-gateway` с эмулятором ИС Сканирование (Срез 3a, раздел 6.8): отдельно от ядра, без UI; загружать
|
||||
`aep_3.pdf` / `aep_6.pdf` из `fixtures/ocr`.
|
||||
- Консолидация через API (Срез 4): версии за период, периметр, блокировка «взять в работу», статусы, привязка подтверждённых
|
||||
@ -33,7 +33,7 @@ Node 24, uv 0.10): от пустого каталога до экрана адм
|
||||
**Нет (это не баги):**
|
||||
|
||||
- Экранов справочников ГК/ЮЛ/ГСК — проверяются только через Swagger (`/docs`) или curl.
|
||||
- Экрана загрузки и таблицы ОСВ (подсрез 2d), экранов консолидации (API есть, раздел 7), ББ/ОФР, распознавания PDF, шахматки, КФО — следующие срезы.
|
||||
- Экрана загрузки и таблицы ОСВ (подсрез 2d), экранов консолидации (API есть, раздел 7), экрана «Проверка распознавания» ББ/ОФР (API есть, раздел 6.9), экрана распознавания PDF, шахматки, КФО — следующие срезы.
|
||||
- Настоящего входа через SSO банка. Вход — dev-форма по email без пароля.
|
||||
|
||||
## 2. Что установить и проверить из ПО
|
||||
@ -433,8 +433,8 @@ ENV_FILE=.env.test .venv/bin/python -m src.parser.cli "/путь/к/файлу.x
|
||||
|
||||
### 6.8 Распознавание PDF: scan-gateway и эмулятор ИС Сканирование (Срез 3a)
|
||||
|
||||
PDF и изображения обрабатывает `scan-gateway`; в ядре маппинг форм из PDF (`statementDocument`) — Срез 3b, поэтому из
|
||||
UI/API ядра PDF-путь пока не проверяется. Сервис и нормализатор проверяются отдельно: поднимите профиль `services`
|
||||
PDF и изображения обрабатывает `scan-gateway`; ядро с Среза 3b маппит формы из PDF (`statementDocument`) в документы так же, как из Excel
|
||||
(раздел 6.9), но на стенде это требует профиля `services` с эмулятором, поэтому здесь сервис проверяется отдельно. Сервис и нормализатор проверяются отдельно: поднимите профиль `services`
|
||||
(`scan-gateway` и `scan-emulator` в `docs/dev/docker-compose.yml`) и пройдите сценарий из README `scan-gateway` («Полный круг против compose и эмулятора»:
|
||||
файл в MinIO → `recognition.image.request` в NATS → ack и итоговый конверт).
|
||||
|
||||
@ -460,6 +460,60 @@ UI/API ядра PDF-путь пока не проверяется. Сервис
|
||||
строка (OQ-152); минус не поставлен при `}` вместо `)` (OQ-147); `is_recognized = false` у прочерка «—» (OQ-148); сумма
|
||||
с тремя знаками после запятой (`1,234`) — `amount = null`, текст в `string_value`.
|
||||
|
||||
### 6.9 Формы ББ/ОФР: просмотр, правка, акцепт, подтверждение (Срез 3b)
|
||||
|
||||
Предусловия: раздел 4.2 (ядро с `NATS_ENABLED=true`, tabular-parser на `:8001`), `make seed-dev` (dev-ГК «Агрофирма Межениновская»,
|
||||
ЮЛ с ИНН `7016006635`, Ростовский РФ — ИНН совпадает с ИНН шапок форм), пользователь `ka_go` (`DOCUMENT_EDIT`, `DOCUMENT_VIEW`,
|
||||
видит всё) и для негатива `expert` (только `DOCUMENT_VIEW`), `client_manager` (чужой филиал). Файлы — книги Excel
|
||||
`../fixtures/statements-xlsx/mezheninovskaya/2025-Q3/1_Форма1_бух_09.xlsx` (ББ, 3 колонки: 30.09.2025, 31.12.2024, 31.12.2023, 37 строк
|
||||
с кодом) и `1_Форма2_бух_09.xlsx` (ОФР, 2 колонки). Подробные схемы и коды ошибок — `dev/api-contract-slice-3b.md`; правила —
|
||||
`context/05-domain-rules.md` §7, §9.3, §9.5. Новый ЮЛ с чужим ИНН для сценария «не совпал контрагент» — создайте через API
|
||||
справочников (раздел 6.2). Ожидаемое время от загрузки до `PROCESSED` — до 15 секунд.
|
||||
|
||||
```bash
|
||||
TOKEN=$(curl -s -X POST localhost:8000/api/v1/dev/token -H 'Content-Type: application/json' \
|
||||
-d '{"email":"ka_go@armka.local"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
|
||||
H="Authorization: Bearer $TOKEN"
|
||||
LE=$(curl -s "localhost:8000/api/v1/directories/legal-entities?q=7016006635" -H "$H" | python3 -c 'import sys,json;print(json.load(sys.stdin)["items"][0]["id"])')
|
||||
curl -s -X POST "localhost:8000/api/v1/legal-entities/$LE/files" -H "$H" \
|
||||
-F 'files=@../fixtures/statements-xlsx/mezheninovskaya/2025-Q3/1_Форма1_бух_09.xlsx'
|
||||
FILE=<file_id из ответа>
|
||||
curl -s "localhost:8000/api/v1/files/$FILE" -H "$H" | python3 -m json.tool # опрос до status = PROCESSED
|
||||
DOC=<documents[0].id из карточки>
|
||||
curl -s "localhost:8000/api/v1/documents/$DOC/statement" -H "$H" | python3 -m json.tool
|
||||
```
|
||||
|
||||
| № | Пользователь | Шаги | Ожидание |
|
||||
|---|---|---|---|
|
||||
| F1 | ka_go | загрузить `1_Форма1_бух_09.xlsx` для ЮЛ с ИНН 7016006635, дождаться `PROCESSED` | один документ: `document_type = BALANCE`, `osv_account_code` пуст, `period_year = 2025`, `period_quarter = Q3`, `counterparty_check = MATCH`, `comments_count = 0` |
|
||||
| F2 | ka_go | `GET /documents/{id}/statement` | 200; `edition = fsbu4`; `document.reporting_date = 2025-09-30`; `columns` — 3 (у колонки 0 `is_current = true`); `lines` — 37, отсортированы по `line_code`, у каждой `values` на все колонки; суммы — строки; `header.inn.value = "7016006635"`, `confidence = "100.00"`, `modified = false`; `checks.failed`, `soft_failed` пусты, `accepted = null` |
|
||||
| F3 | ka_go | `PATCH …/statement` `{"values":[{"line_id":…,"column_id":…,"amount":"<сумма строки 1600, колонки 0, увеличенная на 100>"}]}` (id — из F2) | 200; у ячейки `amount` новая, `original_amount` = прежняя, `is_modified = true`, `modified_by` = пользователь; `checks.failed` непуст (нарушено `1600 = 1100 + 1200` и/или `1600 = 1700`), `document.status = NEEDS_CHECK`; в `audit_event` одно `RECOGNIZED_DATA_EDIT` (раздел `RECOGNITION`, `object_type = document`) с `before`/`after` ячейки (6.4) |
|
||||
| F4 | ka_go | `POST …/confirm` | 409 `CHECKS_NOT_PASSED`, `detail.failures[]` — нарушения (`column_index`, `relation`, `expected`, `actual`) |
|
||||
| F5 | ka_go | `POST …/statement/checks/accept` `{"comment":"округление в форме клиента"}` | 200; `checks.accepted = {by, at, comment}`, `checks.failed` по-прежнему непуст; статус документа пересчитан (`RECOGNIZED`, если других причин `NEEDS_CHECK` нет); `POST …/confirm` теперь проходит (но F11 делайте после возврата значений) |
|
||||
| F6 | ka_go | снова `PATCH` той же ячейки другой суммой | 200; `checks.accepted = null` (акцепт сброшен правкой) |
|
||||
| F7 | ka_go | `PATCH` той же ячейки обратно на исходную сумму (`original_amount` из F3) | 200; `is_modified = false`, `original_amount` остаётся, `checks.failed` пуст; `PATCH` с пустым телом `{}` — 200, новых записей `RECOGNIZED_DATA_EDIT` нет |
|
||||
| F8 | ka_go | `PATCH` `{"header":{"inn":"123"}}` и `{"header":{"inn":null}}`; `{"values":[{"line_id":"<случайный uuid>","column_id":"<случайный uuid>","amount":"1"}]}` | 422 `INVALID_INN` (оба случая); 422 `UNKNOWN_CELL`; данные не изменились |
|
||||
| F9 | ka_go | `PATCH` `{"header":{"report_date":"2025-06-30"}}`, затем вернуть `"2025-09-30"` | период пересчитывается: `period_quarter = Q2` (`reporting_date = 2025-06-30`), `header.report_date.modified = true`, `original_value` не меняется (после возврата на `2025-09-30` — `Q3`, `modified` остаётся `true`); дата не на конец квартала (`2025-09-15`) — 200, `period_quarter = null`, статус `NEEDS_CHECK`, `POST …/confirm` — 409 `PERIOD_REQUIRED` (поправьте дату — подтверждение проходит) |
|
||||
| F10 | ka_go | `POST …/accept` без нарушений | 409 `NOTHING_TO_ACCEPT` |
|
||||
| F11 | ka_go | `POST …/confirm` для чистой формы (после F7/F9 вернуть исходные значения) | 200 `DocumentOut`, `status = CONFIRMED`, `confirmed_by`/`confirmed_at`; повтор — 409 `ALREADY_CONFIRMED`; `PATCH`/`accept` — 409 `DOCUMENT_READONLY`; в БД у шапки `reviewed_by/at` заполнены |
|
||||
| F12 | ka_go | загрузить `1_Форма2_бух_09.xlsx` тому же ЮЛ, дождаться `PROCESSED`; `GET …/statement` | `document_type = INCOME`, `period_quarter = Q3`, 2 колонки, `edition = fsbu4`; у ББ и ОФР поле `checks.cross_form` — либо `null`, либо нарушение `1370 − 1370(начало года) = 2400` (мягкое, не блокирует подтверждение); статус ОФР — `RECOGNIZED` |
|
||||
| F13 | ka_go | `GET /files/{file_id}/content?disposition=inline` и без параметра | 200, тело байт в байт равно загруженному `.xlsx`, `Content-Type` xlsx, заголовки `X-Content-Type-Options: nosniff` и `Cache-Control: private, no-store`; `inline` — ещё `Content-Security-Policy: sandbox`, `Content-Disposition: inline; filename="file.xlsx"; filename*=UTF-8''<имя>` (кириллица в percent-кодировке); `disposition=foo` — 422 |
|
||||
| F14 | ka_go | `POST /documents/{id}/comments` `{"text":"нет подписи руководителя"}`; `{"text":" "}`; затем `GET …/comments` и `GET /files/{file_id}` | 201 с `author.full_name`; пустой после обрезки текст — 422; в списке один комментарий; в карточке файла и `GET /documents/{id}` у документа `comments_count = 1`; комментарий к `CONFIRMED` документу разрешён |
|
||||
| F15 | ka_go | `GET /users/brief?ids=<uploaded_by файла>,<случайный uuid>`; с 201 id; с `ids=abc` | 200, один элемент `{id, full_name, is_active}`; 422 `TOO_MANY_IDS`; 422 `INVALID_IDS`; то же под `expert` (без `USERS_ADMIN`) — 200 |
|
||||
| F16 | ka_go | `GET /directories/legal-entities?q=7016006635` после F1 и F12 | `documents_count = 2` (ББ и ОФР) |
|
||||
| F17 | ka_go | в ГК «Агрофирма Межениновская» завести второе ЮЛ и ГСК из двух ЮЛ (раздел 7, «Подготовка»), создать версию за 2025 Q3; подтвердить ББ и ОФР (F11, F12); `GET …/checklist` и `GET …/checklist?format=csv` | ячейки «Бух. баланс» и «ОФР» ЮЛ — `CONFIRMED`; CSV: файл начинается с BOM (`EF BB BF`), первая строка «Организация;ИНН;Бух. баланс;ОФР;ОСВ …», разделитель `;`, у ЮЛ «Загружен»; `Content-Disposition` с `filename="checklist.csv"` и `filename*=UTF-8''checklist-<ГК>-2025-Q3.csv` |
|
||||
| F18 | expert | `PATCH …/statement`, `POST …/comments`, `POST …/accept` | 403 (нет `DOCUMENT_EDIT`); `GET …/statement` и `GET …/comments` — 200 |
|
||||
| F19 | client_manager | `GET …/statement`, `GET /files/{id}/content` документа ЮЛ другого филиала | 404 `NOT_FOUND`, тело как у случайного uuid |
|
||||
| F20 | ka_go | `GET …/statement` документа `OSV` (раздел 6.6) | 409 `DOCUMENT_NOT_STATEMENT` |
|
||||
| F21 | ka_go | загрузить второй файл с формой ББ за тот же период (книга Form1, пересохранённая в Excel — другой sha256); `PATCH` по документу первого файла | старый файл `SUPERSEDED`; `PATCH`/`accept` по его документу — 409 `FILE_SUPERSEDED`; чтение `GET …/statement` и комментарии работают |
|
||||
|
||||
Что не считать багом: формы из OCR (PDF) почти всегда `NEEDS_CHECK` — уверенность значений меньше 100, ИНН распознан неуверенно
|
||||
(`aep_3.pdf`, `aep_6.pdf`, раздел 6.8), аналитик правит значения и подтверждает; у форм без колонки кодов документ создаётся без
|
||||
строк (OQ-139); повтор одной формы в одном файле — вторая не создаётся, первая `NEEDS_CHECK`; страницы одной формы из разных файлов
|
||||
не склеиваются (OQ-140); строк без кода в ответе нет (OQ-36); скачивание файла и CSV в журнал не пишутся (OQ-153); формулы §7 одни
|
||||
для редакций `66n` и `fsbu4` (OQ-146); уже смапленные до Среза 3b файлы форм не получают — загрузите файл заново (раздел 9 — сброс
|
||||
стенда).
|
||||
|
||||
## 7. Консолидация через API (Срез 4)
|
||||
|
||||
Предусловия: ядро запущено (раздел 4.2); для сценариев с файлами — ещё `NATS_ENABLED=true` и tabular-parser на `:8001`
|
||||
@ -625,11 +679,11 @@ Q3-only — `missing_quarters = ["Q1","Q2"]`.
|
||||
- Таблица пользователей показывает первые 200.
|
||||
- При импорте ИНН, записанный в Excel числом с ведущим нулём, теряет ноль и даёт `INVALID_INN`.
|
||||
- Консолидация (Срез 4): только API; маршрут согласования (`CONSOLIDATION_RF` и далее), «Добавить версию», гудвилл, шахматка — следующие
|
||||
срезы; ББ/ОФР в своде не участвуют; блокировка версии не истекает по времени; `period_check` документов остаётся `UNKNOWN`;
|
||||
срезы; формы ББ/ОФР в сводной ОСВ не участвуют (только в чек-листе); блокировка версии не истекает по времени; `period_check` документов остаётся `UNKNOWN`;
|
||||
`PROCESSING` в чек-листе ставится по всем недостающим столбцам ЮЛ, пока файл не смаплен (OQ-119, OQ-120, OQ-122).
|
||||
- Ручное сопоставление контрагента меняет строку ОСВ для всех версий, куда входит документ (OQ-123); рекомендация кандидата — только
|
||||
при точном совпадении нормализованного имени (OQ-127).
|
||||
- PDF-путь (Срез 3a): ядро PDF в формы не маппит (Срез 3b); эмулятор отвечает только на PDF из `fixtures/ocr`, остальные —
|
||||
- PDF-путь (Срез 3a): эмулятор отвечает только на PDF из `fixtures/ocr`, остальные —
|
||||
`INVALID_DOCUMENT` (раздел 6.8, OQ-151); редакция `fsbu4` не заполнена (OQ-150).
|
||||
|
||||
- Парсер ОСВ: границы и «не баги» — в конце раздела 6.7 (счёт 91 — OQ-132; скользящий период — OQ-131).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user