# Spec Delta ## Purpose Дельта change `quality-metrics` (метрики качества первички). ## MODIFIED Requirements ### Requirement: Цикл маппинга отделён от inbox Фоновая задача ядра SHALL с периодом `MAPPING_POLL_SECONDS` (5) выбирать одно задание со `status ∈ {done, needs_check}`, чей файл имеет `status = PROCESSING` и `current_recognition_job_id` = это задание (`FOR UPDATE SKIP LOCKED`), проверять, что `result_s3_object` относится к бакету хранилища и его ключ начинается с `results/{file_id}/{job_id}/` (OQ-70), читать объект из S3 **вне** транзакции, валидировать `RecognitionResult` v1.x и в одной транзакции создать документы и строки (листы ОСВ и формы ББ/ОФР), записать `recognition_job.group_schema`, перевести файл в `PROCESSED` и выполнить автозамену. Результат без документов SHALL давать неповторяемую ошибку маппинга «в результате нет документов»; результат с формами, но без ОСВ, ошибкой MUST NOT быть. Inbox MUST NOT читать файл результата и MUST NOT создавать документы. После `map_file` и до автоподтверждения транзакция маппинга SHALL писать факты приёма `quality_intake_fact` по документам `BALANCE` / `INCOME` / `OSV`; неповторяемая ошибка маппинга (файл → `ERROR`) SHALL писать `quality_failure_fact` (`failure_kind = MAPPING`, `error_code = MAPPING_FAILED`). #### Scenario: Результат смапплен - **WHEN** в S3 лежит результат парсера для `rb/2024-FY/ОСВ_66 за 2024.xls`, задание `done`, файл `PROCESSING` - **THEN** не позднее чем через `MAPPING_POLL_SECONDS` + время маппинга файл — `PROCESSED`, создан один `document` (`OSV`, `osv_account_code = "66"`, `period_year = 2024`, `period_quarter = Q4`, `is_cumulative = true`, `locator = sheet:Лист_1`), `osv_entry` — все строки результата кроме `TOTAL`, `group_schema` задания содержит `[{"locator": "sheet:Лист_1", "groups": [...]}]` #### Scenario: Ключ результата вне папки задания - **WHEN** `result_s3_object.key = results/<другой file_id>/.../result.json` - **THEN** файл — `ERROR` с ошибкой маппинга, документы не созданы, повтор не выполняется #### Scenario: Сбой маппинга с повтором - **WHEN** вставка строк падает по ошибке БД - **THEN** транзакция откачена, `metrics.mapping_attempts = 1`, файл остаётся `PROCESSING`; на третьей неудаче — `ERROR` с `metrics.mapping_error` #### Scenario: Повторный маппинг идемпотентен - **WHEN** маппинг прерван после частичной вставки (имитация) и выполнен снова - **THEN** у файла ровно один набор документов и строк, дублей по `(document_id, row_number)` нет #### Scenario: Файл только с формами - **WHEN** смаплен golden-снимок `mezheninovskaya/2025-Q3/1_Форма1_бух_09` (одна форма `BALANCE`, ОСВ нет) - **THEN** файл `PROCESSED`, создан документ `BALANCE`, ошибки «нет документов ОСВ» нет #### Scenario: Пустой результат - **WHEN** результат без документов (`documents = []`): контракт v1.x такого результата не допускает — его останавливает проверка схемы, а прямой вызов `map_file` даёт `MappingFatal` - **THEN** файл `ERROR`, причина в `metrics.mapping_error` (при проверке схемы — ошибка схемы, в `map_file` — «в результате нет документов»), повтор не выполняется #### Scenario: Факт приёма в транзакции маппинга - **WHEN** результат смаплен и создан документ ОСВ - **THEN** в той же транзакции записан `quality_intake_fact` документа до перехода в `CONFIRMED` #### Scenario: Неповторяемая ошибка маппинга - **WHEN** маппинг завершился неповторяемой ошибкой - **THEN** файл `ERROR` и записан `quality_failure_fact` с `failure_kind = MAPPING` ### 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 подтверждать документ автоматически. Для каждого автоподтверждённого документа `BALANCE` / `INCOME` / `OSV` SHALL записываться `quality_outcome_fact` (`AUTO_CONFIRMED`, `primary_cause = WITHOUT_PARTICIPATION`, `confirmed_by` пуст); автозамена прежних версий SHALL писать `SUPERSEDED` / `SUPERSEDED_AFTER_CONFIRM` по вытесненным документам. #### 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` и привязан к версии #### Scenario: Факт автоподтверждения - **WHEN** документ автоподтверждён - **THEN** есть `quality_outcome_fact` `AUTO_CONFIRMED` для этого документа