Критично: 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>
110 lines
6.2 KiB
JSON
110 lines
6.2 KiB
JSON
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"$id": "arm-ka/quality-facts/v1",
|
||
"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 этого репозитория).",
|
||
"$defs": {
|
||
"money": {
|
||
"type": ["string", "null"],
|
||
"pattern": "^-?\\d+(\\.\\d{1,2})?$",
|
||
"description": "Decimal как строка (как contracts/recognition-result.schema.json#/$defs/money) — деньги не float"
|
||
},
|
||
"intake-payload": {
|
||
"type": "object",
|
||
"description": "Снимок документа на момент приёма результата (до подтверждения). Для OSV — без lines/values, только osv_warnings (взаимоисключение — см. dependentSchemas ниже).",
|
||
"dependentSchemas": {
|
||
"osv_warnings": {
|
||
"not": { "anyOf": [{ "required": ["lines"] }, { "required": ["values"] }] }
|
||
}
|
||
},
|
||
"properties": {
|
||
"header": {
|
||
"type": "object",
|
||
"description": "header_source и распознанные поля шапки (period, inn, units, organization_name, …)",
|
||
"additionalProperties": true
|
||
},
|
||
"lines": {
|
||
"type": "array",
|
||
"description": "Отсутствует для document_type = OSV",
|
||
"items": {
|
||
"type": "object",
|
||
"required": ["ref", "line_status"],
|
||
"properties": {
|
||
"ref": { "type": "string" },
|
||
"row_index": { "type": ["integer", "null"] },
|
||
"line_code": { "type": ["string", "null"] },
|
||
"line_code_text": { "type": ["string", "null"] },
|
||
"line_status": { "enum": ["ok", "unreadable", "missing", "detail"] },
|
||
"needs_review": { "type": "boolean" }
|
||
}
|
||
}
|
||
},
|
||
"values": {
|
||
"type": "array",
|
||
"description": "Отсутствует для document_type = OSV",
|
||
"items": {
|
||
"type": "object",
|
||
"required": ["line_ref", "column"],
|
||
"properties": {
|
||
"line_ref": { "type": "string" },
|
||
"column": { "type": "string" },
|
||
"amount": { "$ref": "#/$defs/money" },
|
||
"string_value": { "type": ["string", "null"] },
|
||
"source_text": { "type": ["string", "null"] },
|
||
"confidence": { "type": ["number", "null"] },
|
||
"is_recognized": { "type": "boolean" },
|
||
"needs_review": { "type": "boolean" }
|
||
}
|
||
}
|
||
},
|
||
"issues": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "object",
|
||
"required": ["code"],
|
||
"properties": {
|
||
"code": { "type": "string" },
|
||
"level": { "enum": ["relation", "soft", "warning", "info"] }
|
||
}
|
||
}
|
||
},
|
||
"raw_line_codes": {
|
||
"type": "array",
|
||
"description": "Только PDF: четырёхзначные токены сырого ответа ИС, стоящие отдельной строкой — вход для §14.2 (добавленная строка)",
|
||
"items": { "type": "string", "pattern": "^\\d{4}$" }
|
||
},
|
||
"osv_warnings": {
|
||
"type": "array",
|
||
"description": "Только для document_type = OSV, вместо lines/values",
|
||
"items": { "type": "string" }
|
||
}
|
||
}
|
||
},
|
||
"outcome-payload": {
|
||
"type": "object",
|
||
"description": "Подтверждённое состояние (то же строение header/lines/values, что и intake-payload) плюс список правок",
|
||
"properties": {
|
||
"header": { "$ref": "#/$defs/intake-payload/properties/header" },
|
||
"lines": { "$ref": "#/$defs/intake-payload/properties/lines" },
|
||
"values": { "$ref": "#/$defs/intake-payload/properties/values" },
|
||
"fixes": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "object",
|
||
"required": ["field", "after", "source", "rule"],
|
||
"properties": {
|
||
"field": { "enum": ["value", "code", "added_row", "header", "osv_period_account"], "description": "header покрывает и правку даты колонки — §14.2 считает их вместе" },
|
||
"before_raw": { "type": ["string", "null"], "description": "сырой текст сервиса на этом поле, если был" },
|
||
"before_recognized": { "description": "как было распознано до правки" },
|
||
"after": { "description": "подтверждённое значение" },
|
||
"source": { "enum": ["service", "ours"], "description": "§14.2 — только два источника правки" },
|
||
"rule": { "type": "string", "description": "строка таблицы §14.2, которая сработала" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"$comment": "Корневая схема не инстанцируется напрямую: тест в arm-core валидирует quality_intake_fact.payload против #/$defs/intake-payload и quality_outcome_fact.payload против #/$defs/outcome-payload (не oneOf — оба $defs допускают пустой объект, и oneOf отверг бы его как совпавший с обоими)."
|
||
}
|