fix: out-of-range для любых Decimal, знак в скобках недопустим, тесты округления
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
This commit is contained in:
parent
7b14276b1a
commit
5f259be9ac
@ -88,8 +88,6 @@ def _split_core(core: str) -> tuple[str, SignForm, bool, bool] | None:
|
|||||||
fixed = core[-1] != ")"
|
fixed = core[-1] != ")"
|
||||||
core = core[1:-1].strip()
|
core = core[1:-1].strip()
|
||||||
sign_form, negative = SignForm.PARENTHESES, True
|
sign_form, negative = SignForm.PARENTHESES, True
|
||||||
if core and core[0] in "-−+":
|
|
||||||
core = core[1:].strip()
|
|
||||||
elif core[0] in "-−+":
|
elif core[0] in "-−+":
|
||||||
if core[0] in _MINUSES:
|
if core[0] in _MINUSES:
|
||||||
sign_form, negative = SignForm.MINUS, True
|
sign_form, negative = SignForm.MINUS, True
|
||||||
@ -118,8 +116,6 @@ def _number(body: str, source: Source, text: str, sign_form: SignForm, *, negati
|
|||||||
return _guess(digits, text, sign_form, negative=negative)
|
return _guess(digits, text, sign_form, negative=negative)
|
||||||
else:
|
else:
|
||||||
integer, fraction = compact.replace(",", ".").split(".")
|
integer, fraction = compact.replace(",", ".").split(".")
|
||||||
if not integer:
|
|
||||||
return AmountParse(None, text, False, sign_form)
|
|
||||||
if source is Source.OCR and len(fraction) >= 3: # noqa: PLR2004 — три знака: не копейки
|
if source is Source.OCR and len(fraction) >= 3: # noqa: PLR2004 — три знака: не копейки
|
||||||
return AmountParse(None, text, False, sign_form)
|
return AmountParse(None, text, False, sign_form)
|
||||||
if len(integer) > MAX_INTEGER_DIGITS:
|
if len(integer) > MAX_INTEGER_DIGITS:
|
||||||
@ -181,9 +177,11 @@ def build_value(
|
|||||||
string_text = text if text.strip() else None
|
string_text = text if text.strip() else None
|
||||||
value = cell.value
|
value = cell.value
|
||||||
if isinstance(value, Decimal):
|
if isinstance(value, Decimal):
|
||||||
amount = _quantize(value)
|
# длину целой части проверяем до квантования: для 1E+30 и нефинитных значений quantize падает
|
||||||
if _too_long(amount):
|
amount = _quantize(value) if value.is_finite() and value.adjusted() < MAX_INTEGER_DIGITS else None
|
||||||
return ValueBuild(_plain(cell, column_index, AmountParse(None, text, False, None)), out_of_range=True)
|
if amount is None or _too_long(amount):
|
||||||
|
overflow = AmountParse(None, string_text, False, None)
|
||||||
|
return ValueBuild(_plain(cell, column_index, overflow), out_of_range=True)
|
||||||
return ValueBuild(
|
return ValueBuild(
|
||||||
_plain(cell, column_index, AmountParse(amount, None, True, None), recognized=not split_part),
|
_plain(cell, column_index, AmountParse(amount, None, True, None), recognized=not split_part),
|
||||||
out_of_range=False,
|
out_of_range=False,
|
||||||
|
|||||||
@ -82,3 +82,38 @@ def test_build_value_date_missing_multiline_split(rules: Rules) -> None:
|
|||||||
assert (part.amount, part.is_recognized) == (D("917775"), False)
|
assert (part.amount, part.is_recognized) == (D("917775"), False)
|
||||||
dash = build_value(Cell("—", None, 100.0, "row:3"), 2, OCR, rules).value
|
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, "—")
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user