From 4126a44e0ea0bc87ee046bb6b4f03733ab762eff Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 7 Oct 2026 11:07:54 +0400 Subject: [PATCH] =?UTF-8?q?fix(statements):=20=D1=83=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BB=D0=B8=D1=88=D0=BD=D0=B8=D0=B9=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=BF=D1=83=D1=81=D0=BA=20=D1=8F=D0=B2=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D1=81=D0=BE=D0=BE=D1=82=D0=BD=D0=BE=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9=20=C2=A77,=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20?= =?UTF-8?q?=D0=BD=D0=B0=20=C2=AB=D0=BE=D1=82=D1=81=D1=83=D1=82=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D1=83=D1=8E=D1=89=D0=B8=D0=B9=20=D0=BA=D0=BE=D0=B4=20=3D?= =?UTF-8?q?=200=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Удалён пропуск явных правил, когда в колонке нет членов правой части: спека statement-checks требует считать отсутствующий код нулём, при ненулевой цели — жёсткое нарушение. Тест плана test_per_column_and_null_as_zero противоречил спеке (1600 = 1700 без строки 1700 — нарушение) и заменён двумя тестами по спеке. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2 --- api/src/domain/statement_checks.py | 5 ----- api/tests/test_domain_statement_checks.py | 24 ++++++++++++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/api/src/domain/statement_checks.py b/api/src/domain/statement_checks.py index a580eb5..f758e5b 100644 --- a/api/src/domain/statement_checks.py +++ b/api/src/domain/statement_checks.py @@ -188,11 +188,6 @@ def run_checks( for rule in [*explicit, *sublines]: if rule not in sublines and rule.target not in amounts: continue - # Для явных правил (не подстроки): пропустить, если целевой есть, но ни одного члена нет - if rule not in sublines: - term_codes = {code for _, code in rule.terms} - if not any(code in amounts for code in term_codes): - continue holds, expected, actual = _holds(amounts, rule, rules.tolerance) if holds: continue diff --git a/api/tests/test_domain_statement_checks.py b/api/tests/test_domain_statement_checks.py index 682cd24..e98a138 100644 --- a/api/tests/test_domain_statement_checks.py +++ b/api/tests/test_domain_statement_checks.py @@ -73,14 +73,24 @@ def test_sublines_soft() -> None: assert report.failed == () -def test_per_column_and_null_as_zero() -> None: - report = run_checks( - "BALANCE", - _lines(("1100", {0: "300", 1: None}), ("1200", {0: "600", 1: "5"}), ("1600", {0: "900", 1: "5"})), - [0, 1], - RULES, +def test_target_present_no_terms_hard_failure() -> None: + # Спека: отсутствующий код = 0; если цель присутствует но членов нет, это нарушение + # Правило 1600 = 1100 + 1200, в колонке только 1600=1000 → 1600 ≠ 0, жёсткое нарушение + report = run_checks("BALANCE", _lines(("1600", {0: "1000"})), [0], RULES) + assert any( + item.relation == "1600 = 1100 + 1200" and item.expected == D("1000") and item.actual == D("0") + for item in report.failed + ) + + +def test_target_without_self_sum_hard_failure() -> None: + # Спека: пропуск только если цель отсутствует; если цель есть, правило проверяется + # Правило 1600 = 1700, в колонке только 1600=1000 (нет 1300, 1400, 1500) → нарушение + report = run_checks("BALANCE", _lines(("1600", {0: "1000"})), [0], RULES) + assert any( + item.relation == "1600 = 1700" and item.expected == D("1000") and item.actual == D("0") + for item in report.failed ) - assert report.failed == () @pytest.mark.parametrize(