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>
328 lines
24 KiB
Markdown
328 lines
24 KiB
Markdown
# Метрики качества первички — модель данных (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 — метрики качества первички"
|
||
```
|