Перенос трёх решений, делегированных брифом исполнителю и задокументированных в докстринге PrimaryFilesService._write_delete_outcome_facts, в раздел «Decisions made without discussion» по правилу CLAUDE.md о маркировке находок. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
40 KiB
Дизайн: метрики качества первички (AURORA-1759)
1. Зачем и для кого
Заказчик (пользователи АРМ в банке) сам открывает экран в веб-интерфейсе и видит, какую работу снял АРМ: доля документов, прошедших без участия аналитика или подтверждённых без правок, доля правленых, технические отказы — за период, по ГК, по типу документа и по источнику. Цифры подтверждают эффект инициативы.
Владелец проекта использует те же цифры как аргумент о качестве сервиса разбора PDF (ИС Сканирование): если они
плохие — довод за переход на другой сервис. Поэтому каждая ошибка относится к источнику: сервис разбора, наш
разбор (нормализация arm_statements, маппинг ядра, tabular-parser) или документ клиента (контрольные цифры не
сходятся в оригинале). Без такого разделения цифра «30 % правят» против сервиса не работает.
Позже метрики переедут в отдельное решение на платформе банка (там точно есть Superset, хранилище неизвестно) — перенос не должен требовать доработки ядра.
2. Цели, не-цели, анти-цели
Цели
- Факты качества по каждому документу ББ / ОФР / ОСВ, загруженному после выкладки, и по каждому техническому отказу.
- Отнесение каждой правки аналитика к источнику ошибки по сырому тексту сервиса.
- Экран: матрица «источник × причина» за период + динамика по месяцам; переход из ячейки в список документов и далее в документ (правки, комментарии, сам файл).
- Факты пригодны для Superset как есть и для переноса в другое хранилище.
- Сохранить вход для будущего эталонного прогона: сырые ответы ИС и исходные xlsx, подтверждённый результат.
Не-цели — см. proposal.md, раздел Non-goals (в т. ч. ретро-метрики, время аналитика, видимость по филиалам, сам
эталонный прогон).
Анти-цели (провал, даже если работает)
- Цифры, которые нельзя проверить: каждая ячейка матрицы раскрывается до конкретных документов.
- Вина сервиса там, где ошиблись мы (или наоборот): правило атрибуции — механическое и записанное (§5), спорные случаи уходят в «наш разбор», а не в «сервис».
- Лишняя работа аналитика: никаких отметок, анкет и кнопок ради метрик.
- Метрики, которые меняются задним числом: факт записан — не переписывается (append-only), поздние события — новые факты.
- Связка с рабочими таблицами, из-за которой перенос на платформу требует переписать ядро.
3. Ограничения
- Инварианты
../CLAUDE.md: первичка не мутируется; единственный писатель в БД — arm-core; производные не хранятся. Факты — не производные консолидации, а журнал событий (какaudit_event/ex_snapshot); метрики — чистая функция от фактов, не хранятся. - Поля — только из DBML: таблицы фактов, роль бакета, признак скана, объект права появляются в DBML v5.24 (change в
../docs) до кода. - Нормализацию ББ/ОФР делают сервисы; в ядро приходит нормализованный
result.json. «Сырой текст сервиса» в ядре — этоfinancial_statement_value.source_text,financial_statement_line.line_code_text,financial_statement_meta.header_source,osv_entry.row_value(§11.5:text— всегда сырой текст источника). Сырой JSON ИС лежит в S3raw/{job_id}.json, в БД не зарегистрирован. - Пересчёт после правки перезаписывает
needs_review,line_status, статус документа иdocument_issue(relation/soft) — поэтому состояние «как распознано» фиксируется фактом в момент приёма, до любых правок. - Удаление файла физически удаляет строки документа (
primary_files.delete) — факты не ссылаются на них FK. - Сейчас у заказчика все работают под одной учётной записью (до РАИСА/SSO, AURORA-1404).
4. Факты
Отдельная схема PostgreSQL (имя — настройка, по умолчанию dkb_metrics), миграции — общей цепочкой Alembic. Таблицы
плоские: измерения — обычные столбцы, подробности — jsonb с версией схемы (schema_version). Без FK на рабочие
таблицы; идентификаторы документов, файлов, заданий, ЮЛ, ГК — просто uuid, имена ЮЛ и ГК копируются на момент факта.
Контракт столбцов и payload — ../docs/contracts/quality-facts.schema.json. Записи только добавляются.
4.1 quality_intake_fact — документ «как распознано»
Пишется в транзакции маппинга (result_mapping._map_candidate, после map_file, до auto_confirm), по одному на
созданный документ ББ / ОФР / ОСВ (DISCLOSURE / OTHER — не пишутся).
Измерения: document_id, file_id, job_id, attempt, legal_entity_id + имя, group_id + имя (ГК ЮЛ на момент
факта; ЮЛ вне ГК — пусто), document_type, source_kind (pdf / excel), is_scan (только PDF), service_name,
service_version, statements_version (ББ/ОФР), result_schema_version, report_date (как распознано),
intake_at, intake_status (статус документа после маппинга: RECOGNIZED / NEEDS_CHECK), счётчики: строк,
значений, значений с needs_review, строк unreadable / missing, нарушений relation / soft, предупреждений
сервиса; raw_s3_key (PDF), result_s3_key, upload_s3_key.
payload: снимок «как распознано» — шапка (header_source, распознанные поля), строки (ref, row_index,
line_code, line_code_text, line_status, needs_review), значения (line ref, колонка, amount, string_value,
source_text, confidence, is_recognized, needs_review), нарушения и коды предупреждений. Для ОСВ — счётчики
строк и коды предупреждений OSV_*, без построчного снимка (правок ОСВ по суммам в ядре нет).
4.2 quality_outcome_fact — исход документа
Один на документ, пишется при первом из событий:
- подтверждение —
documents.confirm(вручную) иauto_confirm(автоматически), в той же транзакции; - удаление до подтверждения —
primary_files.delete; - замена — автозамена
FILE_REPLACE(документ вытеснен новым файлом).
Измерения: document_id (связь с 4.1), outcome (AUTO_CONFIRMED / CONFIRMED / DELETED / SUPERSEDED),
outcome_at, primary_cause (§6), счётчики правок по источнику (§5): значения, коды, добавленные строки, шапка —
service / ours / client; checks_accepted (подтверждено с акцептом нарушенных проверок), число нарушений на
момент исхода, report_date подтверждённый, confirmed_by (пользователь; для агрегатов не используется).
payload: подтверждённый результат (то же строение, что снимок приёма) и список правок с отнесением — каждая правка:
что, было (сырой текст / распознано), стало, источник, правило §5, которое сработало.
Подтверждённую ОСВ можно править (правка сбрасывает подтверждение) — повторное подтверждение пишет новый факт исхода; метрики берут последний.
Удаление файла с уже подтверждённым документом (автоподтверждённые при версии «в работе», OQ-157) и автозамена
подтверждённого документа пишут новый факт DELETED_AFTER_CONFIRM / SUPERSEDED_AFTER_CONFIRM с исходом, который
был до него. Метрики берут последний факт: такой документ уходит из своей ячейки в отдельный столбец «удалён / заменён
после подтверждения» (решение владельца 27.10.2026) — в доли не входит, но виден, и из него раскрывается список с
прежним исходом.
4.3 quality_failure_fact — технический отказ
Пишется при переходе задания распознавания в error / timeout / dlq (recognition_exchange) и при неповторяемой
ошибке маппинга (MappingFatal, файл → ERROR). Измерения: job_id, file_id, attempt, legal_entity_id,
group_id, source_kind, is_scan, service_name, failure_kind (SERVICE_ERROR / TIMEOUT / NO_ACK /
MAPPING), error_code, failed_at. Повторная обработка (FILE_REPROCESS) создаёт новое задание: отказ первой
попытки остаётся фактом, документы удачной попытки получают свои 4.1 / 4.2.
5. Отнесение правки к источнику
Правка — отличие подтверждённого результата от снимка приёма (4.1). Правило одно: верный ответ есть в сыром тексте сервиса — ошибка наша; нет — ошибка сервиса. Для Excel сервис разбора — наш tabular-parser, поэтому у Excel любая ошибка распознавания — «наш разбор».
| Что правили | «Наш разбор», если | Иначе |
|---|---|---|
| Сумма | цифры подтверждённой суммы (без знака) совпадают с цифрами сырого текста после замены букв-двойников §11.3 — сервис прочитал верно, а мы неверно разобрали (знак, скобки, разряды, мусор по краям) | сервис |
| Код строки | подтверждённый код есть среди кодов, извлекаемых из line_code_text (в т. ч. одна из частей склейки 1250\n1260) |
сервис |
Добавленная строка (row_index IS NULL) |
её код есть в наборе кодов сырого ответа (для PDF — четырёхзначные токены, стоящие отдельной строкой в тексте ячеек raw/{job_id}.json; набор извлекается при приёме, вне транзакции, рядом с чтением result.json, и кладётся в снимок 4.1; для Excel — всегда «наш разбор») |
сервис |
| Шапка: период, ИНН, единицы, наименование | подтверждённое значение извлекается из header_source[*].text |
сервис |
| Дата колонки | подтверждённая дата извлекается из подписи колонки (label) |
сервис |
| Период / счёт ОСВ | всегда «наш разбор» (Excel) | — |
Документ клиента — не правка, а исход: подтверждён вручную с акцептом нарушенных жёстких проверок
(checks_accepted), и нарушенное соотношение не задето правками. Мягкие нарушения (в т. ч. «в пределах округления» и
межформенная 1370↔2400) не учитываются — это в основном шум округления, и подтверждение они не блокируют.
Автоподтверждение жёстких нарушений иметь не может. Если правки чинят часть нарушений, а остальные акцептованы — у
документа и правки, и «документ клиента».
Сравнение — чистая функция domain/quality_attribution.py (§14 05-domain-rules.md), без БД. Разбор сырого текста —
публичным API arm_statements (fix_lookalikes, parse_amount, parse_code, parse_date, default_rules), тем же
кодом, что разбирал сервис; сам пакет не меняется (и не зависит от того, слита ли AURORA-1677: имя модуля то же).
6. Причина документа (ячейка матрицы)
Каждый документ с исходом попадает ровно в одну ячейку — по первому сработавшему правилу:
- исход
DELETED/SUPERSEDED→ «удалён / заменён до подтверждения»;DELETED_AFTER_CONFIRM/SUPERSEDED_AFTER_CONFIRM→ «удалён / заменён после подтверждения» (оба столбца в доли не входят); - есть правки «сервис» → ошибка сервиса;
- есть правки «наш разбор» → наш разбор;
- подтверждён с акцептом нарушений (§5) → документ клиента;
AUTO_CONFIRMED→ без участия;CONFIRMEDбез правок → как есть (распознано верно, но потребовалась проверка аналитика).
Документы без исхода — в работе (отдельный столбец, в доли не входит). Технические отказы — отдельный столбец из 4.3, считаются по заданиям. Все счётчики правок по источнику остаются в факте, поэтому глубже матрицы видно и «сервис + наш разбор» в одном документе.
Знаменатель долей — документы с исходом AUTO_CONFIRMED / CONFIRMED. Технические отказы считаются по файлам
(документов у них нет, тип неизвестен): отдельный столбец со своей долей «отказавших файлов от загруженных файлов того
же источника»; фильтр по типу документа на него не действует (на экране — пометка). Рядом с матрицей — счётчик
«уникальных отчётов» (ЮЛ + подтверждённая отчётная дата + тип), чтобы было видно, сколько повторных загрузок; это
счётчик, а не режим матрицы.
7. Расчёт, API, экран
- Расчёт — чистая функция
domain/quality_metrics.pyот фактов: фильтры (период по дате приёма, ГК, ЮЛ, тип, источник, скан/цифровой), матрица и ряд по месяцам. В БД не пишется. - API (
/api/v1/quality-metrics, право §8): матрица за период; ряд по месяцам по выбранной причине; список документов ячейки (документ, ЮЛ, дата, причина, счётчики правок, ссылка на документ — если он ещё существует); выгрузка фактов за период (CSV / JSON) для переноса. - Экран «Качество первички» (
web/, пункт меню по праву): фильтры, матрица «источник × причина», динамика по месяцам, переход ячейка → список документов → документ (существующий экран проверки распознавания с правками и комментариями). Точная раскладка — при реализации (владелец: «главное наличие данных»).
8. Доступ
Новый объект права QUALITY_METRICS в permission_object, право QUALITY_METRICS_VIEW; seed выдаёт его всем ролям.
Позже права сужаются данными (role_permission), без кода. Видимость по филиалу / ГК не применяется. Список
документов показывает confirmed_by, агрегаты — без фамилий.
Superset читает схему фактов отдельной ролью БД только на чтение (создаёт платформа; в репозитории — описание в
03-architecture.md).
9. Сырые ответы и признак скана
- Сырой ответ ИС. Ядро уже получает
raw_s3_keyв сообщении результата — регистрирует объект вs3_objectс новой ролью бакетаrawи ссылкой изrecognition_job. Удаление файла не ставитexpires_atсырому ответу иuploads/, на которые ссылаются факты: они нужны эталону. Срок хранения — вопрос платформы (OQ-179), фиксируется в07-open-questions.md. - Скан / цифровой PDF определяется при загрузке по наличию текстового слоя (шрифтов в PDF) и хранится в
stored_file. PDF со скрытым OCR-слоем считается цифровым — допущение, записывается в §14.
10. Что решено владельцем (27.10.2026)
- Метрики — для заказчика (эффект инициативы) и как аргумент о сервисе разбора PDF.
- Охват — PDF и Excel; разрезы — качество сервиса, нашего разбора, документа клиента, технические отказы.
- Атрибуция по сырому тексту сервиса + сохранить данные для эталонного прогона («оставляем»).
- Экран в веб-интерфейсе, заказчик открывает сам; матрица и динамика; раскладка — при реализации.
- Только данные после выкладки.
- Без счётчика «есть, но не разбирается».
- Время аналитика — собирать в будущем (визиты с привязкой к документу, без нагрузки на аналитика; фамилии — для разбора, агрегаты — без них); в этом раунде — только технические метрики.
- Отдельное право; сейчас видят все.
- Документ, удалённый или заменённый после подтверждения, — в отдельный столбец «удалён / заменён после подтверждения» (не остаётся в «без участия» и не считается ошибкой).
- Перенос на платформу без доработки ядра; там есть Superset.
11. Оставлено исполнителю
Имена таблиц и столбцов (в рамках DBML v5.24), формат payload, раскладка экрана, способ определения текстового слоя
PDF, точный вид выгрузки, разбиение на задачи.
Decisions made without discussion
| Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|
| 27.10.2026 | Сам эталонный прогон — не в этом change; здесь только сохранение входа (сырые ответы, xlsx, подтверждённый результат) | прогон требует адаптера шага 1 сервисов (он в scan-gateway / tabular-parser) и отдельного инструмента сравнения; без данных он невозможен, а данные копятся только с выкладки | прогон приедет позже; данные для него уже будут |
| 27.10.2026 | Правки шапки (период, ИНН, единицы, наименование) — ошибка распознавания, атрибуция по header_source |
шапку тоже распознаёт сервис | неверная классификация части правок |
| 27.10.2026 | Документы без исхода — столбец «в работе», в доли не входят | неподтверждённый документ ещё не оценён | — |
| 27.10.2026 | Повторная обработка: отказ первой попытки — технический отказ; документ — по итоговой попытке | отказ был, но документ в итоге получен | двойной учёт файла в отказах и в исходах — виден по file_id |
| 27.10.2026 | Одна ячейка на документ по приоритету §6 (сервис > наш разбор > документ клиента > без участия > как есть) | матрица должна суммироваться в 100 %; приоритет «сервис» — потому что правку сервиса аналитик делает руками всегда | документ с ошибками обеих сторон виден только в «сервисе»; счётчики в факте сохраняют обе |
| 27.10.2026 | Спорное — в «наш разбор» (верный ответ есть в сыром тексте, напр. склейка 1250\n1260) |
анти-цель: не приписывать сервису наши недоработки | метрика сервиса выглядит лучше, чем есть |
| 27.10.2026 | Отдельная схема БД dkb_metrics под факты, плоские таблицы + jsonb |
Superset подключается к схеме ролью только на чтение; перенос — одна схема с контрактом | лишняя сложность миграций со второй схемой |
| 27.10.2026 | Признак скана — по текстовому слою PDF в ядре при загрузке, без изменения scan-gateway | ИС такой признак не отдаёт; ядро видит файл при загрузке | PDF со скрытым OCR-слоем — «цифровой» |
| 27.10.2026 | Период метрик — по дате приёма результата | исход приходит позже, документ остаётся в своей когорте | недавние периоды долго «в работе» |
| 27.10.2026 | Право QUALITY_METRICS_VIEW всем ролям в seed |
владелец: сейчас видят все, потом — отдельная роль | — |
| 27.10.2026 | «Документ клиента» — только ручное подтверждение с акцептом жёстких нарушений; мягкие и 1370↔2400 не учитываются (builder check) | мягкие — шум округления и не блокируют подтверждение; иначе автоподтверждённые уйдут из «без участия» | реальные дефекты отчётности в пределах мягких соотношений не видны |
| 27.10.2026 | Код добавленной строки ищется среди четырёхзначных токенов сырого ответа ИС, извлечённых при приёме, без переноса адаптера шага 1 в ядро (builder check) | адаптер живёт в scan-gateway; совпадение с суммой даёт «наш разбор» — по правилу «спорное — нам» | метрика сервиса чуть лучше реальной на добавленных строках |
| 27.10.2026 | Технические отказы — по файлам, своей долей; фильтр по типу документа на них не действует; «уникальные отчёты» — счётчик, не режим (builder check) | у отказавшего файла нет документов и типа; смешивать файлы и документы в одном знаменателе нельзя | доля отказов не складывается с долями матрицы в 100 % |
| 27.10.2026 | ОСВ — без построчного снимка, только статусы, предупреждения, отказы, правки периода/счёта | правок сумм ОСВ в ядре нет | при появлении правок ОСВ — доработка снимка |
| 05.11.2026 | Task 1: api/scripts/dbml_parity.py (схема таблицы — через явный allowlist METRICS_TABLES, не единственная константа SCHEMA), api/scripts/check_dbml_vs_orm.py (фиксирует QUALITY_METRICS_SCHEMA=dkb_metrics в окружении проверки, как уже делает для DB_SCHEMA) и api/alembic/env.py (include_name признаёт обе схемы) — доработаны под вторую схему; план задачи 1 предполагал, что check_dbml_vs_orm.py «скорее всего» не потребует правок |
без этого make dbml-check безусловно падал бы на quality_intake_fact: схема: ожидалось dkb / фактически dkb_metrics» для всех трёх таблиц фактов; это ровно тот структурный сюрприз, о котором бриф просил не обходить тихо, а чинить или сообщать — чинить оказалось дешевле и находится в рамках задачи (make dbml-check` — приёмный критерий шага 7) |
без правки вторая схема логически невозможна — любой PR с dkb_metrics падает на CI/проверке |
| 05.11.2026 | Три новых ORM-класса (QualityIntakeFact/OutcomeFact/FailureFact) — на ОБЩЕМ Base/Base.metadata (как и всё остальное), схема переопределена точечно через __table_args__ = {"schema": METRICS_SCHEMA}; второй DeclarativeBase НЕ заводился |
ровно второй вариант, предложенный самим брифом («…или аналог существующего паттерна»); так Base.metadata остаётся единой точкой для check_dbml_vs_orm.py/alembic/env.py/create_all — отдельная metadata потребовала бы протащить её через все три места отдельно |
при введении третьей схемы в будущем придётся повторить тот же приём, а не скопировать общий механизм |
| 05.11.2026 | Три новых enum-типа (quality_outcome, quality_cause, quality_failure_kind) — значения живут в схеме SCHEMA (dkb), НЕ в METRICS_SCHEMA, хотя используют их только таблицы dkb_metrics |
pg_enum() (src/models/enums.py) без правок — единственный источник схемы enum'а; разводить enum'ы по двум схемам потребовало бы параметра схемы в фабрике без текущей пользы (Superset читает только таблицы, не типы) |
таблица dkb_metrics зависит от типов в dkb; перенос схемы метрик на другой сервер БД без dkb невозможен без правки (сейчас design §10 этого не требует) |
| 05.11.2026 | Откат миграции 0012 НЕ удаляет схему dkb_metrics (оставляет её пустой), симметрично тому, что SCHEMA (dkb) никогда не удаляется в env.py/0001 |
тест test_upgrade_into_preprovisioned_schema_without_database_create (api/tests/test_migrations.py) показал конкретный отказ: роль без CREATE на базу не могла бы создать dkb_metrics заново при повторном upgrade head, если бы платформа выдавала схему готовой, а откат её удалял; симметрия с dkb — тот же риск, то же решение |
если отладочный откат до base оставляет пустую схему dkb_metrics — незначительный мусор, не ошибка |
| 05.11.2026 | В план (docs/superpowers/plans/2026-11-05-quality-metrics-arm-core.md) добавлена строка **Ветка:** (feat/190-ocr-metrics, worktree, issue, Jira) — без неё хук require-plan.sh блокировал любую правку в api/src/**, так как не находил имя ветки ни в одном файле docs/superpowers/plans/*.md |
во всех остальных активных планах эта строка есть (сверено на 2026-10-28-corrections-table-ui.md); её отсутствие — пропуск на этапе написания плана, не содержательное решение |
минимальный — чисто метаданные плана |
| 05.11.2026 | Коммит задачи 1 включает больше файлов, чем перечислено в брифе (Makefile, README.md, api/.env.example, api/.env.test, api/alembic/env.py, api/scripts/check_dbml_vs_orm.py, api/scripts/dbml_parity.py, api/src/models/base.py, api/src/models/__init__.py, восемь файлов api/tests/test_*.py) |
все они требуют правки для согласованности версии v5.25 (имя DBML-файла в Makefile/README, HEAD-ревизия и допущение «всё в dkb» в тестах) или для поддержки второй схемы; список git add в брифе соответствовал более узкому ожидаемому дифу до того, как выяснилось, что check_dbml_vs_orm.py и тесты миграций не знают о второй схеме |
коммит по списку брифа оставил бы make test/make lint/make dbml-check красными — хуже, чем более широкий, но согласованный коммит |
| 05.11.2026 | api/contracts/quality-facts.schema.json НЕ скопирован из ../docs/contracts/ в Task 1 (test_contract.py::test_vendored_contracts_are_identical_to_docs остаётся красным на этом файле) — копирование отложено на задачу, которая реально строит payload по этой схеме |
файл уже есть в ../docs/contracts/ (сиблинг-репозиторий arm-ka-docs, работа по нему уже слита туда), а в api/contracts/ копии нет и несовпадение чисто механическое; копия сейчас была бы мёртвой — Task 1 (схема/ORM/миграция) не читает и не пишет payload, валидация полезной нагрузки — логика, не схема БД, и не входит в её рамки |
следующая задача может не заметить, что это отложено осознанно, и либо повторно принять то же решение не зная о нём, либо оставить контракт невендоренным навсегда |
| 06.11.2026 | Task 7 (CONFIRMED): нет сырого текста для сравнения у правки суммы/кода/шапки (source_text/line_code_text/header_source[field].text = None) — атрибуция «наш разбор» (ours), не пропуск правки. Уточняет направление решения 27.10.2026 «Спорное — в наш разбор» на случай «данных для сравнения нет вообще» — это НЕ тот же случай, что пример 1250\n1260 в исходном решении (там текст есть, но двусмысленный); attribute_* (Task 3) типизированы на str, случай отсутствия текста не предусматривают. Этим же решением подтверждается (не меняется) уже принятое 27.10.2026 направление для добавленной строки PDF (строка «Код добавленной строки ищется среди четырёхзначных токенов…», та же дата) — черновая реализация задачи 7 свела этот случай к вызову attribute_added_row(code, raw_line_codes=(), is_excel=False), который из-за пустого raw_line_codes всегда возвращает «сервис», что противоречило уже принятому там направлению «совпадение с суммой даёт наш разбор»; исправлено прямой атрибуцией «наш разбор» для PDF-добавленных строк без вызова attribute_added_row (сырых токенов всё равно нет, инфраструктура их извлечения не построена — тот же пробел, что в строке 27.10.2026) |
анти-цель §2/§5 («вина сервиса там, где ошиблись мы; спорные случаи — в наш разбор, не в сервис») распространяется и на случай, когда данных для сравнения нет вовсе: отсутствие доказательства ошибки сервиса не есть доказательство ошибки сервиса | если отсутствие сырого текста окажется частым, а не редким краевым случаем, OUR_ERROR может быть систематически завышен, а SERVICE_ERROR — недосчитан; метрика ослабнет как рычаг давления на поставщика распознавания (прямая цель §1), хотя остаётся безопасным дефолтом по анти-цели §2 |
| 06.11.2026 | Task 8 (DELETED/DELETED_AFTER_CONFIRM): (1) документ в статусе, отличном от CONFIRMED/RECOGNIZED/NEEDS_CHECK (практически только ERROR), при удалении файла НЕ получает факт исхода — у него никогда не было содержательного состояния «как распознано» для фиксации (нет формы/строк, не проходил ни через AUTO_CONFIRMED, ни через intake-факт); (2) issue_count_at_outcome вычисляется как counts.error + counts.info из уже посчитанного агрегата issue_counts_by_document вместо отдельного запроса — тот же смысл «все нарушения документа без фильтра», что len(issues) в Task 6/7, дешевле на один запрос; (3) payload для DELETED*-фактов — единый минимальный вызов build_outcome_payload(document, fixes=()) без дополнительных запросов к строкам/значениям формы или ОСВ (эти строки всё равно будут физически удалены этим же вызовом delete, повторный их снимок здесь экономически не оправдан — а в матрицу §6 эти факты и не попадают, доступны только как «удалён/заменён», drill-down уже видит прежний исход через более ранний intake/outcome факт того же document_id) |
бриф задачи 8 явно делегировал все три пункта исполнителю («если неоднозначно — зафиксировать выбор, не угадывать»); исполнитель выбрал и задокументировал в докстрингах кода, перенесено сюда контроллером по правилу маркировки находок (решение агента без обсуждения с человеком — в design.md) | (1): при появлении у ERROR-документов содержательного состояния до удаления (гипотетически) — нужно будет расширить список статусов; (2)/(3): минимальный риск — агрегаты эквивалентны, а payload не используется для атрибуции (там primary_cause=None, вне матрицы) |