fix(#190): header_source и счётчик строк ОСВ в payload фактов качества (§14.1)
Снимки приёма и исхода получают необязательные kwargs: header_source (сырой текст шапки из financial_statement_meta.header_source) кладётся в header для ББ/ОФР, osv_row_count — в payload ОСВ как row_count. Без kwargs ключи не эмитятся. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
26529b3bef
commit
5f1001c808
@ -10,7 +10,7 @@
|
|||||||
Объекты строк, значений и нарушений читаются атрибутами без привязки к сессии: отсутствующий атрибут даёт `None`.
|
Объекты строк, значений и нарушений читаются атрибутами без привязки к сессии: отсутствующий атрибут даёт `None`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections.abc import Iterable, Sequence
|
from collections.abc import Iterable, Mapping, Sequence
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
@ -37,22 +37,33 @@ _VALUE_FIELDS = (
|
|||||||
_ISSUE_FIELDS = ("kind", "code", "severity", "source", "field", "relation", "codes", "line_ids")
|
_ISSUE_FIELDS = ("kind", "code", "severity", "source", "field", "relation", "codes", "line_ids")
|
||||||
|
|
||||||
|
|
||||||
def build_intake_payload(
|
def build_intake_payload( # noqa: PLR0913 - сигнатура снимка фиксирована (design §4.1)
|
||||||
document: Document,
|
document: Document,
|
||||||
*,
|
*,
|
||||||
lines: Sequence[Any] | None = None,
|
lines: Sequence[Any] | None = None,
|
||||||
values: Sequence[Any] | None = None,
|
values: Sequence[Any] | None = None,
|
||||||
issues: Iterable[Any] = (),
|
issues: Iterable[Any] = (),
|
||||||
osv_warnings: Iterable[str] = (),
|
osv_warnings: Iterable[str] = (),
|
||||||
|
header_source: Mapping[str, Any] | None = None,
|
||||||
|
osv_row_count: int | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Снимок «как распознано» для `quality_intake_fact.payload` (§14.1, design §4.1).
|
"""Снимок «как распознано» для `quality_intake_fact.payload` (§14.1, design §4.1).
|
||||||
|
|
||||||
`BALANCE` / `INCOME`: `{"header", "lines", "values", "issues"}`. `OSV`: `{"osv_warnings"}` без `lines` и `values`.
|
`BALANCE` / `INCOME`: `{"header", "lines", "values", "issues"}`; при переданном `header_source` (сырой текст и
|
||||||
|
уверенность по полям шапки, `financial_statement_meta.header_source`) он кладётся в `header["header_source"]`.
|
||||||
|
`OSV`: `{"osv_warnings"}` без `lines` и `values`; при переданном `osv_row_count` добавляется `row_count`.
|
||||||
|
Без необязательных аргументов ключей `header_source` и `row_count` в payload нет.
|
||||||
"""
|
"""
|
||||||
if document.document_type is DocumentType.OSV:
|
if document.document_type is DocumentType.OSV:
|
||||||
return {"osv_warnings": list(osv_warnings)}
|
payload: dict[str, Any] = {"osv_warnings": list(osv_warnings)}
|
||||||
|
if osv_row_count is not None:
|
||||||
|
payload["row_count"] = osv_row_count
|
||||||
|
return payload
|
||||||
|
header = _pick(document, _HEADER_FIELDS)
|
||||||
|
if header_source is not None:
|
||||||
|
header["header_source"] = {key: _plain_mapping(entry) for key, entry in header_source.items()}
|
||||||
return {
|
return {
|
||||||
"header": _pick(document, _HEADER_FIELDS),
|
"header": header,
|
||||||
"lines": [_line(line) for line in lines or ()],
|
"lines": [_line(line) for line in lines or ()],
|
||||||
"values": [_value(value) for value in values or ()],
|
"values": [_value(value) for value in values or ()],
|
||||||
"issues": [_pick(issue, _ISSUE_FIELDS) for issue in issues],
|
"issues": [_pick(issue, _ISSUE_FIELDS) for issue in issues],
|
||||||
@ -67,13 +78,24 @@ def build_outcome_payload( # noqa: PLR0913 - сигнатура снимка ф
|
|||||||
issues: Iterable[Any] = (),
|
issues: Iterable[Any] = (),
|
||||||
osv_warnings: Iterable[str] = (),
|
osv_warnings: Iterable[str] = (),
|
||||||
fixes: Iterable[Any] = (),
|
fixes: Iterable[Any] = (),
|
||||||
|
header_source: Mapping[str, Any] | None = None,
|
||||||
|
osv_row_count: int | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Подтверждённый результат и правки для `quality_outcome_fact.payload` (§14.1, design §4.2).
|
"""Подтверждённый результат и правки для `quality_outcome_fact.payload` (§14.1, design §4.2).
|
||||||
|
|
||||||
Строение — как у снимка приёма (`build_intake_payload`), плюс `fixes`: список правок с отнесением, каждая запись
|
Строение — как у снимка приёма (`build_intake_payload`, с теми же `header_source` и `osv_row_count`), плюс `fixes`:
|
||||||
строится вызывающим (состав записи — задача отнесения правок, не этой функции).
|
список правок с отнесением, каждая запись строится вызывающим (состав записи — задача отнесения правок, не этой
|
||||||
|
функции).
|
||||||
"""
|
"""
|
||||||
payload = build_intake_payload(document, lines=lines, values=values, issues=issues, osv_warnings=osv_warnings)
|
payload = build_intake_payload(
|
||||||
|
document,
|
||||||
|
lines=lines,
|
||||||
|
values=values,
|
||||||
|
issues=issues,
|
||||||
|
osv_warnings=osv_warnings,
|
||||||
|
header_source=header_source,
|
||||||
|
osv_row_count=osv_row_count,
|
||||||
|
)
|
||||||
payload["fixes"] = list(fixes)
|
payload["fixes"] = list(fixes)
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
@ -86,6 +108,13 @@ def _line(line: Any) -> dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _plain_mapping(entry: Any) -> Any:
|
||||||
|
"""Приводит запись `header_source` (`{"text", "confidence"}`) к JSON-совместимому виду; Decimal → строка."""
|
||||||
|
if isinstance(entry, Mapping):
|
||||||
|
return {key: _plain(item) for key, item in entry.items()}
|
||||||
|
return _plain(entry)
|
||||||
|
|
||||||
|
|
||||||
def _value(value: Any) -> dict[str, Any]:
|
def _value(value: Any) -> dict[str, Any]:
|
||||||
return _pick(value, _VALUE_FIELDS)
|
return _pick(value, _VALUE_FIELDS)
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,8 @@ from src.models.enums import (
|
|||||||
from src.models.files import Document
|
from src.models.files import Document
|
||||||
from src.models.statements import DocumentIssue, FinancialStatementLine, FinancialStatementValue
|
from src.models.statements import DocumentIssue, FinancialStatementLine, FinancialStatementValue
|
||||||
|
|
||||||
|
ROW_COUNT = 4_200
|
||||||
|
|
||||||
|
|
||||||
def make_document(document_type: DocumentType = DocumentType.BALANCE) -> Document:
|
def make_document(document_type: DocumentType = DocumentType.BALANCE) -> Document:
|
||||||
return Document(
|
return Document(
|
||||||
@ -125,3 +127,39 @@ def test_build_outcome_payload_osv_has_fixes_and_no_lines():
|
|||||||
assert payload["osv_warnings"] == ["OSV_SOMETHING"]
|
assert payload["osv_warnings"] == ["OSV_SOMETHING"]
|
||||||
assert payload["fixes"] == [fix]
|
assert payload["fixes"] == [fix]
|
||||||
assert "lines" not in payload and "values" not in payload
|
assert "lines" not in payload and "values" not in payload
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_intake_payload_includes_header_source_for_statement():
|
||||||
|
document = make_document(document_type=DocumentType.BALANCE)
|
||||||
|
header_source = {"inn": {"text": "7701234567", "confidence": Decimal("100.00")}}
|
||||||
|
payload = build_intake_payload(document, lines=[], values=[], header_source=header_source)
|
||||||
|
assert payload["header"]["header_source"] == {"inn": {"text": "7701234567", "confidence": "100.00"}}
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_outcome_payload_includes_header_source_for_statement():
|
||||||
|
document = make_document(document_type=DocumentType.INCOME)
|
||||||
|
header_source = {"okpo": {"text": "12345678", "confidence": None}}
|
||||||
|
payload = build_outcome_payload(document, lines=[], values=[], fixes=[], header_source=header_source)
|
||||||
|
assert payload["header"]["header_source"] == {"okpo": {"text": "12345678", "confidence": None}}
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_intake_payload_includes_osv_row_count():
|
||||||
|
document = make_document(document_type=DocumentType.OSV)
|
||||||
|
payload = build_intake_payload(document, osv_warnings=["OSV_SOMETHING"], osv_row_count=ROW_COUNT)
|
||||||
|
assert payload["row_count"] == ROW_COUNT
|
||||||
|
assert payload["osv_warnings"] == ["OSV_SOMETHING"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_outcome_payload_includes_osv_row_count():
|
||||||
|
document = make_document(document_type=DocumentType.OSV)
|
||||||
|
payload = build_outcome_payload(document, osv_warnings=[], fixes=[], osv_row_count=0)
|
||||||
|
assert payload["row_count"] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_header_source_and_row_count_absent_when_kwargs_omitted():
|
||||||
|
balance = build_intake_payload(make_document(document_type=DocumentType.BALANCE), lines=[], values=[])
|
||||||
|
assert "header_source" not in balance["header"]
|
||||||
|
osv = build_intake_payload(make_document(document_type=DocumentType.OSV), osv_warnings=[])
|
||||||
|
assert "row_count" not in osv
|
||||||
|
outcome_osv = build_outcome_payload(make_document(document_type=DocumentType.OSV), osv_warnings=[], fixes=[])
|
||||||
|
assert "row_count" not in outcome_osv
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user