diff --git a/docs/supported_entities.md b/docs/supported_entities.md index 4273461a41..e04f770f0f 100644 --- a/docs/supported_entities.md +++ b/docs/supported_entities.md @@ -154,6 +154,12 @@ For more information, refer to the [adding new recognizers documentation](analyz | TR_NATIONAL_ID | The Turkish National Identification Number (TCKN) is a unique 11-digit number issued to all Turkish citizens. | Pattern match, context and checksum. | | TR_LICENSE_PLATE | Turkish vehicle license plate (plaka): 2-digit province code (01–81), 1–3 letters (A–Z, excluding Q, W, X), and 2–4 digits. Standard civilian format only. Legal basis: KTK Madde 23. | Pattern match, context and province code validation. | +### Philippines + +| FieldType | Description | Detection Method | +|------------|---------------------------------------------------------------------------------------------------------|------------------------------------------| +| PH_UMID | Philippine Unified Multi-Purpose ID (UMID) / Common Reference Number (CRN). 12-digit format (e.g., 0111-1234567-8). Acts as a master ID connecting SSS, GSIS, PhilHealth, and Pag-IBIG. Disabled by default. | Pattern match and context | + ### Germany | Entity Type | Description | Detection Method | diff --git a/presidio-analyzer/presidio_analyzer/conf/default_recognizers.yaml b/presidio-analyzer/presidio_analyzer/conf/default_recognizers.yaml index 0ad50b1603..d383bd355f 100644 --- a/presidio-analyzer/presidio_analyzer/conf/default_recognizers.yaml +++ b/presidio-analyzer/presidio_analyzer/conf/default_recognizers.yaml @@ -343,6 +343,13 @@ recognizers: enabled: false country_code: tr + - name: PhUmidRecognizer + supported_languages: + - en + type: predefined + enabled: false + country_code: ph + - name: HuggingFaceNerRecognizer supported_languages: - en diff --git a/presidio-analyzer/presidio_analyzer/predefined_recognizers/__init__.py b/presidio-analyzer/presidio_analyzer/predefined_recognizers/__init__.py index 2823ff035d..3715da7c30 100644 --- a/presidio-analyzer/presidio_analyzer/predefined_recognizers/__init__.py +++ b/presidio-analyzer/presidio_analyzer/predefined_recognizers/__init__.py @@ -77,6 +77,7 @@ # Philippines recognizers from .country_specific.philippines.ph_tin_recognizer import PhTinRecognizer +from .country_specific.philippines.ph_umid_recognizer import PhUmidRecognizer # Poland recognizers from .country_specific.poland.pl_pesel_recognizer import PlPeselRecognizer @@ -223,6 +224,7 @@ "HuggingFaceNerRecognizer", "PhTinRecognizer", "PlPeselRecognizer", + "PhUmidRecognizer", "AzureAILanguageRecognizer", "InAadhaarRecognizer", "InGstinRecognizer", diff --git a/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/philippines/__init__.py b/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/philippines/__init__.py index be79f1dbe7..ba45a5f6cd 100644 --- a/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/philippines/__init__.py +++ b/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/philippines/__init__.py @@ -1,5 +1,9 @@ """Philippines-specific recognizers package.""" from .ph_tin_recognizer import PhTinRecognizer +from .ph_umid_recognizer import PhUmidRecognizer -__all__ = ["PhTinRecognizer"] +__all__ = [ + "PhTinRecognizer", + "PhUmidRecognizer", +] diff --git a/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/philippines/ph_umid_recognizer.py b/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/philippines/ph_umid_recognizer.py new file mode 100644 index 0000000000..381b2875fc --- /dev/null +++ b/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/philippines/ph_umid_recognizer.py @@ -0,0 +1,53 @@ +from typing import List, Optional + +from presidio_analyzer import Pattern, PatternRecognizer + + +class PhUmidRecognizer(PatternRecognizer): + """ + Recognize PH UMID number using regex. + + :param patterns: List of patterns to be used by this recognizer + :param context: List of context words to increase confidence in detection + :param supported_language: Language this recognizer supports + :param supported_entity: The entity this recognizer can detect + """ + + COUNTRY_CODE = "ph" + + PATTERNS = [ + Pattern("UMID (with dashes)", r"\b\d{4}-\d{7}-\d\b", 0.5), + Pattern("UMID (without dashes)", r"\b\d{12}\b", 0.3), + ] + + CONTEXT = [ + "umid", + "unified multi-purpose id", + "crn", + "common reference number", + "sss", + "gsis", + "philhealth", + "pag-ibig", + "umid number", + "umid card", + "unified multipurpose id", + ] + + def __init__( + self, + patterns: Optional[List[Pattern]] = None, + context: Optional[List[str]] = None, + supported_language: str = "en", + supported_entity: str = "PH_UMID", + name: Optional[str] = None, + ): + patterns = patterns if patterns else self.PATTERNS + context = context if context else self.CONTEXT + super().__init__( + supported_entity=supported_entity, + patterns=patterns, + context=context, + supported_language=supported_language, + name=name, + ) diff --git a/presidio-analyzer/tests/test_ph_umid_recognizer.py b/presidio-analyzer/tests/test_ph_umid_recognizer.py new file mode 100644 index 0000000000..5d5619d2de --- /dev/null +++ b/presidio-analyzer/tests/test_ph_umid_recognizer.py @@ -0,0 +1,98 @@ +"""Tests for Philippine UMID (PH_UMID) recognizer.""" + +import pytest +from presidio_analyzer.predefined_recognizers import PhUmidRecognizer + +from tests import assert_result_within_score_range + + +@pytest.fixture(scope="module") +def recognizer(): + """Return an instance of PhUmidRecognizer.""" + return PhUmidRecognizer() + + +@pytest.fixture(scope="module") +def entities(): + """Return the supported entities for PhUmidRecognizer.""" + return ["PH_UMID"] + + +@pytest.mark.parametrize( + "text, expected_len, expected_positions, expected_score_ranges", + [ + # VALID + ("0111-1234567-8", 1, ((0, 14),), ((0.4, 1.0),)), + ("0000-0000000-0", 1, ((0, 14),), ((0.4, 1.0),)), + ("001112345678", 1, ((0, 12),), ((0.2, 1.0),)), + ("My UMID number is 0111-1234567-8", 1, ((18, 32),), ((0.4, 1.0),)), + ("UMID: 001112345678", 1, ((6, 18),), ((0.2, 1.0),)), + ("CRN: 0111-1234567-8", 1, ((5, 19),), ((0.4, 1.0),)), + ("philhealth 001112345678", 1, ((11, 23),), ((0.2, 1.0),)), + ("gsis 0111-1234567-8", 1, ((5, 19),), ((0.4, 1.0),)), + ("sss: 0111-1234567-8", 1, ((5, 19),), ((0.4, 1.0),)), + ("pag-ibig 0111-1234567-8", 1, ((9, 23),), ((0.4, 1.0),)), + ("umid card 0111-1234567-8", 1, ((10, 24),), ((0.4, 1.0),)), + ("unified multi-purpose id 0111-1234567-8", 1, ((25, 39),), ((0.4, 1.0),)), + ("unified multipurpose id 0111-1234567-8", 1, ((24, 38),), ((0.4, 1.0),)), + ("common reference number 0111-1234567-8", 1, ((24, 38),), ((0.4, 1.0),)), + ("1234-1234567-8", 1, ((0, 14),), ((0.4, 1.0),)), + ("9999-9999999-9", 1, ((0, 14),), ((0.4, 1.0),)), + # INVALID + ("123456789012", 1, ((0, 12),), ((0.2, 1.0),)), + ("987654321098", 1, ((0, 12),), ((0.2, 1.0),)), + ("12345", 0, (), ()), + ("1234567890123", 0, (), ()), + ("hello world", 0, (), ()), + ("0111-123456-8", 0, (), ()), + ("0111-12345678-8", 0, (), ()), + ("011-1234567-8", 0, (), ()), + ("01111-1234567-8", 0, (), ()), + ("0111-1234567-89", 0, (), ()), + ("0111-1234567-", 0, (), ()), + ("-1234567-8", 0, (), ()), + ("0111 1234567 8", 0, (), ()), + ("0111.1234567.8", 0, (), ()), + # MULTIPLE + ( + "First: 0111-1234567-8, Second: 001112345678", + 2, + ((7, 21), (31, 43)), + ((0.4, 1.0), (0.2, 1.0)), + ), + ( + "0000-0000000-0 and 1111-1111111-1", + 2, + ((0, 14), (19, 33)), + ((0.4, 1.0), (0.4, 1.0)), + ), + ], +) +def test_when_umid_in_text_then_all_umids_found( + text, + expected_len, + expected_positions, + expected_score_ranges, + recognizer, + entities, +): + """Test that PH UMID recognizer correctly identifies numbers.""" + results = recognizer.analyze(text, entities) + assert len(results) == expected_len + + for res, (st_pos, fn_pos), (st_score, fn_score) in zip( + results, expected_positions, expected_score_ranges + ): + assert_result_within_score_range( + res, entities[0], st_pos, fn_pos, st_score, fn_score + ) + + +def test_supported_entity(recognizer): + """Test that supported entity is correctly set.""" + assert recognizer.supported_entities == ["PH_UMID"] + + +def test_supported_language(recognizer): + """Test that supported language is correctly set.""" + assert recognizer.supported_language == "en"