arm-ka-docs/docs/superpowers/plans/2026-11-05-quality-metrics-dbml.md
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

328 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Метрики качества первички — модель данных (DBML v5.25) Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Добавить в модель данных DBML v5.25 три таблицы фактов качества распознавания первички, связанные
перечисления и столбцы, контракт payload и правило подсчёта/атрибуции в `05-domain-rules.md` §14 — так, чтобы
`arm-core` могло написать по ним миграцию и ORM без дальнейших решений об именах или типах.
**Architecture:** Чисто документный change в `arm-ka-docs`: новая версия DBML (копия v5.24 + диффы), её
человекочитаемое описание (`entities-v5.25.md`), алгоритм (`05-domain-rules.md` §14), контракт payload
(JSON Schema) и обновление диаграмм/гида. Данных и кода здесь нет — читатель этого change — исполнитель
`arm-core/openspec/changes/quality-metrics`.
**Tech Stack:** DBML (dbdiagram.io синтаксис), Markdown, JSON Schema Draft 2020-12, Python (`scripts/gen_model_diagrams.py`,
`scripts/validate_contracts.py`), `make` (`diagrams`, `check`), Node/npx (`dbml-renderer`, `resvg`) — только для диаграмм.
**Spec:** `openspec/changes/quality-metrics/{proposal,design}.md` (этот репозиторий); контекст решений и WHY —
`../arm-core/openspec/changes/quality-metrics/design.md` (не копируется, только читается).
## Global Constraints
- DBML — единственный источник имён и структур; расхождение с кодом решается в пользу DBML (`../CLAUDE.md`).
- Версия DBML меняется только вперёд, с записью в changelog в шапке файла (`../CLAUDE.md`, «Чего не делать»).
- Факты — append-only, без FK на рабочие таблицы (design §1.4–1.6).
- Язык документов и идентификаторов — русский текст / английские имена, как в остальной DBML.
- `openspec validate --strict` должен проходить без ошибок к концу последней задачи.
- `docs/source/**` не редактируется (не затрагивается этим change).
## Review Focus
- **OSV-документы получают `quality_intake_fact` без построчного снимка** (`payload` без `lines`/`values`, только
счётчики и `osv_warnings[]`) — если это не сказано явно и одинаково в entities.md, домен-правилах и схеме контракта,
читающий код в `arm-core` заполнит `lines: []` вместо того, чтобы не писать их — задача 2 и 4 должны явно повторить
эту оговорку, не только задача 1.
- **`quality_outcome_fact.document_id` не unique** — у документа может быть несколько исходов (переподтверждение ОСВ,
`*_AFTER_CONFIRM`); без явного индекса-подсказки и фразы «метрики берут последний по `outcome_at`» в тексте таблицы
читатель DBML предположит один факт на документ, как у `quality_intake_fact`.
- **Старая заметка `recognition_job`** «сырой ответ ИС Сканирование… в БД не регистрируется» прямо противоречит новому
`raw_s3_object_id` — задача 1 должна отредактировать этот текст в Note самой таблицы, а не только добавить столбец.
- **`make diagrams` жёстко проверяет полноту `PARTS`** (`sys.exit` при несовпадении множества таблиц) — не
зафиксировано, что проверка пройдёт, если забыть одну из трёх новых таблиц или опечатать имя; задача 1 обязана
прогнать `make diagrams` и показать, что он не падает, а не только отредактировать словарь на глаз.
- **`quality-facts.schema.json` сознательно не подключается к `validate_contracts.py`** (design §2) — без явной
строки в самом файле схемы (`"description"` верхнего уровня) читатель контракта через полгода предположит, что
`make check` его проверяет, и не заметит, что сломал JSON без предупреждения.
---
### Task 1: DBML v5.25 — таблицы фактов, перечисления, изменённые столбцы
**Files:**
- Create: `model/data-model-v5.25.dbml` (копия `model/data-model-v5.24.dbml` + диффы ниже)
- Modify: `Makefile:9` (`DBML ?= model/data-model-v5.25.dbml`)
- Modify: `scripts/gen_model_diagrams.py` (словарь `PARTS`: новая часть `10_quality_metrics`)
- Modify: `context/04-data-model-guide.md` (строка 1–7 — имена файлов и диапазон частей; раздел «Девять частей
модели» → «Десять частей модели» с новой строкой; новый абзац `**v5.25:** …` в начале списка изменений)
**Interfaces:**
- Produces (имена, на которые ссылаются задачи 2–4 и `arm-core`): enum `s3_bucket_role.raw`,
`permission_object.QUALITY_METRICS`, `recognition_pipeline` (переиспользуется, без изменений), новые enum
`quality_outcome`, `quality_cause`, `quality_failure_kind` (значения — design §1.3); таблицы
`quality_intake_fact`, `quality_outcome_fact`, `quality_failure_fact` (точный список столбцов — design §1.4–1.6);
новые столбцы `stored_file.is_scan`, `recognition_job.raw_s3_object_id`.
- [ ] **Step 1: Скопировать DBML и поднять версию**
```bash
cp model/data-model-v5.24.dbml model/data-model-v5.25.dbml
```
Поменять заголовок (строка 1: `v5.24` → `v5.25`) и вставить новый блок changelog в начало (после строки 1, перед
блоком `v5.24`) по образцу существующих блоков (см. `v5.24`/`v5.23` в шапке файла):
```
// v5.25 (AURORA-1759, RSHB/arm-ka-docs#202; миграция в arm-core — отдельный change arm-core#190):
// 1. Три новые таблицы фактов качества распознавания первички: quality_intake_fact, quality_outcome_fact,
// quality_failure_fact (отдельная схема dkb_metrics, append-only, без FK на рабочие таблицы) — design §1.4-1.6.
// 2. s3_bucket_role: + raw (сырой ответ ИС Сканирование, не экспирится с файлом); recognition_job.raw_s3_object_id
// — ссылка на него (отменяет решение v5.13 «сырой ответ в БД не регистрируется»).
// 3. stored_file.is_scan — признак скан/цифровой PDF (NULL для не-PDF).
// 4. permission_object: + QUALITY_METRICS (право QUALITY_METRICS_VIEW — seed в arm-core).
```
- [ ] **Step 2: Enum `s3_bucket_role` — добавить `raw`**
В блоке `Enum s3_bucket_role { uploads results exports archive }` добавить строку
`raw [note: 'сырой ответ ИС Сканирование, raw/{job_id}.json; не экспирится с файлом — вход для эталонного прогона (OQ-111)']`.
- [ ] **Step 3: Enum `permission_object` — добавить `QUALITY_METRICS`**
В блоке `Enum permission_object { ... }` добавить `QUALITY_METRICS [note: 'метрики качества распознавания первички']`.
- [ ] **Step 4: Три новых enum для фактов**
Вставить перед `Table quality_intake_fact` (после существующего `Enum recognition_pipeline`, рядом по смыслу)
точные блоки `quality_outcome`, `quality_cause`, `quality_failure_kind` — текст design §1.3 (скопировать как есть,
это уже финальная формулировка).
- [ ] **Step 5: Изменить `stored_file`**
Добавить столбец `is_scan boolean [note: 'только PDF: признак текстового слоя при загрузке; true = цифровой
(в т. ч. скрытый OCR-слой — допущение §14); NULL для не-PDF']` после `superseded_by_file_id`.
- [ ] **Step 6: Изменить `recognition_job`**
Добавить столбец `raw_s3_object_id uuid [ref: > s3_object.id, note: 'сырой ответ ИС Сканирование, роль raw;
заполняется при приёме результата image pipeline']` после `result_s3_object_id`. В тексте столбца
`result_s3_object_id` и в `Note` таблицы убрать/переписать фразу «Сырой ответ ИС Сканирование... в БД не
регистрируется» — он теперь регистрируется.
- [ ] **Step 7: Три новые таблицы**
Добавить `Table quality_intake_fact`, `Table quality_outcome_fact`, `Table quality_failure_fact` в самостоятельном
разделе `// ============ МЕТРИКИ КАЧЕСТВА ============` в самом конце файла, после `Table required_document_rule`
(последняя таблица в текущем v5.24, раздел «ПРОЧЕЕ» — после него файл кончается, отдельных `Ref`-блоков нет).
Точные столбцы, типы, индексы и пояснения — design §1.4 (intake), §1.5 (outcome),
§1.6 (failure); перенести 1:1, включая оговорку про OSV (payload без `lines`/`values`) в `Note` `quality_intake_fact`
и оговорку про неуникальность `document_id` в `Note` `quality_outcome_fact`.
- [ ] **Step 8: Обновить `Makefile`**
`Makefile:9`: `DBML ?= model/data-model-v5.24.dbml` → `DBML ?= model/data-model-v5.25.dbml`.
- [ ] **Step 9: Добавить часть 10 в `scripts/gen_model_diagrams.py`**
В словарь `PARTS` добавить запись после `"09_audit_ex"`:
```python
"10_quality_metrics": (
"Метрики качества первички",
["quality_intake_fact", "quality_outcome_fact", "quality_failure_fact"],
),
```
- [ ] **Step 10: Прогнать рендер диаграмм и убедиться, что скрипт не падает на несовпадении `PARTS`**
Run: `make diagrams DBML=model/data-model-v5.25.dbml`
Expected: скрипт доходит до рендера SVG/PNG (сеть/npx может быть недоступна в песочнице — тогда ожидаемый результат
это ошибка `npx`/сети, а НЕ `sys.exit(...Раскладка частей не совпадает...)`; если видно именно эту ошибку —
значит забыта таблица или опечатка в `PARTS`, вернуться к Step 9).
- [ ] **Step 11: Обновить `context/04-data-model-guide.md`**
Строки 1–7: имена файлов `v5.24` → `v5.25`, диапазон диаграмм `part01…09` → `part01…10`. Заголовок «Девять частей
модели» → «Десять частей модели», новая строка таблицы:
`| 10 Метрики качества первички | \`quality_intake_fact\`, \`quality_outcome_fact\`, \`quality_failure_fact\` | факты распознавания и исхода документа, технические отказы — append-only, без FK |`.
Перед существующим абзацем `**v5.24:** …` вставить `**v5.25:** три таблицы фактов качества распознавания
(\`quality_intake_fact\`/\`_outcome_fact\`/\`_failure_fact\`), \`s3_bucket_role.raw\`, \`stored_file.is_scan\`,
\`recognition_job.raw_s3_object_id\`, \`permission_object.QUALITY_METRICS\` (AURORA-1759).`
- [ ] **Step 12: Коммит**
```bash
git add model/data-model-v5.25.dbml Makefile scripts/gen_model_diagrams.py context/04-data-model-guide.md
git commit -m "docs(#202): DBML v5.25 — факты качества первички (AURORA-1759)"
```
---
### Task 2: `entities-v5.25.md` — бизнес-описание
**Files:**
- Create: `model/entities-v5.25.md` (копия `model/entities-v5.24.md` + диффы)
**Interfaces:**
- Consumes: имена таблиц/столбцов из Task 1 (Produces).
- [ ] **Step 1: Скопировать файл**
```bash
cp model/entities-v5.24.md model/entities-v5.25.md
```
- [ ] **Step 2: Добавить блок изменений**
В начало файла, после заголовка, вставить `## Что изменилось в v5.25 (AURORA-1759, RSHB/arm-ka-docs#202)` с
перечислением: три новые таблицы фактов (append-only, без FK — зачем, одной фразой со ссылкой на
`../arm-core/openspec/changes/quality-metrics/design.md` §1–3 за полным WHY), `s3_bucket_role.raw`,
`stored_file.is_scan`, `recognition_job.raw_s3_object_id`, `permission_object.QUALITY_METRICS`.
- [ ] **Step 3: Дополнить существующие строки «Часть 04»**
В строке `stored_file` добавить предложение `**v5.25:** \`is_scan\` — скан/цифровой PDF по текстовому слою при
загрузке.` В строке `recognition_job` — `**v5.25:** сырой ответ ИС Сканирование теперь регистрируется в
\`s3_object\` (роль \`raw\`) через \`raw_s3_object_id\` — решение v5.13 «не регистрируется» отменено.`
- [ ] **Step 4: Новая «Часть 10 — Метрики качества первички»**
После «Часть 09» добавить раздел с таблицей (формат как в остальных частях: Сущность | Что это | Ключевые связи |
Откуда), строка на каждую из трёх таблиц. В описании `quality_intake_fact` явно сказать: «для ОСВ — без построчного
снимка строк/значений, только счётчики и предупреждения `OSV_*`» (Review Focus, п.1). В описании
`quality_outcome_fact` явно сказать: «не один факт на документ — повторное подтверждение ОСВ и исход после
подтверждения пишут новый факт; метрики берут последний по `outcome_at`» (Review Focus, п.2). Колонка «Откуда» —
`AURORA-1759; arm-core#190` для всех трёх строк.
- [ ] **Step 5: Коммит**
```bash
git add model/entities-v5.25.md
git commit -m "docs(#202): entities-v5.25 — метрики качества первички"
```
---
### Task 3: `05-domain-rules.md` §14 — правило подсчёта и атрибуции
**Files:**
- Modify: `context/05-domain-rules.md` (новый раздел `## §14.` в конце файла, после текущего `## §12`)
**Interfaces:**
- Consumes: enum/таблицы Task 1; структуру `payload` из Task 4 (писать после Task 4, либо держать формулировки
согласованными — Task 4 должен быть написан первым или одновременно, см. порядок задач).
- [ ] **Step 1: Написать §14.1 «Какие документы считаются»**
BALANCE/INCOME/OSV — факт пишется; DISCLOSURE/OTHER — нет (как в `document.document_type`, design §1.4).
- [ ] **Step 2: Написать §14.2 «Атрибуция правки (сервис / наш разбор)»**
Перенести таблицу из `../arm-core/openspec/changes/quality-metrics/design.md` §5 текстуально (источник правды —
design arm-core; здесь — рабочая копия для ссылок `§14.2` из кода `arm-core`, явно подписать
«источник: design arm-core#190 §5»).
- [ ] **Step 3: Написать §14.3 «Причина документа (приоритет `quality_cause`)»**
Перенести правило приоритета из `design.md` (этого репозитория) §6 / арм-core design §6: `SERVICE_ERROR` >
`OUR_ERROR` > `CLIENT_DOCUMENT` > `WITHOUT_PARTICIPATION` > `AS_IS`; `DELETED*`/`SUPERSEDED*` — отдельные столбцы
`outcome`, не входят в матрицу, `primary_cause` = NULL.
- [ ] **Step 4: Написать §14.4 «Документ клиента»**
Правило: ручное подтверждение с `checks_accepted = true` (акцепт жёстких нарушений §7) и нет правки, закрывающей
нарушенное соотношение; мягкие нарушения и межформенная 1370↔2400 не учитываются.
- [ ] **Step 5: Написать §14.5 «Технические отказы»**
Считаются по файлам (`quality_failure_fact`), не по документам; знаменатель — загруженные файлы того же `pipeline`;
`FILE_REPROCESS` — первая неудачная попытка остаётся отказом, успешная — получает свои факты intake/outcome.
- [ ] **Step 6: Написать §14.6 «Признак скана»**
PDF с текстовым слоем — цифровой; PDF со скрытым OCR-слоем — тоже цифровой (ASSUMPTION(OQ-111 не подходит — завести
новый номер следующей свободной OQ, см. Task 5) явно помечено как допущение.
- [ ] **Step 7: Коммит**
```bash
git add context/05-domain-rules.md
git commit -m "docs(#202): §14 — счёт и атрибуция метрик качества первички"
```
---
### Task 4: `docs/contracts/quality-facts.schema.json` — контракт payload
**Files:**
- Create: `contracts/quality-facts.schema.json`
**Interfaces:**
- Consumes: имена и смысл полей `payload` из Task 1 (`quality_intake_fact.payload`, `quality_outcome_fact.payload`).
- Produces: `$id`/`$defs` `intake-payload`, `outcome-payload` — имена, под которыми на схему будет ссылаться тест
в `arm-core` (design §2).
- [ ] **Step 1: Написать JSON Schema**
Draft 2020-12, `$defs.intake-payload` и `$defs.outcome-payload` по составу из design §2 (header/lines/values/issues/
raw_line_codes для intake; + fixes[] для outcome; у OSV — `osv_warnings`/счётчики вместо `lines`/`values`, выразить
через `oneOf` по наличию `document_type` или через оба варианта структуры — на выбор исполнителя, с комментарием
в самой схеме). Верхнеуровневое `"description"` схемы должно явно сказать: «не валидируется `make check`
(`scripts/validate_contracts.py`) — проверяется тестом в arm-core, который формирует payload» (Review Focus, п.5).
- [ ] **Step 2: Проверить, что файл — валидный JSON Schema**
Run: `python3 -c "import json, jsonschema; jsonschema.Draft202012Validator.check_schema(json.load(open('contracts/quality-facts.schema.json')))"`
Expected: без вывода и без исключения (код возврата 0).
- [ ] **Step 3: Коммит**
```bash
git add contracts/quality-facts.schema.json
git commit -m "docs(#202): контракт payload фактов качества первички"
```
---
### Task 5: Документы контекста и `openspec validate`
**Files:**
- Modify: `context/07-open-questions.md` (новый OQ — срок хранения `raw/{job_id}.json`, design §9 арм-core/design §1.1 этого файла)
- Modify: `context/03-architecture.md` (если там есть раздел про S3/бакеты — отметить роль `raw`; раздел про внешних
читателей БД — отметить read-only роль Superset к схеме `dkb_metrics`)
- Modify: `openspec/changes/quality-metrics/proposal.md` / `design.md` — поставить реальный номер OQ вместо
плейсхолдера OQ-111, если он занят (проверить в Step 1)
- [ ] **Step 1: Проверить следующий свободный номер OQ**
Run: `grep -n "^### OQ-" context/07-open-questions.md | tail -5`
Записать новый вопрос под следующим свободным номером (не обязательно OQ-111 — он мог быть занят после 27.10);
везде, где в `design.md`/`proposal.md` этого change и арм-core стоит плейсхолдер OQ-111, заменить на реальный номер
(если он совпал — ничего менять не нужно).
- [ ] **Step 2: Дописать вопрос**
Текст: «Срок хранения сырых ответов ИС Сканирование (`raw/{job_id}.json`, роль `raw`) и подтверждённых `uploads/`,
на которые ссылаются факты качества — решает платформа (эталонный прогон использует их неопределённо долго, пока
change не отменён)».
- [ ] **Step 3: Отметить в `03-architecture.md`**
Если в файле есть таблица ролей бакета S3 — добавить строку `raw`; если есть раздел про внешние читатели БД —
добавить абзац про read-only роль Superset к схеме `dkb_metrics` (design арм-core §8). Если ни одного из разделов
нет — создать короткий абзац в конце файла, не переписывая остальное.
- [ ] **Step 4: Прогнать валидацию OpenSpec**
Run: `openspec validate quality-metrics --strict`
Expected: без ошибок.
- [ ] **Step 5: Прогнать `make check`**
Run: `make check`
Expected: код возврата 0 (`validate-contracts`, `validate-openspec`, `test-snapshot`, `test-sync-statements` — этот
change их не касается, должны остаться зелёными как были).
- [ ] **Step 6: Коммит**
```bash
git add context/07-open-questions.md context/03-architecture.md openspec/changes/quality-metrics/
git commit -m "docs(#202): открытые вопросы и architecture — метрики качества первички"
```