From d82940a8acc246896e5fe5b5ac4df5e24a39eca0 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 6 Oct 2026 21:04:50 +0400 Subject: [PATCH] =?UTF-8?q?fix(statements):=20actual=20=D0=BD=D0=B0=D1=80?= =?UTF-8?q?=D1=83=D1=88=D0=B5=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BE=D1=82=D0=BD=D0=BE=D1=88=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=E2=80=94=20=D0=B1=D0=BB=D0=B8=D0=B6=D0=B0=D0=B9=D1=88=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=B2=D0=B0=D1=80=D0=B8=D0=B0=D0=BD=D1=82=20abs=5Fter?= =?UTF-8?q?ms=20(T14.I1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #14 Co-Authored-By: Claude Sonnet 5.5 Claude-Session: cedd7915-2efe-41e5-988c-a9117033598b --- api/src/parser/statements/checks.py | 5 ++++- .../unit/parser/statements/test_checks.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/api/src/parser/statements/checks.py b/api/src/parser/statements/checks.py index 4d52634..3daa136 100644 --- a/api/src/parser/statements/checks.py +++ b/api/src/parser/statements/checks.py @@ -42,7 +42,8 @@ def _holds(amounts: dict[str, Decimal], rule: RelationRule, tolerance: Decimal) """Проверка соотношения в пределах допуска. Вычисляет правую часть два раза: как записано и с abs() для членов в abs_terms. - Правило выполняется, если хотя бы один вариант в пределах tolerance. + Правило выполняется, если хотя бы один вариант в пределах tolerance. При нарушении возвращается вариант, ближайший + к левой части (при равенстве — «как записано»): аналитик видит реальную величину расхождения. """ expected = amounts.get(rule.target, _ZERO) plain = _rhs(amounts, rule, absolute=False) @@ -52,6 +53,8 @@ def _holds(amounts: dict[str, Decimal], rule: RelationRule, tolerance: Decimal) with_abs = _rhs(amounts, rule, absolute=True) if abs(expected - with_abs) <= tolerance: return True, expected, with_abs + if abs(expected - with_abs) < abs(expected - plain): + return False, expected, with_abs return False, expected, plain diff --git a/api/tests/unit/parser/statements/test_checks.py b/api/tests/unit/parser/statements/test_checks.py index 28d9d78..2d7cde2 100644 --- a/api/tests/unit/parser/statements/test_checks.py +++ b/api/tests/unit/parser/statements/test_checks.py @@ -213,3 +213,22 @@ def test_failed_and_empty_relation_lists_reach_the_wire(rules: Rules) -> None: assert wire["soft_relations_failed"] == [] assert clean.to_wire()["relations_failed"] == [] assert clean.to_wire()["soft_relations_failed"] == [] + + +def test_failed_relation_reports_the_nearest_variant_as_actual(rules: Rules) -> None: + """T14.I1: при нарушении `actual` — вариант (как записано / с abs_terms), ближайший к `expected`.""" + lines = [line("2110", "1000"), line("2120", "-600"), line("2100", "410")] # как записано 1600, с abs 400 + + checks, _ = run_statement_checks(lines, 1, "INCOME", rules) + + failed = {item.relation: item for item in checks.relations_failed} + assert failed["2100 = 2110 - 2120"].expected == Decimal("410") + assert failed["2100 = 2110 - 2120"].actual == Decimal("400") + + +def test_failed_relation_keeps_as_written_variant_on_a_tie(rules: Rules) -> None: + lines = [line("2110", "1000"), line("2120", "0"), line("2100", "900")] # оба варианта дают 1000 + + checks, _ = run_statement_checks(lines, 1, "INCOME", rules) + + assert {item.relation: item.actual for item in checks.relations_failed}["2100 = 2110 - 2120"] == Decimal("1000")