Merge pull request 'Пустая детализация не проверяется соотношением подстрок (#45, AURORA-1527)' (#46) from feat/45-subline-empty into main

This commit is contained in:
aep 2026-10-16 18:35:27 +03:00
commit 3f740c969b
2 changed files with 40 additions and 0 deletions

View File

@ -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

View File

@ -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"
]