15 KiB
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 в модуль без обратной зависимости |