diff --git a/api/src/parser/statements/checks.py b/api/src/parser/statements/checks.py index 338b199..563141c 100644 --- a/api/src/parser/statements/checks.py +++ b/api/src/parser/statements/checks.py @@ -6,6 +6,7 @@ Правило проверяется, если целевой код в данных (подстроки всегда). OQ-138: жёсткое нарушение не больше `rounding_tolerance_percent` от наибольшего |числа| соотношения — округление, уходит в мягкие. +AURORA-1527: «родитель = Σ подстрок» по колонке — только если хотя бы одна подстрока в ней непуста (amount не null). M-9: пятизначная подстрока относится к ближайшей четырёхзначной строке выше с теми же первыми тремя цифрами. """ @@ -33,6 +34,16 @@ def _amounts(lines: Sequence[StatementLine], column_count: int) -> list[dict[str return columns +def _filled(lines: Sequence[StatementLine], column_count: int) -> list[set[str]]: + """Коды строк, у которых в колонке есть значение (`amount` не null; ноль — значение).""" + columns: list[set[str]] = [set() for _ in range(column_count)] + for item in lines: + for value in item.values: + if 0 <= value.column_index < column_count and value.amount is not None: + columns[value.column_index].add(item.line_code) + return columns + + def _rhs(amounts: dict[str, Decimal], rule: RelationRule, *, absolute: bool) -> Decimal: total = _ZERO for sign, code in rule.terms: @@ -102,6 +113,7 @@ def run_statement_checks( hard: list[FailedRelation] = [] soft: list[FailedRelation] = [] warnings: list[str] = [] + filled = _filled(lines, column_count) for column_index, amounts in enumerate(_amounts(lines, column_count)): for rule in relations: # Явные правила из конфига пропускаются, если цель отсутствует в данных колонки. @@ -109,6 +121,9 @@ def run_statement_checks( is_subline = rule in subline_rules if not is_subline and rule.target not in amounts: continue + # AURORA-1527: детализации нет (все подстроки родителя в колонке пусты) — соотношение не проверяется + if is_subline and not any(code in filled[column_index] for _, code in rule.terms): + continue holds, expected, actual = _holds(amounts, rule, config.tolerance) if holds: continue diff --git a/api/tests/unit/parser/statements/test_checks.py b/api/tests/unit/parser/statements/test_checks.py index 95b9ab4..2a2f7ac 100644 --- a/api/tests/unit/parser/statements/test_checks.py +++ b/api/tests/unit/parser/statements/test_checks.py @@ -300,3 +300,28 @@ def test_rounding_base_is_the_largest_number_of_the_relation(rules: Rules) -> No assert checks.relations_failed == [] assert "2200 = 2100 - 2210 - 2220" in [item.relation for item in checks.soft_relations_failed] + + +# --- AURORA-1527: детализации нет — соотношение подстрок не проверяется --- + + +def test_sublines_all_empty_in_column_are_not_checked(rules: Rules) -> None: + lines = [line("1150", "90", "90"), line("11501", None, None), line("11502", None, None)] + checks, warnings = run_statement_checks(lines, 2, "BALANCE", rules) + assert [f for f in checks.soft_relations_failed if f.relation.startswith("1150 =")] == [] + assert not any("1150 =" in w for w in warnings) + + +def test_sublines_check_is_per_column(rules: Rules) -> None: + lines = [line("1150", "90", "90"), line("11501", None, "10"), line("11502", None, None)] + checks, _ = run_statement_checks(lines, 2, "BALANCE", rules) + failed = [f for f in checks.soft_relations_failed if f.relation == "1150 = 11501 + 11502"] + assert [(f.column_index, f.expected, f.actual) for f in failed] == [(1, Decimal("90"), Decimal("10"))] + + +def test_subline_zero_is_a_value(rules: Rules) -> None: + lines = [line("1150", "90"), line("11501", "0"), line("11502", None)] + checks, _ = run_statement_checks(lines, 1, "BALANCE", rules) + assert [f.relation for f in checks.soft_relations_failed if f.relation.startswith("1150 =")] == [ + "1150 = 11501 + 11502" + ]