diff --git a/backend/app/services/llm/redaction.py b/backend/app/services/llm/redaction.py index b64deae3..f4ee7004 100644 --- a/backend/app/services/llm/redaction.py +++ b/backend/app/services/llm/redaction.py @@ -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): (? bool: diff --git a/backend/tests/services/llm/test_redaction.py b/backend/tests/services/llm/test_redaction.py index ced78899..b29050d6 100644 --- a/backend/tests/services/llm/test_redaction.py +++ b/backend/tests/services/llm/test_redaction.py @@ -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 ((? None: + """Цифры ИНН как суффикс буквенно-цифрового токена НЕ редактируются. + + Регрессия: (? 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, (? None: + """Валидный ИНН, отделённый знаком препинания (не word-char), редактируется. + + Двоеточие, запятая, скобка — не \\w → (? None: + """Валидный ИНН в конце строки без символов после редактируется. + + Конец строки — не \\w → (?!\\w) пропускает. Базовый smoke. + """ + out = scrub_text("ИНН 7707083893") + assert "[REDACTED:inn]" in out + assert "7707083893" not in out