feat(#190): метрики качества первички (AURORA-1759) #229
@ -10,7 +10,7 @@
|
||||
Объекты строк, значений и нарушений читаются атрибутами без привязки к сессии: отсутствующий атрибут даёт `None`.
|
||||
"""
|
||||
|
||||
from collections.abc import Iterable, Sequence
|
||||
from collections.abc import Iterable, Mapping, Sequence
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from enum import Enum
|
||||
@ -37,22 +37,33 @@ _VALUE_FIELDS = (
|
||||
_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,
|
||||
*,
|
||||
lines: Sequence[Any] | None = None,
|
||||
values: Sequence[Any] | None = None,
|
||||
issues: Iterable[Any] = (),
|
||||
osv_warnings: Iterable[str] = (),
|
||||
header_source: Mapping[str, Any] | None = None,
|
||||
osv_row_count: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Снимок «как распознано» для `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:
|
||||
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 {
|
||||
"header": _pick(document, _HEADER_FIELDS),
|
||||
"header": header,
|
||||
"lines": [_line(line) for line in lines or ()],
|
||||
"values": [_value(value) for value in values or ()],
|
||||
"issues": [_pick(issue, _ISSUE_FIELDS) for issue in issues],
|
||||
@ -67,13 +78,24 @@ def build_outcome_payload( # noqa: PLR0913 - сигнатура снимка ф
|
||||
issues: Iterable[Any] = (),
|
||||
osv_warnings: Iterable[str] = (),
|
||||
fixes: Iterable[Any] = (),
|
||||
header_source: Mapping[str, Any] | None = None,
|
||||
osv_row_count: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Подтверждённый результат и правки для `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)
|
||||
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]:
|
||||
return _pick(value, _VALUE_FIELDS)
|
||||
|
||||
|
||||
@ -15,6 +15,8 @@ from src.models.enums import (
|
||||
from src.models.files import Document
|
||||
from src.models.statements import DocumentIssue, FinancialStatementLine, FinancialStatementValue
|
||||
|
||||
ROW_COUNT = 4_200
|
||||
|
||||
|
||||
def make_document(document_type: DocumentType = DocumentType.BALANCE) -> 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["fixes"] == [fix]
|
||||
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