Andrey Epifancev 46ac9367ee chore(arm_statements): копия пакета arm-statements@6bce80b (#128)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:38:30 +04:00

109 lines
3.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Сериализация в json-совместимые значения и обратно (для golden-тестов и обмена)."""
from collections.abc import Mapping
from dataclasses import fields, is_dataclass
from datetime import date
from decimal import Decimal
from enum import Enum
from typing import Any
from arm_statements.model import (
Cell,
CellValue,
DocumentType,
Evidence,
FormCandidate,
HeaderCells,
Severity,
Source,
StatementWarning,
)
def _value_to_jsonable(value: CellValue) -> Any:
"""Тег типа значения ячейки; bool проверяется раньше прочего (не подкласс Decimal/date, но явно)."""
if value is None:
return None
if isinstance(value, bool):
return {"bool": value}
if isinstance(value, Decimal):
return {"decimal": str(value)}
return {"date": value.isoformat()}
def _scalar_to_jsonable(obj: object) -> Any:
if isinstance(obj, Enum):
return obj.value
if isinstance(obj, Decimal):
return str(obj)
if isinstance(obj, date):
return obj.isoformat()
return obj
def to_jsonable(obj: object) -> Any:
"""Dataclass пакета → json-совместимое значение."""
if isinstance(obj, Cell):
return {
"text": obj.text,
"value": _value_to_jsonable(obj.value),
"confidence": obj.confidence,
"ref": obj.ref,
}
if is_dataclass(obj) and not isinstance(obj, type):
return {f.name: to_jsonable(getattr(obj, f.name)) for f in fields(obj)}
if isinstance(obj, tuple | list):
return [to_jsonable(item) for item in obj]
return _scalar_to_jsonable(obj)
def _value_from_dict(raw: Mapping[str, Any] | None) -> CellValue:
if raw is None:
return None
if "decimal" in raw:
return Decimal(raw["decimal"])
if "date" in raw:
return date.fromisoformat(raw["date"])
if "bool" in raw:
return bool(raw["bool"])
raise ValueError(f"неизвестный тег значения ячейки: {sorted(raw)}")
def _cell_from_dict(raw: Mapping[str, Any] | None) -> Cell | None:
if raw is None:
return None
return Cell(
text=raw["text"],
value=_value_from_dict(raw["value"]),
confidence=float(raw["confidence"]),
ref=raw["ref"],
)
def _warning_from_dict(raw: Mapping[str, Any]) -> StatementWarning:
return StatementWarning(
code=raw["code"],
severity=Severity(raw["severity"]),
message=raw["message"],
line_refs=tuple(raw.get("line_refs", ())),
column_index=raw.get("column_index"),
field=raw.get("field"),
locator=raw.get("locator"),
)
def candidate_from_dict(data: Mapping[str, Any]) -> FormCandidate:
"""Обратная к `to_jsonable` для кандидата."""
type_hint = data["type_hint"]
evidence = data["evidence"]
return FormCandidate(
source=Source(data["source"]),
locator=data["locator"],
type_hint=None if type_hint is None else DocumentType(type_hint),
evidence=Evidence(okud=_cell_from_dict(evidence["okud"]), title=_cell_from_dict(evidence["title"])),
header=HeaderCells(**{name: _cell_from_dict(raw) for name, raw in data["header"].items()}),
rows=tuple(tuple(_cell_from_dict(raw) for raw in row) for row in data["rows"]),
segment_breaks=tuple(data.get("segment_breaks", ())),
warnings=tuple(_warning_from_dict(raw) for raw in data.get("warnings", ())),
)