Andrey Epifancev 9bcf6373fe Архив change quality-metrics (AURORA-1759)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 08:51:25 +04:00

124 lines
12 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.

# quality-metrics Specification
## Purpose
Факты качества первички (приём «как распознано», исход, технический отказ), отнесение правок аналитика к источнику
ошибки, матрица причин и API `/quality-metrics` с отдельным правом. Правила подсчёта — `05-domain-rules.md` §14,
модель — DBML v5.25, дизайн — `design.md` этого change.
## Requirements
### Requirement: Факты качества — отдельная схема, только добавление
Ядро SHALL хранить факты качества тремя плоскими таблицами `quality_intake_fact`, `quality_outcome_fact`,
`quality_failure_fact` в отдельной схеме PostgreSQL (`QUALITY_METRICS_SCHEMA`, по умолчанию `dkb_metrics`; значение MUST
отличаться от `DB_SCHEMA`). Таблицы MUST NOT иметь внешних ключей на рабочие таблицы: идентификаторы документов, файлов,
заданий, ЮЛ и ГК — просто `uuid`, имена ЮЛ и ГК копируются на момент факта. Записи SHALL только добавляться (UPDATE и
DELETE по фактам MUST NOT выполняться); `payload` — `jsonb` с `payload_schema_version`, форма по контракту
`quality-facts.schema.json`. Факты пишутся только для документов, принятых после выкладки.
#### Scenario: Факт переживает удаление файла
- **WHEN** файл с документом, у которого есть факт приёма, удаляется
- **THEN** строки документа и файла удалены, а `quality_intake_fact` остаётся нетронутым
#### Scenario: Схема метрик совпала со схемой модели
- **WHEN** `DB_SCHEMA` и `QUALITY_METRICS_SCHEMA` заданы одним значением
- **THEN** приложение не стартует с ошибкой конфигурации
### Requirement: Факт приёма «как распознано»
В транзакции маппинга результата, после `map_file` и до автоподтверждения, ядро SHALL записать по одному
`quality_intake_fact` на каждый созданный документ `BALANCE`, `INCOME`, `OSV` (`DISCLOSURE` и `OTHER` факта не
получают): измерения (ЮЛ и ГК с именами, тип документа, `source_kind` pdf/excel, `is_scan` для PDF, сервис и версии,
`intake_at`, `intake_status`), счётчики строк, значений, нарушений и предупреждений, ключи S3, и `payload` со снимком
«как распознано». Запись факта MUST NOT менять документы, статусы и результат маппинга.
#### Scenario: Приём ББ
- **WHEN** маппинг создал документ `BALANCE` из PDF
- **THEN** есть ровно один `quality_intake_fact` с `document_id`, `source_kind = pdf`, `intake_status` как у документа после
маппинга и снимком строк и значений в `payload`
#### Scenario: Расшифровка не учитывается
- **WHEN** маппинг создал документ `DISCLOSURE`
- **THEN** факта приёма по нему нет
### Requirement: Факт исхода документа
Ядро SHALL записать `quality_outcome_fact` в транзакции события, которое завершает документ: ручное подтверждение
(`CONFIRMED`), автоподтверждение (`AUTO_CONFIRMED`), удаление файла (`DELETED`), автозамена файла (`SUPERSEDED`). Если
документ на момент удаления или замены уже `CONFIRMED`, исход SHALL быть `DELETED_AFTER_CONFIRM` /
`SUPERSEDED_AFTER_CONFIRM`. Факт содержит `primary_cause`, счётчики правок по источнику (`service` / `ours` / `client`),
`checks_accepted`, число нарушений на момент исхода, подтверждённую отчётную дату. Повторное подтверждение ОСВ после
правки SHALL писать новый факт; метрики берут последний факт документа.
#### Scenario: Автоподтверждение
- **WHEN** документ без замечаний автоподтверждён
- **THEN** факт с `outcome = AUTO_CONFIRMED`, `primary_cause = WITHOUT_PARTICIPATION`, `confirmed_by` пуст
#### Scenario: Удаление после подтверждения
- **WHEN** удаляется файл с автоподтверждённым документом
- **THEN** новый факт `DELETED_AFTER_CONFIRM`; прежний факт `AUTO_CONFIRMED` не изменён
### Requirement: Факт технического отказа
Ядро SHALL записывать `quality_failure_fact` при переходе задания распознавания в `error`, `timeout`, `dlq` и при
неповторяемой ошибке маппинга (файл → `ERROR`). `failure_kind` — `SERVICE_ERROR` / `TIMEOUT` / `NO_ACK` / `MAPPING`;
`error_code` отказа маппинга — константа `MAPPING_FAILED`. Повторная обработка создаёт новое задание: отказ первой
попытки остаётся фактом.
#### Scenario: Таймаут задания
- **WHEN** вахтёр переводит задание в `timeout`
- **THEN** есть факт `failure_kind = TIMEOUT` с `job_id`, `file_id`, `failed_at`
### Requirement: Отнесение правки к источнику ошибки
Правка аналитика (отличие подтверждённого результата от снимка приёма) SHALL относиться к источнику чистой функцией без
БД по правилу «верный ответ есть в сыром тексте сервиса — ошибка наша, иначе сервиса»: сумма, код строки, добавленная
строка, поля шапки (отчётная дата сравнивается как дата после разбора `parse_date`), период и счёт ОСВ. Для Excel любая
ошибка — «наш разбор». Спорные случаи и случаи без сырого текста SHALL относиться к «нашему разбору». Правки колонок дат
и набор кодов сырого ответа для добавленных строк PDF в этом change не учитываются (отложено).
#### Scenario: Сервис прочитал верно
- **WHEN** аналитик исправил сумму, цифры которой совпадают с цифрами сырого текста
- **THEN** правка относится к «нашему разбору»
#### Scenario: Сервис ошибся
- **WHEN** подтверждённый код строки не извлекается из сырого текста кода
- **THEN** правка относится к «ошибке сервиса»
### Requirement: Причина документа и матрица
Каждый документ с исходом SHALL попадать ровно в одну ячейку по первому сработавшему правилу: удалён/заменён до
подтверждения; удалён/заменён после подтверждения; есть правки «сервис» → ошибка сервиса; есть правки «наш разбор» →
наш разбор; ручное подтверждение с акцептом жёстких нарушений → документ клиента; `AUTO_CONFIRMED` → без участия;
иначе → как есть. Документы без исхода — «в работе». Доли считаются от документов с исходом `AUTO_CONFIRMED` /
`CONFIRMED`; удалённые, заменённые и «в работе» в знаменатель не входят. Технические отказы считаются по файлам своей
долей. Расчёт — чистая функция от фактов, в БД не хранится; последний факт документа выбирается по времени исхода, при
равенстве — детерминированно.
#### Scenario: Документ с правкой сервиса и клиента
- **WHEN** у подтверждённого документа есть правка «сервис» и акцепт нарушений
- **THEN** документ в ячейке «ошибка сервиса», счётчики обеих сторон сохранены в факте
#### Scenario: Нулевой знаменатель
- **WHEN** за период нет документов с исходом
- **THEN** доли равны 0, `denominator = 0`
### Requirement: API метрик качества
Система SHALL предоставлять под правом `QUALITY_METRICS_VIEW` маршруты `/api/v1/quality-metrics`: `GET /matrix`
(матрица за период с фильтрами период по дате приёма, ГК, ЮЛ, тип, источник, скан/цифровой, отказы по файлам, счётчик
уникальных отчётов), `GET /series` (ряд по месяцам даты приёма для причины, пропущенные месяцы — нули), `GET
/cells/{cell}/documents` (постраничный список документов ячейки со счётчиками правок, ссылкой на документ, если он
существует, и `confirmed_by`), `GET /export` (факты за период, CSV с BOM и JSON; значения, начинающиеся с символов
формулы, экранируются). Выгрузка SHALL писать `audit_event(EXPORT)` с `object_type = quality_metrics` и фильтрами. Агрегаты
MUST NOT содержать фамилий пользователей.
#### Scenario: Матрица без права
- **WHEN** пользователь без `QUALITY_METRICS_VIEW` запрашивает `/quality-metrics/matrix`
- **THEN** 403
#### Scenario: Выгрузка оставляет след
- **WHEN** пользователь выгружает факты за период
- **THEN** записан `audit_event(EXPORT)` с форматом, фильтрами и числом строк
### Requirement: Право QUALITY_METRICS_VIEW
Объект `QUALITY_METRICS` в `permission_object` и право `QUALITY_METRICS_VIEW` SHALL быть в seed и выдаваться всем
ролям; позже права сужаются данными (`role_permission`) без изменения кода. Видимость по филиалу и ГК не применяется.
#### Scenario: Право у всех ролей
- **WHEN** выполнен `make seed` на чистой БД
- **THEN** `GET /me` любой роли содержит `QUALITY_METRICS_VIEW`