Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
120 lines
7.2 KiB
Python
120 lines
7.2 KiB
Python
"""Суммы из OCR-ячеек (§11.1 п. 7): перенос `_parse_amount`/`_is_blank` старого ядра."""
|
||
|
||
from decimal import Decimal
|
||
|
||
import pytest
|
||
|
||
from src.normalizer.ocr.amounts import ParsedAmount, is_blank, parse_amount
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("raw", "amount", "verified"),
|
||
[
|
||
("13 070", Decimal("13070"), True),
|
||
("(6032409)", Decimal("-6032409"), True),
|
||
("(7744536}", Decimal("7744536"), False), # закрывающая скобка не распознана: минус не ставится (решение №12)
|
||
("1 234,56", Decimal("1234.56"), True),
|
||
("1.234.567", Decimal("1234567"), True), # точки строго через три цифры — разделители тысяч (AURORA-1405, #37)
|
||
("30127", Decimal("30127"), True),
|
||
("211(1 |", Decimal("2111"), False),
|
||
("не ****", None, False),
|
||
("", None, True),
|
||
("--", None, True), # прочерк: ничего не отброшено, суммы нет (NULL)
|
||
(":", None, False),
|
||
# несколько точек: удаляются ВСЕ точки, десятичная часть теряется (`1.234,56` — не 1234.56), всегда не verified
|
||
("1.234,56", Decimal("123456"), False),
|
||
("1,234.56", Decimal("123456"), False),
|
||
# запятая-разделитель тысяч (AURORA-1405, scan-gateway#35): удаляется до замены запятой на точку, verified=True
|
||
("1,234", Decimal("1234"), True),
|
||
("4,200", Decimal("4200"), True),
|
||
("(4,200)", Decimal("-4200"), True),
|
||
("1,234,567", Decimal("1234567"), True),
|
||
("-4,200", Decimal("-4200"), True),
|
||
("+4,200", Decimal("4200"), True),
|
||
("(-4,200)", Decimal("-4200"), True),
|
||
("4,2", Decimal("4.2"), True),
|
||
("12,34", Decimal("12.34"), True),
|
||
("1,23", Decimal("1.23"), True),
|
||
# не тысячи: больше двух знаков после запятой остаётся десятичным — `Money` отсекает `document._value` (№30)
|
||
("0,125", Decimal("0.125"), True),
|
||
("12,3456", Decimal("12.3456"), True),
|
||
("4,200,", Decimal("4200"), False), # хвостовая запятая: правило не применяется (две точки удалены, как раньше)
|
||
("(4,200}", Decimal("4.200"), False), # нет пары скобок: правило не применяется, минус не ставится
|
||
# скобки не на всю строку — правило тысяч не применяется, прежний разбор (запятая → точка)
|
||
("(4,200) руб", Decimal("-4.200"), False),
|
||
("abc(4,200)", Decimal("-4.200"), False),
|
||
("(4,200)x", Decimal("-4.200"), False),
|
||
("(4,200)(1,000)", Decimal("-4.200"), False),
|
||
("1,234(5,678)", Decimal("-5.678"), False),
|
||
("((4,200))", Decimal("-4.200"), True),
|
||
("1234,567", Decimal("1234.567"), True), # запятая НЕ расширена (R-7): четыре цифры до неё — не тысячи
|
||
("1 234,567", Decimal("1234.567"), True),
|
||
("01,234", Decimal("1.234"), True), # ведущий ноль — не тысячи
|
||
("4,2000", Decimal("4.2000"), True),
|
||
("1, 234", Decimal("1234"), True), # пробелы убираются первыми
|
||
# точка-разделитель тысяч (AURORA-1405, scan-gateway#37): то же правило, что для запятой, verified=True
|
||
("4.200", Decimal("4200"), True),
|
||
("837.994", Decimal("837994"), True),
|
||
("(4.200)", Decimal("-4200"), True),
|
||
("-4.200", Decimal("-4200"), True),
|
||
("+4.200", Decimal("4200"), True),
|
||
("(-4.200)", Decimal("-4200"), True),
|
||
("1.234", Decimal("1234"), True),
|
||
("100.000", Decimal("100000"), True),
|
||
("1. 234", Decimal("1234"), True), # пробелы убираются первыми
|
||
# ровно три цифры после точки — тысячи независимо от числа цифр до неё (R-3, решение владельца 16.10.2026)
|
||
("1 234.567", Decimal("1234567"), True),
|
||
("1234.567", Decimal("1234567"), True),
|
||
("12345.678", Decimal("12345678"), True),
|
||
("(1 234.567)", Decimal("-1234567"), True),
|
||
("-1234.567", Decimal("-1234567"), True),
|
||
("1234.567.890", Decimal("1234567890"), True),
|
||
# не тысячи: не три цифры после точки — десятичная дробь, как раньше
|
||
("4.2", Decimal("4.2"), True),
|
||
("4.20", Decimal("4.20"), True),
|
||
("12.34", Decimal("12.34"), True),
|
||
("1.2345", Decimal("1.2345"), True),
|
||
("0.125", Decimal("0.125"), True), # ведущий ноль — не тысячи
|
||
("01.234", Decimal("1.234"), True),
|
||
("1234.56", Decimal("1234.56"), True), # дробная часть не из трёх цифр — дробь
|
||
("1234.5678", Decimal("1234.5678"), True),
|
||
# смешанные разделители и прочий мусор — правило не применяется (R-1): прежний разбор
|
||
("1.234,567", Decimal("1234567"), False),
|
||
("1,234.567", Decimal("1234567"), False),
|
||
("1.234.56", Decimal("123456"), False),
|
||
("4.200.", Decimal("4200"), False), # хвостовая точка
|
||
("(4.200}", Decimal("4.200"), False), # нет пары скобок: правило не применяется, минус не ставится
|
||
("(4.200) руб", Decimal("-4.200"), False),
|
||
("abc(4.200)", Decimal("-4.200"), False),
|
||
("(4.200)(1.000)", Decimal("-4.200"), False),
|
||
("1.234(5.678)", Decimal("-5.678"), False),
|
||
# пробел внутри суммы допустим только как разделитель тысяч (AURORA-1511/1519): иначе догадка, verified=False
|
||
("42831 1", Decimal("428311"), False),
|
||
("1 2", Decimal("12"), False),
|
||
("1234 567", Decimal("1234567"), False),
|
||
("12 34", Decimal("1234"), False),
|
||
("1 2345", Decimal("12345"), False),
|
||
("(42831 1)", Decimal("-428311"), False),
|
||
("1 234,5 6", Decimal("1234.56"), False),
|
||
("12 345 678", Decimal("12345678"), True),
|
||
("(12 345)", Decimal("-12345"), True),
|
||
("-1 234", Decimal("-1234"), True),
|
||
("123 456", Decimal("123456"), True),
|
||
# №13: скобки без содержимого — не сумма, но и не «неразобранный текст»
|
||
("()", None, True),
|
||
("( )", None, True),
|
||
("(.)", None, True),
|
||
("(+5)", Decimal("-5"), True), # в скобках знак заменяется минусом
|
||
],
|
||
)
|
||
def test_parse_amount(raw: str, amount: Decimal | None, verified: bool) -> None:
|
||
assert parse_amount(raw) == ParsedAmount(amount, verified)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("raw", "expected"),
|
||
[("", True), ("-", True), ("—", True), (" – ", True), ("0", False), ("x", False)],
|
||
)
|
||
def test_is_blank(raw: str, expected: bool) -> None:
|
||
assert is_blank(raw) is expected
|