diff --git a/context/05-domain-rules.md b/context/05-domain-rules.md index a483672..73cfd14 100644 --- a/context/05-domain-rules.md +++ b/context/05-domain-rules.md @@ -2072,3 +2072,78 @@ Excel — §11.2, раскладка — §11.3, строки — §11.4, зна `43b4b80`. Ссылки «§12 п. N» в старых документах: п. 1 → §11.2 п. 1; п. 2 → §11.3 п. 1; п. 3 → §11.3 п. 7; п. 4 → §11.3 п. 3, п. 6; п. 5 → §11.2 п. 3, §11.4, §11.5; п. 6 → §11.2 п. 2; п. 7 → §7, §11.8; п. 8 → §11.7 п. 1; п. 9 → §11.2 п. 4; п. 10 → §11.9; п. 11 → §11.11. + +--- + +## §14. Метрики качества первички (AURORA-1759; модель — `model/data-model-v5.25.dbml`, таблицы `quality_*_fact`) + +Полное WHY, кому и зачем — `../arm-core/openspec/changes/quality-metrics/design.md` §1-3, §10 (решения владельца +27.10.2026, подтверждено 05.11.2026). Здесь — только алгоритм подсчёта и атрибуции, на который ссылается код ядра +(`§14.N` в докстринге). + +### §14.1. Какие документы считаются + +Факт `quality_intake_fact`/`quality_outcome_fact` пишется на документ типа `BALANCE`, `INCOME`, `OSV`. +`DISCLOSURE`/`OTHER` факт не получают (это расшифровки и прочие документы — не форма с правилами подсчёта). + +### §14.2. Атрибуция правки (сервис / наш разбор) + +Правка — отличие подтверждённого результата от снимка приёма (`quality_intake_fact.payload`). Правило одно: **верный +ответ есть в сыром тексте сервиса — ошибка наша; нет — ошибка сервиса.** Для Excel сервис разбора — наш +tabular-parser, поэтому у Excel любая ошибка распознавания — «наш разбор» (источник правды — design arm-core#190 §5): + +| Что правили | «Наш разбор», если | Иначе | +|---|---|---| +| Сумма | цифры подтверждённой суммы (без знака) совпадают с цифрами сырого текста после замены букв-двойников §11.3 — сервис прочитал верно, а мы неверно разобрали (знак, скобки, разряды, мусор по краям) | сервис | +| Код строки | подтверждённый код есть среди кодов, извлекаемых из `line_code_text` (в т. ч. одна из частей склейки `1250\n1260`) | сервис | +| Добавленная строка (`row_index IS NULL`) | её код есть в наборе кодов сырого ответа (для PDF — четырёхзначные токены, стоящие отдельной строкой в тексте ячеек `raw/{job_id}.json`, извлечённые при приёме; для Excel — всегда «наш разбор») | сервис | +| Шапка: период, ИНН, единицы, наименование | подтверждённое значение извлекается из `header_source[*].text` | сервис | +| Дата колонки | подтверждённая дата извлекается из подписи колонки (`label`) | сервис | +| Период / счёт ОСВ | всегда «наш разбор» (Excel) | — | + +Сравнение — чистая функция (без БД), тем же кодом, что разбирал сервис (`arm_statements`: `fix_lookalikes`, +`parse_amount`, `parse_code`, `parse_date`, `default_rules`). Только два источника правки — «сервис» и «наш разбор»; +«документ клиента» (§14.4) — свойство исхода, не правки. + +### §14.3. Причина документа (`quality_outcome_fact.primary_cause`) + +Каждый документ с исходом попадает ровно в одну ячейку матрицы, по первому сработавшему правилу (приоритет +убывает сверху вниз): + +1. `outcome ∈ {DELETED, SUPERSEDED, DELETED_AFTER_CONFIRM, SUPERSEDED_AFTER_CONFIRM}` → `primary_cause = NULL`, + отдельный столбец «удалён/заменён (до/после подтверждения)» — в доли матрицы не входит; +2. есть правки «сервис» (§14.2) → `SERVICE_ERROR`; +3. есть правки «наш разбор» → `OUR_ERROR`; +4. подтверждён с акцептом нарушений (§14.4) → `CLIENT_DOCUMENT`; +5. `outcome = AUTO_CONFIRMED` → `WITHOUT_PARTICIPATION`; +6. `outcome = CONFIRMED` без правок → `AS_IS`. + +Знаменатель долей — документы с `outcome ∈ {AUTO_CONFIRMED, CONFIRMED}`. Документы без исхода (нет +`quality_outcome_fact`) — отдельный столбец «в работе», в доли не входит. + +### §14.4. «Документ клиента» + +Не правка, а исход: подтверждён **вручную** (не `AUTO_CONFIRMED`) с акцептом нарушенных **жёстких** проверок §7 +(`checks_accepted = true`), и нарушенное соотношение не закрыто правками. Мягкие нарушения (в т. ч. «в пределах +округления» и межформенная 1370↔2400) не учитываются — подтверждение они не блокируют и в основном шум округления. +Если часть нарушений закрыта правками, а остальные акцептованы — у документа одновременно есть и правки (§14.2), и +`CLIENT_DOCUMENT`; приоритет §14.3 отдаёт ячейку правкам (`SERVICE_ERROR`/`OUR_ERROR` стоят выше `CLIENT_DOCUMENT`). + +### §14.5. Технические отказы + +`quality_failure_fact` пишется при переходе `recognition_job.status` в `error`/`timeout`/`dlq` и при неповторяемой +ошибке маппинга (`MappingFatal`, файл → `ERROR`). Считаются **по файлам**, не по документам (у отказавшего файла +документов и типа нет): знаменатель — «отказавшие файлы от загруженных файлов того же `pipeline`», отдельная доля, +не складывается с долями §14.3 в 100%; фильтр по типу документа на отказы не действует. + +`FILE_REPROCESS` (повторная обработка файла, новое `recognition_job` с `attempt + 1`): отказ первой попытки остаётся +фактом `quality_failure_fact`, документы успешной попытки получают свои `quality_intake_fact`/`quality_outcome_fact` +по новому `job_id`/`attempt` — один файл может быть и в отказах, и в исходах (виден по `file_id`), это ожидаемо, не +двойной учёт одного события. + +### §14.6. Признак скана (`stored_file.is_scan`) + +Определяется при загрузке PDF по наличию текстового слоя (шрифтов в PDF). ASSUMPTION: PDF со скрытым OCR-слоем +считается цифровым (`is_scan = false`) — слой есть, но получен постобработкой, а не исходно цифровым документом; +различить программно эти два случая без эвристик за пределами среза не планируется. Для не-PDF (`pipeline = tabular`) +`is_scan = NULL`.