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 textstart- character offset of the match startend- character offset of the match endconfidence- confidence score between 0.0 and 1.0region- 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 usedvalue- the input valueis_valid- whether the value passed validationmetadata- 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:
- type priority (from the
prioritydict) - confidence
- 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
| Region | Code | Detectors |
|---|---|---|
| Czech Republic | cz | 15 detectors |
| Poland | pl | 6 detectors |
| Germany | de | 6 detectors |
| France | fr | 6 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 implementationsThen 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', ...)