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>
This commit is contained in:
parent
2af056df22
commit
2676dfd731
@ -1,4 +1,4 @@
|
||||
# 03. Архитектура (целевая, по решениям DECISION-1…5 и ARCH-* в DBML v5.24)
|
||||
# 03. Архитектура (целевая, по решениям DECISION-1…5 и ARCH-* в DBML v5.25)
|
||||
|
||||
## Компоненты (= репозитории, см. `00-workspace-layout.md`)
|
||||
|
||||
@ -213,9 +213,11 @@ advisory-lock по `(ЮЛ, sha256)` (двойной клик не проскак
|
||||
|
||||
**Один бакет** (`OPENBAO__MINIO__MINIO_BUCKET` шаблона); роль объекта — первый сегмент ключа:
|
||||
`{bucket_role}/{file_id}/{job_id}/{name}`. Роли: `uploads` (исходники, срок — весь срок хранения), `results`
|
||||
(нормализованные JSON; ретеншн — ARCH-010, открыт), `exports` (7 дней), `archive` (data lake). Все объекты регистрируются
|
||||
в `s3_object` (`bucket_role`, `bucket`, `key`, sha256, size, expires_at). Сырой ответ OCR `raw/{job_id}.json` — по
|
||||
конвенции, без записи в БД. Lifecycle-правила — по префиксам.
|
||||
(нормализованные JSON; ретеншн — ARCH-010, открыт), `exports` (7 дней), `archive` (data lake), **`raw`** (v5.25,
|
||||
AURORA-1759: сырой ответ ИС Сканирование `raw/{job_id}.json` — теперь регистрируется в `s3_object` через
|
||||
`recognition_job.raw_s3_object_id`; `expires_at` не ставится при удалении файла — вход для эталонного прогона
|
||||
метрик качества, срок хранения — OQ-179). Все объекты регистрируются в `s3_object` (`bucket_role`, `bucket`, `key`,
|
||||
sha256, size, expires_at). Lifecycle-правила — по префиксам.
|
||||
|
||||
Срез 2c: ядро кладёт исходник по ключу `uploads/{file_id}/{job_id}/{безопасное имя}` (`job_id` задаётся заранее — ключ
|
||||
известен до записи в БД; имя очищается от путей и управляющих символов); `results/{file_id}/{job_id}/…` пишет сервис
|
||||
@ -251,7 +253,7 @@ api/src/
|
||||
# машины статусов, policy — pydantic-модели на входе и выходе
|
||||
services/ # use-cases: оркестрация domain + repositories + outbox + audit_event
|
||||
repositories/ # SQLAlchemy-запросы, в т. ч. запросы-вычисления (сводная ОСВ, шахматка)
|
||||
models/ # ORM 1:1 с DBML v5.24; имена таблиц/колонок/enum — как в DBML
|
||||
models/ # ORM 1:1 с DBML v5.25; имена таблиц/колонок/enum — как в DBML
|
||||
schemas/ # pydantic DTO API /api/v1
|
||||
core/ # config, clients (S3; NATS outbox-publisher и result-consumer), auth (JWT РАИСА / dev-эмитент),
|
||||
# logger, exception_handlers
|
||||
@ -341,7 +343,11 @@ FastAPI» платформы РАИСА/aurora): дерево импортиро
|
||||
`version_table_schema` и `include_name` Alembic). Следуем шаблону, не `public`.
|
||||
- Alembic берёт URL из настроек, не из `alembic.ini`; ревизии — `api/alembic/versions/`, имя файла стандартное
|
||||
`<rev>_<slug>.py`. Автогенерация — только черновик: итоговую ревизию сверяет с DBML `api/scripts/check_dbml_vs_orm.py`.
|
||||
- PK везде `uuid` через `UUIDPrimaryMixin`; имена таблиц, колонок и enum'ов — ровно как в DBML v5.24.
|
||||
- PK везде `uuid` через `UUIDPrimaryMixin`; имена таблиц, колонок и enum'ов — ровно как в DBML v5.25.
|
||||
- **v5.25 (AURORA-1759):** факты качества первички (`quality_*_fact`) — в отдельной схеме PostgreSQL `dkb_metrics`
|
||||
(имя — настройка), той же БД, миграции — общей цепочкой Alembic с `dkb`. Платформенный Superset читает её отдельной
|
||||
ролью БД **только на чтение** (создаёт платформа); arm-core не пишет код для Superset и не выдаёт ему прав сверх
|
||||
`GRANT SELECT` на эту схему.
|
||||
|
||||
### Инфраструктурные клиенты
|
||||
|
||||
|
||||
@ -183,3 +183,4 @@
|
||||
| OQ-175 | Процент расхождения шахматки при отрицательной меньшей стороне: `variance_pct = variance / min(amount_a, amount_b) * 100` при отрицательном сальдо (возможно по §1) уходит в минус — пара (100, −5) даёт −2100 %, порог > 5 % не срабатывает, пара не подсвечивается. Считать от `\|min\|`, от меньшей по модулю стороны или подсвечивать пары с разными знаками всегда? | ревью arm-core #165 (AURORA-1651), 22.10.2026 | §6, шахматка, xlsx | открыт; до решения — как в коде (min со знаком); у пар ВГО §5 суммы сторон тоже со знаком (решение владельца 23.10.2026), но при `min ≤ 0` процент не считается и тройка подсвечивается |
|
||||
| OQ-176 | Счёт `000` «Вспомогательный» (технический счёт 1С для ввода остатков, `kind = AP`, `is_off_balance = false`) входит в шахматку «все счета». Исключать его вместе с забалансовыми? | ревью arm-core #165 (AURORA-1651), 22.10.2026 | §6, режим «все счета» | открыт; до решения — входит (решение владельца 22.10.2026: не трогать) |
|
||||
| OQ-178 | Порог превышения отклонения агрегата в таблице корректировок — ровно 5 % в допуске (строго `>`) или на проверку («от 5 % включительно», как читается DOC-272613/ТЗ)? Аналитик (Лысенко) на прямой вопрос не отвечал | `dev/api-contract-corrections.md` §2; R-34/R-35 (`arm-core`, `openspec/changes/corrections-table-ui/design.md`) | §5 п. 2a, §8 «Корректировка», экран «Таблица корректировок» | открыт; реализовано строго `>` 5 % (R-35, `domain/corrections.py`) — фронт и сервер синхронны между собой, но не точно со спорным текстом ТЗ; правка при подтверждении — один оператор в `editorModel.ts`/`CorrectionTable.tsx` и в `domain/corrections.py` |
|
||||
| OQ-179 | Срок хранения сырых ответов ИС Сканирование (`raw/{job_id}.json`, роль `s3_bucket_role.raw`) и подтверждённых `uploads/`, на которые ссылаются факты качества (`quality_intake_fact`/`quality_outcome_fact`): эталонный прогон перенормализации использует их неопределённо долго, пока change `quality-metrics` не отменён | `openspec/changes/quality-metrics/design.md` §1.1; `arm-core/openspec/changes/quality-metrics/design.md` §9 | §14, ретеншн S3 (решает платформа) | открыт; до решения — `expires_at` не ставится ни одному из этих объектов (05.11.2026) |
|
||||
|
||||
327
docs/superpowers/plans/2026-11-05-quality-metrics-dbml.md
Normal file
327
docs/superpowers/plans/2026-11-05-quality-metrics-dbml.md
Normal file
@ -0,0 +1,327 @@
|
||||
# Метрики качества первички — модель данных (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 — метрики качества первички"
|
||||
```
|
||||
@ -365,7 +365,7 @@ Enum s3_bucket_role {
|
||||
results
|
||||
exports
|
||||
archive
|
||||
raw [note: 'сырой ответ ИС Сканирование, raw/{job_id}.json; не экспирится с файлом — вход для эталонного прогона (OQ-111)']
|
||||
raw [note: 'сырой ответ ИС Сканирование, raw/{job_id}.json; не экспирится с файлом — вход для эталонного прогона (OQ-179)']
|
||||
}
|
||||
|
||||
Enum recognition_status {
|
||||
|
||||
180
openspec/changes/quality-metrics/design.md
Normal file
180
openspec/changes/quality-metrics/design.md
Normal file
@ -0,0 +1,180 @@
|
||||
# Дизайн: модель данных для метрик качества первички (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-справочник для отказов |
|
||||
65
openspec/changes/quality-metrics/proposal.md
Normal file
65
openspec/changes/quality-metrics/proposal.md
Normal file
@ -0,0 +1,65 @@
|
||||
# Proposal: Метрики качества первички (AURORA-1759)
|
||||
|
||||
**Срез:** вне `docs/context/09-delivery-plan.md` (спринт `AWPCA_Sprint_3_Corrections`, эпик AURORA-1353).
|
||||
**Репозиторий:** arm-ka-docs (сквозная часть — модель данных). **Issue:** RSHB/arm-ka-docs#202.
|
||||
**Дочерние change'и:** `arm-core/openspec/changes/quality-metrics` (arm-core#190) — запись фактов, атрибуция,
|
||||
API, экран. `tabular-parser` и `scan-gateway` не меняются.
|
||||
|
||||
Полное обоснование и все решения владельца — `../../arm-core/openspec/changes/quality-metrics/design.md`
|
||||
(брейнсторминг 27.10.2026, подтверждено владельцем 05.11.2026). Этот change переносит из него в модель данных
|
||||
только то, что становится структурой: таблицы, перечисления, столбцы, контракт payload, правила подсчёта.
|
||||
|
||||
## Why
|
||||
|
||||
Заказчику нужно подтверждать эффект АРМ КА цифрами (доля ББ/ОФР, дошедшая без правок), владельцу — доказательная база
|
||||
о качестве сервиса разбора PDF с разделением «ошибка сервиса / наша нормализация / документ клиента». Ядро сейчас не
|
||||
хранит ничего для этого: `document`/`financial_statement_*` переписываются при правке и перестройке, подтверждённый
|
||||
результат недоступен после следующей правки, сырой ответ ИС Сканирование не регистрируется в БД (решение v5.13,
|
||||
отменяется этим change).
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Три новые таблицы фактов** (плоские, append-only, без FK на рабочие таблицы, отдельная схема PostgreSQL
|
||||
`dkb_metrics` — имя настройка): `quality_intake_fact` (документ «как распознано», пишется в момент маппинга),
|
||||
`quality_outcome_fact` (исход: автоподтверждён / подтверждён / удалён / заменён — в т. ч. после подтверждения — с
|
||||
атрибуцией правок и причиной попадания в ячейку матрицы), `quality_failure_fact` (технический отказ задания
|
||||
распознавания). Точные столбцы — `design.md` §1.
|
||||
- **`s3_bucket_role`**: новое значение `raw` — сырой ответ ИС Сканирование (`raw/{job_id}.json`) регистрируется в
|
||||
`s3_object`, не удаляется вместе с файлом (вход для будущего эталонного прогона, ASSUMPTION(OQ-179) — срок хранения
|
||||
решает платформа).
|
||||
- **`recognition_job.raw_s3_object_id`** — ссылка на зарегистрированный сырой ответ (image pipeline).
|
||||
- **`stored_file.is_scan`** — признак скан/цифровой PDF, определяется при загрузке по текстовому слою.
|
||||
- **`permission_object`**: новое значение `QUALITY_METRICS` — объект права для экрана (действие `VIEW`,
|
||||
право `QUALITY_METRICS_VIEW`; seed — в arm-core).
|
||||
- **`docs/contracts/quality-facts.schema.json`** — JSON Schema payload (jsonb) трёх фактов.
|
||||
- **`docs/context/05-domain-rules.md` §14** — счёт документов/матрица причин, правило атрибуции правки
|
||||
(сервис/наш разбор), правило «документ клиента» (акцепт жёстких нарушений), признак скана.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `quality-metrics-model`: модель данных и контракт фактов качества распознавания первички — какие факты пишутся,
|
||||
в каком формате, как считается матрица причин.
|
||||
|
||||
### Modified Capabilities
|
||||
<!-- openspec/specs пуст для этой области — изменяемых capability нет -->
|
||||
|
||||
## Impact
|
||||
|
||||
- Пишет факты только `arm-core` (единственный писатель в БД, см. `../CLAUDE.md`). Читает — Superset платформы
|
||||
отдельной read-only ролью к схеме `dkb_metrics` (создаёт платформа, описание в `03-architecture.md`).
|
||||
- Таблицы DBML v5.24, которые это затрагивает: `recognition_job`, `s3_object`, `s3_bucket_role`, `stored_file`,
|
||||
`document`, `financial_statement_meta/column/line/value`, `osv_entry`, `document_issue`, `permission_object`.
|
||||
Новых FK на них из таблиц фактов нет (факты самодостаточны).
|
||||
- Миграция Alembic — в `arm-core` (общая цепочка, не в этом репозитории).
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Метрики по документам до выкладки фичи (ретро-подсчёт) — не считаем.
|
||||
- Время работы аналитика (визиты документа) — будущий раунд; факты уже ключуются `document_id` для последующего
|
||||
присоединения.
|
||||
- Отдельный счётчик «документ есть, но сервис его не разбирает» — не отделить от технических отказов (решение
|
||||
владельца 27.10.2026).
|
||||
- Сам эталонный прогон перенормализации сохранённых сырых ответов — этот change только обеспечивает вход (хранение
|
||||
`raw/{job_id}.json`, отказ от ретеншна).
|
||||
- Ограничение видимости метрик по филиалу/ГК — пока все роли видят всё.
|
||||
@ -0,0 +1,60 @@
|
||||
# Spec Delta: quality-metrics-model
|
||||
|
||||
## Purpose
|
||||
|
||||
Модель данных и контракт фактов качества распознавания первички (AURORA-1759): какие факты пишутся, в каком
|
||||
формате, как они переживают удаление/перераспознавание документа и чем отличаются от рабочих таблиц. Алгоритм
|
||||
подсчёта и атрибуции — `context/05-domain-rules.md` §14 (не дублируется здесь).
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Факты качества — append-only и без FK на рабочие таблицы
|
||||
`quality_intake_fact`, `quality_outcome_fact`, `quality_failure_fact` SHALL не иметь внешних ключей на `document`,
|
||||
`stored_file`, `recognition_job`, `legal_entity`, `company_group` — только простые `uuid` и денормализованные имена
|
||||
ЮЛ/ГК. Факты SHALL жить в отдельной схеме PostgreSQL (имя — настройка, по умолчанию `dkb_metrics`).
|
||||
|
||||
#### Scenario: Документ удалён после записи факта
|
||||
- **WHEN** документ, по которому есть `quality_intake_fact`, физически удалён (`primary_files.delete`)
|
||||
- **THEN** строка факта остаётся читаемой, её `document_id` не разрешается в существующий `document`, и запрос к
|
||||
факту не падает на отсутствующем FK
|
||||
|
||||
### Requirement: `quality_intake_fact` — один факт на документ, кроме ОСВ без построчного снимка
|
||||
Таблица SHALL иметь ровно один факт на `document_id` (`unique`). Для `document_type = OSV` `payload` SHALL не
|
||||
содержать `lines`/`values` — только счётчики строк и предупреждения `OSV_*`; для `BALANCE`/`INCOME` `payload` SHALL
|
||||
содержать построчный снимок (`lines`, `values`).
|
||||
|
||||
#### Scenario: Приём результата ОСВ
|
||||
- **WHEN** ядро мапит результат распознавания ОСВ в документ `OSV`
|
||||
- **THEN** `quality_intake_fact.payload` не содержит ключей `lines`/`values`, но содержит `osv_warnings`
|
||||
|
||||
#### Scenario: Приём результата ББ
|
||||
- **WHEN** ядро мапит результат распознавания в документ `BALANCE`
|
||||
- **THEN** `quality_intake_fact.payload` содержит `lines` и `values` построчно
|
||||
|
||||
### Requirement: `quality_outcome_fact` — не unique на документ, метрики берут последний факт
|
||||
Таблица SHALL допускать несколько строк с одним `document_id` (повторное подтверждение ОСВ после правки,
|
||||
исход `*_AFTER_CONFIRM` поверх прежнего). Любой читатель метрик SHALL использовать факт с максимальным `outcome_at`
|
||||
для данного `document_id`.
|
||||
|
||||
#### Scenario: Документ удалён после подтверждения
|
||||
- **WHEN** подтверждённый документ впоследствии удалён или вытеснен автозаменой
|
||||
- **THEN** пишется новый факт с `outcome ∈ {DELETED_AFTER_CONFIRM, SUPERSEDED_AFTER_CONFIRM}`, прежний факт
|
||||
(`CONFIRMED`/`AUTO_CONFIRMED`) не удаляется и не перезаписывается
|
||||
|
||||
### Requirement: Сырой ответ ИС Сканирование регистрируется и не экспирится с файлом
|
||||
`s3_object` SHALL поддерживать роль бакета `raw`; `recognition_job.raw_s3_object_id` SHALL ссылаться на
|
||||
зарегистрированный объект для image pipeline. Удаление `stored_file` SHALL НЕ устанавливать `expires_at` объекту
|
||||
с ролью `raw`.
|
||||
|
||||
#### Scenario: Удаление файла с зарегистрированным сырым ответом
|
||||
- **WHEN** PDF-файл с заполненным `recognition_job.raw_s3_object_id` удаляется
|
||||
- **THEN** `s3_object.expires_at` объекта роли `raw` остаётся `NULL`, в отличие от объектов ролей `uploads`/`results`
|
||||
того же файла
|
||||
|
||||
### Requirement: Контракт payload не входит в `make check` этого репозитория
|
||||
`contracts/quality-facts.schema.json` SHALL существовать как валидная JSON Schema (Draft 2020-12), но SHALL NOT
|
||||
быть подключена к `scripts/validate_contracts.py` — payload не является сообщением NATS-контракта распознавания.
|
||||
|
||||
#### Scenario: `make check` в arm-ka-docs
|
||||
- **WHEN** выполняется `make check` в этом репозитории
|
||||
- **THEN** `quality-facts.schema.json` не упоминается в выводе `validate-contracts`, и это не считается ошибкой
|
||||
Loading…
x
Reference in New Issue
Block a user