Добавлены evaluation.py (EvalLine/EvalValue/is_check_line) и checks.py (form_variant, subline_parents, evaluate_relations, checks_from). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
108 lines
5.3 KiB
Python
108 lines
5.3 KiB
Python
from decimal import Decimal as D
|
|
|
|
from arm_statements.checks import checks_from, evaluate_relations, form_variant, subline_parents
|
|
from arm_statements.evaluation import EvalLine, EvalValue, is_check_line
|
|
from arm_statements.loader import Rules
|
|
from arm_statements.model import DocumentType, FormVariant, LineStatus, StatementChecks
|
|
|
|
BAL, INC = DocumentType.BALANCE, DocumentType.INCOME
|
|
|
|
|
|
def line(code: str | None, *amounts: str | None, status: LineStatus = LineStatus.OK, order: int = 0) -> EvalLine:
|
|
values = tuple(EvalValue(str(i), None if a is None else D(a), a, 100.0, True) for i, a in enumerate(amounts))
|
|
return EvalLine(f"k{order}{code}", order, status, code, 100.0, False, values)
|
|
|
|
|
|
def failed(lines: list[EvalLine], doc: DocumentType, variant: FormVariant | None, rules: Rules) -> StatementChecks:
|
|
outcomes = evaluate_relations(lines, ["0"], doc, variant, rules)
|
|
return checks_from(outcomes, {"0": 0})
|
|
|
|
|
|
def test_form_variant() -> None:
|
|
assert form_variant(BAL, ["1150", "1210", "1600", "1310", "1700"]) is FormVariant.SIMPLIFIED
|
|
assert form_variant(BAL, ["1100", "1600"]) is FormVariant.FULL
|
|
assert form_variant(INC, ["2110", "2400"]) is FormVariant.SIMPLIFIED
|
|
assert form_variant(INC, ["2110", "2100", "2400"]) is FormVariant.FULL
|
|
assert form_variant(DocumentType.DISCLOSURE, ["5101"]) is None
|
|
|
|
|
|
def test_empty_target_is_violation(rules: Rules) -> None:
|
|
lines = [
|
|
line("1110", "30"),
|
|
line("1100", "30"),
|
|
line("1210", "70"),
|
|
line("1200", "70"),
|
|
line(None, "100", status=LineStatus.DETAIL),
|
|
]
|
|
checks = failed(lines, BAL, FormVariant.FULL, rules)
|
|
assert [f.relation for f in checks.relations_failed] == ["1600 = 1100 + 1200"]
|
|
assert (checks.relations_failed[0].expected, checks.relations_failed[0].actual) == (None, D("100"))
|
|
|
|
|
|
def test_target_without_members_is_violation(rules: Rules) -> None:
|
|
lines = [line("1110", "30"), line("1100", "30"), line("1210", "70"), line("1200", "70"), line("1600", "100")]
|
|
checks = failed(lines, BAL, FormVariant.FULL, rules)
|
|
assert [f.relation for f in checks.relations_failed] == ["1600 = 1700"]
|
|
assert checks.relations_failed[0].codes == ("1600", "1700")
|
|
assert (checks.relations_failed[0].expected, checks.relations_failed[0].actual) == (D("100"), None)
|
|
|
|
|
|
def test_zero_target_with_empty_members_is_violation(rules: Rules) -> None:
|
|
lines = [line("1600", "0"), line("1110", "0"), line("1100", "0")]
|
|
hard = failed(lines, BAL, FormVariant.FULL, rules).relations_failed
|
|
assert [(f.relation, f.expected, f.actual) for f in hard] == [("1600 = 1700", D("0"), None)]
|
|
|
|
|
|
def test_duplicate_code_is_summed(rules: Rules) -> None:
|
|
lines = [line("2300", "849"), line("2410", "100"), line("2410", "649"), line("2400", "100")]
|
|
outcomes = evaluate_relations(lines, ["0"], INC, FormVariant.FULL, rules)
|
|
target = next(o for o in outcomes if o.rule.relation == "2400 = 2300 - 2410 + 2420")
|
|
assert target.within_tolerance
|
|
|
|
|
|
def test_simplified_income_soft(rules: Rules) -> None:
|
|
lines = [line("2110", "1000"), line("2120", "-600"), line("2410", "50"), line("2400", "500")]
|
|
checks = failed(lines, INC, FormVariant.SIMPLIFIED, rules)
|
|
assert checks.relations_failed == ()
|
|
assert [f.relation for f in checks.soft_relations_failed] == ["2400 = 2110 - 2120 - 2330 + 2340 - 2350 - 2410"]
|
|
|
|
|
|
def test_rounding_goes_soft(rules: Rules) -> None:
|
|
lines = [
|
|
line("1600", "17000000009.02"),
|
|
line("1700", "17000000000"),
|
|
line("1100", "17000000009.02"),
|
|
line("1300", "17000000000"),
|
|
]
|
|
checks = failed(lines, BAL, FormVariant.FULL, rules)
|
|
assert "1600 = 1700" in [f.relation for f in checks.soft_relations_failed]
|
|
assert "1600 = 1700" not in [f.relation for f in checks.relations_failed]
|
|
|
|
|
|
def test_abs_terms_hold_with_minus(rules: Rules) -> None:
|
|
lines = [line("2110", "100"), line("2120", "-60"), line("2100", "40")]
|
|
hard = failed(lines, INC, FormVariant.FULL, rules).relations_failed
|
|
assert "2100 = 2110 - 2120" not in [f.relation for f in hard]
|
|
|
|
|
|
def test_sublines(rules: Rules) -> None:
|
|
assert subline_parents(["1230", "12301", "12302", "1240", "11101"]) == [None, "1230", "1230", None, "1110"]
|
|
# расхождение 2 (>1): в плане было 10 против 4 + 5 = 9, это в пределах допуска ±1 — спека «Шаг 7» (T13.1)
|
|
lines = [line("1230", "10"), line("12301", "4"), line("12302", "4")]
|
|
soft = failed(lines, BAL, FormVariant.FULL, rules).soft_relations_failed
|
|
assert [f.relation for f in soft] == ["1230 = 12301 + 12302"]
|
|
within = [line("1230", "10"), line("12301", "4"), line("12302", "5")]
|
|
assert failed(within, BAL, FormVariant.FULL, rules).soft_relations_failed == ()
|
|
empty = [line("1230", "10"), line("12301", None), line("12302", None)]
|
|
assert failed(empty, BAL, FormVariant.FULL, rules).soft_relations_failed == ()
|
|
|
|
|
|
def test_is_check_line() -> None:
|
|
assert is_check_line(line("1600", "1"))
|
|
assert not is_check_line(line("1600", "1", status=LineStatus.DETAIL))
|
|
assert not is_check_line(line(None, "1"))
|
|
correction = EvalLine("c", None, LineStatus.OK, "1600", 100.0, False, line("1600", None).values)
|
|
assert not is_check_line(correction)
|
|
with_amount = EvalLine("c", None, LineStatus.OK, "1600", 100.0, False, line("1600", "5").values)
|
|
assert is_check_line(with_amount)
|