fix(parser): inn_digits склеивает только группы цифр через пробел (T7.4)

ИНН из текста ячейки колонки ИНН принимается, только если в тексте одна запись цифр:
группы, разделённые пробелом/NBSP/узким пробелом («3528 052718», «ИНН 352819549855»).
«№ 12 от 01.02.2025» и «№ 12 / 3528052718 / …» больше не дают ложный ИНН.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
Andrey Epifancev 2026-10-05 14:07:33 +04:00
parent 46c427e3c1
commit 663ba17716
2 changed files with 19 additions and 6 deletions

View File

@ -35,7 +35,10 @@ _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]+")
# Одна запись цифр в тексте: группы цифр, разделённые только пробелом/NBSP/узким пробелом («3528 052718»), с нецифровым
# обрамлением («ИНН: …»). Две записи, разделённые иным знаком («№ 12 от 01.02.2025», «12 / 3528052718»), — не ИНН
_SINGLE_DIGIT_RUN = re.compile(r"^\D*([0-9]+(?:[\s\u00a0\u202f][0-9]+)*)\D*$")
_DIGIT_GROUP_SEPARATOR = re.compile(r"[\s\u00a0\u202f]")
class MoneyParse(NamedTuple):
@ -99,10 +102,11 @@ def inn_digits(cell: Cell) -> str | None:
"""ИНН из ячейки уже выбранной колонки ИНН: единая точка извлечения `counterparty_inn` (Task 8 вызывает её).
1. Строгое правило детектора `detect.inn_from_cell` (10/12 цифр; число с потерянным ведущим нулём дополняется).
2. Текстовая ячейка с обрамлением («ИНН 352819549855», «ИНН: 3528052718», «3528 052718»): цифры всех групп
склеиваются и принимаются, если их ровно 10 или 12. Ведущий ноль к склеенным цифрам не добавляется
(11 цифр -> `None`). Ограничение: склейка не отличает ИНН от иного текста с 10/12 цифрами в сумме, поэтому
применяется только к ячейкам колонки, уже признанной колонкой ИНН.
2. Текстовая ячейка с обрамлением («ИНН 352819549855», «ИНН: 3528052718», «3528 052718»): в тексте должна быть
ровно одна запись цифр — группы, разделённые только пробелом/NBSP/узким пробелом (T7.4); группы склеиваются
и принимаются, если цифр ровно 10 или 12. Цифры, разделённые иным знаком («№ 12 от 01.02.2025»,
«№ 12 / 3528052718 / …»), — не ИНН (`None`). Ведущий ноль к склеенным цифрам не добавляется (11 цифр -> `None`).
Применяется только к ячейкам колонки, уже признанной колонкой ИНН.
Числовые ячейки во второй шаг не попадают (`3528052718.5` -> `None`). Мягкий второй шаг нельзя переносить в
`inn_from_cell`: она решает, является ли колонка колонкой ИНН, и обычные текстовые колонки (номера договоров,
телефоны) не должны под неё подпадать.
@ -112,5 +116,8 @@ def inn_digits(cell: Cell) -> str | None:
return strict
if cell.kind != "text":
return None
digits = "".join(_DIGIT_RUN.findall(cell.text))
found = _SINGLE_DIGIT_RUN.match(cell.text)
if found is None:
return None
digits = _DIGIT_GROUP_SEPARATOR.sub("", found.group(1))
return digits if len(digits) in INN_LENGTHS else None

View File

@ -203,6 +203,12 @@ def test_too_large_text_is_none_with_warning() -> None:
(text("ИНН 35280527181"), None), # 11 цифр: ведущий ноль к извлечённым цифрам не добавляется
(text("ИНН 352805271"), None), # 9 цифр
(text("№ 12/2025"), None),
(text("№ 12 от 01.02.2025"), None), # T7.4: 2+2+2+4 = 10 цифр из разных групп — не ИНН
(text("№ 12 / 3528052718 / …"), None), # T7.4: ИНН рядом с другими числами не склеивается
(text("ИНН/КПП 3528052718/352801001"), None),
(text("3528\u00a0052718"), "3528052718"), # группы через неразрывный пробел
(text("3528\u202f052718"), "3528052718"), # и через узкий неразрывный
(text("ИНН 3528 052718 (Вологда)"), "3528052718"),
(text("ИНН"), None),
(num("3528052718.5"), None),
(num("-3528052718"), None),