Andrey Epifancev e7bef704cf docs: автоподтверждение чистых документов в CLAUDE.md и README (#64)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:31:01 +04:00

116 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Design: автоподтверждение чистых документов
## Context
Маппинг результата (`services/result_mapping.py`, `_map_candidate`) в одной транзакции: `map_file` создаёт документы
файла со статусом `RECOGNIZED` или `NEEDS_CHECK` (ОСВ — `domain/osv_mapping.document_status`, §9.3; формы —
`domain/statement_mapping`, §9.5 D-6; `DISCLOSURE`/`OTHER` — всегда `RECOGNIZED`), автозамена снимает привязки
документов прежних файлов (`on_documents_superseded`), затем хук `on_documents_mapped` переводит версии в `DATA_CORRECTION`
по документам `NEEDS_CHECK` (`changed_by = file.uploaded_by`, design №2 Среза 4). Привязки к версиям создаёт только
ручное подтверждение (`DocumentService.confirm` → `on_document_confirmed`) и подхват при создании версии и правке
периметра (`bind_confirmed`). Удаление файла (`PrimaryFilesService.delete`) запрещено при любом `CONFIRMED` документе.
Порядок блокировок (`CLAUDE.md` arm-core): задание → файл → документы → advisory ГК → версии по `id`. Маппинг уже
держит задание, файл и документы, когда вызывает хуки версий.
## Goals / Non-Goals
**Goals:** чистый документ сразу `CONFIRMED` и в своде без действий аналитика; поведение привязки и статусов версии —
то же, что при ручном подтверждении; ошибочно загруженный чистый файл можно удалить, пока версия не ушла дальше
корректировки.
**Non-Goals:** см. proposal — новые поля, UI, миграция данных, пересмотр `NEEDS_CHECK`.
## Decisions
### D-1. Где: шаг маппинга после автозамены
`_map_candidate`: `map_file` → автозамена (как сейчас) → **`auto_confirm(session, file, documents, now)`** →
`on_documents_mapped`. После автозамены — чтобы привязки прежних версий файла были уже сняты, а замена по ключу (R-1)
работала как при ручном подтверждении. До `on_documents_mapped` — тот смотрит только `NEEDS_CHECK`, автоподтверждённые
его не касаются. Всё в одной транзакции маппинга: при ошибке откатывается и маппинг, и подтверждение (повтор задания —
по счётчику попыток, как сейчас).
### D-2. Кто: система за документ, загрузивший — за переход версии (решение владельца 08.10.2026)
`document.confirmed_by = NULL`, `confirmed_at = now`; у формы `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 = «автоподтверждение: без замечаний»`
(DBML: `audit_event.user_id` — «NULL для системных событий»). Переход версии — через `consolidation_status.transition`
с `changed_by = stored_file.uploaded_by` (`consolidation_status_history.changed_by` NOT NULL в DBML) и `comment =
«автоподтверждение документа <тип> <счёт>»`. `on_document_confirmed` получает `changed_by: uuid | None` и `comment`
от вызывающего: ручное подтверждение передаёт пользователя и `None`, автоподтверждение — `file.uploaded_by` и
комментарий; при `changed_by = None` привязка создаётся, переход пропускается с записью в лог.
Признак автоподтверждения — `CONFIRMED` и `confirmed_by IS NULL`: ручное подтверждение всегда пишет пользователя
(`DOCUMENT_EDIT` на маршруте).
### D-3. Что: документ, ставший `RECOGNIZED` при маппинге, и только он
Кандидаты — документы файла со статусом `RECOGNIZED` сразу после `map_file`. Для `BALANCE`/`INCOME` с шапкой
дополнительно — условия ручного подтверждения (D-9 Среза 3b): `period_year` и `period_quarter` определены, жёстких
нарушений §7 нет (на момент маппинга акцептов нет). Условия выносятся из `DocumentService._check_statement` в общую
функцию (одна точка правды; ручной путь продолжает отвечать 409 `PERIOD_REQUIRED`/`CHECKS_NOT_PASSED`, автоматический —
просто оставляет `RECOGNIZED`). По §9.5 D-6 `RECOGNIZED` формы уже требует период и пройденные проверки, так что
расхождение на практике — только защита от рассинхронизации правил. `DISCLOSURE`/`OTHER` (`RECOGNIZED` по D-3 Среза 3b)
подтверждаются тоже: проверять в них нечего, в версии они не привязываются (`fits` — ОСВ и формы).
Пересчёт статуса формы после правки или акцепта (`RECOGNIZED`/`NEEDS_CHECK`) автоподтверждения не запускает: после
исправлений аналитик нажимает «Подтвердить данные» (ТЗ 03_9, 10_10).
### D-4. Удаление: только автоподтверждённое и только до согласования
`PrimaryFilesService.delete` под существующими блокировками (задания → файл → документы) проверяет: если у файла есть
`CONFIRMED` документ с `confirmed_by IS NOT NULL` — 409 `FILE_HAS_CONFIRMED_DOCUMENTS` (как сейчас). Если все
`CONFIRMED` документы автоподтверждены — дальше блокируются версии ЮЛ (`lock_versions_for_entity` по всем статусам, где
есть привязки этих документов; порядок «… → документы → advisory ГК → версии» соблюдён) и проверяется, что каждая
версия с привязкой в `INITIATED`/`DOCUMENTS_PROCESSING`/`DATA_CORRECTION`; иначе 409 `FILE_HAS_CONFIRMED_DOCUMENTS` с
текстом «документ уже участвует в версии на согласовании». Затем привязки снимаются (`consolidation_document`), файл
удаляется как сейчас; `FILE_DELETE.after_value = {unbound_version_ids: [...]}` (по возрастанию; пусто — пустой список),
`before_value` — карточка файла, как сейчас. Статус версии не меняется (свод — чистая функция, пересчитается).
`FILE_SUPERSEDES_OTHERS` — без изменений.
### D-5. Блокировки
`auto_confirm` вызывается под заданием, файлом и документами маппинга; версии берёт `on_document_confirmed` через
`lock_versions_for_entity` (advisory ГК → версии `FOR UPDATE` по `id`) — тот же путь, что у ручного подтверждения.
Несколько документов файла — повторные вызовы в одной транзакции по одному ЮЛ: блокировки уже взяты, порядок не
нарушается. Документы файла уже под `FOR NO KEY UPDATE` маппинга (R-3 совместимость со вставкой привязки).
### D-6. Данные и совместимость
Миграции схемы и данных нет. Старые `RECOGNIZED` остаются и подтверждаются кнопкой. API не меняется:
`DocumentOut.confirmed_by` уже `nullable`; `POST …/confirm` для автоподтверждённого отвечает 409 `ALREADY_CONFIRMED`.
Чек-лист: ячейка с автоподтверждённым документом — `CONFIRMED` («Загружен»).
## Risks / Trade-offs
- **Неверный, но «чистый» файл сразу в своде** (например, ОСВ не того ЮЛ с совпавшим ИНН шапки). Раньше его ловил
взгляд аналитика при подтверждении. Смягчение: удаление разрешено до согласования (D-4), автозамена новой версией
файла работает как раньше; документ виден в панели и чек-листе как «Загружен».
- **Нагрузка на транзакцию маппинга** растёт на блокировку версий ЮЛ для каждого документа — то же, что делает хук
`on_documents_mapped`; в комплекте десятки документов, не тысячи.
- **Журнал**: событие `DOCUMENT_CONFIRM` без пользователя — системное, как `FILE_REPLACE`; фильтр журнала по
пользователю его не покажет.
## Migration Plan
Выкатка обычная: миграций нет. После обновления ядра новые результаты подтверждаются автоматически; уже загруженные
`RECOGNIZED` — вручную (или перезагрузкой файла).
## Open Questions
- OQ-157 (новый, docs): автоподтверждение чистых документов — решено владельцем 08.10.2026; пересматривает OQ-05
(`RECOGNIZED` остаётся статусом модели, но после маппинга на нём документ не задерживается) и уточняет OQ-110
(удаление при консолидациях — для автоподтверждённых до `DATA_CORRECTION` включительно).
## Decisions made without discussion
| № | Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|---|
| 1 | 2026-10-08 | Автоподтверждение — после автозамены и до `on_documents_mapped` в той же транзакции маппинга | замена привязки по ключу работает как при ручном подтверждении; откат целиком при ошибке | неверный порядок — лишняя/потерянная привязка, ловится тестом автозамены |
| 2 | 2026-10-08 | `DISCLOSURE`/`OTHER` тоже подтверждаются автоматически | в них нечего проверять; ТЗ 03_9 п. 4 не различает типы | расшифровку нельзя удалить без условий D-4 — те же, что у ОСВ |
| 3 | 2026-10-08 | Условия формы — общая функция с ручным подтверждением; при невыполнении документ остаётся `RECOGNIZED` без ошибки | одна точка правды; автоматический путь не должен падать | форма без периода ждёт кнопку, как раньше |
| 4 | 2026-10-08 | После правки/акцепта формы автоподтверждения нет | ТЗ: «Подтвердить данные» после исправлений | лишний клик после правки — осознанный |
| 5 | 2026-10-08 | Существующие `RECOGNIZED` не мигрируются | контур без продовых данных; миграция данных с аудитом без пользователя — лишний риск | на стенде старые документы подтверждаются кнопкой |
| 6 | 2026-10-08 | Комментарий перехода версии — «автоподтверждение документа <тип> <счёт>», описание аудита — «автоподтверждение: без замечаний» | история версии и журнал объясняют, откуда переход без клика | тексты меняются без миграции |
| 7 | 2026-10-08 | Удаление: проверка статусов версий только тех, к которым привязаны документы файла; `FILE_DELETE.after_value.unbound_version_ids` | симметрично `FILE_REPLACE.unbound_version_ids` | — |
| 8 | 2026-10-08 | Тест `form_confirmable` ломает строку 2200 (строгое соотношение), а не 2400 (соотношения с 2400 в `statement_checks.yaml` нежёсткие) | иначе тест не проверяет жёсткое нарушение | только тест |
| 9 | 2026-10-08 | Тесты автоподтверждения, маппинга и удаления берут `uploaded_by` у реального пользователя (FK `app_user`), а не случайный uuid (ошибка плана) | случайный uuid не проходит FK | нет |
| 10 | 2026-10-08 | В `tests/test_api_files_delete.py` тесты, имитирующие подтверждение пользователем (`test_confirmed_document_blocks_delete`, `test_delete_rereads_state_after_locks`), получили `confirmed_by` | иначе по правилу удаления (D-4) документ считается автоподтверждённым и удаление проходит | нет |
| 11 | 2026-10-08 | Импорт `auto_confirm` в `result_mapping._map_candidate` — локальный | цикл `documents` → `result_mapping` → `auto_confirm` → `statements` → `documents` | техдолг: вынести `form_confirmable`/`FORM_TYPES` в модуль без обратной зависимости |