From b87b3cefd70a68667dc32178f27b8fbe85e7dd02 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Mon, 5 Oct 2026 20:24:20 +0400 Subject: [PATCH] =?UTF-8?q?contract:=20=D0=BA=D0=BE=D0=BF=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=D1=83=D0=BB=D1=8C=D1=82=D0=B0=D1=82=D0=B0=20?= =?UTF-8?q?v1.1=20=E2=80=94=20rows[].extra,=20=D0=BF=D1=80=D0=B8=D0=BC?= =?UTF-8?q?=D0=B5=D1=80=D1=8B=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Схема recognition-result и примеры result-* скопированы байт в байт из docs/contracts (v1.1); OsvRow.extra добавлен в pydantic-модель; в тест примеров добавлен result-osv-needs-check.json. CONTRACT_VERSION не менялся: ядро — потребитель, принимает любой MINOR 1.x, запрос не менялся. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2 --- .../examples/result-osv-minimal.json | 5 +- .../examples/result-osv-needs-check.json | 125 ++++++++++++++++++ api/contracts/recognition-result.schema.json | 7 +- api/src/contract/result.py | 1 + api/tests/test_contract.py | 3 +- 5 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 api/contracts/examples/result-osv-needs-check.json diff --git a/api/contracts/examples/result-osv-minimal.json b/api/contracts/examples/result-osv-minimal.json index 6b6d122..f883a8d 100644 --- a/api/contracts/examples/result-osv-minimal.json +++ b/api/contracts/examples/result-osv-minimal.json @@ -1,5 +1,5 @@ { - "schema_version": "1.0", + "schema_version": "1.1", "job_id": "3f2a9c1e-7b4d-4e0a-9d51-2c8e6a1b0f01", "file_id": "a1c4e7d2-5b36-4f89-8e12-9d0c3b7a4e02", "service": { "name": "tabular-parser", "version": "0.1.0" }, @@ -66,7 +66,8 @@ "turnover_debit": "500.00", "turnover_credit": "200.50", "closing_debit": "0.00", - "closing_credit": "700.50" + "closing_credit": "700.50", + "extra": { "КПП": "352801001" } } ], "checks": { diff --git a/api/contracts/examples/result-osv-needs-check.json b/api/contracts/examples/result-osv-needs-check.json new file mode 100644 index 0000000..81d64a8 --- /dev/null +++ b/api/contracts/examples/result-osv-needs-check.json @@ -0,0 +1,125 @@ +{ + "schema_version": "1.1", + "job_id": "3f2a9c1e-7b4d-4e0a-9d51-2c8e6a1b0f01", + "file_id": "a1c4e7d2-5b36-4f89-8e12-9d0c3b7a4e02", + "service": { "name": "tabular-parser", "version": "1.0.0" }, + "status": "needs_check", + "metrics": { + "rows": 3, + "warnings": [ + "sheet=Лист_1 hierarchy=outline", + "§1.4: сальдо на начало + обороты ≠ сальдо на конец (±0.01): строки 3" + ] + }, + "documents": [ + { + "document_type": "OSV", + "locator": "sheet:Лист_1", + "header": { + "organization_name": "ООО Ромашка", + "organization_inn": null, + "account_code_raw": "60", + "period_text": "1 квартал 2025 г.", + "period_year": 2025, + "period_quarter": "Q1", + "is_cumulative": false, + "data_type": "BU", + "currency": "RUB", + "header_variant": 1 + }, + "group_schema": [ + { "type": "ACCOUNT", "name": "Счет" }, + { "type": "COUNTERPARTY", "name": "Контрагенты", "has_inn": true } + ], + "columns": { + "opening_debit": "D", + "opening_credit": "E", + "turnover_debit": "F", + "turnover_credit": "G", + "closing_debit": "H", + "closing_credit": "I", + "inn": "B" + }, + "rows": [ + { + "row_number": 1, + "level": 0, + "parent_row_number": null, + "row_value": "60", + "group_type": "ACCOUNT", + "group_name": "Счет", + "account_code_raw": "60", + "counterparty_name": null, + "counterparty_inn": null, + "data_type": "BU", + "opening_debit": "0.00", + "opening_credit": "1000.00", + "turnover_debit": "500.00", + "turnover_credit": "200.50", + "closing_debit": "0.00", + "closing_credit": "700.50" + }, + { + "row_number": 2, + "level": 1, + "parent_row_number": 1, + "row_value": "ООО Лютик", + "group_type": "COUNTERPARTY", + "group_name": "Контрагенты", + "account_code_raw": "60", + "counterparty_name": "ООО Лютик", + "counterparty_inn": "7711111111", + "data_type": "BU", + "opening_debit": "0.00", + "opening_credit": "1000.00", + "turnover_debit": "500.00", + "turnover_credit": "200.50", + "closing_debit": "0.00", + "closing_credit": "700.50", + "extra": { "КПП": "352801001" } + }, + { + "row_number": 3, + "level": 2, + "parent_row_number": 2, + "row_value": "Вал.", + "group_type": "OTHER", + "group_name": null, + "account_code_raw": "60", + "counterparty_name": null, + "counterparty_inn": null, + "data_type": "CURRENCY", + "opening_debit": "0.00", + "opening_credit": "400.00", + "turnover_debit": "100.00", + "turnover_credit": "50.00", + "closing_debit": "0.00", + "closing_credit": "400.00" + }, + { + "row_number": 4, + "level": 0, + "parent_row_number": null, + "row_value": "Итого", + "group_type": "TOTAL", + "group_name": null, + "account_code_raw": null, + "counterparty_name": null, + "counterparty_inn": null, + "data_type": "BU", + "opening_debit": "0.00", + "opening_credit": "1000.00", + "turnover_debit": "500.00", + "turnover_credit": "200.50", + "closing_debit": "0.00", + "closing_credit": "700.50" + } + ], + "checks": { + "total_row_matches_root": true, + "balance_identity_violations": [3], + "parent_sum_violations": [] + } + } + ] +} diff --git a/api/contracts/recognition-result.schema.json b/api/contracts/recognition-result.schema.json index c5eea87..5d329a8 100644 --- a/api/contracts/recognition-result.schema.json +++ b/api/contracts/recognition-result.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "arm-ka/recognition-result/v1", - "title": "Результат распознавания файла (tabular-parser / scan-gateway), контракт v1.0", + "title": "Результат распознавания файла (tabular-parser / scan-gateway), контракт v1.1", "description": "Один файл (stored_file) → один job → N документов. Сервисы не знают БД: только структура файла. Нормализация счетов, резолв контрагентов, проверки — в arm-core.", "$comment": "Версионирование: schema_version = MAJOR.MINOR. MINOR (1.1) — необязательные поля и новые значения enum; MAJOR (2.0) — удаление/переименование полей, смена типов или семантики. Потребитель принимает тот же MAJOR и любой MINOR. Новый MINOR раскатывается сначала на потребителей (ядро и сервисы обновляют копии схем раньше, чем производитель начнёт слать 1.1), потому что закрытые enum отвергают незнакомые значения.", "type": "object", "required": ["schema_version", "job_id", "file_id", "service", "status", "documents"], "properties": { - "schema_version": { "type": "string", "pattern": "^1\\.\\d+$", "description": "MAJOR.MINOR; производитель контракта v1.0 пишет \"1.0\"" }, + "schema_version": { "type": "string", "pattern": "^1\\.\\d+$", "description": "MAJOR.MINOR; производитель контракта v1.1 пишет \"1.1\"" }, "job_id": { "type": "string", "format": "uuid" }, "file_id": { "type": "string", "format": "uuid" }, "service": { @@ -114,7 +114,8 @@ "turnover_debit": { "$ref": "#/$defs/money" }, "turnover_credit": { "$ref": "#/$defs/money" }, "closing_debit": { "$ref": "#/$defs/money" }, - "closing_credit": { "$ref": "#/$defs/money" } + "closing_credit": { "$ref": "#/$defs/money" }, + "extra": { "type": ["object", "null"], "description": "нераспознанные колонки внутри таблицы: заголовок колонки → текст ячейки (§1.2)", "additionalProperties": { "type": ["string", "null"] } } } } }, diff --git a/api/src/contract/result.py b/api/src/contract/result.py index d591872..5ed740c 100644 --- a/api/src/contract/result.py +++ b/api/src/contract/result.py @@ -100,6 +100,7 @@ class OsvRow(ContractModel): turnover_credit: Money = None closing_debit: Money = None closing_credit: Money = None + extra: dict[str, str | None] | None = None # нераспознанные колонки таблицы: заголовок → текст ячейки (§1.2) class OsvChecks(ContractModel): diff --git a/api/tests/test_contract.py b/api/tests/test_contract.py index 4d315f9..b498efa 100644 --- a/api/tests/test_contract.py +++ b/api/tests/test_contract.py @@ -54,9 +54,10 @@ EXAMPLE_KIND = { "result-message-processing.json": "message", "result-error.json": "result", "result-osv-minimal.json": "result", + "result-osv-needs-check.json": "result", "result-statement-minimal.json": "result", } -EXAMPLES_COUNT = 8 +EXAMPLES_COUNT = 9 def _load(path: Path) -> Any: