diff --git a/CHANGELOG.md b/CHANGELOG.md index 230f79f..f90babb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ 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.9] - 2026-07 + +### Fixed +- Official letter openings are no longer masked as PIB: + `Повідомляємо Вам, що …` was treated as "Surname Name". Words with + 1st/2nd-person-plural verb endings (`-ємо`, `-имо`, `-емо`, `-єте`, + `-ите`, `-ете`) are rejected as name candidates; pronouns + `Вам/Вами/Ваш(-а/-е)` and common verbs (`Повідомляємо`, `Просимо`, + `Направляємо`, `Надаємо`) added to `EXCLUDE_WORDS`. Real PIB after such + phrases is still masked. + ## [2.6.8] - 2026-07 ### Added diff --git a/data_masking.py b/data_masking.py index d53027b..18d2fc4 100644 --- a/data_masking.py +++ b/data_masking.py @@ -30,7 +30,7 @@ # Re-exports from masking package for backward compatibility # ============================================================================ -__version__ = "2.6.8" +__version__ = "2.6.9" from masking.constants import ( __version__, __author__, __contact__, __phone__, __license__, __year__, diff --git a/masking/constants.py b/masking/constants.py index 222a183..279c468 100644 --- a/masking/constants.py +++ b/masking/constants.py @@ -26,7 +26,7 @@ # ============================================================================ # МЕТАДАНІ # ============================================================================ -__version__ = "2.6.8" +__version__ = "2.6.9" __author__ = "Vladyslav V. Prodan" __contact__ = "github.com/click0" __phone__ = "+38(099)6053340" @@ -118,6 +118,9 @@ "доповідаю", "прошу", "наказую", "призначити", "звільнити", "проявив", "виконав", "оголосити", "оголосити", "про", "по", "від", "до", "за", "суті", "мною", "Вас", "вас", + "Вам", "вам", "Вами", "вами", "Ваш", "ваш", "Ваша", "ваша", "Ваше", "ваше", + "Повідомляємо", "повідомляємо", "Просимо", "просимо", + "Направляємо", "направляємо", "Надаємо", "надаємо", "зв'язку", "порушення", "вимог", "Збройних", "збройних", "України", "україни", "служби", "Служби", "військової", "Військової", "частини", "Частини", "взводу", "Взводу", diff --git a/masking/language.py b/masking/language.py index a7e9566..3087d50 100644 --- a/masking/language.py +++ b/masking/language.py @@ -27,6 +27,10 @@ def looks_like_name(word: str) -> bool: clean_word = word.strip(_cfg.QUOTE_CHARS).rstrip(',.!?;:') if len(clean_word) < 3: return False if '.' in clean_word: return False + # Дієслова 1-2 особи множини (Повідомляємо, Просимо, Надаєте) — + # ніколи не імена/прізвища + if clean_word.lower().endswith(('ємо', 'имо', 'емо', 'єте', 'ите', 'ете')): + return False if clean_word.lower() in _cfg.EXCLUDE_WORDS_LOWER: return False if re.search(r'\d', clean_word): return False if clean_word.lower() in _cfg.RANKS_LIST_LOWER: return False diff --git a/tests/test_quoted.py b/tests/test_quoted.py index 2fc2d11..f39837d 100644 --- a/tests/test_quoted.py +++ b/tests/test_quoted.py @@ -115,3 +115,28 @@ def test_two_distinct_quoted_ranks_roundtrip(self): def test_non_rank_in_quotes_untouched(self): assert mask('слово «важливо» тут')[0] == 'слово «важливо» тут' assert mask('позиція «138» ключ')[0] == 'позиція «138» ключ' + + +class TestOfficialPhrases: + """Канцелярські звороти не маскуються як ПІБ (v2.6.9).""" + + def test_povidomlyaemo_vam_untouched(self): + t = ('Повідомляємо Вам, що відповідно до пункту 15 розділу XII ' + 'Інструкції з організації обліку особового складу в системі ' + 'Міністерства оборони України,') + masked, _ = mask(t) + assert masked == t, f"Канцеляризм замасковано: {masked!r}" + + def test_first_person_plural_verbs_not_names(self): + for t in [ + 'Просимо Вас надати необхідні документи до кінця тижня', + 'Направляємо Вам копію наказу для ознайомлення згідно вимог', + ]: + masked, _ = mask(t) + assert masked == t, f"Хибне спрацювання: {t!r} -> {masked!r}" + + def test_real_pib_after_official_phrase_still_masked(self): + t = 'Повідомляємо, що сержант Коваленко Марія Іванівна прибула' + masked, _ = mask(t) + assert 'Коваленко' not in masked + assert masked.startswith('Повідомляємо, що ') diff --git a/unmasking/cli.py b/unmasking/cli.py index 2115b07..ddb7caa 100644 --- a/unmasking/cli.py +++ b/unmasking/cli.py @@ -58,7 +58,7 @@ # ============================================================================ # МЕТАДАНІ # ============================================================================ -__version__ = "2.6.8" +__version__ = "2.6.9" def main():