Andrey Epifancev 56c0f01d86 chore(arm_statements): копия пакета arm-statements@6bce80b (#44)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:38:33 +04:00

140 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from datetime import date
from decimal import Decimal as D
from arm_statements.columns import DocumentDates
from arm_statements.evaluation import EvalLine, EvalValue
from arm_statements.header import parse_header
from arm_statements.loader import Rules
from arm_statements.model import DocumentType, LineStatus
from arm_statements.review import evaluate, review_header
from arm_statements.tests.builders import hdr
OK, DETAIL, UNREADABLE = LineStatus.OK, LineStatus.DETAIL, LineStatus.UNREADABLE
NO_DATES = DocumentDates(None, None, None, None)
def ln( # noqa: PLR0913, PLR0917 -- сигнатура задана планом (Task 15)
code: str | None,
text: str,
amount: str | None,
conf: float = 100.0,
recognized: bool = True,
status: LineStatus = OK,
order: int = 0,
) -> EvalLine:
value = EvalValue("0", None if amount is None else D(amount), text, conf, recognized)
return EvalLine(f"{code}-{order}", order, status, code, 100.0, False, (value,))
def flags(lines: list[EvalLine], doc: DocumentType, rules: Rules) -> dict[str, bool]:
result = evaluate(lines, ["0"], doc, date(2025, 9, 30), rules)
return {key: flag for (key, _), flag in result.value_needs_review.items()}
def test_guess_confirmed_by_relation(rules: Rules) -> None:
lines = [
ln("1210", "100", "100", order=0),
ln("1230", "42831 1", "428311", recognized=False, order=1),
ln("1200", "428411", "428411", order=2),
]
assert flags(lines, DocumentType.BALANCE, rules)["1230-1"] is False
def test_sign_in_curly_bracket_confirmed(rules: Rules) -> None:
lines = [
ln("2110", "10000000", "10000000", order=0),
ln("2120", "(7744536}", "-7744536", recognized=False, order=1),
ln("2100", "2255464", "2255464", order=2),
ln("2210", "(100)", "-100", order=3),
ln("2220", "(200)", "-200", order=4),
ln("2200", "2255164", "2255164", order=5),
]
assert flags(lines, DocumentType.INCOME, rules)["2120-1"] is False
def test_sign_outside_convention(rules: Rules) -> None:
lines = [
ln("2110", "100000", "100000", order=0),
ln("2120", "38378", "38378", conf=60.0, order=1),
ln("2100", "61622", "61622", order=2),
ln("2210", "(100)", "-100", order=3),
ln("2220", "(200)", "-200", order=4),
]
assert flags(lines, DocumentType.INCOME, rules)["2120-1"] is True
def test_null_amount_and_unchecked_code(rules: Rules) -> None:
lines = [ln("2120", "{38378)", None, recognized=False, order=0), ln("2900", "15", "15", conf=80.0, order=1)]
result = flags(lines, DocumentType.INCOME, rules)
assert (result["2120-0"], result["2900-1"]) == (True, True)
def test_detail_values_never_flagged(rules: Rules) -> None:
lines = [ln("2110", "100", "100", order=0), ln(None, "1 425 130", "1425130", conf=78.0, status=DETAIL, order=1)]
assert flags(lines, DocumentType.INCOME, rules)["None-1"] is False
def test_soft_relation_covers_only_exactly(rules: Rules) -> None:
exact = [
ln("2300", "1000", "1000", order=0),
ln("2410", "200", "200", order=1),
ln("2400", "800", "800", conf=66.0, order=2),
]
assert flags(exact, DocumentType.INCOME, rules)["2400-2"] is False
rounding = [
ln("2300", "17000000000", "17000000000", order=0),
ln("2410", "0", "0", order=1),
ln("2400", "17000000009", "17000000009", conf=66.0, order=2),
]
assert flags(rounding, DocumentType.INCOME, rules)["2400-2"] is True
def test_hard_failure_flags_clean_cells(rules: Rules) -> None:
lines = [ln("1210", "100", "100", order=0), ln("1230", "50", "50", order=1), ln("1200", "999", "999", order=2)]
result = evaluate(lines, ["0"], DocumentType.BALANCE, None, rules)
assert result.hard_failures >= 1
assert all(result.value_needs_review[(f"{c}-{i}", "0")] for i, c in enumerate(["1210", "1230", "1200"]))
accepted = evaluate(lines, ["0"], DocumentType.BALANCE, None, rules, accepted=True)
assert not any(accepted.value_needs_review.values())
def test_unreadable_values(rules: Rules) -> None:
lines = [ln(None, "7", "7", conf=60.0, status=UNREADABLE), ln(None, "5", "5", status=UNREADABLE, order=1)]
result = flags(lines, DocumentType.BALANCE, rules)
assert (result["None-0"], result["None-1"]) == (True, False)
def test_report_date_confirmed_or_not(rules: Rules) -> None:
head = parse_header(
hdr(report_date=("30.09.2025", 60.0), organization_name="ООО «Пример»", inn="7700000000", units="тыс. руб."),
rules,
)
ok_dates = DocumentDates(date(2025, 9, 30), date(2025, 9, 30), date(2025, 9, 30), date(2025, 9, 30))
reviewed, warnings = review_header(head, ok_dates, rules)
assert (reviewed.needs_review_fields, warnings) == ((), [])
relative_only = DocumentDates(date(2025, 9, 30), None, None, None)
reviewed, warnings = review_header(head, relative_only, rules)
assert reviewed.needs_review_fields == ("report_date",)
found = [(w.code, w.severity.value, w.field) for w in warnings]
assert found == [("REPORT_DATE_UNCERTAIN", "error", "report_date")]
def test_header_presence_warnings(rules: Rules) -> None:
cells = hdr(organization_name="ООО «Пример»", report_date="30.09.2025", units="тыс. руб.")
_, no_inn = review_header(parse_header(cells, rules), NO_DATES, rules)
assert [w.code for w in no_inn] == ["INN_NOT_FOUND"]
_, nothing = review_header(parse_header(hdr(), rules), NO_DATES, rules)
assert {w.code for w in nothing} == {"NO_ORGANIZATION_NO_INN", "PERIOD_UNKNOWN", "UNITS_NOT_FOUND"}
def test_uncertain_inn_and_unparsed_date(rules: Rules) -> None:
cells = hdr(organization_name=("ООО «Пример»", 70.0), inn=("7700000000", 80.0), report_date="дата?", units="руб.")
dates = DocumentDates(None, None, date(2025, 9, 30), None)
reviewed, warnings = review_header(parse_header(cells, rules), dates, rules)
assert reviewed.needs_review_fields == ()
assert [(w.code, w.field) for w in warnings] == [
("ORGANIZATION_UNCERTAIN", "organization_name"),
("INN_UNCERTAIN", "inn"),
("REPORT_DATE_UNPARSED", "report_date"),
]