Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,14 @@ recognizers:
enabled: false
country_code: kr

- name: KrPassportRecognizer
supported_languages:
- ko
- kr
type: predefined
enabled: false
country_code: kr

- name: SeOrganisationsnummerRecognizer
supported_languages:
- sv
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def __init__(
supported_language: str = "ko",
supported_entity: str = "KR_BRN",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = replacement_pairs if replacement_pairs else [("-", "")]

Expand All @@ -68,6 +69,7 @@ def __init__(
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

def validate_result(self, pattern_text: str) -> Union[bool, None]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def __init__(
supported_language: str = "ko",
supported_entity: str = "KR_DRIVER_LICENSE",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs if replacement_pairs else [("-", ""), (" ", "")]
Expand All @@ -82,6 +83,7 @@ def __init__(
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

def validate_result(self, pattern_text: str) -> bool:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ class KrPassportRecognizer(PatternRecognizer):
the previous passport number format:
- one letter 'M' or 'm' or 'S' or 's' or 'R' or 'r' or 'O' or 'o' or 'D' or 'd'
- eight digits

: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
:param name: Name of the recognizer
"""

COUNTRY_CODE = "kr"
Expand Down Expand Up @@ -48,8 +54,9 @@ def __init__(
self,
patterns: Optional[List[Pattern]] = None,
context: Optional[List[str]] = None,
supported_language: str = "kr",
supported_language: str = "ko",
supported_entity: str = "KR_PASSPORT",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
Expand All @@ -58,4 +65,5 @@ def __init__(
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def __init__(
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "US_MBI",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
Expand All @@ -98,4 +99,5 @@ def __init__(
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
74 changes: 66 additions & 8 deletions presidio-analyzer/tests/test_kr_passport_recognizer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
from pathlib import Path

import presidio_analyzer
import pytest
import yaml
from presidio_analyzer.predefined_recognizers.country_specific.korea.kr_passport_recognizer import (
KrPassportRecognizer,
)
from presidio_analyzer.recognizer_registry import RecognizerRegistryProvider

from tests import assert_result_within_score_range
from presidio_analyzer.predefined_recognizers.country_specific.korea.kr_passport_recognizer import KrPassportRecognizer


@pytest.fixture(scope="module")
def recognizer():
Expand All @@ -25,18 +33,18 @@ def entities():
("My passport number is M123A4567", 1, ((22, 31),), ((0.1, 0.1),)),
("Korean passport: M456B7890", 1, ((17, 26),), ((0.1, 0.1),)),
("여권번호는 M789C1234입니다", 1, ((6, 15),), ((0.1, 0.1),)),

# Valid previous format passports (M + 8 digits)
("M12345678", 1, ((0, 9),), ((0.05, 0.05),)),
("m87654321", 1, ((0, 9),), ((0.05, 0.05),)),
("d11223344", 1, ((0, 9),), ((0.05, 0.05),)),
("s99887766", 1, ((0, 9),), ((0.05, 0.05),)),
("My old passport M12345678", 1, ((16, 25),), ((0.05, 0.05),)),
("대한민국 여권 S87654321", 1, ((8, 17),), ((0.05, 0.05),)),

# Multiple passport numbers
("M123A4567 and M456B7890", 2, ((0, 9), (14, 23)), ((0.1, 0.1), (0.1, 0.1))),

# Invalid formats - should not match
("A123B4567", 0, (), ()), # Wrong first letter
("M12A4567", 0, (), ()), # Too few digits before letter
Expand Down Expand Up @@ -69,8 +77,7 @@ def test_when_all_passports_then_succeed(
assert len(results) == expected_len
for res, (st_pos, fn_pos), (st_score, fn_score) in zip(
results, expected_positions, expected_score_ranges
):
print(f"res: {res}, st_pos: {st_pos}, fn_pos: {fn_pos}, st_score: {st_score}, fn_score: {fn_score}")
):
if fn_score == "max":
Comment thread
omri374 marked this conversation as resolved.
fn_score = max_score
assert_result_within_score_range(
Expand Down Expand Up @@ -104,7 +111,58 @@ def test_when_no_passport_then_no_results(recognizer, entities):
"M123456789", # Too long for old format
"123A4567", # Missing M prefix
]

for text in invalid_texts:
results = recognizer.analyze(text, entities)
assert len(results) == 0, f"Expected no results for text: {text}"
assert len(results) == 0, f"Expected no results for text: {text}"


def test_default_supported_language_is_ko():
"""Default language must be ``ko``, like the other Korean recognizers.

It was previously ``kr``, which registered the recognizer under a language
code that an AnalyzerEngine running ``ko`` never queried.
"""
assert KrPassportRecognizer().supported_language == "ko"


def test_accepts_name_kwarg():
"""Constructor must accept the ``name`` kwarg the YAML loader passes.

Without it, loading the recognizer from a registry YAML raises
``TypeError``, which is why it could not be listed there before.
"""
recognizer = KrPassportRecognizer(name="CustomKrPassport")
assert recognizer.name == "CustomKrPassport"


@pytest.mark.parametrize("language", ["ko", "kr"])
def test_loads_from_default_recognizers_yaml(language):
"""Recognizer is registered in the default YAML and loads once enabled.

The constructor default is ``ko`` (see above), but the shipped entry
advertises ``kr`` as well, matching the four sibling ``Kr*`` entries already
in the file. Both codes are asserted here because an entry that lists a
language it cannot serve is the same class of defect this PR fixes.
"""
conf = Path(presidio_analyzer.__file__).parent / "conf" / "default_recognizers.yaml"
recognizers = yaml.safe_load(conf.read_text(encoding="utf-8"))["recognizers"]
entries = [r for r in recognizers if r.get("name") == "KrPassportRecognizer"]
assert len(entries) == 1, "KrPassportRecognizer missing from YAML"
entry = entries[0]
assert entry["country_code"] == "kr"
assert language in entry["supported_languages"]

# Handed to the provider in memory rather than written to a temporary file:
# ``registry_configuration`` takes the same mapping the YAML parses to, so a
# round trip through the filesystem would only add a directory to clean up.
registry = RecognizerRegistryProvider(
registry_configuration={
"supported_languages": [language],
"recognizers": [dict(entry, enabled=True)],
}
).create_recognizer_registry()

assert [type(r).__name__ for r in registry.recognizers] == ["KrPassportRecognizer"]
entities = {e for rec in registry.recognizers for e in rec.supported_entities}
assert "KR_PASSPORT" in entities
Loading
Loading