From 7f94d25a54692180d96fe5f1b90f444ff6be88e8 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Thu, 8 Oct 2026 18:11:56 +0400 Subject: [PATCH] =?UTF-8?q?docs(openspec):=20=D0=B0=D1=80=D1=85=D0=B8?= =?UTF-8?q?=D0=B2=20auto-confirm-clean-documents=20=E2=80=94=20=D1=81?= =?UTF-8?q?=D0=BF=D0=B5=D0=BA=D0=B8=20osv-result-mapping,=20primary-files,?= =?UTF-8?q?=20audit-trail=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/audit-trail/spec.md | 0 .../specs/osv-result-mapping/spec.md | 0 .../specs/primary-files/spec.md | 0 openspec/specs/audit-trail/spec.md | 13 ++- openspec/specs/osv-result-mapping/spec.md | 96 +++++++++++++++---- openspec/specs/primary-files/spec.md | 26 ++++- 9 files changed, 112 insertions(+), 23 deletions(-) rename openspec/changes/{auto-confirm-clean-documents => archive/2026-10-08-auto-confirm-clean-documents}/.openspec.yaml (100%) rename openspec/changes/{auto-confirm-clean-documents => archive/2026-10-08-auto-confirm-clean-documents}/design.md (100%) rename openspec/changes/{auto-confirm-clean-documents => archive/2026-10-08-auto-confirm-clean-documents}/proposal.md (100%) rename openspec/changes/{auto-confirm-clean-documents => archive/2026-10-08-auto-confirm-clean-documents}/specs/audit-trail/spec.md (100%) rename openspec/changes/{auto-confirm-clean-documents => archive/2026-10-08-auto-confirm-clean-documents}/specs/osv-result-mapping/spec.md (100%) rename openspec/changes/{auto-confirm-clean-documents => archive/2026-10-08-auto-confirm-clean-documents}/specs/primary-files/spec.md (100%) diff --git a/openspec/changes/auto-confirm-clean-documents/.openspec.yaml b/openspec/changes/archive/2026-10-08-auto-confirm-clean-documents/.openspec.yaml similarity index 100% rename from openspec/changes/auto-confirm-clean-documents/.openspec.yaml rename to openspec/changes/archive/2026-10-08-auto-confirm-clean-documents/.openspec.yaml diff --git a/openspec/changes/auto-confirm-clean-documents/design.md b/openspec/changes/archive/2026-10-08-auto-confirm-clean-documents/design.md similarity index 100% rename from openspec/changes/auto-confirm-clean-documents/design.md rename to openspec/changes/archive/2026-10-08-auto-confirm-clean-documents/design.md diff --git a/openspec/changes/auto-confirm-clean-documents/proposal.md b/openspec/changes/archive/2026-10-08-auto-confirm-clean-documents/proposal.md similarity index 100% rename from openspec/changes/auto-confirm-clean-documents/proposal.md rename to openspec/changes/archive/2026-10-08-auto-confirm-clean-documents/proposal.md diff --git a/openspec/changes/auto-confirm-clean-documents/specs/audit-trail/spec.md b/openspec/changes/archive/2026-10-08-auto-confirm-clean-documents/specs/audit-trail/spec.md similarity index 100% rename from openspec/changes/auto-confirm-clean-documents/specs/audit-trail/spec.md rename to openspec/changes/archive/2026-10-08-auto-confirm-clean-documents/specs/audit-trail/spec.md diff --git a/openspec/changes/auto-confirm-clean-documents/specs/osv-result-mapping/spec.md b/openspec/changes/archive/2026-10-08-auto-confirm-clean-documents/specs/osv-result-mapping/spec.md similarity index 100% rename from openspec/changes/auto-confirm-clean-documents/specs/osv-result-mapping/spec.md rename to openspec/changes/archive/2026-10-08-auto-confirm-clean-documents/specs/osv-result-mapping/spec.md diff --git a/openspec/changes/auto-confirm-clean-documents/specs/primary-files/spec.md b/openspec/changes/archive/2026-10-08-auto-confirm-clean-documents/specs/primary-files/spec.md similarity index 100% rename from openspec/changes/auto-confirm-clean-documents/specs/primary-files/spec.md rename to openspec/changes/archive/2026-10-08-auto-confirm-clean-documents/specs/primary-files/spec.md diff --git a/openspec/specs/audit-trail/spec.md b/openspec/specs/audit-trail/spec.md index 1a73ec1..05b12ba 100644 --- a/openspec/specs/audit-trail/spec.md +++ b/openspec/specs/audit-trail/spec.md @@ -102,16 +102,23 @@ ORM-модель `audit_event` MUST NOT использоваться для `UPD | файл принят | `FILE_UPLOAD` | `PRIMARY_DOCUMENTS` | `stored_file` | инициатор | | прежний файл заменён новым (автозамена после маппинга) | `FILE_REPLACE` | `PRIMARY_DOCUMENTS` | `stored_file` (заменённый) | NULL (система; инициатор загрузки — в `description`) | | файл удалён | `FILE_DELETE` | `PRIMARY_DOCUMENTS` | `stored_file` | инициатор | -| документ подтверждён | `DOCUMENT_CONFIRM` | `PRIMARY_DOCUMENTS` | `document` | инициатор | +| документ подтверждён пользователем | `DOCUMENT_CONFIRM` | `PRIMARY_DOCUMENTS` | `document` | инициатор | +| документ подтверждён автоматически при маппинге (чистый) | `DOCUMENT_CONFIRM` | `PRIMARY_DOCUMENTS` | `document` | NULL (система; `description` = «автоподтверждение: без замечаний») | `FILE_REPLACE` SHALL содержать в `after_value` новый `file_id`, в `description` — перечень заменённых документов и -`confirmed=true`, если среди них был `CONFIRMED`. Смена статусов задания и файла в обмене и в цикле маппинга -`audit_event` не пишет (`ASSUMPTION(OQ-55)`). +`confirmed=true`, если среди них был `CONFIRMED`. `FILE_DELETE` SHALL содержать в `after_value.unbound_version_ids` +версии, где сняты привязки удалённых автоподтверждённых документов. Смена статусов задания и файла в обмене и в цикле +маппинга `audit_event` не пишет (`ASSUMPTION(OQ-55)`). #### Scenario: Загрузка двух файлов - **WHEN** принято два файла одним запросом - **THEN** два события `FILE_UPLOAD` с `object_id` каждого файла и `after_value` (`original_name`, `content_hash`, `job_id`) +#### Scenario: Автоподтверждение +- **WHEN** смаплен чистый результат ОСВ +- **THEN** одно событие `DOCUMENT_CONFIRM` с `object_id` документа, `user_id` NULL, `before_value.status = RECOGNIZED`, + `after_value.status = CONFIRMED`, `description = «автоподтверждение: без замечаний»` + ### Requirement: События консолидации Действия с консолидацией SHALL писать `audit_event` через сервис аудита с заполненным `consolidation_version_id`: diff --git a/openspec/specs/osv-result-mapping/spec.md b/openspec/specs/osv-result-mapping/spec.md index 3591a8f..2462769 100644 --- a/openspec/specs/osv-result-mapping/spec.md +++ b/openspec/specs/osv-result-mapping/spec.md @@ -96,8 +96,9 @@ LOCKED`), проверять, что `result_s3_object` относится к б `period_check` SHALL быть `UNKNOWN` до появления версии консолидации (Срез 4). Для `period_quarter ∈ {Q2, Q3, Q4}` с `is_cumulative = false` ядро SHALL сверить сальдо на начало корневой строки с документом `Q1` того же года, ЮЛ и счёта (файл не `SUPERSEDED`) и при совпадении (±0.01) установить `is_cumulative = true` с предупреждением -(`ASSUMPTION(OQ-02)`). Статус документа SHALL быть `RECOGNIZED`, если задание `done`, `checks` без нарушений, -`counterparty_check ≠ MISMATCH` и нет предупреждений маппинга; иначе `NEEDS_CHECK` (OQ-05). +(`ASSUMPTION(OQ-02)`). Статус документа при маппинге SHALL быть `RECOGNIZED`, если задание `done`, `checks` без +нарушений, `counterparty_check ≠ MISMATCH` и нет предупреждений маппинга; иначе `NEEDS_CHECK` (OQ-05). Документ +`RECOGNIZED` в той же транзакции SHALL подтверждаться автоматически (требование «Автоподтверждение чистого документа»). #### Scenario: Шапка другой организации - **WHEN** файл загружен для ЮЛ «Пекарня Счастливочная», а в шапке `АО "Русский бисквит"` @@ -109,21 +110,27 @@ LOCKED`), проверять, что `result_s3_object` относится к б #### Scenario: Чистый результат - **WHEN** задание `done`, шапка совпадает с владельцем, нарушений нет -- **THEN** `status = RECOGNIZED`, `counterparty_check = MATCH`, `period_check = UNKNOWN` +- **THEN** после маппинга `status = CONFIRMED` (автоподтверждение), `counterparty_check = MATCH`, `period_check = UNKNOWN` ### Requirement: Документ NEEDS_CHECK переводит версию в корректировку -После фиксации документов файла цикл маппинга SHALL для каждой версии в `DOCUMENTS_PROCESSING`, в периметр которой -входит ЮЛ файла, проверить документы файла: если среди них есть документ `NEEDS_CHECK`, подходящий версии по периоду -(правила привязки `documents-api`), версия SHALL перейти в `DATA_CORRECTION` с `changed_by = stored_file.uploaded_by` -в той же транзакции маппинга (версии блокируются `FOR UPDATE` по `id` после файла). Если `uploaded_by` NULL, переход -SHALL пропускаться с записью в лог. Привязка `consolidation_document` при маппинге MUST NOT создаваться. +После фиксации документов файла и автоподтверждения чистых документов цикл маппинга SHALL для каждой версии в +`DOCUMENTS_PROCESSING`, в периметр которой входит ЮЛ файла, проверить документы файла: если среди них есть документ +`NEEDS_CHECK`, подходящий версии по периоду (правила привязки `documents-api`), версия SHALL перейти в `DATA_CORRECTION` +с `changed_by = stored_file.uploaded_by` в той же транзакции маппинга (версии блокируются `FOR UPDATE` по `id` после +файла). Если `uploaded_by` NULL, переход SHALL пропускаться с записью в лог. Привязка `consolidation_document` для +документа `NEEDS_CHECK` при маппинге MUST NOT создаваться (привязку при маппинге создаёт только автоподтверждение). #### Scenario: Предупреждение маппинга двигает статус - **WHEN** версия 2024 Q3 в `DOCUMENTS_PROCESSING`; смапплен файл ЮЛ периметра с ОСВ 62 за 2024 Q3 в `NEEDS_CHECK` - **THEN** версия в `DATA_CORRECTION`, запись истории с `changed_by` = загрузивший; `consolidation_document` нет #### Scenario: Документ RECOGNIZED -- **WHEN** смапплен файл с единственным документом `RECOGNIZED` +- **WHEN** версия 2024 Q3 в `DOCUMENTS_PROCESSING`; смапплен файл с единственным чистым документом за 2024 Q3 +- **THEN** документ `CONFIRMED` и привязан; переход версии в `DATA_CORRECTION` записан автоподтверждением (комментарий + «автоподтверждение документа …»), этот хук второго перехода не делает + +#### Scenario: Документ не подходит версии +- **WHEN** версия 2024 Q3 в `DOCUMENTS_PROCESSING`; смапплен файл с единственным документом `NEEDS_CHECK` за 2025 Q1 - **THEN** статус версии не изменился ### Requirement: Документ формы ББ/ОФР @@ -160,12 +167,12 @@ SHALL пропускаться с записью в лог. Привязка `co ### Requirement: Повтор формы в файле и прочие документы Второй документ `BALANCE` или `INCOME` в одном файле MUST NOT создаваться: предупреждение маппинга, первый документ — `NEEDS_CHECK`. Документы `DISCLOSURE` и `OTHER` результата SHALL давать по одному `document` каждого типа на файл без -данных (`locator` — все `locator` этого типа через запятую, статус `RECOGNIZED`). Форма `BALANCE`/`INCOME` без строк -SHALL создаваться с шапкой и колонками (если колонки есть в результате). +данных (`locator` — все `locator` этого типа через запятую, статус при маппинге `RECOGNIZED`, затем автоподтверждение → +`CONFIRMED`). Форма `BALANCE`/`INCOME` без строк SHALL создаваться с шапкой и колонками (если колонки есть в результате). #### Scenario: Расшифровки - **WHEN** смаплен синтетический снимок `st06_header_variants` (форма `INCOME` и пять `DISCLOSURE`) -- **THEN** у файла два документа: `INCOME` и один `DISCLOSURE` с пятью `locator` через запятую +- **THEN** у файла два документа: `INCOME` и один `DISCLOSURE` с пятью `locator` через запятую; `DISCLOSURE` — `CONFIRMED` #### Scenario: Форма без колонки кодов - **WHEN** смаплен синтетический снимок `st03_no_codes` (`BALANCE`, строк 0) @@ -176,13 +183,15 @@ SHALL создаваться с шапкой и колонками (если к неакцептованных жёстких проверок §7 (`statement-checks`); `counterparty_check ≠ MISMATCH`; `period_year` и `period_quarter` определены; у каждого значения `is_modified = true` либо (`is_recognized` не `false` и `confidence` равна 100 или `NULL`); нет предупреждений маппинга по документу; есть хотя бы одна строка. Иначе `NEEDS_CHECK`. Статус -SHALL пересчитываться при маппинге, после правки формы и после акцепта, кроме документа `CONFIRMED`. Документ формы -`NEEDS_CHECK` SHALL участвовать в переходе версии в `DATA_CORRECTION` наравне с ОСВ (требование «Документ NEEDS_CHECK -переводит версию в корректировку»). +SHALL пересчитываться при маппинге, после правки формы и после акцепта, кроме документа `CONFIRMED`. Документ +`RECOGNIZED` после маппинга SHALL подтверждаться автоматически (требование «Автоподтверждение чистого документа»); +`RECOGNIZED`, полученный пересчётом после правки или акцепта, — только вручную. Документ формы `NEEDS_CHECK` SHALL +участвовать в переходе версии в `DATA_CORRECTION` наравне с ОСВ (требование «Документ NEEDS_CHECK переводит версию в +корректировку»). #### Scenario: Чистая форма Excel - **WHEN** смаплен `mezheninovskaya/2025-Q3/1_Форма2_бух_09` (ОФР, `done`, соотношения сходятся, ИНН шапки = ИНН владельца файла) -- **THEN** документ `INCOME` `RECOGNIZED` +- **THEN** документ `INCOME` `CONFIRMED` (автоподтверждение) #### Scenario: Форма OCR - **WHEN** смаплен эталон `ocr/expected_normalize/aep_3` (уверенность значений < 100, ИНН шапки ≠ ИНН владельца) @@ -191,3 +200,58 @@ SHALL пересчитываться при маппинге, после пра #### Scenario: Форма двигает версию - **WHEN** версия ГК 2025 Q3 в `DOCUMENTS_PROCESSING`, в периметре ЮЛ файла; смаплена форма `BALANCE` за 2025 Q3 в `NEEDS_CHECK` - **THEN** версия в `DATA_CORRECTION`, привязки `consolidation_document` нет + +#### Scenario: Чистая после правки — вручную +- **WHEN** форма `NEEDS_CHECK` исправлена аналитиком (`PATCH …/statement`) и пересчёт дал `RECOGNIZED` +- **THEN** документ остаётся `RECOGNIZED` до `POST …/confirm` + +### Requirement: Автоподтверждение чистого документа +В транзакции маппинга после автозамены прежних версий файла и до перехода версий по `NEEDS_CHECK` цикл маппинга SHALL +перевести каждый документ файла, получивший при маппинге статус `RECOGNIZED`, в `CONFIRMED` (ТЗ 03_9 п. 4, 10_10; +решение владельца 08.10.2026, OQ-157): `confirmed_by = NULL`, `confirmed_at = now`; у формы `BALANCE`/`INCOME` — +`financial_statement_meta.reviewed_by = NULL`, `reviewed_at = now`; `audit_event(DOCUMENT_CONFIRM, PRIMARY_DOCUMENTS, +object_type = document)` с `user_id = NULL`, `before_value.status = RECOGNIZED`, `after_value.status = CONFIRMED` и +`description = «автоподтверждение: без замечаний»`. Форма `BALANCE`/`INCOME` с шапкой SHALL подтверждаться +автоматически, только если выполнены условия ручного подтверждения (`period_year` и `period_quarter` определены, нет +непройденных неакцептованных жёстких проверок §7); иначе она MUST оставаться `RECOGNIZED` без ошибки. Документы +`NEEDS_CHECK` MUST NOT подтверждаться автоматически. + +Автоподтверждённый документ SHALL привязываться к версиям так же, как при ручном подтверждении (`documents-api` +«Подтверждение документа»: все подходящие версии «в работе», замена привязки того же ключа R-1), и каждая версия, +получившая привязку, из `INITIATED` или `DOCUMENTS_PROCESSING` SHALL переходить в `DATA_CORRECTION` с `changed_by = +stored_file.uploaded_by` и `comment = «автоподтверждение документа »` (счёт — только у +ОСВ). Если `uploaded_by` NULL, привязка SHALL создаваться, а переход — пропускаться с записью в лог. Пересчёт статуса +формы после правки или акцепта MUST NOT подтверждать документ автоматически. + +#### Scenario: Чистая ОСВ сразу «Загружен» +- **WHEN** смаплен результат `fixtures/osv/rb/2025-Q1/ОСВ сч 60 за 1 кв 2025 год.xlsx` для ЮЛ АО «Русский бисквит» + (задание `done`, шапка совпадает с владельцем, нарушений и предупреждений нет) +- **THEN** документ `OSV` 60 — `CONFIRMED`, `confirmed_by` NULL, `confirmed_at` заполнен; есть `audit_event(DOCUMENT_CONFIRM)` + с `user_id` NULL и описанием «автоподтверждение: без замечаний» + +#### Scenario: Привязка и переход версии +- **WHEN** версия ГК «Русский Бисквит» 2025 Q1 в `INITIATED`, РБ в периметре; смаплен чистый результат ОСВ 60 РБ за 2025 Q1, + файл загрузил `ka_go` +- **THEN** `consolidation_document(version, document)` создан; версия в `DATA_CORRECTION`; запись истории + `INITIATED → DATA_CORRECTION` с `changed_by` = `ka_go` и комментарием «автоподтверждение документа OSV 60» + +#### Scenario: Документ с замечанием ждёт аналитика +- **WHEN** файл ОСВ загружен для ЮЛ «Пекарня Счастливочная», а в шапке `АО "Русский бисквит"` +- **THEN** документ `NEEDS_CHECK`, `confirmed_at` пуст, события `DOCUMENT_CONFIRM` нет, привязки нет + +#### Scenario: Чистая форма Excel +- **WHEN** смаплен `statements-xlsx/mezheninovskaya/2025-Q3/1_Форма2_бух_09` (ОФР, `done`, соотношения сходятся, период определён) +- **THEN** документ `INCOME` `CONFIRMED`, `reviewed_by` NULL, `reviewed_at` заполнен + +#### Scenario: Форма OCR не подтверждается сама +- **WHEN** смаплен эталон `ocr/expected_normalize/БО 1 кв 2025` (уверенность значений < 100, дата отчёта не разобрана) +- **THEN** документы `BALANCE` и `INCOME` — `NEEDS_CHECK`, не `CONFIRMED` + +#### Scenario: Файл без загрузившего +- **WHEN** версия в `INITIATED`, ЮЛ в периметре; смаплен чистый результат файла с `uploaded_by` NULL +- **THEN** документ `CONFIRMED` и привязан к версии; версия осталась `INITIATED`; в логе — строка о пропуске перехода + +#### Scenario: Автозамена чистой версией файла +- **WHEN** документ ОСВ 60 РБ за 2025 Q1 из файла A привязан к версии 2025 Q1; загружен и смаплен файл B того же ключа + (чистый), A заменён автозаменой +- **THEN** привязка документа A снята, документ B `CONFIRMED` и привязан к версии diff --git a/openspec/specs/primary-files/spec.md b/openspec/specs/primary-files/spec.md index 2a1bd25..6de8384 100644 --- a/openspec/specs/primary-files/spec.md +++ b/openspec/specs/primary-files/spec.md @@ -109,14 +109,32 @@ queued`, `pipeline` по типу: xls/xlsx/xlsb → `tabular`, pdf/png/jpg → ### Requirement: Удаление файла `DELETE /api/v1/files/{file_id}` (`DOCUMENT_EDIT`, policy) SHALL удалить строки ОСВ, документы, задания и `stored_file`, проставить `s3_object.expires_at = now()` (объект физически не удаляется, OQ-22) и записать `audit_event(FILE_DELETE, -before_value = карточка файла)`. Удаление MUST быть отклонено 409 `FILE_HAS_CONFIRMED_DOCUMENTS`, если у файла есть -документ `CONFIRMED`, и 409 `FILE_SUPERSEDES_OTHERS`, если на файл ссылаются `superseded_by_file_id`. Файл -`SUPERSEDED` удалить можно. +before_value = карточка файла, after_value = {unbound_version_ids})`. Удаление MUST быть отклонено 409 +`FILE_HAS_CONFIRMED_DOCUMENTS`, если у файла есть документ `CONFIRMED`, подтверждённый пользователем (`confirmed_by` не +NULL), или если автоподтверждённый документ файла (`confirmed_by` NULL) привязан к версии в статусе, отличном от +`INITIATED`, `DOCUMENTS_PROCESSING`, `DATA_CORRECTION` (OQ-157, уточняет OQ-110); и 409 `FILE_SUPERSEDES_OTHERS`, если на +файл ссылаются `superseded_by_file_id`. При удалении файла с автоподтверждёнными документами их привязки +`consolidation_document` SHALL сниматься, статус версий MUST NOT меняться, `after_value.unbound_version_ids` — id версий, +где привязки сняты (по возрастанию; пустой список, если привязок не было). Блокировки: задания → файл → документы → +advisory-блокировка привязок ГК → версии по `id`. Файл `SUPERSEDED` удалить можно. #### Scenario: Удаление ошибочного файла - **WHEN** файл в `ERROR` удаляется владельцем - **THEN** 204; `stored_file`, `recognition_job` отсутствуют; `s3_object.expires_at` заполнен; `audit_event(FILE_DELETE)` + с `after_value.unbound_version_ids = []` #### Scenario: Подтверждённый документ -- **WHEN** удаляется файл с документом `CONFIRMED` +- **WHEN** удаляется файл с документом `CONFIRMED`, у которого `confirmed_by` = пользователь - **THEN** 409 `FILE_HAS_CONFIRMED_DOCUMENTS`, ничего не изменилось + +#### Scenario: Автоподтверждённый документ в корректировке +- **WHEN** версия 2025 Q1 в `DATA_CORRECTION`, к ней привязан автоподтверждённый документ ОСВ 60 РБ; файл удаляется +- **THEN** 204; привязка снята, версия осталась в `DATA_CORRECTION`; `FILE_DELETE.after_value.unbound_version_ids` = [id версии] + +#### Scenario: Версия ушла на согласование +- **WHEN** автоподтверждённый документ файла привязан к версии в `CONSOLIDATION_RF` +- **THEN** 409 `FILE_HAS_CONFIRMED_DOCUMENTS`, привязка и файл на месте + +#### Scenario: Автоподтверждённый без привязок +- **WHEN** удаляется файл, все документы которого автоподтверждены и ни к одной версии не привязаны +- **THEN** 204; `after_value.unbound_version_ids = []`