From 21c0f900bd2c09b3b93fe9e9402c4d26e8e58a37 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Thu, 22 Oct 2026 17:12:04 +0400 Subject: [PATCH 1/2] =?UTF-8?q?=C2=A79.4=E2=80=939.5,=20=D0=B3=D0=B0=D0=B9?= =?UTF-8?q?=D0=B4=20=D1=84=D1=80=D0=BE=D0=BD=D1=82=D0=B0:=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2=D1=82=D0=BE=D1=80=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B8=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=20=D0=B8=20?= =?UTF-8?q?=D1=83=D1=82=D0=BE=D1=87=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=88?= =?UTF-8?q?=D0=B0=D0=BF=D0=BA=D0=B8=20=D0=9E=D0=A1=D0=92=20(#156,=20AURORA?= =?UTF-8?q?-1442)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §9.4 — автоповтор публикации не наращивает attempt, ручной повтор — новое задание. §9.5 п. 12 — ключ автозамены после уточнения; п. 13 — коды, 202, файл из консолидации, состав пакета (только файлы в ERROR), accepted/skipped. frontend-api-guide — маршруты retry и osv-header, новые поля DocumentOut, коды ошибок. Co-Authored-By: Claude Opus 5.5 --- context/05-domain-rules.md | 23 +++++++++++++++++----- dev/frontend-api-guide.md | 39 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/context/05-domain-rules.md b/context/05-domain-rules.md index 64d21ba..77b117c 100644 --- a/context/05-domain-rules.md +++ b/context/05-domain-rules.md @@ -833,7 +833,10 @@ INITIATED; в Срезе 2c консолидаций ещё нет, поэтом `info`) хранятся в `document_issue` документа ОСВ и отдаются в `DocumentOut.issues`. ### 9.4 Задание (`recognition_status`): queued → published → processing → done | needs_check | error | timeout → dlq; -replay: dlq → queued (`attempt + 1`); по кнопке пользователя — повторная обработка файла (§9.5 п. 13). `max_attempts`: OCR 1, парсер 3; `reply_deadline` OCR 20 мин (решение владельца 09.10.2026; note DBML «5 мин» — OQ-159), парсер 5 мин. +автоповтор публикации без ack (`OUTBOX_MAX_REPUBLISH`) — то же задание, `attempt` не меняется, счётчик — +`metrics.republish_count`, после исчерпания — `dlq` (`NO_ACK`); ручной повтор по кнопке пользователя — **новое** задание +с `attempt + 1`, прежнее остаётся в истории (§9.5 п. 13). Note DBML у `recognition_job` («replay = сброс в queued с +attempt+1») устарела — исправить в следующей версии DBML. `max_attempts`: OCR 1, парсер 3; `reply_deadline` OCR 20 мин (решение владельца 09.10.2026; note DBML «5 мин» — OQ-159), парсер 5 мин. ### 9.5 Первичные документы: загрузка, маппинг, замена, удаление, подтверждение (Срез 2c) @@ -1065,8 +1068,9 @@ replay: dlq → queued (`attempt + 1`); по кнопке пользовател Пересчитывается то, что выводится из периода и счёта: подходящесть версиям, чек-лист, колонка «ОСВ» (чистые функции, отдельно не хранятся); `period_check` и `counterparty_check` не меняются (§9.3). Автозамена при правке не запускается (только при маппинге, п. 3): документ с ключом, как у другого файла ЮЛ, тот файл не заменяет; при привязке документ с - тем же ключом в версии заменяется (§3 п. 1). Перезагрузка файла создаёт документы заново из результата — уточнения - прежних документов не переносятся. + тем же ключом в версии заменяется (§3 п. 1). Ключ документа после правки — исправленные период и счёт: при маппинге + следующего файла ЮЛ автозамена (п. 3) сравнивает уже их, а не распознанные значения. Перезагрузка файла создаёт + документы заново из результата — уточнения прежних документов не переносятся. Аудит — одно `audit_event(RECOGNIZED_DATA_EDIT)` (`object_type = document`): в `before/after` изменённые поля, при сброшенном подтверждении — `status`, `confirmed_by`, `confirmed_at`, в `after_value.unbound_version_ids` — версии, из которых снята привязка. Запрос без изменений — 200 без аудита. Порядок блокировок — п. 7 (файл → документ → версии по @@ -1079,8 +1083,17 @@ replay: dlq → queued (`attempt + 1`); по кнопке пользовател задания нет ни документов, ни подтверждений, ни правок — переносить и сбрасывать нечего. Ядро создаёт новое `recognition_job` с `attempt + 1` (уникальность `(file_id, attempt)`), делает его текущим и публикует через outbox; файл снова уходит в обработку (§9.2). Прежнее задание остаётся в истории. Предел — `max_attempts` задания (у парсера - 3): на нём — отказ. В остальных случаях — 409. Права — `DOCUMENT_EDIT` и policy по ЮЛ. **Пакет версии** — то же - одним запросом для всех файлов версии, подходящих под условие; версия после `VERIFIED` read-only — запрет. + 3): на нём — 409 `MAX_ATTEMPTS_REACHED`; не подходит под условие — 409 `FILE_NOT_RETRYABLE`. Права — `DOCUMENT_EDIT` + и policy по ЮЛ. Ответ — 202 и карточка файла с новым заданием. Файл, загруженный в консолидацию + (`stored_file.consolidation_id`), повторяется, только если у этой консолидации есть версия «в работе» с ЮЛ файла в + периметре, иначе 409 `VERSION_NOT_EDITABLE`; файл без контекста — без этого условия. + **Пакет версии** (`POST /consolidations/versions/{id}/files/retry`) — то же одним запросом. Версия должна быть «в работе» + (после `VERIFIED` и `CANCELLED` — 409 `VERSION_NOT_EDITABLE`); блокировка версии (`consolidation_lock`) не нужна. + Рассматриваются файлы в `ERROR`, загруженные в консолидацию этой версии (`stored_file.consolidation_id`), ЮЛ которых + в периметре версии; файлы без контекста и файлы других статусов в пакет не входят и не блокируются. Ответ — 202 + `{accepted[] {file_id, job_id, attempt}, skipped[] {file_id, code}}`: в `skipped` — рассмотренные файлы, которые + повторить нельзя, с кодом `FILE_NOT_RETRYABLE` или `MAX_ATTEMPTS_REACHED`; пустой пакет — 202 с пустыми списками. + Статус версии и периметр перепроверяются под блокировкой. Аудит — `audit_event(FILE_REPROCESS)` на каждый файл, отправленный на повтор (раздел `PRIMARY_DOCUMENTS`, `object_type = stored_file`, `object_id` — файл, `user_id` — инициатор): `before_value` — прежнее задание (`job_id`, `attempt`, `status`, `error_code`), `after_value` — новое (`job_id`, `attempt`); при обработке пакета — ещё diff --git a/dev/frontend-api-guide.md b/dev/frontend-api-guide.md index cf340b1..3fc790d 100644 --- a/dev/frontend-api-guide.md +++ b/dev/frontend-api-guide.md @@ -333,8 +333,11 @@ UI прячет элементы по `GET /me → permissions` (объедин | `GET /consolidations/{consolidation_id}/legal-entities/{legal_entity_id}/files` | VIEW | `limit`, `offset`, `include_superseded` | `Page[FileOut]` — **рабочий стол консоли** для ЮЛ: файлы с этим `consolidation_id` плюс файлы, чьи документы привязаны к любой версии консолидации; новые первыми; `SUPERSEDED` — только при `include_superseded=true` (скрыт по умолчанию) | 404 | | `GET /files/{file_id}` | VIEW | — | `FileOut` | — | | `DELETE /files/{file_id}` | EDIT | — | 204 (удаляет документы, строки ОСВ, формы ББ/ОФР, комментарии, задания) | 409 `FILE_HAS_CONFIRMED_DOCUMENTS`, 409 `FILE_SUPERSEDES_OTHERS` | +| `POST /files/{file_id}/retry` | EDIT | — | 202 `FileOut` — новое задание (`job.attempt + 1`), файл снова `UPLOADED` | 409 `FILE_NOT_RETRYABLE`, `MAX_ATTEMPTS_REACHED`; для файла консолидации — 409 `VERSION_NOT_EDITABLE` (нет версии «в работе» с его ЮЛ) | +| `POST /consolidations/versions/{version_id}/files/retry` | EDIT | — | 202 `RetryBatchOut {accepted[] {file_id, job_id, attempt}, skipped[] {file_id, code}}` | 409 `VERSION_NOT_EDITABLE` (версия не «в работе») | | `GET /files/{file_id}/content` | VIEW | `disposition` = `attachment` (по умолчанию) / `inline` | поток байт; `Content-Type` по типу файла | 404 `FILE_CONTENT_NOT_FOUND` (в БД есть, в хранилище нет), 503 `STORAGE_UNAVAILABLE` | | `GET /documents/{document_id}` | VIEW | — | `DocumentOut` | — | +| `PATCH /documents/{document_id}/osv-header` | EDIT | любые из `{period_year, period_quarter, is_cumulative, osv_account_code}`; отсутствующее поле не меняется | `DocumentOut` | по порядку: 409 `DOCUMENT_NOT_OSV`, `FILE_SUPERSEDED`; 422 `INVALID_PERIOD`, `INVALID_ACCOUNT_CODE`; 409 `DUPLICATE_ACCOUNT` (`document_id`), `VERSION_NOT_EDITABLE` (`version_ids`); 422 массивом — неверный тип поля | | `GET /documents/{document_id}/entries` | VIEW | `limit` (≤ 2000, 500), `offset` | `Page[EntryOut]` по `row_number`; дерево — по `parent_entry_id` | — | | `POST /documents/{document_id}/confirm` | EDIT | — | `DocumentOut` | 409 `FILE_SUPERSEDED`, `ALREADY_CONFIRMED`, `DOCUMENT_NOT_CONFIRMABLE`; для ББ/ОФР — `PERIOD_REQUIRED`, `CHECKS_NOT_PASSED` (`failures[]`) — именно в этом порядке, если совпало несколько | | `GET /documents/{document_id}/comments` | VIEW | — | `CommentOut[] {id, text, author {id, full_name}, created_at}` по возрастанию | — | @@ -379,9 +382,36 @@ UI прячет элементы по `GET /me → permissions` (объедин `period_quarter = null` — «период не определён» (например, ОСВ со скользящим периодом) — не подставлять квартал самим. - Проверки: `counterparty_check` — сверка шапки с ЮЛ-владельцем; `period_check` **всегда `UNKNOWN`** — показывать «—» (OQ-120). Подходит ли документ версии — только по чек-листу версии. -- Подтверждённый документ необратим (отмены нет); исправление — загрузкой нового файла (автозамена). +- Подтверждённый документ отменить нельзя; исправление — загрузкой нового файла (автозамена). Исключение — уточнение + периода и счёта ОСВ (ниже): оно снимает подтверждение. - Строки ОСВ (`entries`) — только чтение; экрана правки ОСВ в макете нет. +Повторная обработка (`…/retry`, AURORA-1442, правила — `05-domain-rules.md` §9.5 п. 13): + +- Доступна только для файла в `ERROR`, у которого текущее задание табличного парсера (`job.pipeline = tabular`) упало на + обмене: `job.status = timeout` (`error_code = TIMEOUT`) или `dlq` (`NO_ACK`). PDF/сканы (OCR) — нет. Кнопку + «Повторно обработать» показывать только при этом условии; остальное — 409 `FILE_NOT_RETRYABLE`. +- Попыток у парсера 3 (`job.max_attempts`); на последней — 409 `MAX_ATTEMPTS_REACHED`, кнопку прятать по + `job.attempt >= job.max_attempts`. +- Ответ 202: файл снова `UPLOADED` с новым заданием — опрашивать как после загрузки. Прежнее задание остаётся в истории. +- Файл, загруженный в консолидацию (`consolidation_id` не `null`), повторяется, только пока есть версия «в работе» с его ЮЛ + в периметре (иначе 409 `VERSION_NOT_EDITABLE`); файл без контекста — без этого условия. +- Пакет версии: рассматриваются файлы в `ERROR`, загруженные в консолидацию этой версии, ЮЛ которых в её периметре. + Подошедшие — в `accepted`, остальные файлы в `ERROR` — в `skipped` с кодом `FILE_NOT_RETRYABLE` или + `MAX_ATTEMPTS_REACHED`. Пустой пакет — 202 с двумя пустыми списками. Версия не «в работе» — 409 `VERSION_NOT_EDITABLE`. + Блокировка версии не нужна. +- В журнал пишется `FILE_REPROCESS` на каждый принятый файл. + +Уточнение периода и счёта ОСВ (`PATCH …/osv-header`, правила — §9.5 п. 12): + +- Только документ `OSV`. Если передано хоть одно поле периода, после правки все три должны быть заполнены, у `Q1` — + `is_cumulative = true` (иначе 422 `INVALID_PERIOD`). Счёт нормализуется как при разборе (`60.1` → `60.01`). +- Новые поля `DocumentOut`: `is_modified` (текущие значения отличаются от распознанных), `original_values` (распознанные + `period_year`, `period_quarter`, `is_cumulative`, `osv_account_code` — заполняется при первой правке и дальше не + меняется), `modified_by`, `modified_at`. Возврат к исходным значениям снимает `is_modified`. +- Подтверждённый документ после правки — `NEEDS_CHECK`, привязки к версиям «в работе» сняты; подтверждать заново вручную. +- Запрос без изменений — 200 без записи в журнал. Строки ОСВ не меняются. + ### 5.6 Формы ББ/ОФР | Метод и путь | Право | Запрос | Ответ | Ошибки | @@ -719,7 +749,11 @@ async function download(path: string, fallbackName: string): Promise { | `ENTITY_NOT_IN_PERIMETER` | 422 | загрузка в консолидацию | — | «ЮЛ не входит в периметр ни одной версии консолидации «в работе»» (`05-domain-rules.md` §9.5 п. 8) | | `QUERY_TOO_SHORT` | 422 | кандидаты `scope=all` | — | «Введите не менее 3 символов» | | `COMMENT_REQUIRED` | 422 | отмена версии | — | ошибка поля «Комментарий» | -| `VERSION_NOT_EDITABLE` | 409 | правки версии, взятие `CANCELLED` | — | перевести экран в просмотр | +| `VERSION_NOT_EDITABLE` | 409 | правки версии, взятие `CANCELLED`; повтор обработки; уточнение шапки ОСВ | у шапки ОСВ — `version_ids` | перевести экран в просмотр | +| `FILE_NOT_RETRYABLE`, `MAX_ATTEMPTS_REACHED` | 409 / в `skipped[]` | повторная обработка | — | спрятать кнопку повтора, показать причину | +| `DOCUMENT_NOT_OSV` | 409 | уточнение шапки ОСВ | — | правка только для ОСВ | +| `INVALID_PERIOD`, `INVALID_ACCOUNT_CODE` | 422 | уточнение шапки ОСВ | — | ошибка поля | +| `DUPLICATE_ACCOUNT` | 409 | уточнение шапки ОСВ: счёт уже есть у другой ОСВ файла | `document_id` | ссылка на документ с этим счётом | | `CONSOLIDATION_NOT_EDITABLE` | 409 | загрузка в консолидацию, у которой нет версии «в работе» (`POST /consolidations/{cid}/legal-entities/{le}/files`) | — | заблокировать кнопку загрузки | | `LOCK_REQUIRED` | 409 | правки версии | — | «Возьмите версию в работу» | | `LOCKED_BY_OTHER` | 409 | взятие и правки версии | `locked_by`, `locked_at`, из `POST …/lock` ещё `locked_by_name` | «Версию держит <имя>», предложить перехват | @@ -757,6 +791,7 @@ async function download(path: string, fallbackName: string): Promise { |---|---|---| | OQ-54 — SSO РАИСА | Меняется только получение токена (вместо dev-формы) | держать слой получения токена изолированным (`frontend-auth-guide.md` §9) | | arm-ka-docs#116 — единая нормализация ББ/ОФР | Выполнено в `arm-core` `main` (#129, #132) — см. §1 и §5.6.1; фронт (план 6) — в `arm-core/web`; выкладка сервисов на контракт 2.0 — `qa-statements-v2.md` | типы из §5.6.1 | +| arm-core#161 — повторная обработка файла и уточнение шапки ОСВ (AURORA-1442) | Маршруты и коды в §5.5 и §9 уже описаны; на `main` появятся после слияния PR | кнопки — AURORA-1679 (повтор), правка шапки ОСВ — отдельной задачей | Состояние этих задач — в Gitea (`RSHB/arm-core`, `RSHB/arm-ka-docs`); после слияния каждой этот раздел переезжает в §1. From 148b944402f5146645b00aef8138d03cf6b989b9 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Thu, 22 Oct 2026 18:52:30 +0400 Subject: [PATCH 2/2] =?UTF-8?q?03-architecture:=20=D1=80=D1=83=D1=87=D0=BD?= =?UTF-8?q?=D0=BE=D0=B9=20=D0=BF=D0=BE=D0=B2=D1=82=D0=BE=D1=80=20=D0=B8=20?= =?UTF-8?q?=D1=83=D1=82=D0=BE=D1=87=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=9E?= =?UTF-8?q?=D0=A1=D0=92=20(#156,=20AURORA-1442;=20=D0=B8=D0=B7=20docs=20#1?= =?UTF-8?q?52)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- context/03-architecture.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/context/03-architecture.md b/context/03-architecture.md index fa900f0..da98595 100644 --- a/context/03-architecture.md +++ b/context/03-architecture.md @@ -458,3 +458,12 @@ JetStream** (3 subject `files.*` — как в старом АРМ, решени `expires_at` → `status ready|timeout|error` + `error_code`, без ack `processing`, без `Nats-Msg-Id`, без queue group (один экземпляр), дедуп только в памяти процесса — доставка at-most-once. Это параллельный сервис старого АРМ, а не замена scan-gateway; из него берём только инфраструктурные решения (как и из `reference-old-arm`). + +### Ручной повтор и уточнение ОСВ (AURORA-1442) + +Повтор `tabular` после `TIMEOUT`/`NO_ACK` создаёт новое `recognition_job` (`attempt + 1`) и публикует его через +существующий outbox; прежнее задание и исходник в S3 сохраняются, аудит — `FILE_REPROCESS` (v5.21). Пакет версии +рассматривает только файлы в `ERROR` консолидации версии с ЮЛ из периметра. Реализация — `services/file_retry.py`. +`services/osv_header.py` уточняет период и счёт ОСВ на месте с `document.original_values` (v5.20), снимает +подтверждение и привязки к версиям «в работе», пишет `RECOGNIZED_DATA_EDIT`. Правила — `05-domain-rules.md` §9.4–9.5 +п. 12–13, контракт — `dev/frontend-api-guide.md` §5.5. Миграции — 0008 (v5.20) и 0009 (v5.21) отдельно.