docs(#202): контракт payload фактов качества первички

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-11-05 14:07:06 +04:00
parent 13eec70aba
commit 2af056df22

View File

@ -0,0 +1,99 @@
{
"$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": {
"intake-payload": {
"type": "object",
"description": "Снимок документа на момент приёма результата (до подтверждения). Для OSV — без lines/values, только osv_warnings.",
"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": { "type": ["number", "null"] },
"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"] },
"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 отверг бы его как совпавший с обоими)."
}