Andrey Epifancev 2676dfd731 docs(#202): открытые вопросы, architecture и openspec-change — метрики качества первички
OQ-179 (срок хранения raw/{job_id}.json), роль raw и dkb_metrics в 03-architecture.md,
openspec proposal/design/specs, план реализации (AURORA-1759).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-11-05 14:11:16 +04:00

13 KiB
Raw Blame History

Дизайн: модель данных для метрик качества первички (DBML v5.25)

Зачем, для кого и все решения владельца (27.10.2026, подтверждено 05.11.2026) — в ../../arm-core/openspec/changes/quality-metrics/design.md (разделы 1–3, 10). Здесь — только то, что входит в DBML v5.25 и производные документы этого репозитория: точные имена, типы, перечисления.

1. Новые и изменённые объекты модели

1.1 Enum s3_bucket_role — добавить значение

raw — сырой ответ ИС Сканирование, raw/{job_id}.json. Не получает expires_at при удалении файла/uploads/, на которые ссылаются факты (вход для эталонного прогона).

1.2 Enum permission_object — добавить значение

QUALITY_METRICS — объект права экрана «Качество первички» (действие VIEW; seed права QUALITY_METRICS_VIEW — в arm-core, не в DBML).

1.3 Новые enum для фактов

Enum quality_pipeline_is_scan — не нужен: используется существующий recognition_pipeline (tabular=Excel, image=PDF)
  и новый boolean stored_file.is_scan (NULL для не-PDF).

Enum quality_outcome {
  AUTO_CONFIRMED
  CONFIRMED
  DELETED
  SUPERSEDED
  DELETED_AFTER_CONFIRM
  SUPERSEDED_AFTER_CONFIRM
}

Enum quality_cause {
  SERVICE_ERROR        [note: 'есть правка, не найденная в сыром тексте сервиса']
  OUR_ERROR            [note: 'есть правка, найденная в сыром тексте сервиса (наш разбор ошибся)']
  CLIENT_DOCUMENT      [note: 'подтверждён с акцептом нарушенных жёстких проверок, без несписанных правок на это нарушение']
  WITHOUT_PARTICIPATION [note: 'AUTO_CONFIRMED']
  AS_IS                [note: 'CONFIRMED без правок и без акцепта']
}
// NULL у исходов DELETED* / SUPERSEDED* (не входят в матрицу)

Enum quality_failure_kind {
  SERVICE_ERROR [note: 'recognition_job.status = error']
  TIMEOUT
  NO_ACK        [note: 'dlq — redelivery после max_deliver']
  MAPPING       [note: 'MappingFatal, файл → ERROR']
}

Приоритет значений quality_cause при вычислении (несколько правил могут совпасть) — пункт §14.3 05-domain-rules.md: SERVICE_ERROR > OUR_ERROR > CLIENT_DOCUMENT > WITHOUT_PARTICIPATION > AS_IS.

1.4 Таблица quality_intake_fact — документ «как распознано»

Отдельная схема dkb_metrics (имя — настройка). Без FK на рабочие таблицы.

Столбец Тип Прим.
id uuid [pk]
document_id uuid [not null] без FK; один факт на документ
file_id uuid [not null]
job_id uuid [not null]
attempt smallint [not null]
legal_entity_id uuid [not null]
legal_entity_name varchar [not null] копия имени на момент факта
group_id uuid NULL — ЮЛ вне ГК
group_name varchar
document_type document_type [not null] BALANCE / INCOME / OSV (DISCLOSURE/OTHER не пишут факт)
pipeline recognition_pipeline [not null] tabular = Excel, image = PDF
is_scan boolean только pipeline = image
service_name varchar
service_version varchar
statements_version varchar
result_schema_version varchar
report_date date как распознано
intake_at timestamptz [not null]
intake_status document_status [not null] RECOGNIZED / NEEDS_CHECK
line_count int
value_count int
value_needs_review_count int
line_unreadable_count int
line_missing_count int
issue_relation_count int
issue_soft_count int
warning_count int
raw_s3_key varchar PDF; NULL для Excel
result_s3_key varchar
upload_s3_key varchar
payload jsonb [not null] снимок — §2
payload_schema_version varchar схема как в ex_snapshot_item.payload_schema_version; формат «1», «1.1»

Индексы: (document_id) unique; (legal_entity_id, intake_at); (group_id, intake_at).

1.5 Таблица quality_outcome_fact — исход документа

Столбец Тип Прим.
id uuid [pk]
document_id uuid [not null] связь с 1.4 по значению, без FK
outcome quality_outcome [not null]
outcome_at timestamptz [not null]
primary_cause quality_cause NULL для DELETED*/SUPERSEDED*
fixes_value_service int [not null, default: 0]
fixes_value_ours int [not null, default: 0]
fixes_code_service int [not null, default: 0]
fixes_code_ours int [not null, default: 0]
fixes_added_row_service int [not null, default: 0]
fixes_added_row_ours int [not null, default: 0]
fixes_header_service int [not null, default: 0]
fixes_header_ours int [not null, default: 0]
fixes_osv_period_account int [not null, default: 0] всегда «наш разбор» (Excel)
checks_accepted boolean [not null, default: false] подтверждён с акцептом жёстких нарушений
issue_count_at_outcome int [not null, default: 0]
report_date date подтверждённая
confirmed_by uuid для агрегатов не используется
payload jsonb [not null] подтверждённый результат + список правок — §2
payload_schema_version varchar схема как в ex_snapshot_item.payload_schema_version; формат «1», «1.1»

Индексы: (document_id, outcome_at); (outcome, outcome_at). Не unique на document_id — повторное подтверждение ОСВ и исходы after-confirm пишут новый факт; метрики берут последний по outcome_at.

1.6 Таблица quality_failure_fact — технический отказ

Столбец Тип Прим.
id uuid [pk]
job_id uuid [not null]
file_id uuid [not null]
attempt smallint [not null]
legal_entity_id uuid [not null]
legal_entity_name varchar [not null]
group_id uuid
group_name varchar
pipeline recognition_pipeline [not null]
is_scan boolean
service_name varchar
failure_kind quality_failure_kind [not null]
error_code varchar
failed_at timestamptz [not null]

Нет payload/payload_schema_version — все поля плоские, в отличие от intake/outcome (§2).

Индексы: (job_id) unique; (file_id, attempt).

1.7 Изменения существующих таблиц

  • stored_file.is_scan boolean — NULL для не-PDF; PDF — определяется при загрузке по текстовому слою (ASSUMPTION §14: скрытый OCR-слой считается цифровым).
  • recognition_job.raw_s3_object_id uuid [ref: > s3_object.id] — заменяет прежнюю заметку «сырой ответ в БД не регистрируется» (v5.13); заполняется при приёме результата image pipeline.

2. Контракт docs/contracts/quality-facts.schema.json

JSON Schema (Draft 2020-12), три подсхемы с $id intake-payload, outcome-payload, failure-fact (у failure payload нет — все поля плоские). Описывает только payload интейка и исхода:

  • intake-payload: header (header_source, распознанные поля шапки), lines[] (ref, row_index, line_code, line_code_text, line_status, needs_review), values[] (line_ref, column, amount, string_value, source_text, confidence, is_recognized, needs_review), issues[] (код, уровень), raw_line_codes[] (только PDF — четырёхзначные токены сырого ответа, см. §5 arm-core/.../design.md); для OSV — osv_warnings[] и счётчики строк вместо lines/values.
  • outcome-payload: тот же header/lines/values, что и intake (подтверждённое состояние), плюс fixes[] (field, before_raw, before_recognized, after, source, rule).

Валидация. scripts/validate_contracts.py не расширяется этим change: он жёстко завязан на три сообщения NATS-контракта (request-/result-message-/result- префиксы примеров). Payload фактов не ходит по NATS и не исполняет формат идемпотентности — расширение скрипта под него добавило бы ветвление без необходимости. Схема проверяется отдельным тестом в arm-core (где и формируется payload), а не make check здесь. Решение записано, так как расходится с общим правилом «каждый контракт в contracts/ валидируется этим скриптом».

3. §14 05-domain-rules.md — состав раздела

  1. Какие документы получают факты (BALANCE/INCOME/OSV, не DISCLOSURE/OTHER).
  2. Правило атрибуции правки «сервис / наш разбор» (таблица из arm-core/.../design.md §5) — переносится как есть, источник правды один (здесь — табличная форма для ссылок из кода).
  3. Правило «документ клиента» (ручное подтверждение с акцептом жёстких нарушений, мягкие и 1370↔2400 не считаются).
  4. Приоритет quality_cause (§1.3) и знаменатель долей матрицы.
  5. Признак скана — определение по текстовому слою PDF, допущение о скрытом OCR-слое.
  6. Технические отказы считаются по файлам, не по документам; FILE_REPROCESS — первая попытка остаётся отказом.

Decisions made without discussion

Дата Решение Обоснование Цена ошибки
05.11.2026 Имена таблиц/столбцов/enum — как в разделе 1 (DBML v5.25) исполнитель решает в рамках design arm-core §11 («оставлено исполнителю») переименование потребует новой мажорной версии DBML
05.11.2026 quality_cause — отдельный enum-столбец (денормализация §6 вместо вычисления на лету при каждом запросе матрицы) факт append-only, пересчёт не нужен; упрощает API метрик (GROUP BY primary_cause) расхождение между сохранённым primary_cause и правилом §14, если правило изменится — старые факты не пересчитываются (ожидаемо для append-only)
05.11.2026 Атрибуция правок — 2 источника (service/ours), не 3: «документ клиента» — свойство исхода, не правки (см. §5 исходного design: таблица даёт только «сервис» или «наш разбор») в design arm-core таблица §5 бинарна; «документ клиента» в §6 — отдельное правило по checks_accepted лишний enum-источник правки, если окажется нужен позже
05.11.2026 quality-facts.schema.json не подключается к scripts/validate_contracts.py скрипт — для NATS wire-контракта с дедупом/версией сообщений; payload фактов — внутренний формат arm-core, не обмен между сервисами изменение формата payload не поймает make check в этом репозитории; ловит тест arm-core
05.11.2026 legal_entity_name/group_name добавлены и в quality_failure_fact, хотя design arm-core §4.3 их не перечислял общее правило §4 design arm-core («имена ЮЛ и ГК копируются на момент факта») относится ко всем фактам; итемized список там был неполным лишние 2 столбца, если экран всё же джойнит live-справочник для отказов