FastPII Docs
Getting Started

Core Concepts

Detection, validation, transformation strategies, overlap resolution, confidence scoring, and country pack extensibility in FastPII.

Core Concepts

The FastPII Engine

FastPII uses a single explicit engine class. You control overlap priority, confidence scoring, and detector registration explicitly. No implicit behavior, no hidden defaults.

from fastpii import FastPII, DEFAULT_PRIORITY
from fastpii.countries.cz import CzechPack

engine = FastPII(priority=DEFAULT_PRIORITY)
engine.register(CzechPack())

result = engine.detect("Email: jan.novak@example.cz, RČ: 8001011238")

DEFAULT_PRIORITY provides sensible overlap resolution out of the box:

DEFAULT_PRIORITY = {
    "rodne_cislo": 100, "pesel": 100, "steuer_id": 100, "siren": 100, "insee": 100,
    "ico": 95, "nip": 95, "ust_id": 95, "siret": 95,
    "dic": 90, "regon": 90, "handelsregister": 90,
    "bank_account": 85,
    "address": 80,
    "email": 70,
    "date_of_birth": 60, "date": 60,
    "name": 50,
    "postal_code": 40,
    "vehicle_plate": 30,
    "phone": 20,
}

Detection

Use detect() to scan text for supported entities.

from fastpii import FastPII, DEFAULT_PRIORITY
from fastpii.countries.cz import CzechPack

engine = FastPII(priority=DEFAULT_PRIORITY)
engine.register(CzechPack())

result = engine.detect("Email: jan.novak@example.cz, RČ: 8001011238")

Multi-region detection

from fastpii import FastPII, DEFAULT_PRIORITY
from fastpii.countries.cz import CzechPack
from fastpii.countries.pl import PolishPack
from fastpii.countries.de import GermanPack
from fastpii.countries.fr import FrenchPack

engine = FastPII(priority=DEFAULT_PRIORITY)
engine.register_many([CzechPack(), PolishPack(), GermanPack(), FrenchPack()])

result = engine.detect("PESEL: 44051401458, Steuer-ID: 86095742719")

Selective detection

Use detector_names to run only specific detectors:

result = engine.detect(text, detector_names=["rodne_cislo", "email"])

detect() returns a DetectionResult with a list[Finding] in result.findings.

Each Finding contains:

  • type - the detector type that matched (e.g., "rodne_cislo", "email")
  • value - the matched text
  • start - character offset of the match start
  • end - character offset of the match end
  • confidence - confidence score between 0.0 and 1.0
  • region - region code (e.g., "cz", "pl")
  • metadata - detector-specific metadata dict

Validation

Use validate() when you already know the detector type and need to confirm whether a single value is structurally valid.

result = engine.validate("8001011238", "rodne_cislo")
print(result.is_valid)
print(result.metadata)

validate() returns a ValidationResult with:

  • detector - the detector name used
  • value - the input value
  • is_valid - whether the value passed validation
  • metadata - detector-specific metadata (e.g., birth date, gender)

For checksum-backed identifiers, validation uses official structural rules such as Mod 11, Mod 10, Luhn, ISO 7064 MOD 11,10, and Mod-97 checksum calculations.

Transformation Strategies

FastPII provides four built-in transformation strategies through the TransformationEngine. Each strategy defines how detected PII is replaced in the output text.

Convenience methods

The FastPII engine provides convenience methods for each strategy:

engine.anonymize(text)              # [REDACTED]
engine.anonymize(text, replacement="<PII>")  # custom replacement
engine.redact(text)                 # [EMAIL], [RODNE_CISLO]
engine.mask(text)                   # **********
engine.remove(text)                 # (deletes PII)

Direct strategy usage

You can also use the TransformationEngine directly with any strategy:

from fastpii.core.transform import TransformationEngine, AnonymizeStrategy, RedactStrategy, MaskStrategy, RemoveStrategy

result = engine.detect(text)

# Anonymize with custom replacement
anonymized = TransformationEngine.apply(result, AnonymizeStrategy(replacement="<PII>"))

# Redact with type labels
redacted = TransformationEngine.apply(result, RedactStrategy())

# Mask preserving span length
masked = TransformationEngine.apply(result, MaskStrategy())

# Remove entirely
removed = TransformationEngine.apply(result, RemoveStrategy())

Custom strategy

Implement the TransformationStrategy protocol to create your own transformation:

from fastpii.core.transform import TransformationStrategy
from fastpii.models import Finding

class HashStrategy:
    """Replace PII with its SHA-256 hash prefix."""

    def replace(self, finding: Finding, text: str) -> str:
        import hashlib
        return hashlib.sha256(finding.value.encode()).hexdigest()[:8]

result = engine.detect(text)
transformed = TransformationEngine.apply(result, HashStrategy())

Overlap resolution

When multiple findings overlap, FastPII deduplicates them before returning the final DetectionResult. The priority dict determines which finding wins:

Resolution order:

  1. type priority (from the priority dict)
  2. confidence
  3. span length

Priority determines which finding wins when spans overlap. A typical priority order places:

  • checksum-backed identifiers (national IDs, tax IDs, company IDs) at the highest priority
  • broader entities (address, email, date, name) at medium priority
  • lower-specificity spans (postal codes, phone numbers) at lower priority

This reduces false positives when one detector captures a substring that belongs to a stronger match.

Confidence scoring

Configure confidence scoring through ConfidenceScorer:

from fastpii import FastPII, DEFAULT_PRIORITY, DEFAULT_CONFIDENCE_SCORES, DEFAULT_CONTEXT_BOOST
from fastpii.core.confidence import ConfidenceScorer

scorer = ConfidenceScorer(
    base_scores=DEFAULT_CONFIDENCE_SCORES,
    context_boost=DEFAULT_CONTEXT_BOOST,
)

engine = FastPII(priority=DEFAULT_PRIORITY, confidence_scorer=scorer)

The scorer adjusts confidence based on:

  • Whether a checksum was validated (1.0)
  • Whether surrounding context matched (0.95)
  • A configurable context boost amount (0.10)

Default confidence scores:

DEFAULT_CONFIDENCE_SCORES = {
    "checksum_validated": 1.0,
    "context_match": 0.95,
    "pattern_match": 0.85,
    "no_context": 0.70,
}

Country Packs

FastPII uses a Country Pack architecture. You register the packs you need explicitly.

Built-in country packs

RegionCodeDetectors
Czech Republiccz15 detectors
Polandpl6 detectors
Germanyde6 detectors
Francefr6 detectors

Registering country packs

from fastpii import FastPII, DEFAULT_PRIORITY
from fastpii.countries.cz import CzechPack
from fastpii.countries.pl import PolishPack

engine = FastPII(priority=DEFAULT_PRIORITY)
engine.register(CzechPack())
engine.register(PolishPack())

# Or register multiple at once
engine.register_many([CzechPack(), PolishPack()])

Listing available detectors

detectors = engine.list_detectors()
for d in detectors:
    print(f"{d.name} ({d.region}): {d.description}")

Custom country packs

To add a new region, implement CountryPack and register detectors:

from fastpii.countries import CountryPack
from fastpii.detectors.base import Detector
from fastpii import Finding

class SlovakPack(CountryPack):
    code = "sk"

    @property
    def name(self) -> str:
        return "Slovakia"

    @property
    def detectors(self) -> list[Detector]:
        return [...]  # your detector implementations

Then register:

engine.register(SlovakPack())

Data Modules

Each country pack can optionally provide structured data through the CountryModule system. This enables data validation, benchmarking, and lookup capabilities.

from fastpii.data.registry import CountryRegistry

# Get a country module
cz_module = CountryRegistry.get("cz")
metadata = cz_module.get_metadata()
# CountryMetadata(code='CZ', name='Czech Republic', ...)

# Access specific data
cities = cz_module.get_cities().get_data()
bank_codes = cz_module.get_bank_codes().get_data()

# Validate data integrity
results = cz_module.validate_all()
# {'bank_codes': True, 'cities': True, 'postal_codes': True, ...}

The CountryData generic type allows type-safe access:

from fastpii.data.base import CountryData, DataSource

data_module = cz_module.get_cities()
data: set[str] = data_module.get_data()
source: DataSource = data_module.get_source()
# DataSource(name='Czech Cities', url='...', license='CC BY 4.0', ...)

On this page