From eeb0ce48da01d52a28abcc840d7bb1ea1712a2b1 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Fri, 6 Nov 2026 18:26:17 +0400 Subject: [PATCH] =?UTF-8?q?=D0=93=D1=83=D0=B4=D0=B2=D0=B8=D0=BB=D0=BB:=20?= =?UTF-8?q?=D0=B5=D0=B4=D0=B8=D0=BD=D0=B8=D1=86=D1=8B=20=D0=91=D0=91=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D1=81=D0=BB=D0=BE=D0=B2=D0=B0=D1=80=D1=8E=20arm?= =?UTF-8?q?=5Fstatements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit StatementsEngine.canonical_units — нормализация подписи единиц шаблонами пакета; УК ДО переводится в рубли и при единицах, исправленных аналитиком вручную. Co-Authored-By: Claude Opus 5.5 --- api/src/services/goodwill_source.py | 5 ++++- api/src/services/statements_engine.py | 9 +++++++++ api/tests/fake_engine.py | 3 +++ api/tests/test_goodwill_source.py | 26 ++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/api/src/services/goodwill_source.py b/api/src/services/goodwill_source.py index f5f2b20..762eaeb 100644 --- a/api/src/services/goodwill_source.py +++ b/api/src/services/goodwill_source.py @@ -13,6 +13,9 @@ from src.domain.goodwill import Investment from src.domain.statement_aggregate import AggLine, aggregate_by_code from src.repositories import goodwill as repo from src.services.consolidated_osv import ConsolidatedOsvBuildResult, ConsolidatedOsvService +from src.services.statements_engine import default_engine + +UNIT_FACTORS: dict[str | None, Decimal] = {"руб.": Decimal(1), "тыс. руб.": Decimal(1000), "млн руб.": Decimal(1000000)} @dataclass @@ -26,7 +29,7 @@ class Sources: def capital_amounts(meta_id, units, columns, values, date) -> tuple[Decimal | None, Decimal | None]: selected = [c.id for c in columns if c.meta_id == meta_id and c.period_date == date] - factor = {"руб.": Decimal(1), "тыс. руб.": Decimal(1000), "млн руб.": Decimal(1000000)}.get(units) + factor = UNIT_FACTORS.get(default_engine().canonical_units(units)) if len(selected) != 1 or factor is None: return None, None lines = {} diff --git a/api/src/services/statements_engine.py b/api/src/services/statements_engine.py index 0ca01bd..7ca839a 100644 --- a/api/src/services/statements_engine.py +++ b/api/src/services/statements_engine.py @@ -29,6 +29,7 @@ from arm_statements import ( statements_version, status_input_from_contract, ) +from arm_statements.text import normalize_caption __all__ = [ "ArmStatementsEngine", @@ -62,6 +63,7 @@ class StatementsEngine(Protocol): def editions(self) -> tuple[Edition, ...]: ... def resolve_relative(self, kind: PeriodKind, effective_date: datetime.date | None) -> datetime.date | None: ... def tolerance(self) -> Decimal: ... + def canonical_units(self, text: str | None) -> str | None: ... class ArmStatementsEngine: @@ -105,6 +107,13 @@ class ArmStatementsEngine: def tolerance(self) -> Decimal: return self._rules.checks.tolerance + def canonical_units(self, text: str | None) -> str | None: + """Единица формы по словарю пакета («тыс.руб.», «в тысячах рублей» → «тыс. руб.»); не узнали — None.""" + if text is None: + return None + caption = normalize_caption(text) + return next((canonical for pattern, canonical in self._rules.units.spellings if pattern.search(caption)), None) + @functools.cache def default_engine() -> StatementsEngine: diff --git a/api/tests/fake_engine.py b/api/tests/fake_engine.py index 84fa0fd..3e840de 100644 --- a/api/tests/fake_engine.py +++ b/api/tests/fake_engine.py @@ -58,6 +58,9 @@ class FakeEngine: def version(self) -> str | None: return self._version + def canonical_units(self, text: str | None) -> str | None: + return text + def recompute(self, form: StoredForm) -> RecomputeResult: self.forms.append(form) return self.result diff --git a/api/tests/test_goodwill_source.py b/api/tests/test_goodwill_source.py index 1fa7bc0..b72bb9e 100644 --- a/api/tests/test_goodwill_source.py +++ b/api/tests/test_goodwill_source.py @@ -57,3 +57,29 @@ def test_capital_aggregates_duplicates_at_period_end() -> None: assert capital_amounts(mid, "тыс. руб.", columns, values, dt.date(2025, 9, 30)) == (Decimal("120000"), None) assert capital_amounts(mid, "тыс. руб.", columns, values, dt.date(2025, 6, 30)) == (None, None) assert capital_amounts(mid, None, columns, values, dt.date(2025, 9, 30)) == (None, None) + + +@pytest.mark.parametrize( + ("units", "factor"), + [ + ("тыс. руб.", Decimal(1000)), + ("тыс.руб.", Decimal(1000)), + ("в тысячах рублей", Decimal(1000)), + ("руб", Decimal(1)), + ("млн. руб.", Decimal(1000000)), + ], +) +def test_capital_units_spellings(units: str, factor: Decimal) -> None: + """Единицы, исправленные аналитиком вручную, читаются словарём единиц arm_statements.""" + mid, col = UUID(int=1), UUID(int=2) + columns = [N(id=col, meta_id=mid, period_date=dt.date(2025, 9, 30), is_current=True)] + line = N(id=UUID(int=4), meta_id=mid, line_code="1310", row_index=0, line_status=StatementLineStatus.ok) + values = [(line, col, Decimal("7"))] + assert capital_amounts(mid, units, columns, values, dt.date(2025, 9, 30)) == (Decimal("7") * factor, None) + + +def test_capital_unknown_units() -> None: + mid, col = UUID(int=1), UUID(int=2) + columns = [N(id=col, meta_id=mid, period_date=dt.date(2025, 9, 30), is_current=True)] + line = N(id=UUID(int=4), meta_id=mid, line_code="1310", row_index=0, line_status=StatementLineStatus.ok) + assert capital_amounts(mid, "штук", columns, [(line, col, Decimal("7"))], dt.date(2025, 9, 30)) == (None, None)