fix(llm): word-boundary INN match, tighten edge cases (#1640 follow-up)
Some checks failed
CI / backend-tests (push) Has been cancelled
CI / frontend-tests (push) Has been cancelled
CI / openapi-codegen-check (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / changes (pull_request) Has been cancelled
CI / backend-tests (pull_request) Has been cancelled
CI / frontend-tests (pull_request) Has been cancelled
CI / openapi-codegen-check (pull_request) Has been cancelled
Some checks failed
CI / backend-tests (push) Has been cancelled
CI / frontend-tests (push) Has been cancelled
CI / openapi-codegen-check (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / changes (pull_request) Has been cancelled
CI / backend-tests (pull_request) Has been cancelled
CI / frontend-tests (pull_request) Has been cancelled
CI / openapi-codegen-check (pull_request) Has been cancelled
Replace (?<!\d)/(?!\d) lookarounds on the digit block in _INN_RE with (?<!\w)/(?!\w). The old (?<!\d) did not block alpha-prefixed tokens (e.g. «ИНН ref7707083893»), and (?!\d) did not block alpha-suffixed tokens (e.g. «ИНН 7707083893more»). \b is unsuitable here because Python \w covers both letters and digits, so there is no \b boundary between an alpha char and a digit char. The (?<!\w)/(?!\w) pair correctly anchors the 10/12-digit INN block to non-word boundaries on both sides. Context anchor and checksum gate from #1640 are unchanged. Adds 6 regression tests covering: alpha-prefix, alpha-suffix, embedded mid-token, 12-digit alpha-prefix, punctuation separator (should match), and end-of-string (should match).
This commit is contained in:
parent
f55e83a150
commit
4309a12c67
2 changed files with 71 additions and 1 deletions
|
|
@ -94,7 +94,13 @@ _EMAIL_RE = re.compile(r"\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b")
|
|||
# «Прокладка» (?:[^\d\n]{0,20}) позволяет «ИНН физлица 500100732259» (слово между
|
||||
# маркером и числом), но не перешагивает через строки или другие цифровые блоки.
|
||||
# Группа 1 захватывает только цифры — передаётся в _inn_checksum_valid.
|
||||
_INN_RE = re.compile(r"(?i:инн|inn)\s*[:№]?\s*(?:[^\d\n]{0,20})(\d{12}|\d{10})(?!\d)")
|
||||
# Границы слова (#1640 follow-up): (?<!\w) / (?!\w) вокруг цифрового блока гарантируют,
|
||||
# что блок не является частью более длинного алфавитно-цифрового токена.
|
||||
# \b не подходит: Python \w включает цифры и буквы, поэтому \b между буквой и цифрой
|
||||
# отсутствует (оба — \w). Lookaround'ы (?<!\w)/(?!\w) дают нужную границу «не-\w перед/после».
|
||||
# (?<!\d) заменён на (?<!\w): закрывает случай «ref7707083893» (alpha-префикс).
|
||||
# (?!\d) заменён на (?!\w): закрывает случай «7707083893more» (alpha-суффикс).
|
||||
_INN_RE = re.compile(r"(?i:инн|inn)\s*[:№]?\s*(?:[^\d\n]{0,20})(?<!\w)(\d{12}|\d{10})(?!\w)")
|
||||
|
||||
|
||||
def _inn_checksum_valid(token: str) -> bool:
|
||||
|
|
|
|||
|
|
@ -268,3 +268,67 @@ def test_inn_bad_checksum_with_keyword_not_redacted() -> None:
|
|||
out = scrub_text("ИНН 1234567890 (некорректный)")
|
||||
assert "[REDACTED:inn]" not in out
|
||||
assert "1234567890" in out
|
||||
|
||||
|
||||
# ── #1640 follow-up: word-boundary guards ((?<!\w)/(?!\w)) ───────────────────
|
||||
|
||||
|
||||
def test_inn_digits_alpha_prefix_not_redacted() -> None:
|
||||
"""Цифры ИНН как суффикс буквенно-цифрового токена НЕ редактируются.
|
||||
|
||||
Регрессия: (?<!\\d) не защищал от «ИНН ref7707083893» — 'f' не цифра, старый
|
||||
lookaround пропускал. Новый (?<!\\w) блокирует любой word-char перед цифровым блоком.
|
||||
"""
|
||||
out = scrub_text("ИНН ref7707083893")
|
||||
assert "[REDACTED:inn]" not in out
|
||||
assert "7707083893" in out
|
||||
|
||||
|
||||
def test_inn_digits_alpha_suffix_not_redacted() -> None:
|
||||
"""Цифры ИНН как префикс буквенно-цифрового токена НЕ редактируются.
|
||||
|
||||
Регрессия: старый (?!\\d) не блокировал буквенный суффикс — «7707083893more»
|
||||
совпадало, т.к. 'm' не цифра. Новый (?!\\w) закрывает alpha-суффикс.
|
||||
"""
|
||||
out = scrub_text("ИНН 7707083893more")
|
||||
assert "[REDACTED:inn]" not in out
|
||||
assert "7707083893" in out
|
||||
|
||||
|
||||
def test_inn_digits_embedded_in_alphanumeric_token_not_redacted() -> None:
|
||||
"""ИНН-подобный блок цифр внутри алфавитно-цифрового токена НЕ редактируется.
|
||||
|
||||
«ИНН ref7707083893xyz» — цифры с обеих сторон окружены word-char'ами.
|
||||
"""
|
||||
out = scrub_text("ИНН ref7707083893xyz")
|
||||
assert "[REDACTED:inn]" not in out
|
||||
|
||||
|
||||
def test_inn_alpha_prefix_on_12digit_block_not_redacted() -> None:
|
||||
"""12-значный валидный ИНН с буквенным префиксом (без пробела) НЕ редактируется.
|
||||
|
||||
«ИНН ABC500100732259» — 'C' является word-char, (?<!\\w) блокирует совпадение.
|
||||
"""
|
||||
out = scrub_text("ИНН ABC500100732259")
|
||||
assert "[REDACTED:inn]" not in out
|
||||
assert "500100732259" in out
|
||||
|
||||
|
||||
def test_inn_standalone_with_punctuation_is_redacted() -> None:
|
||||
"""Валидный ИНН, отделённый знаком препинания (не word-char), редактируется.
|
||||
|
||||
Двоеточие, запятая, скобка — не \\w → (?<!\\w) пропускает. Это корректное поведение.
|
||||
"""
|
||||
out = scrub_text("ИНН:7707083893 в реестре")
|
||||
assert "[REDACTED:inn]" in out
|
||||
assert "7707083893" not in out
|
||||
|
||||
|
||||
def test_inn_standalone_at_end_of_string_is_redacted() -> None:
|
||||
"""Валидный ИНН в конце строки без символов после редактируется.
|
||||
|
||||
Конец строки — не \\w → (?!\\w) пропускает. Базовый smoke.
|
||||
"""
|
||||
out = scrub_text("ИНН 7707083893")
|
||||
assert "[REDACTED:inn]" in out
|
||||
assert "7707083893" not in out
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue