docs(#202): правки ревью ветки — is_scan, ОСВ без lines/values, retention raw/uploads
Критично: stored_file.is_scan был описан наоборот (true = цифровой вместо true = скан), противоречил §14.6 и собственному имени столбца. Важно: §14 не говорил явно, что ОСВ не получает lines/values в payload (теперь dependentSchemas в контракте это ещё и проверяет); §14.3 не говорил, что quality_outcome_fact.document_id не unique и метрики берут последний факт; амount в контракте был float вместо decimal-строки (как money в recognition-result.schema.json); правка "дата колонки" не имела счётчика — явно отнесена к fixes_header_*; правило хранения S3 при удалении файла (OQ-179) не было согласовано между domain-rules, architecture и spec.md (spec.md ошибочно утверждал, что uploads экспирится в отличие от raw — не экспирится ни то, ни другое, если на них ссылается факт). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
2676dfd731
commit
8abbf50dd3
@ -223,7 +223,9 @@ sha256, size, expires_at). Lifecycle-правила — по префиксам.
|
|||||||
известен до записи в БД; имя очищается от путей и управляющих символов); `results/{file_id}/{job_id}/…` пишет сервис
|
известен до записи в БД; имя очищается от путей и управляющих символов); `results/{file_id}/{job_id}/…` пишет сервис
|
||||||
распознавания, а ядро перед чтением проверяет, что ключ лежит именно в папке задания (OQ-70). При удалении файла объектам
|
распознавания, а ядро перед чтением проверяет, что ключ лежит именно в папке задания (OQ-70). При удалении файла объектам
|
||||||
`uploads` и `results` проставляется `s3_object.expires_at = now()`, физически они не стираются (ретеншн — OQ-111, ждёт
|
`uploads` и `results` проставляется `s3_object.expires_at = now()`, физически они не стираются (ретеншн — OQ-111, ждёт
|
||||||
lifecycle-правил платформы); объект, записанный в S3 до сбоя БД, остаётся сиротой (то же правило).
|
lifecycle-правил платформы); объект, записанный в S3 до сбоя БД, остаётся сиротой (то же правило). **v5.25:** исключение —
|
||||||
|
объект роли `raw` и объект `uploads`, на который ссылается `quality_intake_fact`: `expires_at` им не ставится вовсе
|
||||||
|
(вход для эталонного прогона метрик качества, срок — OQ-179).
|
||||||
|
|
||||||
## Аутентификация и авторизация
|
## Аутентификация и авторизация
|
||||||
|
|
||||||
|
|||||||
@ -1192,7 +1192,7 @@ attempt+1») устарела — исправить в следующей ве
|
|||||||
как у ОСВ (§3 п. 1, §9.1): ББ/ОФР за квартал версии (`Q1` — только версии за `Q1`, R-14a Среза 4).
|
как у ОСВ (§3 п. 1, §9.1): ББ/ОФР за квартал версии (`Q1` — только версии за `Q1`, R-14a Среза 4).
|
||||||
5. **Удаление** (`DELETE /files/{id}`, `DOCUMENT_EDIT`): запрещено, если у файла есть документ, подтверждённый пользователем (`confirmed_by` не NULL), или автоподтверждённый документ привязан к версии вне `INITIATED`/`DOCUMENTS_PROCESSING`/`DATA_CORRECTION` (`FILE_HAS_CONFIRMED_DOCUMENTS`; OQ-157, уточняет OQ-110); привязки автоподтверждённых документов снимаются, статус версий не меняется, `FILE_DELETE.after_value.unbound_version_ids`; запрещено также, если файл заменил другие (`FILE_SUPERSEDES_OTHERS`); `SUPERSEDED` файл удалить
|
5. **Удаление** (`DELETE /files/{id}`, `DOCUMENT_EDIT`): запрещено, если у файла есть документ, подтверждённый пользователем (`confirmed_by` не NULL), или автоподтверждённый документ привязан к версии вне `INITIATED`/`DOCUMENTS_PROCESSING`/`DATA_CORRECTION` (`FILE_HAS_CONFIRMED_DOCUMENTS`; OQ-157, уточняет OQ-110); привязки автоподтверждённых документов снимаются, статус версий не меняется, `FILE_DELETE.after_value.unbound_version_ids`; запрещено также, если файл заменил другие (`FILE_SUPERSEDES_OTHERS`); `SUPERSEDED` файл удалить
|
||||||
можно. Удаляются строки, документы (вместе с данными форм `financial_statement_*` и комментариями документов), задания и `stored_file`; объектам S3 ставится `expires_at = now()` (физически не
|
можно. Удаляются строки, документы (вместе с данными форм `financial_statement_*` и комментариями документов), задания и `stored_file`; объектам S3 ставится `expires_at = now()` (физически не
|
||||||
стираются — OQ-111); аудит `FILE_DELETE` с карточкой файла в `before_value`. **OQ-110 закрыт для Среза 4:** к версии
|
стираются — OQ-111) — **кроме** объекта роли `raw` и объекта `uploads`, на который ссылается `quality_intake_fact` (`upload_s3_key`): им `expires_at` не ставится вовсе, срок — OQ-179 (§14); аудит `FILE_DELETE` с карточкой файла в `before_value`. **OQ-110 закрыт для Среза 4:** к версии
|
||||||
привязываются только `CONFIRMED` документы, а файл с таким документом удалить нельзя — удалить файл с документом в
|
привязываются только `CONFIRMED` документы, а файл с таким документом удалить нельзя — удалить файл с документом в
|
||||||
версии невозможно; расширять удаление не требуется (уточнено OQ-157: для автоподтверждённых документов — см. выше).
|
версии невозможно; расширять удаление не требуется (уточнено OQ-157: для автоподтверждённых документов — см. выше).
|
||||||
6. **Аудит.** `FILE_UPLOAD`, `FILE_REPLACE`, `FILE_DELETE`, `FILE_REPROCESS` (п. 13, v5.21), `DOCUMENT_CONFIRM` (раздел
|
6. **Аудит.** `FILE_UPLOAD`, `FILE_REPLACE`, `FILE_DELETE`, `FILE_REPROCESS` (п. 13, v5.21), `DOCUMENT_CONFIRM` (раздел
|
||||||
@ -2086,6 +2086,10 @@ Excel — §11.2, раскладка — §11.3, строки — §11.4, зна
|
|||||||
Факт `quality_intake_fact`/`quality_outcome_fact` пишется на документ типа `BALANCE`, `INCOME`, `OSV`.
|
Факт `quality_intake_fact`/`quality_outcome_fact` пишется на документ типа `BALANCE`, `INCOME`, `OSV`.
|
||||||
`DISCLOSURE`/`OTHER` факт не получают (это расшифровки и прочие документы — не форма с правилами подсчёта).
|
`DISCLOSURE`/`OTHER` факт не получают (это расшифровки и прочие документы — не форма с правилами подсчёта).
|
||||||
|
|
||||||
|
Для `OSV` `payload` обоих фактов **не содержит `lines`/`values`** — построчного снимка нет (правок сумм ОСВ в ядре
|
||||||
|
нет, §14.2 для ОСВ не применяется, кроме строки «Период / счёт ОСВ»); вместо них — счётчики строк и предупреждения
|
||||||
|
`OSV_*` (`osv_warnings`). Для `BALANCE`/`INCOME` `payload` содержит `lines`/`values` построчно.
|
||||||
|
|
||||||
### §14.2. Атрибуция правки (сервис / наш разбор)
|
### §14.2. Атрибуция правки (сервис / наш разбор)
|
||||||
|
|
||||||
Правка — отличие подтверждённого результата от снимка приёма (`quality_intake_fact.payload`). Правило одно: **верный
|
Правка — отличие подтверждённого результата от снимка приёма (`quality_intake_fact.payload`). Правило одно: **верный
|
||||||
@ -2101,6 +2105,10 @@ tabular-parser, поэтому у Excel любая ошибка распозна
|
|||||||
| Дата колонки | подтверждённая дата извлекается из подписи колонки (`label`) | сервис |
|
| Дата колонки | подтверждённая дата извлекается из подписи колонки (`label`) | сервис |
|
||||||
| Период / счёт ОСВ | всегда «наш разбор» (Excel) | — |
|
| Период / счёт ОСВ | всегда «наш разбор» (Excel) | — |
|
||||||
|
|
||||||
|
Правка «Дата колонки» считается вместе с «Шапкой»: обе пишутся как `fixes_header_service`/`fixes_header_ours`
|
||||||
|
(`quality_outcome_fact`) и как `fixes[].field = "header"` (`quality-facts.schema.json`) — отдельного счётчика для
|
||||||
|
колонки нет, это та же структурная область документа, что и шапка.
|
||||||
|
|
||||||
Сравнение — чистая функция (без БД), тем же кодом, что разбирал сервис (`arm_statements`: `fix_lookalikes`,
|
Сравнение — чистая функция (без БД), тем же кодом, что разбирал сервис (`arm_statements`: `fix_lookalikes`,
|
||||||
`parse_amount`, `parse_code`, `parse_date`, `default_rules`). Только два источника правки — «сервис» и «наш разбор»;
|
`parse_amount`, `parse_code`, `parse_date`, `default_rules`). Только два источника правки — «сервис» и «наш разбор»;
|
||||||
«документ клиента» (§14.4) — свойство исхода, не правки.
|
«документ клиента» (§14.4) — свойство исхода, не правки.
|
||||||
@ -2121,6 +2129,10 @@ tabular-parser, поэтому у Excel любая ошибка распозна
|
|||||||
Знаменатель долей — документы с `outcome ∈ {AUTO_CONFIRMED, CONFIRMED}`. Документы без исхода (нет
|
Знаменатель долей — документы с `outcome ∈ {AUTO_CONFIRMED, CONFIRMED}`. Документы без исхода (нет
|
||||||
`quality_outcome_fact`) — отдельный столбец «в работе», в доли не входит.
|
`quality_outcome_fact`) — отдельный столбец «в работе», в доли не входит.
|
||||||
|
|
||||||
|
У документа может быть несколько фактов `quality_outcome_fact` во времени (переподтверждение ОСВ после правки,
|
||||||
|
исход `*_AFTER_CONFIRM` поверх прежнего, §1.5 модели — `document_id` там не unique). Правило этого параграфа
|
||||||
|
применяется к **последнему** факту по `outcome_at`; более ранние факты того же документа в матрицу не входят.
|
||||||
|
|
||||||
### §14.4. «Документ клиента»
|
### §14.4. «Документ клиента»
|
||||||
|
|
||||||
Не правка, а исход: подтверждён **вручную** (не `AUTO_CONFIRMED`) с акцептом нарушенных **жёстких** проверок §7
|
Не правка, а исход: подтверждён **вручную** (не `AUTO_CONFIRMED`) с акцептом нарушенных **жёстких** проверок §7
|
||||||
|
|||||||
@ -4,9 +4,19 @@
|
|||||||
"title": "Payload фактов качества распознавания первички (AURORA-1759)",
|
"title": "Payload фактов качества распознавания первички (AURORA-1759)",
|
||||||
"description": "Структура jsonb-столбца payload таблиц quality_intake_fact и quality_outcome_fact (model/data-model-v5.25.dbml, context/05-domain-rules.md §14). quality_failure_fact не описан здесь — у него нет payload, все поля плоские. НЕ валидируется make check / scripts/validate_contracts.py: этот скрипт жёстко завязан на три сообщения NATS-контракта распознавания (request-/result-message-/result- примеры с дедупом и версией сообщения); payload фактов не ходит по NATS и не требует той же идемпотентности. Схему проверяет тест в arm-core, где payload формируется (см. openspec/changes/quality-metrics/design.md §2 этого репозитория).",
|
"description": "Структура jsonb-столбца payload таблиц quality_intake_fact и quality_outcome_fact (model/data-model-v5.25.dbml, context/05-domain-rules.md §14). quality_failure_fact не описан здесь — у него нет payload, все поля плоские. НЕ валидируется make check / scripts/validate_contracts.py: этот скрипт жёстко завязан на три сообщения NATS-контракта распознавания (request-/result-message-/result- примеры с дедупом и версией сообщения); payload фактов не ходит по NATS и не требует той же идемпотентности. Схему проверяет тест в arm-core, где payload формируется (см. openspec/changes/quality-metrics/design.md §2 этого репозитория).",
|
||||||
"$defs": {
|
"$defs": {
|
||||||
|
"money": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"pattern": "^-?\\d+(\\.\\d{1,2})?$",
|
||||||
|
"description": "Decimal как строка (как contracts/recognition-result.schema.json#/$defs/money) — деньги не float"
|
||||||
|
},
|
||||||
"intake-payload": {
|
"intake-payload": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Снимок документа на момент приёма результата (до подтверждения). Для OSV — без lines/values, только osv_warnings.",
|
"description": "Снимок документа на момент приёма результата (до подтверждения). Для OSV — без lines/values, только osv_warnings (взаимоисключение — см. dependentSchemas ниже).",
|
||||||
|
"dependentSchemas": {
|
||||||
|
"osv_warnings": {
|
||||||
|
"not": { "anyOf": [{ "required": ["lines"] }, { "required": ["values"] }] }
|
||||||
|
}
|
||||||
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"header": {
|
"header": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -38,7 +48,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"line_ref": { "type": "string" },
|
"line_ref": { "type": "string" },
|
||||||
"column": { "type": "string" },
|
"column": { "type": "string" },
|
||||||
"amount": { "type": ["number", "null"] },
|
"amount": { "$ref": "#/$defs/money" },
|
||||||
"string_value": { "type": ["string", "null"] },
|
"string_value": { "type": ["string", "null"] },
|
||||||
"source_text": { "type": ["string", "null"] },
|
"source_text": { "type": ["string", "null"] },
|
||||||
"confidence": { "type": ["number", "null"] },
|
"confidence": { "type": ["number", "null"] },
|
||||||
@ -83,7 +93,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["field", "after", "source", "rule"],
|
"required": ["field", "after", "source", "rule"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"field": { "enum": ["value", "code", "added_row", "header", "osv_period_account"] },
|
"field": { "enum": ["value", "code", "added_row", "header", "osv_period_account"], "description": "header покрывает и правку даты колонки — §14.2 считает их вместе" },
|
||||||
"before_raw": { "type": ["string", "null"], "description": "сырой текст сервиса на этом поле, если был" },
|
"before_raw": { "type": ["string", "null"], "description": "сырой текст сервиса на этом поле, если был" },
|
||||||
"before_recognized": { "description": "как было распознано до правки" },
|
"before_recognized": { "description": "как было распознано до правки" },
|
||||||
"after": { "description": "подтверждённое значение" },
|
"after": { "description": "подтверждённое значение" },
|
||||||
|
|||||||
@ -897,7 +897,7 @@ Table stored_file {
|
|||||||
status file_status [not null]
|
status file_status [not null]
|
||||||
version_no int [not null, default: 1, note: 'ADD ТЗ 8.2: версия при замене']
|
version_no int [not null, default: 1, note: 'ADD ТЗ 8.2: версия при замене']
|
||||||
superseded_by_file_id uuid [ref: > stored_file.id, note: 'ссылка на файл-замену; статус SUPERSEDED. Замена — файл ЦЕЛИКОМ со всеми документами в нём (решение 01.10)']
|
superseded_by_file_id uuid [ref: > stored_file.id, note: 'ссылка на файл-замену; статус SUPERSEDED. Замена — файл ЦЕЛИКОМ со всеми документами в нём (решение 01.10)']
|
||||||
is_scan boolean [note: 'v5.25: только PDF — признак текстового слоя при загрузке; true = цифровой (в т. ч. скрытый OCR-слой — допущение §14); NULL для не-PDF']
|
is_scan boolean [note: 'v5.25: только PDF — признак скана по отсутствию текстового слоя при загрузке; true = скан (нет текстового слоя); false = цифровой, в т. ч. скрытый OCR-слой — допущение §14; NULL для не-PDF']
|
||||||
current_recognition_job_id uuid [ref: > recognition_job.id, note: 'авторитетный прогон; его результат породил актуальные document']
|
current_recognition_job_id uuid [ref: > recognition_job.id, note: 'авторитетный прогон; его результат породил актуальные document']
|
||||||
uploaded_by uuid [ref: > app_user.id]
|
uploaded_by uuid [ref: > app_user.id]
|
||||||
uploaded_at timestamptz
|
uploaded_at timestamptz
|
||||||
|
|||||||
@ -43,12 +43,15 @@
|
|||||||
|
|
||||||
### Requirement: Сырой ответ ИС Сканирование регистрируется и не экспирится с файлом
|
### Requirement: Сырой ответ ИС Сканирование регистрируется и не экспирится с файлом
|
||||||
`s3_object` SHALL поддерживать роль бакета `raw`; `recognition_job.raw_s3_object_id` SHALL ссылаться на
|
`s3_object` SHALL поддерживать роль бакета `raw`; `recognition_job.raw_s3_object_id` SHALL ссылаться на
|
||||||
зарегистрированный объект для image pipeline. Удаление `stored_file` SHALL НЕ устанавливать `expires_at` объекту
|
зарегистрированный объект для image pipeline. Удаление `stored_file` SHALL НЕ устанавливать `expires_at` ни объекту
|
||||||
с ролью `raw`.
|
с ролью `raw`, ни объекту `uploads`, на который ссылается `quality_intake_fact` (`upload_s3_key`) — оба нужны
|
||||||
|
эталонному прогону (OQ-179). Объекты `results` и прочие `uploads` (без факта) — без изменений, `expires_at = now()`
|
||||||
|
при удалении файла, как и раньше.
|
||||||
|
|
||||||
#### Scenario: Удаление файла с зарегистрированным сырым ответом
|
#### Scenario: Удаление файла с зарегистрированным сырым ответом
|
||||||
- **WHEN** PDF-файл с заполненным `recognition_job.raw_s3_object_id` удаляется
|
- **WHEN** PDF-файл с заполненным `recognition_job.raw_s3_object_id` и существующим `quality_intake_fact` удаляется
|
||||||
- **THEN** `s3_object.expires_at` объекта роли `raw` остаётся `NULL`, в отличие от объектов ролей `uploads`/`results`
|
- **THEN** `s3_object.expires_at` остаётся `NULL` у объекта роли `raw` и у объекта `uploads`, на который ссылается
|
||||||
|
факт; у объекта `results` того же файла `expires_at` проставляется `now()`, как для любого удаления
|
||||||
того же файла
|
того же файла
|
||||||
|
|
||||||
### Requirement: Контракт payload не входит в `make check` этого репозитория
|
### Requirement: Контракт payload не входит в `make check` этого репозитория
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user