From 1b17f2e7482108071f5eb2f7fd7fd50601e147c1 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 6 Oct 2026 20:55:43 +0400 Subject: [PATCH] =?UTF-8?q?fix(statements):=20relations=5Ffailed=20=D0=B8?= =?UTF-8?q?=20soft=5Frelations=5Ffailed=20=D0=BF=D0=BE=D0=BF=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D1=8E=D1=82=20=D0=B2=20wire?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit StatementChecks собирался пустым и дополнялся append, а to_wire сериализует только заданные поля: нарушения не доходили до JSON. Списки теперь задаются явно, пустой список у проверенного документа сериализуется (checks.relations_failed = []). Refs #14 Co-Authored-By: Claude Sonnet 5.5 Claude-Session: cedd7915-2efe-41e5-988c-a9117033598b --- api/src/parser/statements/checks.py | 10 ++++--- .../unit/parser/statements/test_checks.py | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/api/src/parser/statements/checks.py b/api/src/parser/statements/checks.py index c7ce031..4d52634 100644 --- a/api/src/parser/statements/checks.py +++ b/api/src/parser/statements/checks.py @@ -77,7 +77,8 @@ def run_statement_checks( relations = list(config.relations_for(form)) subline_rules = _subline_rules(lines, form, rules) if config.subline_check else [] relations.extend(subline_rules) - checks = StatementChecks() + hard: list[FailedRelation] = [] + soft: list[FailedRelation] = [] warnings: list[str] = [] for column_index, amounts in enumerate(_amounts(lines, column_count)): for rule in relations: @@ -91,13 +92,14 @@ def run_statement_checks( continue failed = FailedRelation(column_index=column_index, relation=rule.relation, expected=expected, actual=actual) if rule.strict: - checks.relations_failed.append(failed) + hard.append(failed) warning_msg = f"соотношение «{rule.relation}» нарушено (колонка {column_index}): {expected} ≠ {actual}" warnings.append(warning_msg) else: - checks.soft_relations_failed.append(failed) + soft.append(failed) warning_msg = ( f"мягкое соотношение «{rule.relation}» нарушено (колонка {column_index}): {expected} ≠ {actual}" ) warnings.append(warning_msg) - return checks, warnings + # списки задаются явно: `to_wire` сериализует только заданные поля, пустой список = «проверено, сошлось» + return StatementChecks(relations_failed=hard, soft_relations_failed=soft), warnings diff --git a/api/tests/unit/parser/statements/test_checks.py b/api/tests/unit/parser/statements/test_checks.py index 8083ba0..28d9d78 100644 --- a/api/tests/unit/parser/statements/test_checks.py +++ b/api/tests/unit/parser/statements/test_checks.py @@ -187,3 +187,29 @@ def test_target_present_members_absent_as_zero(rules: Rules) -> None: # Правило 1100 = 1105 + … (strict: true) должно нарушиться failed_relations = [item.relation for item in checks.relations_failed] assert any("1100 =" in rel for rel in failed_relations) + + +def _full_balance(total: str) -> list[StatementLine]: + return [ + line("1150", "100"), + line("1100", "100"), + line("1210", "50"), + line("1200", "50"), + line("1600", total), + line("1310", "150"), + line("1300", "150"), + line("1700", "150"), + ] + + +def test_failed_and_empty_relation_lists_reach_the_wire(rules: Rules) -> None: + """Проверенный документ сериализует `relations_failed`/`soft_relations_failed` всегда: с нарушением и пустыми.""" + broken, _ = run_statement_checks(_full_balance("155"), 1, "BALANCE", rules) + clean, _ = run_statement_checks(_full_balance("150"), 1, "BALANCE", rules) + + wire = broken.to_wire() + assert [item["relation"] for item in wire["relations_failed"]] == ["1600 = 1100 + 1200", "1600 = 1700"] + assert wire["relations_failed"][0]["expected"] == "155" + assert wire["soft_relations_failed"] == [] + assert clean.to_wire()["relations_failed"] == [] + assert clean.to_wire()["soft_relations_failed"] == []