Skip to content

Personal identifiers

pii

Detect and mask Indian PII in Odia text: phone, email, URL, Aadhaar, PAN and PIN code.

Rule-based and offline: patterns plus the Verhoeff checksum for Aadhaar. Person names, street addresses, dates and amounts are not detected, so masking is not anonymisation.

Examples:

>>> from openodia import pii
>>> pii.mask("ମୋ ଫୋନ ୯୮୭୬୫୪୩୨୧୦ ।")
'ମୋ ଫୋନ <PHONE> ।'

PIIEntity dataclass

One detected span; start and end are character offsets into the original input.

detect

detect(
    text: str,
    types: Iterable[str] | None = None,
    min_confidence: float = 0.0,
) -> list[PIIEntity]

Find PII spans in text, sorted by position and never overlapping.

Odia and ASCII digits are both recognised. types limits the search to some of PII_TYPES. min_confidence (0 to 1) drops weaker matches first. Matches that overlap are merged into one entity spanning all of them, so for every detected value no part of it is left visible; see the limits below. Its type and confidence come from the longest match; on a tie the earlier of EMAIL, URL, AADHAAR, PAN, PHONE, PINCODE wins.

Limits: an email local part over 64 characters is not found, and one with punctuation keeps its earlier part visible; one that starts with non-ASCII letters keeps those letters visible.

Raises:

Type Description
TypeError

text is not a str, or types is a bare string.

ValueError

types names an unknown type, or min_confidence is outside 0 to 1.

mask

mask(
    text: str,
    types: Iterable[str] | None = None,
    min_confidence: float = 0.0,
    placeholder: Callable[[PIIEntity], str] = _tag,
) -> str

Replace every detected span with placeholder(entity), <TYPE> by default.

Replacing a value can expose a neighbour that was glued to it, such as a PAN written directly before a URL. So detection and replacement repeat until nothing changes, at most 10 times. With the default tags the result is stable: masking it again changes nothing, unless the input chains more than 10 such glued values. From the second pass on, placeholder receives entities whose offsets index the partly masked text. A custom placeholder that returns text matching a detector can keep the loop going until the limit.

Past 10 glued values the result can still contain raw PII, so do not rely on it for input that chains that many. types is read once, so an iterator works.