arm-core/api/arm_statements/tests/test_amounts.py
Andrey Epifancev 46ac9367ee chore(arm_statements): копия пакета arm-statements@6bce80b (#128)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:38:30 +04:00

129 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
import pytest
from arm_statements.amounts import build_value, parse_amount, sign_form_of
from arm_statements.loader import Rules
from arm_statements.model import Cell, SignForm, Source
OCR, EXCEL = Source.OCR, Source.EXCEL
@pytest.mark.parametrize(
("raw", "amount", "string_value", "recognized"),
[
("1 2О5", D("1205"), None, False),
("42831 1", D("428311"), None, False),
("(7744536}", D("-7744536"), None, False),
("211(1 |", None, "211(1 |", False),
("(Ю4)", None, "(Ю4)", False),
(": : 2856983 = ■", D("2856983"), None, False),
("| : 122477", D("122477"), None, False),
("|122477", D("122477"), None, False),
("{38378)", None, "{38378)", False),
("4,200", D("4200"), None, True),
("1,234,567", D("1234567"), None, True),
("837.994", D("837994"), None, True),
("1 234.567", D("1234567"), None, True),
("4,2", D("4.2"), None, True),
("1 234,56", D("1234.56"), None, True),
("1.234,56", D("123456"), None, False),
("0,125", None, "0,125", False),
("1 234 567", D("1234567"), None, True),
("(1 234)", D("-1234"), None, True),
("−500", D("-500"), None, True),
("12:34", None, "12:34", False),
("1 234 руб.", D("1234"), None, False),
("—", None, None, True),
("-", None, None, True),
("", None, None, True),
("I", None, "I", False),
],
)
def test_parse_amount_ocr(rules: Rules, raw: str, amount: D | None, string_value: str | None, recognized: bool) -> None:
parsed = parse_amount(raw, OCR, rules)
assert (parsed.amount, parsed.string_value, parsed.is_recognized) == (amount, string_value, recognized)
def test_comma_depends_on_source(rules: Rules) -> None:
assert parse_amount("4,200", EXCEL, rules).amount == D("4.20")
assert parse_amount("1.500", EXCEL, rules).amount == D("1.5")
def test_sign_forms(rules: Rules) -> None:
assert sign_form_of("(7744536}", rules) is SignForm.PARENTHESES
assert sign_form_of("-1 234", rules) is SignForm.MINUS
assert sign_form_of("38378", rules) is SignForm.UNSIGNED
assert sign_form_of("{38378)", rules) is None
def test_build_value_excel_number_and_out_of_range(rules: Rules) -> None:
ok = build_value(Cell("106 311", D("106311.456"), 100.0, "Баланс!D27"), 3, EXCEL, rules)
assert (ok.value.amount, ok.value.is_recognized, ok.out_of_range) == (D("106311.46"), True, False)
big = build_value(Cell("1E+17", D("123456789012345678"), 100.0, "Баланс!D28"), 3, EXCEL, rules)
assert (big.value.amount, big.value.string_value, big.value.is_recognized, big.out_of_range) == (
None,
"1E+17",
False,
True,
)
def test_build_value_date_missing_multiline_split(rules: Rules) -> None:
as_date = build_value(Cell("30.09.2025", date(2025, 9, 30), 100.0, "Баланс!D3"), 3, EXCEL, rules).value
assert (as_date.amount, as_date.string_value, as_date.is_recognized) == (None, "30.09.2025", False)
missing = build_value(None, 4, OCR, rules).value
assert (missing.amount, missing.string_value, missing.text, missing.is_recognized) == (None, None, None, True)
assert missing.confidence == pytest.approx(100.0)
multi = build_value(Cell("917 775\n2 371 333", None, 100.0, "row:9"), 2, OCR, rules).value
assert (multi.amount, multi.string_value, multi.is_recognized) == (None, "917 775\n2 371 333", False)
part = build_value(Cell("917 775", None, 100.0, "row:9#1"), 2, OCR, rules, split_part=True).value
assert (part.amount, part.is_recognized) == (D("917775"), False)
dash = build_value(Cell("—", None, 100.0, "row:3"), 2, OCR, rules).value
assert (dash.amount, dash.string_value, dash.is_recognized, dash.text) == (None, None, True, "—")
@pytest.mark.parametrize("raw", ["1E+30", "Infinity", "NaN", "-Infinity", "10000000000000000"])
def test_build_value_decimal_out_of_range(rules: Rules, raw: str) -> None:
built = build_value(Cell(raw, D(raw), 100.0, "Баланс!D1"), 3, EXCEL, rules)
assert (built.value.amount, built.value.string_value, built.value.is_recognized, built.out_of_range) == (
None,
raw,
False,
True,
)
def test_build_value_decimal_empty_text_and_boundary(rules: Rules) -> None:
empty = build_value(Cell("", D("1E+30"), 100.0, "Баланс!D1"), 3, EXCEL, rules)
assert (empty.value.string_value, empty.out_of_range) == (None, True)
edge = build_value(Cell("x", D("9999999999999999"), 100.0, "Баланс!D1"), 3, EXCEL, rules)
assert (edge.value.amount, edge.out_of_range) == (D("9999999999999999"), False)
carry = build_value(Cell("x", D("9999999999999999.999"), 100.0, "Баланс!D1"), 3, EXCEL, rules)
assert (carry.value.amount, carry.out_of_range) == (None, True)
def test_build_value_rounds_half_up(rules: Rules) -> None:
assert build_value(Cell("0,125", D("0.125"), 100.0, "Баланс!D1"), 3, EXCEL, rules).value.amount == D("0.13")
assert build_value(Cell("-0,125", D("-0.125"), 100.0, "Баланс!D1"), 3, EXCEL, rules).value.amount == D("-0.13")
def test_sign_inside_parentheses_is_invalid(rules: Rules) -> None:
parsed = parse_amount("(-5)", OCR, rules)
assert (parsed.amount, parsed.string_value, parsed.is_recognized, parsed.sign_form) == (None, "(-5)", False, None)
def test_leading_comma_is_edge_trash(rules: Rules) -> None:
parsed = parse_amount(",5", OCR, rules)
assert (parsed.amount, parsed.is_recognized) == (D("5"), False)
def test_guess_amount_is_quantized_to_cents(rules: Rules) -> None:
"""T20.1: «лучшая догадка» квантуется как прочие пути; в контракте — две цифры после точки."""
parsed = parse_amount("42831 1", Source.OCR, rules)
assert parsed.amount == D("428311.00") and parsed.amount.as_tuple().exponent == -2 # noqa: PLR2004 — знаков после запятой
assert format(parsed.amount, "f") == "428311.00"
negative = parse_amount("(42831 1)", Source.OCR, rules)
assert negative.amount is not None and format(negative.amount, "f") == "-428311.00"