From 017bcf1594a58b84724cdfa74488354b7b182d43 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 12:49:32 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20mask=20PIB=20after=20service=20labels/no?= =?UTF-8?q?ise,=20ignore=20=D0=86=D0=9F=D0=9D=3D=E2=80=A6=20as=20anchor=20?= =?UTF-8?q?(v2.6.7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In 'ПІБ: Петренко...' and data-ipn log lines the real name was skipped: the PIB anchor latched onto 'ІПН=NNNN' (uppercase start) or the marker 'ПІБ', then stopped before the actual name. is_pib_anchor now strips trailing punctuation and rejects tokens with digits/'='; 'ПІБ'/'звання' are excluded markers. +3 tests. Bare rank without PIB still unmasked (documented known limitation). https://claude.ai/code/session_01XT6iUWaQgahXDB9TWX9Bq7 --- CHANGELOG.md | 16 ++++++++++++++++ data_masking.py | 2 +- masking/constants.py | 5 ++++- masking/helpers.py | 4 +++- tests/test_quoted.py | 26 ++++++++++++++++++++++++++ unmasking/cli.py | 2 +- 6 files changed, 51 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba99c0b..5c668f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [2.6.7] - 2026-07 + +### Fixed +- **PIB preceded by service labels / noise is now masked.** In lines like + `ПІБ: Петренко Іван Васильович` or log lines + `… ІПН=3698521592 — ПІБ «138» → «Міронов Андрій Петрович» …` the real name + was skipped: the PIB anchor latched onto `ІПН=…` (starts uppercase) or the + marker `ПІБ`, then stopped. Now `is_pib_anchor` strips trailing punctuation + and rejects tokens containing digits/`=`; `ПІБ` and `звання` are excluded + markers. + +### Known limitation +- A bare rank with **no** following PIB (e.g. `звання «…» → «молодший сержант»`) + is still not masked — long-standing behavior that avoids false positives on + rank words in prose; independent of this fix. + ## [2.6.6] - 2026-06 ### Fixed diff --git a/data_masking.py b/data_masking.py index 818e4a5..582e8a6 100644 --- a/data_masking.py +++ b/data_masking.py @@ -30,7 +30,7 @@ # Re-exports from masking package for backward compatibility # ============================================================================ -__version__ = "2.6.6" +__version__ = "2.6.7" from masking.constants import ( __version__, __author__, __contact__, __phone__, __license__, __year__, diff --git a/masking/constants.py b/masking/constants.py index d533694..227b613 100644 --- a/masking/constants.py +++ b/masking/constants.py @@ -26,7 +26,7 @@ # ============================================================================ # МЕТАДАНІ # ============================================================================ -__version__ = "2.6.6" +__version__ = "2.6.7" __author__ = "Vladyslav V. Prodan" __contact__ = "github.com/click0" __phone__ = "+38(099)6053340" @@ -129,6 +129,9 @@ "неналежііе", "инутрішньої", "радіовіzUділення", "с~гатуту", "року", "Року", "числа", "місяця", "один", "два", "три", "чотири", "п'ять", + # Службові маркери-мітки (щоб не плутались із самим ПІБ у форматах + # "ПІБ: Петренко..." чи логах "ПІБ «138» → «Міронов...»") + "ПІБ", "піб", "Піб", "звання", "Звання", "ЗВАННЯ", ] # Regex паттерни для виявлення звань різних типів служб diff --git a/masking/helpers.py b/masking/helpers.py index a904bcb..144d44a 100644 --- a/masking/helpers.py +++ b/masking/helpers.py @@ -107,8 +107,10 @@ def normalize_identifier(identifier: str) -> str: def is_pib_anchor(word: str) -> bool: if word.startswith("___") and word.endswith("___"): return False - word = word.strip(_cfg.QUOTE_CHARS) + word = word.strip(_cfg.QUOTE_CHARS).strip(",.!?;:") if not word or len(word) <= 2: return False + # Слова з цифрами/= — не ПІБ (ІПН=3698521592, «138», кодування) + if re.search(r'[\d=]', word): return False word_lower = word.lower() if word_lower in _cfg.EXCLUDE_WORDS_LOWER: return False if word_lower in _cfg.RANKS_LIST_LOWER: return False diff --git a/tests/test_quoted.py b/tests/test_quoted.py index 364caeb..64aa0b7 100644 --- a/tests/test_quoted.py +++ b/tests/test_quoted.py @@ -60,3 +60,29 @@ def test_matches_unquoted_behaviour(self): a, _ = mask('Петренко Іван Сергійович') b, _ = mask('«Петренко Іван Сергійович»') assert b == '«' + a + '»' + + +class TestServiceMarkers: + """ПІБ маскується, коли перед ним стоять службові мітки/шум + ("ПІБ:", "ІПН=…", лог-формати), а не сам ПІБ.""" + + def test_label_pib_colon(self): + masked, _ = mask('ПІБ: Петренко Іван Васильович') + assert 'Петренко' not in masked + assert masked.startswith('ПІБ: ') # мітка лишається + + def test_data_ipn_log_line(self): + line = ('[WARNING] data-ipn: ІПН=3698521592 — ПІБ «138» → ' + '«Міронов Андрій Петрович» (джерело data-ipn головне)') + masked, md = mask(line) + assert 'Міронов' not in masked + assert '3698521592' not in masked # ІПН теж + from unmasking.engine import unmask_text_v2 + restored, _ = unmask_text_v2(masked, md, check_mapping_version(md)) + assert restored == line + + def test_ipn_assignment_not_treated_as_name(self): + # ІПН=NNNNNNNNNN не має ламати розпізнавання ПІБ далі в рядку + masked, _ = mask('ІПН=1234567890 Петренко Іван Васильович') + assert 'Петренко' not in masked + assert '1234567890' not in masked diff --git a/unmasking/cli.py b/unmasking/cli.py index 23663d9..244c41e 100644 --- a/unmasking/cli.py +++ b/unmasking/cli.py @@ -58,7 +58,7 @@ # ============================================================================ # МЕТАДАНІ # ============================================================================ -__version__ = "2.6.6" +__version__ = "2.6.7" def main():