Skip to content
Open
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
3 changes: 2 additions & 1 deletion presidio-analyzer/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
from typing import Tuple

from flask import Flask, Response, jsonify, request
from werkzeug.exceptions import HTTPException

from presidio_analyzer import (
AnalyzerEngine,
AnalyzerEngineProvider,
AnalyzerRequest,
BatchAnalyzerEngine,
)
from werkzeug.exceptions import HTTPException

DEFAULT_PORT = "3000"
DEFAULT_BATCH_SIZE = "500"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Taiwan specific recognizers."""

from .tw_national_id_recognizer import TwNationalIdRecognizer

__all__ = ["TwNationalIdRecognizer"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Taiwan National ID Recognizer."""

from typing import List, Optional

from presidio_analyzer import Pattern, PatternRecognizer


class TwNationalIdRecognizer(PatternRecognizer):
"""Recognize Taiwan National ID using patterns and checksums."""

PATTERNS = [
Pattern(
"TW National ID (Strict)",
r"(?<![A-Za-z0-9])[A-Z][1289][0-9]{8}(?![A-Za-z0-9])",
0.3,
)
]
Comment on lines +11 to +17

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path you used for the recognizer is incorrect.
It should be presidio/presidio-analyzer/presidio_analyzer/predefined_recognizers

CC: @SharonHart


CONTEXT = [
"身分證",
"統一證號",
"國民身分證",
"tw id",
"taiwan id",
]

def __init__(
self,
patterns: Optional[List[Pattern]] = None,
context: Optional[List[str]] = None,
supported_language: str = "zh",
supported_entity: str = "TW_NATIONAL_ID",
):
"""Initialize Taiwan National ID Recognizer."""
patterns = patterns if patterns is not None else self.PATTERNS
context = context if context is not None else self.CONTEXT
super().__init__(
supported_entity=supported_entity,
patterns=patterns,
context=context,
supported_language=supported_language,
)

def invalidate_result(self, pattern_text: str) -> bool:
"""Reject invalid Taiwan ID structures via Modulus-10 checksum validation."""
if len(pattern_text) != 10:
return True

# Presidio uses IGNORECASE by default; explicitly reject lowercase initial letters
first_char = pattern_text[0]
if not first_char.isupper():
return True

letter_codes = {
'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15, 'G': 16, 'H': 17,
'I': 34, 'J': 18, 'K': 19, 'L': 20, 'M': 21, 'N': 22, 'O': 35, 'P': 23,
'Q': 24, 'R': 25, 'S': 26, 'T': 27, 'U': 28, 'V': 29, 'W': 32, 'X': 30,
'Y': 31, 'Z': 33
}

if first_char not in letter_codes:
return True

if pattern_text[1] not in ('1', '2', '8', '9'):
return True

try:
code = letter_codes[first_char]
n1 = code // 10
n2 = code % 10

digits = [n1, n2] + [int(char) for char in pattern_text[1:]]
weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1]
total = sum(d * w for d, w in zip(digits, weights))

return total % 10 != 0
except ValueError:
return True
33 changes: 33 additions & 0 deletions presidio-analyzer/tests/test_tw_national_id_recognizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pytest
from presidio_analyzer.predefined_recognizers.country_specific.tw import TwNationalIdRecognizer


@pytest.fixture(scope="module")
def recognizer():
return TwNationalIdRecognizer()


@pytest.mark.parametrize(
"text, expected_len, expected_positions",
[
# Valid Taiwan IDs (Verified with Modulus-10 checksum)
("My ID is A123456789.", 1, ((9, 19),)),
("B120000008", 1, ((0, 10),)),
("F120000002", 1, ((0, 10),)),
("H120000004", 1, ((0, 10),)),
# Adjacent to Chinese characters
("身分證A123456789", 1, ((3, 13),)),
# Invalid Formats / Non-Matches / Checksum Failures
("A323456789", 0, ()), # Invalid gender code (3)
("A12345678", 0, ()), # Too short
("A1234567890", 0, ()), # Too long
("1123456789", 0, ()), # Missing letter
("a123456789", 0, ()), # Lowercase prefix rejected
("A123456780", 0, ()), # Checksum failure
],
)
def test_tw_national_id_recognizer(text, expected_len, expected_positions, recognizer):
results = recognizer.analyze(text, entities=["TW_NATIONAL_ID"])
assert len(results) == expected_len
if expected_len > 0:
assert (results[0].start, results[0].end) == expected_positions[0]
1 change: 0 additions & 1 deletion presidio-cli/presidio_cli/analyzer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import Generator, Optional, Union

from presidio_analyzer import RecognizerResult

from presidio_cli.config import PresidioCLIConfig


Expand Down
1 change: 1 addition & 0 deletions presidio-cli/presidio_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import pathspec
import yaml

from presidio_analyzer import AnalyzerEngine


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import PIL
import pydicom
from PIL import Image
from presidio_analyzer import PatternRecognizer

from presidio_analyzer import PatternRecognizer
from presidio_image_redactor import (
OCR,
BboxProcessor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
import pydicom
from matplotlib import pyplot as plt # necessary import for PIL typing # noqa: F401
from PIL import Image, ImageOps
from presidio_analyzer import PatternRecognizer
from pydicom.multival import MultiValue
from pydicom.pixel_data_handlers.util import apply_voi_lut

from presidio_analyzer import PatternRecognizer
from presidio_image_redactor import (
ImageAnalyzerEngine, # noqa: F401
ImageRedactorEngine,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image, ImageChops
from presidio_analyzer import AnalyzerEngine, RecognizerResult

from presidio_analyzer import AnalyzerEngine, RecognizerResult
from presidio_image_redactor import OCR, ImagePreprocessor, TesseractOCR
from presidio_image_redactor.entities import ImageRecognizerResult

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from typing import List, Optional

from PIL import Image, ImageChops
from presidio_analyzer import PatternRecognizer

from presidio_analyzer import PatternRecognizer
from presidio_image_redactor.image_redactor_engine import ImageRedactorEngine


Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import List, Optional, Tuple, Union

import presidio_analyzer # required for isinstance check which throws an error when trying to specify PatternRecognizer # noqa: E501
from PIL import Image, ImageChops, ImageDraw
from presidio_analyzer import PatternRecognizer

import presidio_analyzer # required for isinstance check which throws an error when trying to specify PatternRecognizer # noqa: E501
from presidio_analyzer import PatternRecognizer
from presidio_image_redactor import BboxProcessor, ImageAnalyzerEngine
from presidio_image_redactor.entities import ImageRecognizerResult

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
from typing import Dict, Iterator, List, Optional, Union

from pandas import DataFrame

from presidio_analyzer import (
AnalyzerEngine,
BatchAnalyzerEngine,
DictAnalyzerResult,
RecognizerResult,
)

from presidio_structured.config import StructuredAnalysis

NON_PII_ENTITY_TYPE = "NON_PII"
Expand Down
Loading