fix(parser): inn_digits извлекает ИНН из текста с обрамлением (T7.1, R-15)
Строгая detect.inn_from_cell не меняется (ей детектор отличает колонку ИНН). inn_digits — режим извлечения для ячеек выбранной колонки: сначала строгое правило, затем для текстовых ячеек цифры всех групп склеиваются и принимаются при длине 10/12 (без дополнения нулём). Числовые ячейки во второй шаг не попадают. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
parent
b8f3e32984
commit
46c427e3c1
@ -332,7 +332,12 @@ def _text_col(sheet: Sheet, first_row: int, last_row: int, first_sum: int) -> in
|
||||
|
||||
|
||||
def inn_from_cell(cell: Cell) -> str | None:
|
||||
"""ИНН из ячейки колонки ИНН: 10 или 12 цифр, иначе `None` (единое правило для детектора и классификатора).
|
||||
"""ИНН из ячейки колонки ИНН: 10 или 12 цифр, иначе `None`
|
||||
(строгое правило: им детектор решает, что колонка — колонка ИНН).
|
||||
|
||||
Извлечение значения из ячеек уже выбранной колонки (в том числе из текста с обрамлением, «ИНН 3528052718») —
|
||||
`numbers.inn_digits`; её вызывает классификатор строк. Смягчать это правило нельзя: обычные текстовые колонки
|
||||
(номера договоров, телефоны) начнут выглядеть как колонка ИНН.
|
||||
|
||||
Текст — как есть (после trim), только цифры. Целое число — без дробной части и экспоненты; если при записи числом
|
||||
потерян ведущий ноль (ИНН регионов 01–09: 9 или 11 цифр), он восстанавливается. Текст из 9/11 цифр не дополняется:
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
"""Числа ячеек (design D-5; `05-domain-rules.md` §1.0): суммы -> `Decimal` с двумя знаками, ИНН.
|
||||
|
||||
`inn_digits` — режим извлечения ИНН для ячеек выбранной колонки (строгое правило детектора + цифры из обрамляющего
|
||||
текста); для `counterparty_inn` её вызывает классификатор строк.
|
||||
|
||||
`to_money` — единственное место парсера, где суммы квантуются до 0.01 (ROUND_HALF_UP) и где разбирается текст.
|
||||
Читатели кладут число как `Decimal(str(value))` без квантования, поэтому float-хвосты (`0.30000000000000004`) приходят
|
||||
сюда уже как точная десятичная запись и округляются предсказуемо. `float` в домене не используется: ни `float()`, ни
|
||||
@ -10,7 +13,7 @@ import re
|
||||
from decimal import ROUND_HALF_UP, Decimal, InvalidOperation
|
||||
from typing import NamedTuple
|
||||
|
||||
from src.parser.detect import inn_from_cell
|
||||
from src.parser.detect import INN_LENGTHS, inn_from_cell
|
||||
from src.parser.grid import Cell
|
||||
|
||||
__all__ = ["MONEY_Q", "MoneyParse", "inn_digits", "to_money"]
|
||||
@ -32,6 +35,7 @@ _WHITESPACE = re.compile(r"[\s ]+")
|
||||
_PARENTHESES = re.compile(r"^\((.*)\)$")
|
||||
_NUMBER = re.compile(r"^-?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?$")
|
||||
_MINUS_SIGN = "−"
|
||||
_DIGIT_RUN = re.compile(r"[0-9]+")
|
||||
|
||||
|
||||
class MoneyParse(NamedTuple):
|
||||
@ -92,5 +96,21 @@ def to_money(cell: Cell) -> MoneyParse:
|
||||
|
||||
|
||||
def inn_digits(cell: Cell) -> str | None:
|
||||
"""ИНН из ячейки: 10 или 12 цифр, иначе `None`. Правило единое с детектором колонки ИНН — `detect.inn_from_cell`."""
|
||||
return inn_from_cell(cell)
|
||||
"""ИНН из ячейки уже выбранной колонки ИНН: единая точка извлечения `counterparty_inn` (Task 8 вызывает её).
|
||||
|
||||
1. Строгое правило детектора `detect.inn_from_cell` (10/12 цифр; число с потерянным ведущим нулём дополняется).
|
||||
2. Текстовая ячейка с обрамлением («ИНН 352819549855», «ИНН: 3528052718», «3528 052718»): цифры всех групп
|
||||
склеиваются и принимаются, если их ровно 10 или 12. Ведущий ноль к склеенным цифрам не добавляется
|
||||
(11 цифр -> `None`). Ограничение: склейка не отличает ИНН от иного текста с 10/12 цифрами в сумме, поэтому
|
||||
применяется только к ячейкам колонки, уже признанной колонкой ИНН.
|
||||
Числовые ячейки во второй шаг не попадают (`3528052718.5` -> `None`). Мягкий второй шаг нельзя переносить в
|
||||
`inn_from_cell`: она решает, является ли колонка колонкой ИНН, и обычные текстовые колонки (номера договоров,
|
||||
телефоны) не должны под неё подпадать.
|
||||
"""
|
||||
strict = inn_from_cell(cell)
|
||||
if strict is not None:
|
||||
return strict
|
||||
if cell.kind != "text":
|
||||
return None
|
||||
digits = "".join(_DIGIT_RUN.findall(cell.text))
|
||||
return digits if len(digits) in INN_LENGTHS else None
|
||||
|
||||
@ -196,7 +196,14 @@ def test_too_large_text_is_none_with_warning() -> None:
|
||||
(num("123456789"), "0123456789"), # потерян ведущий ноль: общее правило detect.inn_from_cell
|
||||
(text("123"), None),
|
||||
(num("123"), None),
|
||||
(text("ИНН 352819549855"), None), # текст не «чистится»: ИНН — колонка из одних цифр
|
||||
(text("ИНН 352819549855"), "352819549855"), # R-15: цифры из обрамляющего текста
|
||||
(text("ИНН: 3528052718"), "3528052718"),
|
||||
(text("3528 052718"), "3528052718"),
|
||||
(text("123 456"), None),
|
||||
(text("ИНН 35280527181"), None), # 11 цифр: ведущий ноль к извлечённым цифрам не добавляется
|
||||
(text("ИНН 352805271"), None), # 9 цифр
|
||||
(text("№ 12/2025"), None),
|
||||
(text("ИНН"), None),
|
||||
(num("3528052718.5"), None),
|
||||
(num("-3528052718"), None),
|
||||
(EMPTY_CELL, None),
|
||||
@ -207,6 +214,12 @@ def test_inn_digits(cell: Cell, expected: str | None) -> None:
|
||||
assert inn_digits(cell) == expected
|
||||
|
||||
|
||||
def test_inn_digits_delegates_to_detector_rule() -> None:
|
||||
for cell in (text("3528052718"), num("123456789"), text("abc"), num("1.5")):
|
||||
def test_inn_digits_is_superset_of_strict_detector_rule() -> None:
|
||||
cells = (text("3528052718"), text(" 352819549855 "), num("123456789"), num("3528052718.0"), num("352819549855"))
|
||||
for cell in cells:
|
||||
assert detect.inn_from_cell(cell) is not None
|
||||
assert inn_digits(cell) == detect.inn_from_cell(cell)
|
||||
|
||||
|
||||
def test_detector_rule_stays_strict_for_prefixed_text() -> None:
|
||||
assert detect.inn_from_cell(text("ИНН 352819549855")) is None
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user