feat(statements): парсер ББ и ОФР из Excel, контракт v1.2 (Срез 2b) #16

Merged
aep merged 43 commits from feat/14-slice-2b-statement-parser into main 2026-10-06 21:38:00 +03:00
Showing only changes of commit d73836e30a - Show all commits

View File

@ -94,16 +94,24 @@ def test_relation_holds_with_abs_terms(rules: Rules) -> None:
one_c = [
line("2110", "1000"),
line("2120", "-600"),
line("2100", "400"), # 2100 = 2110 - 2120 = 1000 - (-600) = 1600 (plain) | 1000 - abs(-600) = 400 (abs)
line("2200", "400"), # 2200 = 2100 - 2210 - 2220 = 400 - 0 - 0 = 400 (plain) | abs
line("2300", "400"), # 2300 = 2200 + 2310 + … = 400 + 0 + … = 400
line("2100", "400"), # 1000 - (-600) = 1600 plain | 1000 - abs(-600) = 400 (abs) ✓
line("2210", "-50"),
line("2220", "-30"),
line("2200", "320"), # 400 - (-50) - (-30) = 480 plain | 400 - 50 - 30 = 320 (abs) ✓
line("2330", "-20"),
line("2350", "-10"),
line("2300", "290"), # 320 - (-20) - (-10) = 350 plain | 320 - 20 - 10 = 290 (abs) ✓
]
brackets = [
line("2110", "1000"),
line("2120", "600"),
line("2100", "400"), # 2100 = 2110 - 2120 = 1000 - 600 = 400
line("2200", "400"), # 2200 = 2100 - 2210 - 2220 = 400 - 0 - 0 = 400
line("2300", "400"), # 2300 = 2200 + … = 400
line("2100", "400"), # 1000 - 600 = 400 ✓
line("2210", "50"),
line("2220", "30"),
line("2200", "320"), # 400 - 50 - 30 = 320 ✓
line("2330", "20"),
line("2350", "10"),
line("2300", "290"), # 320 - 20 - 10 = 290 ✓
]
assert run_statement_checks(one_c, 1, "INCOME", rules)[0].relations_failed == []
@ -153,3 +161,29 @@ def test_null_values_count_as_zero_and_empty_form_passes(rules: Rules) -> None:
checks, _ = run_statement_checks(lines, 1, "BALANCE", rules)
assert checks.relations_failed == []
def test_target_absent_no_violations(rules: Rules) -> None:
"""R-6: Если цели правила нет в данных, проверка пропускается → нарушений нет."""
# INCOME: 2100, 2110, 2120 есть; 2200 НЕТУ → правило 2200 не проверяется
lines_income = [line("2110", "1000"), line("2120", "-600"), line("2100", "400")]
checks_income, _ = run_statement_checks(lines_income, 1, "INCOME", rules)
assert checks_income.relations_failed == []
assert checks_income.soft_relations_failed == []
# BALANCE: только 1150 БЕЗ 1100 → правило 1100 не проверяется (1100 это цель правила)
lines_balance = [line("1150", "100")]
checks_balance, _ = run_statement_checks(lines_balance, 1, "BALANCE", rules)
assert checks_balance.relations_failed == []
def test_target_present_members_absent_as_zero(rules: Rules) -> None:
"""R-6: Цель есть, члены отсутствуют (= 0) → соотношение с нарушением."""
# BALANCE: 1100 = 100 БЕЗ подстатей → 1100 = 0 + 0 + … = 0 (не совпадает с 100)
lines = [line("1100", "100")]
checks, _ = run_statement_checks(lines, 1, "BALANCE", rules)
# Правило 1100 = 1105 + … (strict: true) должно нарушиться
failed_relations = [item.relation for item in checks.relations_failed]
assert any("1100 =" in rel for rel in failed_relations)