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>
181 lines
13 KiB
Markdown
181 lines
13 KiB
Markdown
# Дизайн: модель данных для метрик качества первички (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-справочник для отказов |
|