diff --git a/openspec/changes/quality-metrics/design.md b/openspec/changes/archive/2026-11-07-quality-metrics/design.md similarity index 100% rename from openspec/changes/quality-metrics/design.md rename to openspec/changes/archive/2026-11-07-quality-metrics/design.md diff --git a/openspec/changes/quality-metrics/proposal.md b/openspec/changes/archive/2026-11-07-quality-metrics/proposal.md similarity index 100% rename from openspec/changes/quality-metrics/proposal.md rename to openspec/changes/archive/2026-11-07-quality-metrics/proposal.md diff --git a/openspec/changes/quality-metrics/specs/quality-metrics-model/spec.md b/openspec/changes/archive/2026-11-07-quality-metrics/specs/quality-metrics-model/spec.md similarity index 100% rename from openspec/changes/quality-metrics/specs/quality-metrics-model/spec.md rename to openspec/changes/archive/2026-11-07-quality-metrics/specs/quality-metrics-model/spec.md diff --git a/openspec/specs/quality-metrics-model/spec.md b/openspec/specs/quality-metrics-model/spec.md new file mode 100644 index 0000000..da453c4 --- /dev/null +++ b/openspec/specs/quality-metrics-model/spec.md @@ -0,0 +1,62 @@ +# quality-metrics-model Specification + +## Purpose +Модель данных и контракт фактов качества распознавания первички (AURORA-1759): какие факты пишутся, в каком +формате, как они переживают удаление/перераспознавание документа и чем отличаются от рабочих таблиц. Алгоритм +подсчёта и атрибуции — `context/05-domain-rules.md` §14 (не дублируется здесь). + +## 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`, ни объекту `uploads`, на который ссылается `quality_intake_fact` (`upload_s3_key`) — оба нужны +эталонному прогону (OQ-179). Объекты `results` и прочие `uploads` (без факта) — без изменений, `expires_at = now()` +при удалении файла, как и раньше. + +#### Scenario: Удаление файла с зарегистрированным сырым ответом +- **WHEN** PDF-файл с заполненным `recognition_job.raw_s3_object_id` и существующим `quality_intake_fact` удаляется +- **THEN** `s3_object.expires_at` остаётся `NULL` у объекта роли `raw` и у объекта `uploads`, на который ссылается + факт; у объекта `results` того же файла `expires_at` проставляется `now()`, как для любого удаления + того же файла + +### Requirement: Контракт payload не входит в `make check` этого репозитория +`contracts/metrics/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`, и это не считается ошибкой