fa-redact
English
fa-redact is a lightweight, zero-dependency, privacy-first Python toolkit for Persian/Iranian Personally Identifiable Information (PII) detection, redaction, and pseudonymization, designed especially for healthcare and AI/LLM applications.
Status: Alpha
Package version in this source tree:v0.2.0.
Published releases are available on PyPI and GitHub Releases. The PyPI badge above reflects the latest published PyPI version.
Table of Contents
- Overview
- Why fa-redact?
- Installation
- Quick Start
- Detailed Capabilities
- 1. Position-Preserving Normalization
- 2. Detection Model & Pipeline
- 3. Iranian National ID Validation & Detection
- 4. Iranian Mobile Number Validation & Detection
- 5. Iranian IBAN / Sheba Validation & Detection
- 6. Conservative ASCII Email Validation & Detection (Opt-in)
- 7. Bank Card / PAN Validation & Detection (Opt-in)
- 8. Configurable Institutional / Healthcare Identifiers (Opt-in)
- 9. Explicit Detection Conflict Resolution (Opt-in)
- 10. Redaction Semantics
- 11. Stateful Pseudonymization Sessions
- Custom Detectors
- Healthcare & AI/LLM Usage Pattern
- Current Coverage & Limitations
- Privacy and Security Model
- Privacy-Safe Test Data Policy
- Development & Quality Checks
- Release Process
- License
Overview
In healthcare, enterprise, and AI workflows, sending raw Persian clinical notes or customer communications to external Large Language Models (LLMs) or third-party APIs poses significant privacy risks. fa-redact addresses these challenges by providing:
- Position-Preserving Normalization: Maps Persian and Arabic digits and letters 1-to-1 to canonical representations without altering string length or offset positions.
- Algorithmic Validation: Implements checksum rules and prefix checks for Iranian identifiers (National IDs and mobile numbers) rather than relying on loose regular expressions.
- Safe Placeholder Redaction: Deterministically substitutes detected spans with typed placeholders.
- Stateful Pseudonymization & Local Restoration: Generates stable aliases across conversational turns and restores original values locally inside your trusted boundary.
Why fa-redact?
- Handling Persian Text Peculiarities: Persian text frequently mixes Persian digits (
۰-۹), Arabic-Indic digits (٠-٩), and ASCII digits (0-9), alongside letter variants like Arabic Yeh (ي) and Kaf (ك). Simple regex patterns fail on mixed-script variations, while naive string replacements drift character offsets. - Offset Integrity:
fa-redactensureslen(normalized) == len(original), guaranteeing that slice offsetstext[start:end]map directly back to the original source text. - Privacy-First AI Integrations: Mask supported direct identifiers before sending prompts to external LLMs, then restore LLM outputs locally without exposing raw PII outside your trusted perimeter.
- Zero Runtime Dependencies: Written entirely in pure Python (3.10+) with full typing (
py.typed).
Installation
Install the package directly from PyPI:
pip install fa-redact
For development or building from source:
git clone https://github.com/mehdimt1980/fa-redact.git
cd fa-redact
pip install -e ".[dev]"
Quick Start
1. Detect PII
Identify sensitive spans with exact source offsets and normalized values:
from fa_redact import detect
text = "بیمار با کد ملی ۱۲۳۴۵۶۷۸۹۱ و شماره ۰۹۱۲۳۴۵۶۷۸۹ مراجعه کرد."
detections = detect(text)
for d in detections:
print(
f"Type: {d.type} | Value: {d.value} | Normalized: {d.normalized_value} | Span: [{d.start}:{d.end}]"
)
assert text[d.start : d.end] == d.value
2. Redact PII (Stateless)
Sanitize text by replacing detected spans with deterministic, typed placeholders. Repeated identical identifiers within the same call receive the same placeholder:
from fa_redact import redact
text = "کد ملی: ۱۲۳۴۵۶۷۸۹۱، تماس: ۰۹۱۲۳۴۵۶۷۸۹، تماس مجدد: 09123456789"
redacted_text = redact(text)
print(redacted_text)
# Output: "کد ملی: [IR_NATIONAL_ID_1]، تماس: [IR_MOBILE_1]، تماس مجدد: [IR_MOBILE_1]"
3. Stateful Pseudonymization for AI/LLMs
Maintain consistent entity mappings across conversation turns and restore placeholders locally:
from fa_redact import PseudonymizationSession
session = PseudonymizationSession()
# 1. Pseudonymize prompt locally inside your trusted boundary:
prompt = "کد ملی بیمار ۱۲۳۴۵۶۷۸۹۱ و شماره تماس ۰۹۱۲۳۴۵۶۷۸۹ است."
pseudonymized_prompt = session.pseudonymize(prompt)
print(pseudonymized_prompt)
# Output: "کد ملی بیمار [IR_NATIONAL_ID_1] و شماره تماس [IR_MOBILE_1] است."
# 2. Send ONLY pseudonymized_prompt to external LLM. Simulated LLM response:
llm_response = "جهت هماهنگی با بیمار با [IR_MOBILE_1] تماس حاصل فرمایید."
# 3. Restore placeholders locally:
restored = session.restore(llm_response)
print(restored)
# Output: "جهت هماهنگی با بیمار با ۰۹۱۲۳۴۵۶۷۸۹ تماس حاصل فرمایید."
Detailed Capabilities
1. Position-Preserving Normalization
fa-redact provides pure, deterministic normalization where each character maps 1-to-1 to a normalized code point (len(normalized) == len(original)):
from fa_redact import normalize_digits, normalize_letters, normalize_text
# Normalizes Persian (۰-۹) and Arabic-Indic (٠-٩) digits to ASCII (0-9)
normalize_digits("کد بیمار: ۱۲۳٤٥")
# Returns: "کد بیمار: 12345"
# Normalizes Arabic letter variants (ي -> ی, ك -> ک)
normalize_letters("پزشك و دكتر")
# Returns: "پزشک و دکتر"
# Full position-preserving normalization
normalize_text("كد ملي: ۰۰۱٢٣٤٥٦٧٨")
# Returns: "کد ملی: 0012345678"
What is not normalized: Whitespace, punctuation, zero-width non-joiners (ZWNJ), diacritics, and arbitrary unicode characters are preserved as-is so that source offsets remain perfectly aligned.
2. Detection Model & Pipeline
Identified spans are represented by the immutable Detection dataclass:
type: Identifier category (e.g.,IR_NATIONAL_ID,IR_MOBILE).value: Exact substring as written in the original input text.normalized_value: Canonical normalized form of the span.start/end: Zero-indexed Python string slice boundaries (text[start:end] == value).
The detection pipeline aggregates detector results and sorts them deterministically by (start, end, type). It intentionally preserves overlaps and duplicate detections. Overlap rejection is applied only when producing replacement text via redact() or PseudonymizationSession.pseudonymize(), where overlapping or nested spans will raise a ValueError.
3. Iranian National ID Validation & Detection
Validates and detects 10-digit Iranian National IDs (کد ملی / Code Melli):
from fa_redact import IranianNationalIDDetector, is_valid_national_id
# Algorithmic test vectors (not personal data)
is_valid_national_id("1234567891") # True
is_valid_national_id("۱۲۳۴۵۶۷۸۹۱") # True (Persian digits)
is_valid_national_id("1234567890") # False (invalid check digit)
is_valid_national_id("1111111111") # False (repeated digits rejected)
- Modulo-11 Checksum: Applies the implemented modulo-11 checksum rule for Iranian National IDs.
- Repeated-Digit Rejection: Rejects pseudo-values like
0000000000or1111111111. - Exact Length & Formatting: Requires exactly 10 digits in compact format; does not strip spaces, remove hyphens, or auto-pad.
- Verification Notice: Validation confirms mathematical format only without querying official registries. Checksum validity does not establish whether an identifier has been officially issued to an individual.
4. Iranian Mobile Number Validation & Detection
Validates and detects Iranian mobile numbers using the bundled 2026 CRA/ITU mobile-service NDC allocation snapshot:
from fa_redact import IranianMobileNumberDetector, is_valid_mobile_number
# Supported compact domestic and international formats
is_valid_mobile_number("09123456789") # True (domestic)
is_valid_mobile_number("۰۹۱۲۳۴۵۶۷۸۹") # True (Persian digits)
is_valid_mobile_number("+989123456789") # True (+98 format)
is_valid_mobile_number("00989351234567") # True (0098 format)
is_valid_mobile_number("09412345678") # False (fixed non-geographical)
- Prefix-Aware Allocation: Validates against prefixes allocated for mobile services in the bundled National Numbering Plan snapshot.
- Exclusion of Non-Mobile Prefixes: Non-mobile prefixes such as
094(fixed non-geographic) and09950(Public Trunk) are rejected. - Strict Formatting: Only compact forms (
09xxxxxxxxx,+989xxxxxxxxx,00989xxxxxxxxx) are accepted. The validator does not strip spaces, remove hyphens, or auto-format. - Verification Notice: Prefix validation confirms structural numbering-plan compliance only; it does not verify active SIM status, subscriber identity, carrier ownership, or number portability status.
5. Iranian IBAN / Sheba Validation & Detection
fa-redact provides an offline, deterministic Iranian International Bank Account Number (IBAN / شماره شبا) validator (is_valid_iranian_iban) and detector (IranianIBANDetector). An Iranian IBAN in compact electronic form contains 26 characters: IR, followed by 2 check digits and a 22-digit BBAN (or IR followed by 24 numeric digits):
from fa_redact import (
IranianIBANDetector,
PseudonymizationSession,
detect,
is_valid_iranian_iban,
redact,
)
# 1. Standalone Validation (Exact 26 chars: uppercase 'IR' + 24 digits, MOD-97)
is_valid_iranian_iban("IR641234567890123456789012") # True
is_valid_iranian_iban("IR۶۴۱۲۳۴۵۶۷۸۹۰۱۲۳۴۵۶۷۸۹۰۱۲") # True (Persian digits)
is_valid_iranian_iban("ir641234567890123456789012") # False (lowercase 'ir' rejected)
is_valid_iranian_iban("IR 64 1234 5678 9012 3456 7890 12") # False (spaces rejected)
is_valid_iranian_iban("IR001234567890123456789012") # False (invalid checksum)
# 2. Default Detection (Included in default detector set)
text = "شماره شبا واریز: IR641234567890123456789012 و تماس ۰۹۱۲۳۴۵۶۷۸۹"
detections = detect(text)
# Returns: [Detection(type='IR_IBAN', value='IR641234567890123456789012', ...), Detection(type='IR_MOBILE', ...)]
# 3. Default Redaction & Pseudonymization
redacted_text = redact(text)
# Output: "شماره شبا واریز: [IR_IBAN_1] و تماس [IR_MOBILE_1]"
session = PseudonymizationSession()
pseudonymized = session.pseudonymize("واریز به شبا IR641234567890123456789012")
# Output: "واریز به شبا [IR_IBAN_1]"
restored = session.restore("تایید واریز به [IR_IBAN_1]")
# Output: "تایید واریز به IR641234567890123456789012"
- MOD-97-10 Checksum Algorithm: Validates the standard ISO 7064 MOD 97-10 checksum by rearranging to
BBAN (22 digits) + 1827 (IR) + CheckDigits (2 digits)(representing22-digit BBAN + 1827 + 2 check digits) and verifyingremainder == 1. - Default Detector Inclusion: Unlike
EmailDetector,IranianIBANDetectoris included in_DEFAULT_DETECTORSbecause its structured boundary (IRfollowed by 24 digits) has no syntactic collision with National IDs or mobile numbers. - Position-Preserving & Digit Normalization: Supports Persian (
۰-۹) and Arabic-Indic (٠-٩) digits across the 24 numeric characters followingIR(the 2 check digits plus the 22-digit BBAN), preserving the raw surface script inDetection.valueand mapping to canonical ASCII inDetection.normalized_value. - Electronic Compact Format Only: Only compact format without spaces, hyphens, or formatting separators is accepted. Lowercase prefix (
ir) is strictly rejected. - Privacy & Financial Disclaimer:
is_valid_iranian_ibanperforms purely local, offline mathematical checksum validation. It does not perform bank API lookups, bank branch routing, or account status verification, and does not verify whether an account exists or is active.
6. Conservative ASCII Email Validation & Detection (Opt-in)
fa-redact provides a conservative, zero-dependency ASCII email address validator (is_valid_email) and detector (EmailDetector):
from fa_redact import (
EmailDetector,
PseudonymizationSession,
detect,
is_valid_email,
redact,
)
# 1. Standalone Validation
is_valid_email("user.name@example.com") # True
is_valid_email("doctor@sub.hospital.ir") # True
is_valid_email("user@localhost") # False (single-label domain rejected)
is_valid_email("user+tag@invalid_domain.com") # False (underscore in domain)
is_valid_email("user@مثال.ایران") # False (non-ASCII / EAI unsupported)
# 2. Opt-in Detection (Pass EmailDetector explicitly)
text = "مکاتبه با دکتر احمدی: dr.ahmadi@hospital.ir و تماس 09123456789"
detections = detect(text, detectors=[EmailDetector()])
# Returns: [Detection(type='EMAIL', value='dr.ahmadi@hospital.ir', ...)]
# 3. Opt-in Redaction & Pseudonymization
redacted_text = redact(text, detectors=[EmailDetector()])
# Output: "مکاتبه با دکتر احمدی: [EMAIL_1] و تماس 09123456789"
session = PseudonymizationSession()
pseudonymized = session.pseudonymize(
"تماس با info@clinic.ir یا dr.ahmadi@hospital.ir انجام شد.",
detectors=[EmailDetector()],
)
# Output: "تماس با [EMAIL_1] یا [EMAIL_2] انجام شد."
restored = session.restore("پیام به [EMAIL_1] ارسال شد.")
# Output: "پیام به info@clinic.ir ارسال شد."
- Opt-in Architecture:
EmailDetectoris intentionally opt-in in Phase 12 and is not included in the default detector set. Numeric-looking email local parts (such as09123456789@example.comor1234567891@example.com) can produce overlapping spans with mobile number or National ID detectors; whiledetect()permits overlaps, downstream redaction and pseudonymization fail-loud on overlaps until general conflict resolution is implemented. - Per-Call Detector Configuration:
PseudonymizationSessionstores pseudonym mappings and counters, not a persistent detector configuration. The detector set is selected independently for eachpseudonymize()call. - Conservative ASCII Specification: Validates dot-atom local parts (
1-64chars) and DNS-style domain names (1-253total domain chars,1-63chars per label,2-63chars for TLD, total address<= 254characters). - Unsupported Complex/Obsolete Forms: Quoted local parts (
"john doe"@example.com), IP domain literals (user@[192.168.1.1]), comments, folding whitespace, single-label domains (user@localhost), and internationalized/Unicode email addresses (EAI / RFC 6530+) are rejected. - Privacy & Verification Disclaimer:
is_valid_emailperforms purely local, offline syntactic validation. It performs no DNS queries, MX record lookups, mailbox verification, or network requests, and logs no PII. Syntactic validity does not verify that a mailbox exists or is deliverable.
7. Bank Card / PAN Validation & Detection (Opt-in)
fa-redact provides an offline, deterministic 16-digit payment card (Primary Account Number / PAN) validator (is_valid_bank_card_number) and detector (BankCardDetector):
from fa_redact import (
BankCardDetector,
PseudonymizationSession,
detect,
is_valid_bank_card_number,
redact,
)
# 1. Standalone Validation (Exact 16 digits, Luhn MOD-10 checksum)
is_valid_bank_card_number("1234567890123452") # True (valid synthetic card)
is_valid_bank_card_number("۱۲۳۴۵۶۷۸۹۰۱۲۳۴۵۲") # True (Persian digits)
is_valid_bank_card_number("١٢٣٤٥٦٧٨٩٠١٢٣٤٥٢") # True (Arabic-Indic digits)
is_valid_bank_card_number("1234 5678 9012 3452") # False (spaces rejected)
is_valid_bank_card_number("1234-5678-9012-3452") # False (hyphens rejected)
is_valid_bank_card_number("0000000000000000") # False (all-identical rejected)
is_valid_bank_card_number("1234567890123453") # False (checksum mismatch)
# 2. Opt-in Detection (Pass BankCardDetector explicitly)
text = "شماره کارت: 1234567890123452"
detections = detect(text, detectors=[BankCardDetector()])
# Returns: [Detection(type='BANK_CARD', value='1234567890123452', ...)]
# 3. Opt-in Redaction & Pseudonymization
redacted_text = redact(text, detectors=[BankCardDetector()])
# Output: "شماره کارت: [BANK_CARD_1]"
session = PseudonymizationSession()
pseudonymized = session.pseudonymize(
"واریز به کارت ۱۲۳۴۵۶۷۸۹۰۱۲۳۴۵۲ انجام شد.",
detectors=[BankCardDetector()],
)
# Output: "واریز به کارت [BANK_CARD_1] انجام شد."
restored = session.restore("تایید واریز به [BANK_CARD_1]")
# Output: "تایید واریز به ۱۲۳۴۵۶۷۸۹۰۱۲۳۴۵۲"
- Opt-in Architecture:
BankCardDetectoris intentionally opt-in and is not included in the default detector set (_DEFAULT_DETECTORS). - Standard Luhn MOD-10 Checksum: Validates 16-digit payment card numbers using the standard Luhn algorithm (doubling digits at odd offsets from the right, subtracting 9 if the product exceeds 9, and verifying
sum % 10 == 0). - Position-Preserving Digit Normalization: Accepts ASCII (
0-9), Persian (۰-۹), and Arabic-Indic (٠-٩) digits. Preserves the exact surface script inDetection.valueand normalizes to canonical ASCII inDetection.normalized_value. - Defensive Sequence Filtering: Trivial all-identical sequences (such as
0000000000000000through9999999999999999) are defensively rejected regardless of their Luhn checksum status. - Strict Electronic Compact Format: Only compact 16-digit strings without spaces, hyphens, or formatting separators are accepted. The validator does not strip whitespace, remove hyphens, or clean up formatting.
- Issuer Neutrality:
BankCardDetectoruses issuer-neutral terminology and entities (BANK_CARD). The library does not maintain an Iranian BIN/IIN registry and does not verify whether a card was issued by an Iranian bank or any specific card network. - Privacy & Financial Disclaimer:
is_valid_bank_card_numberperforms purely local, offline mathematical checksum validation. It does not perform payment gateway verification, card activation status checks, CVV2/expiry checks, or account balance lookups. Checksum validity does not prove that a payment card exists, is active, or belongs to a specific cardholder.
8. Configurable Institutional / Healthcare Identifiers (Opt-in)
MRNs (Medical Record Numbers), Patient IDs, Admission IDs, Encounter IDs, Case IDs, and similar clinical/enterprise identifiers are institution-specific. fa-redact does not pretend there is one universal regex for them, nor does it hardcode synthetic assumptions into package defaults.
Instead, fa-redact provides a lightweight, immutable configuration layer (PatternRule and PatternDetector) allowing hospitals and applications to supply their own identifier patterns:
import re
from fa_redact import (
PatternDetector,
PatternRule,
PseudonymizationSession,
detect,
redact,
)
# 1. Define institution-specific identifier rules
hospital_detector = PatternDetector(
[
# Standard full-match identifier (MRN)
PatternRule(
type="MRN",
pattern=r"(?<!\w)MRN-[0-9]{6}(?!\w)",
),
# Context-aware rule extracting only the ID using a capture group
PatternRule(
type="PATIENT_ID",
pattern=r"Patient\s*ID\s*:\s*(?P<id>PAT-[A-Z]{2}-[0-9]{8})",
group="id",
flags=re.IGNORECASE,
),
# Admission ID
PatternRule(
type="ADMISSION_ID",
pattern=r"(?<!\w)ADM-20[0-9]{2}-[0-9]{6}(?!\w)",
),
# Encounter ID
PatternRule(
type="ENCOUNTER_ID",
pattern=r"(?<!\w)ENC-[0-9]{10}(?!\w)",
),
]
)
# 2. Detect with normalized Persian digits matching ASCII regexes
text = "پرونده: MRN-۱۲۳۴۵۶ و Patient ID: PAT-TE-12345678"
detections = detect(text, detectors=[hospital_detector])
# Returns:
# - Detection(type='MRN', value='MRN-۱۲۳۴۵۶', normalized_value='MRN-123456', span=[8:18])
# - Detection(type='PATIENT_ID', value='PAT-TE-12345678', normalized_value='PAT-TE-12345678', span=[33:48])
# 3. Redact (leaving context labels like 'Patient ID: ' untouched)
redacted = redact(text, detectors=[hospital_detector])
# Output: "پرونده: [MRN_1] و Patient ID: [PATIENT_ID_1]"
# 4. Stateful Pseudonymization across mixed scripts
session = PseudonymizationSession()
# Turn 1: Persian digits
turn1 = session.pseudonymize("پرونده: MRN-۱۲۳۴۵۶", detectors=[hospital_detector])
# Output: "پرونده: [MRN_1]"
# Turn 2: ASCII digits (resolves to same [MRN_1] identity)
turn2 = session.pseudonymize("پیگیری پرونده MRN-123456", detectors=[hospital_detector])
# Output: "پیگیری پرونده [MRN_1]"
# Restore recovers first-observed Persian representation locally
restored = session.restore("پاسخ به [MRN_1]")
# Output: "پاسخ به MRN-۱۲۳۴۵۶"
- Opt-in Architecture:
PatternDetectoris strictly opt-in and is not included in_DEFAULT_DETECTORS. Explicitdetectors=[hospital_detector]replaces default detectors for that call. - Position-Preserving Digit Normalization: By default (
source="normalized"), patterns match against position-preserving normalized text. A single ASCII regex such asMRN-[0-9]{6}matches Persian (MRN-۱۲۳۴۵۶), Arabic-Indic (MRN-١٢٣٤٥٦), and ASCII (MRN-123456) forms, preserving surface script inDetection.valueand mapping to identical(type, normalized_value)pseudonym identities. - Raw Matching Mode: For institutions requiring candidate matching strictly on unnormalized text,
source="original"is supported. - Context-Aware Capture Groups: Supports integer indices (
group=1) and named capture groups (group="id"), allowing the detector to match context prefixes (e.g.Patient ID:) while extracting only the identifier into the detection span. Nonparticipating capture groups fail loud with aValueError. - Regex Boundary Ownership:
PatternDetectordoes not wrap configured patterns with word boundaries (\bor(?<!\w)). Institutions own full regex semantics. - Pre-Compiled & Immutable:
PatternRuleis an immutable frozen dataclass. Rules are compiled once duringPatternDetectorconstruction.
9. Explicit Detection Conflict Resolution (Opt-in)
detect() always returns raw detector evidence and intentionally preserves overlapping and duplicate detections. This design allows callers to audit and inspect raw matches without hidden priority heuristics.
By default, downstream functions (redact() and PseudonymizationSession.pseudonymize()) operate under conflict_policy="reject", failing loudly if any overlapping, nested, or duplicate detections exist:
from fa_redact import BankCardDetector, EmailDetector, redact
text = "ایمیل: 1234567890123452@example.com"
detectors = [EmailDetector(), BankCardDetector()]
# Default reject policy: raises ValueError because EMAIL and BANK_CARD spans overlap
# redact(text, detectors=detectors) # -> ValueError: Overlapping detections at spans [7:35] (EMAIL) and [7:23] (BANK_CARD)
For callers who understand the ambiguity and choose to resolve conflicts explicitly, fa-redact provides two opt-in resolution policies:
1. Longest Span Policy (conflict_policy="longest")
Greedily selects the longest matching span and discards shorter overlapping candidates. Exact duplicates are collapsed into one. Ambiguous equal-length overlapping spans raise a ValueError:
# Longest policy selects the full EMAIL span (length 28) over BANK_CARD (length 16)
redacted_longest = redact(text, detectors=detectors, conflict_policy="longest")
# Output: "ایمیل: [EMAIL_1]"
2. Explicit Type Priority Policy (conflict_policy="priority")
Resolves conflicts based on an explicit user-configured sequence of entity types in descending priority order (type_priority=[...]). Higher priority types win over lower priority types regardless of span length:
# Priority: BANK_CARD > EMAIL
card_first = redact(
text,
detectors=detectors,
conflict_policy="priority",
type_priority=["BANK_CARD", "EMAIL"],
)
# Output: "ایمیل: [BANK_CARD_1]@example.com"
# Priority: EMAIL > BANK_CARD
email_first = redact(
text,
detectors=detectors,
conflict_policy="priority",
type_priority=["EMAIL", "BANK_CARD"],
)
# Output: "ایمیل: [EMAIL_1]"
Standalone Conflict Resolver for Auditing
Callers can inspect and resolve raw detections independently before redaction:
from fa_redact import detect, resolve_detection_conflicts
raw_detections = detect(text, detectors=detectors)
# Returns 2 overlapping detections: EMAIL and BANK_CARD
resolved_detections = resolve_detection_conflicts(
raw_detections,
policy="longest",
)
# Returns 1 detection: EMAIL
10. Redaction Semantics
- Exact Span Reconstruction:
redact()rebuilds the output from the original Detection spans, preserving untouched source slices exactly and replacing only detected spans. It does not perform global value-basedstr.replace(). - Typed Placeholders: Placeholders follow the format
[<TYPE>_<INDEX>](e.g.,[IR_NATIONAL_ID_1],[IR_MOBILE_1],[IR_IBAN_1],[EMAIL_1],[BANK_CARD_1],[MRN_1]). - Deterministic Numbering: Identifiers receive sequential numbering based on their order of first appearance.
- Collision Avoidance: If an input already contains a literal string matching the placeholder syntax, newly generated placeholders increment past the colliding index.
- Fail-Loud on Overlap by Default: Under default
conflict_policy="reject", if overlapping or duplicate spans are passed toredact(), it raises aValueError.
11. Stateful Pseudonymization Sessions
PseudonymizationSession manages persistent mappings across multi-turn AI interactions:
- Local Sensitive Mapping:
session.mappingcontains{placeholder: original_raw_value}. Keep this mapping strictly inside your trusted boundary. - Cross-Call Identity: Entities are tracked by
(type, normalized_value). For example,۰۹۱۲۳۴۵۶۷۸۹in turn 1 and09123456789in turn 2 both resolve to[IR_MOBILE_1]. - Domestic vs. International Limitation:
09123456789and+989123456789have different normalized strings and are not canonicalized into the same identity in v0.2.0. - First-Observed Representative Restoration: Placeholders are restored using the first-observed raw representation. Restoration is semantic placeholder restoration and is not guaranteed to reconstruct the exact original surface representation of every occurrence byte-for-byte.
- Non-Cascading Restoration:
restore()performs an escaped single-pass replacement, preventing recursive expansion if restored values contain placeholder syntax. - Atomic Updates: If a call fails during processing, the session state, mappings, and counters remain unmodified.
- Historical Literal Token Reservation: Literal placeholder-like tokens observed in prior calls remain reserved so they are never assigned to real PII in later calls.
- Unknown Placeholders: Unmapped placeholders (e.g.,
[IR_MOBILE_99]) are left untouched without error.
Custom Detectors
fa-redact uses Python's structural typing (protocols). Any class implementing the two-argument detect(self, original_text: str, normalized_text: str) -> Sequence[Detection] method can be passed to detect(), redact(), or session.pseudonymize():
import re
from collections.abc import Sequence
from fa_redact import Detection, detect
class MedicalRecordNumberDetector:
"""Example custom detector for synthetic institutional MRNs."""
def detect(
self,
original_text: str,
normalized_text: str,
) -> Sequence[Detection]:
detections: list[Detection] = []
for match in re.finditer(r"\bMRN-\d{6}\b", normalized_text):
detections.append(
Detection.from_texts(
type="MRN",
original_text=original_text,
normalized_text=normalized_text,
start=match.start(),
end=match.end(),
)
)
return detections
# Note: Passing an explicit detector list replaces the default detector set for that call.
text = "پرونده با MRN-123456 ثبت شد."
detections = detect(text, detectors=[MedicalRecordNumberDetector()])
Healthcare & AI/LLM Usage Pattern
Local Hospital / Trusted Boundary
│
├── 1. Raw clinical text with sensitive identifiers
│ ↓
├── 2. session.pseudonymize(raw_text)
│ ↓
├── 3. Pseudonymized prompt (supported identifiers replaced)
│
▼ (Transmit ONLY sanitized text to External AI / Cloud)
External LLM API (e.g., Clinical Summary / Diagnostics)
▲
│ (Receive response containing placeholders)
▼
Local Hospital / Trusted Boundary
│
└── 4. session.restore(llm_response)
↓
5. Final clinical report with restored original identifiers
Current Coverage & Limitations
| Identifier / Capability | v0.1.0 | v0.2.0 | Notes |
|---|---|---|---|
Iranian National ID (کد ملی) |
✅ Supported | ✅ Default | Strict 10-digit modulo-11 checksum validation |
| Iranian Mobile Number | ✅ Supported | ✅ Default | Prefix-aware validation against 2026 CRA numbering plan |
Iranian IBAN / Sheba (شبا) |
❌ Not Supported | ✅ Default | Strict 26-char MOD-97 checksum validation (IR + 24 digits) |
| Email Addresses | ❌ Not Supported | 🧪 Opt-in | Conservative ASCII email validation and detection (detectors=[EmailDetector()]) |
| 16-digit Bank Card (PAN) | ❌ Not Supported | 🧪 Opt-in | 16-digit compact PAN + Luhn checksum validation (detectors=[BankCardDetector()]) |
| Institutional / Healthcare IDs (MRN, Patient ID) | ❌ Not Supported | 🧪 Opt-in | Configurable via user-defined PatternRule / PatternDetector |
| Explicit Conflict Resolution | ❌ Not Supported | 🧪 Opt-in Policy | Resolves overlaps/duplicates via "longest" or "priority" policy |
| Personal Names | ❌ Not Supported | ❌ Not Supported | Planned for future versions (requires NER/contextual models) |
| Postal Addresses | ❌ Not Supported | ❌ Not Supported | Unstructured spatial entities |
| Dates of Birth / Timestamps | ❌ Not Supported | ❌ Not Supported | Planned for future versions |
| Health Insurance Numbers | ❌ Not Supported | ❌ Not Supported | Institution-specific |
Privacy and Security Model
Privacy-Safe Test Data Policy
All examples, test fixtures, and documentation in fa-redact are constructed from synthetic test vectors, algorithmic patterns, and non-personal sample data. No real patient records, clinical charts, credentials, or personal datasets are used or included in the repository.
Development & Quality Checks
Run the test suite:
python -m pytest
Check code formatting and linting:
ruff check .
ruff format --check .
Run static type checking:
mypy src
mypy tests
Build and validate distribution packages:
python -m build
python -m twine check dist/*
For guidelines on contributing, see CONTRIBUTING.md and SECURITY.md.
Release Process
For release procedures and PyPI publishing setup using GitHub Actions OIDC Trusted Publishing, see RELEASING.md.
License
This project is licensed under the MIT License.
فارسی
fa-redact یک کتابخانهٔ پایتونی سبک، مستقل (بدون وابستگی خارجی / Zero-dependency) و مبتنی بر حریم خصوصی (Privacy-first) است که با هدف تشخیص (Detection)، پنهانسازی (Redaction) و ناممستعارسازی (Pseudonymization) اطلاعات هویتی و حساس در متون فارسی و دادههای مرتبط با ایران طراحی شده است.
وضعیت: آلفا (Alpha)
نسخه بسته در این درخت منبع:v0.2.0
نسخههای منتشرشده در PyPI و گیتهاب در دسترس هستند. نشان (Badge) بالای صفحه آخرین نسخهٔ منتشرشده در PyPI را نمایش میدهد.
فهرست مطالب
- نمای کلی
- چرا fa-redact؟
- نصب
- شروع سریع
- قابلیتهای تفصیلی
- ۱. نرمسازی با حفظ موقعیت کاراکترها
- ۲. مدل داده و پایپلاین تشخیص
- ۳. اعتبارسنجی و تشخیص کد ملی ایران
- ۴. اعتبارسنجی و تشخیص شماره موبایل ایران
- ۵. اعتبارسنجی و تشخیص شماره شبا / IBAN ایران
- ۶. اعتبارسنجی و تشخیص آدرس ایمیل اسکی (اختیاری)
- ۷. اعتبارسنجی و تشخیص شماره کارت بانکی / PAN (اختیاری)
- ۸. شناسههای سازمانی / درمانی قابل پیکربندی (اختیاری)
- ۹. حل صریح تعارض تشخیصها (اختیاری)
- ۱۰. بازسازی دقیق بر اساس span در پنهانسازی
- ۱۱. ویژگیهای امنیتی و رفتاری نشست ناممستعارسازی
- تشخیصدهندههای سفارشی (Custom Detectors)
- کاربرد در حوزهٔ سلامت و هوش مصنوعی
- جدول پوشش و قابلیتها
- مدل حریم خصوصی و سلب مسئولیتهای امنیتی
- سیاست دادههای آزمایشی امن
- توسعه و کنترل کیفیت
- مجوز
نمای کلی (Overview)
در سیستمهای درمانی، سازمانی و کاربردهای نوین هوش مصنوعی، ارسال متنهای حاوی اطلاعات شناساییکنندهٔ شخصی (Personally Identifiable Information یا به اختصار PII) به مدلهای زبانی بزرگ (LLM) یا سرویسهای ابری خارجی میتواند حریم خصوصی بیماران و کاربران را به خطر بیندازد.
کتابخانهٔ fa-redact با ارائهٔ ابزارهای سبک و کارآمد، متن را پیش از خروج از محیط امن شما پردازش کرده و شناسههای حساس پشتیبانیشده را با نشانگذارهای جایگزین (Placeholders) تعویض میکند. پس از دریافت پاسخ از هوش مصنوعی، میتوان نشانگذارها را در محیط محلی و امن خود به مقادیر اصلی بازگرداند (Restoration).
چرا fa-redact؟ (Motivation)
پردازش متون فارسی در زمینهٔ امنیت داده و پالایش متون، چالشهای منحصربهفردی دارد:
- تنوع ارقام و حروف: در متون فارسی، شمارهها ممکن است با ارقام فارسی (
۰-۹)، ارقام عربی (٠-٩) یا ارقام لاتین (0-9) نوشته شده باشند. همچنین تفاوت حروف مانند «ي» و «ك» عربی با «ی» و «ک» فارسی مانع عملکرد صحیح الگوهای ساده میشود. - حفظ موقعیت کاراکترها (Offsets): روشهای مرسوم نرمسازی متن اغلب طول رشته را تغییر میدهند (مثلاً با حذف فاصلهها یا اعراب)؛ این کار باعث جابهجایی ایندکسهای کاراکتری شده و جایگزینی دقیق در متن اصلی را غیرممکن میسازد.
fa-redactتضمین میکند که طول رشته قبل و بعد از نرمسازی یکسان باشد (len(normalized) == len(original)). - اعتبارسنجی الگوریتمی: بهجای استفاده از رجکسهای حدسی، شناسههای ملی و شمارههای موبایل با قواعد کنترلی و پیششمارههای رگولاتوری ایران بررسی میشوند.
- حفظ یکپارچگی ارجاعات در هوش مصنوعی: در مکالمات چندمرحلهای با LLMها، یک موجودیت مشخص همواره به یک ناممستعار یکسان نگاشت میشود تا ساختار معنایی متن برای مدل حفظ شود.
- بدون وابستگی و کاملاً تایپشده: بدون نیاز به بستههای جانبی سنگین، کاملاً با پایتون خالص (+3.10) پیادهسازی شده و از تایپینگ کامل (
py.typed) پشتیبانی میکند.
نصب (Installation)
نصب آخرین نسخهٔ پایدار از طریق مخزن رسمی PyPI:
pip install fa-redact
نصب برای توسعهدهندگان و از روی کد منبع:
git clone https://github.com/mehdimt1980/fa-redact.git
cd fa-redact
pip install -e ".[dev]"
شروع سریع (Quick Start)
۱. تشخیص اطلاعات هویتی (detect)
شناسایی بخشهای حساس متن به همراه موقعیت دقیق کاراکتری و مقدار نرمالشده:
from fa_redact import detect
text = "بیمار با کد ملی ۱۲۳۴۵۶۷۸۹۱ و شماره ۰۹۱۲۳۴۵۶۷۸۹ مراجعه کرد."
detections = detect(text)
for item in detections:
print(f"نوع شناسه: {item.type}")
print(f"مقدار در متن اصلی: {item.value}")
print(f"مقدار نرمالشده: {item.normalized_value}")
print(f"موقعیت کاراکتری: [{item.start}:{item.end}]")
# اطمینان از تطابق دقیق موقعیت با متن اصلی:
assert text[item.start : item.end] == item.value
فیلدهای شیء Detection:
type: نوع شناسهٔ شناساییشده (مانندIR_NATIONAL_IDیاIR_MOBILE).value: مقدار دقیق همانطور که در متن ورودی نوشته شده است.normalized_value: مقدار نرمالشده و یکپارچهٔ شناسه.startوend: موقعیت شروع و پایان شناسه در متن ورودی (text[start:end] == value).
۲. پنهانسازی بدونحالت (redact)
جایگزینی شناسهها با نشانگذارهای نوعدار و امن. در هر فراخوانی، شناسههای تکراری نشانگذار یکسانی دریافت میکنند:
from fa_redact import redact
text = "کد ملی: ۱۲۳۴۵۶۷۸۹۱، تماس: ۰۹۱۲۳۴۵۶۷۸۹، تماس مجدد: 09123456789"
redacted_text = redact(text)
print(redacted_text)
# خروجی: "کد ملی: [IR_NATIONAL_ID_1]، تماس: [IR_MOBILE_1]، تماس مجدد: [IR_MOBILE_1]"
- تابع
redact()بدونحالت (Stateless) است و نگاشتی برای بازگردانی ذخیره نمیکند. - شمارهگذاری در هر فراخوانی از
1آغاز میشود.
۳. ناممستعارسازی و بازگردانی برای هوش مصنوعی (PseudonymizationSession)
مدیریت حالت در تعاملات چندمرحلهای با مدلهای زبانی (LLM) و بازگردانی محلی پاسخها:
from fa_redact import PseudonymizationSession
# ایجاد یک نشست جدید
session = PseudonymizationSession()
# ۱. پنهانسازی متن حساس پیش از ارسال به سرویس خارجی:
prompt = "کد ملی بیمار ۱۲۳۴۵۶۷۸۹۱ و شماره تماس ۰۹۱۲۳۴۵۶۷۸۹ است."
pseudonymized_prompt = session.pseudonymize(prompt)
print(pseudonymized_prompt)
# خروجی: "کد ملی بیمار [IR_NATIONAL_ID_1] و شماره تماس [IR_MOBILE_1] است."
# ۲. ارسال فقط متن ناممستعارسازیشده (pseudonymized_prompt) به مدل زبانی. پاسخ فرضی مدل:
llm_response = "جهت هماهنگی با بیمار با [IR_MOBILE_1] تماس حاصل فرمایید."
# ۳. بازگردانی مقادیر واقعی به صورت محلی در محیط امن شما:
restored = session.restore(llm_response)
print(restored)
# خروجی: "جهت هماهنگی با بیمار با ۰۹۱۲۳۴۵۶۷۸۹ تماس حاصل فرمایید."
قابلیتهای تفصیلی
۱. نرمسازی با حفظ موقعیت کاراکترها (Position-Preserving Normalization)
در fa-redact نرمسازی به شیوهای کاملاً قطعی و نگاشت یکبهیک کاراکترها انجام میشود تا ایندکس کاراکترها هیچ تغییری نکند (len(normalized) == len(original)):
from fa_redact import normalize_digits, normalize_letters, normalize_text
# تبدیل ارقام فارسی (۰-۹) و عربی (٠-٩) به ارقام اسکی (0-9)
normalize_digits("کد بیمار: ۱۲۳٤٥")
# خروجی: "کد بیمار: 12345"
# تبدیل حروف عربی به فارسی (ي -> ی ، ك -> ک)
normalize_letters("پزشك و دكتر")
# خروجی: "پزشک و دکتر"
# نرمسازی جامع با حفظ طول رشته
normalize_text("كد ملي: ۰۰۱٢٣٤٥٦٧٨")
# خروجی: "کد ملی: 0012345678"
این توابع فاصلهها، نیمفاصلهها، اعراب یا علائم نگارشی را حذف نمیکنند تا آفستها با دقت صد در صد حفظ شوند.
۲. مدل داده و پایپلاین تشخیص
تابع detect() نتایج تشخیصدهندهها را جمعآوری و بر اساس (start, end, type) مرتب میکند. این لایه عمداً همپوشانی یا تشخیصهای تکراری را حذف یا رد نمیکند.
کنترل همپوشانی زمانی اعمال میشود که قرار است متن واقعاً تغییر کند؛ یعنی در redact() و PseudonymizationSession.pseudonymize(). در این دو API، وجود spanهای همپوشان، تودرتو یا تکراری باعث ValueError میشود تا از جایگزینیهای اشتباه و مخدوش شدن دادههای محرمانه جلوگیری شود.
۳. اعتبارسنجی و تشخیص کد ملی ایران
تشخیص و اعتبارسنجی کد ملی ۱۰ رقمی با قاعدهٔ کنترل Modulo-11 پیادهسازیشده برای کد ملی ایران:
from fa_redact import IranianNationalIDDetector, is_valid_national_id
# مقادیر نمونهٔ الگوریتمی (غیرواقعی):
is_valid_national_id("1234567891") # True (معتبر)
is_valid_national_id("۱۲۳۴۵۶۷۸۹۱") # True (پشتیبانی از ارقام فارسی)
is_valid_national_id("1234567890") # False (رقم کنترلی نامعتبر)
is_valid_national_id("1111111111") # False (رد ارقام تکراری جعلی)
- بررسی چکسام: اعمال قاعدهٔ کنترلی Modulo-11 کد ملی ایران.
- رد کدهای تکراری جعلی: کدهایی مانند
0000000000یا1111111111که فرمول چکسام را پاس میکنند اما نامعتبرند، رد میشوند. - طول و قالب دقیق: ورودی باید دقیقاً ۱۰ رقم در قالب فشرده باشد؛ فاصلهها یا خط تیره حذف نمیشوند و صفرهای ابتدایی بهطور خودکار اضافه نمیشوند.
- سلب مسئولیت استعلام: این اعتبارسنجی صرفاً ساختار ریاضی را تایید میکند و استعلامی از سامانههای ثبت احوال یا احراز هویت انجام نمیدهد.
۴. اعتبارسنجی و تشخیص شماره موبایل ایران
اعتبارسنجی بر اساس snapshot شمارهگذاری ۲۰۲۶ CRA/ITU و پیششمارههایی انجام میشود که در آن برای خدمات موبایل تخصیص یافتهاند:
from fa_redact import IranianMobileNumberDetector, is_valid_mobile_number
# قالبهای فشردهٔ داخلی و بینالمللی پشتیبانیشده
is_valid_mobile_number("09123456789") # True (قالب داخلی)
is_valid_mobile_number("۰۹۱۲۳۴۵۶۷۸۹") # True (ارقام فارسی)
is_valid_mobile_number("+989123456789") # True (قالب بینالمللی با +98)
is_valid_mobile_number("00989351234567") # True (قالب بینالمللی با 0098)
is_valid_mobile_number("09412345678") # False (شماره ثابت غیرجغرافیایی)
- تفکیک پیششمارههای خدمات سیار: بررسی پیششمارهها بر اساس snapshot طرح شمارهگذاری کشوری.
- رد پیششمارههای غیرموبایل: پیششمارههایی نظیر
094(ثابت غیرجغرافیایی) یا09950(ترانک عمومی) به عنوان موبایل پذیرفته نمیشوند. - قالب فشرده: فقط قالبهای فشرده (
09xxxxxxxxx،+989xxxxxxxxx،00989xxxxxxxxx) پذیرفته میشوند و حذف فاصله، پرانتز یا خط تیره انجام نمیشود. - سلب مسئولیت مالکیت: اعتبارسنجی ساختاری است و وضعیت فعال بودن سیمکارت، هویت مشترک، اپراتور فعلی یا ترابردپذیری را بررسی نمیکند.
۵. اعتبارسنجی و تشخیص شماره شبا / IBAN ایران
کتابخانهٔ fa-redact تابع اعتبارسنجی مستقل is_valid_iranian_iban و تشخیصدهندهٔ IranianIBANDetector را برای شماره شبا / شناسه حساب بانکی ایران (IBAN) به صورت کاملاً آفلاین، قطعی و بدون وابستگی خارجی ارائه میدهد. ساختار شماره شبا در قالب فشرده الکترونیکی شامل ۲۶ کاراکتر است: پیشوند IR، به همراه ۲ رقم کنترلی و BBAN بیستودورقمی (یا IR به همراه ۲۴ رقم عددی):
from fa_redact import (
IranianIBANDetector,
PseudonymizationSession,
detect,
is_valid_iranian_iban,
redact,
)
# ۱. اعتبارسنجی مستقل (دقیقاً ۲۶ کاراکتر: IR بزرگ + ۲۴ رقم با الگوریتم MOD-97):
is_valid_iranian_iban("IR641234567890123456789012") # True (معتبر)
is_valid_iranian_iban("IR۶۴۱۲۳۴۵۶۷۸۹۰۱۲۳۴۵۶۷۸۹۰۱۲") # True (پشتیبانی از ارقام فارسی)
is_valid_iranian_iban("ir641234567890123456789012") # False (رد حروف کوچک ir)
is_valid_iranian_iban("IR 64 1234 5678 9012 3456 7890 12") # False (رد فاصله)
is_valid_iranian_iban("IR001234567890123456789012") # False (چکسام نامعتبر)
# ۲. تشخیص پیشفرض (قرارگیری در مجموعهٔ تشخیصدهندههای پیشفرض):
text = "شماره شبا واریز: IR641234567890123456789012 و تماس ۰۹۱۲۳۴۵۶۷۸۹"
detections = detect(text)
# خروجی: [Detection(type='IR_IBAN', ...), Detection(type='IR_MOBILE', ...)]
# ۳. پنهانسازی و ناممستعارسازی پیشفرض:
redacted_text = redact(text)
# خروجی: "شماره شبا واریز: [IR_IBAN_1] و تماس [IR_MOBILE_1]"
session = PseudonymizationSession()
pseudonymized = session.pseudonymize("واریز به شبا IR641234567890123456789012")
# خروجی: "واریز به شبا [IR_IBAN_1]"
restored = session.restore("تایید واریز به [IR_IBAN_1]")
# خروجی: "تایید واریز به IR641234567890123456789012"
- الگوریتم چکسام MOD-97-10: اعتبارسنجی استاندارد بینالمللی ISO 7064 MOD 97-10 از طریق بازآرایی رشته به فرمت
BBAN (۲۲ رقم) + 1827 (IR) + ارقام کنترلی (۲ رقم)و بررسی شرطباقیمانده == 1. - حضور در پایپلاین پیشفرض: برخلاف
EmailDetector، تشخیصدهندهٔIranianIBANDetectorدر مجموعهٔ پیشفرض (_DEFAULT_DETECTORS) قرار دارد؛ زیرا ساختار مشخص آن (IRبه همراه ۲۴ رقم) هیچگونه تداخل یا ابهام سینتکسی با کدهای ملی یا شمارههای موبایل ایجاد نمیکند. - حفظ موقعیت کاراکتری و پشتیبانی از ارقام متنوع: امکان استفاده از ارقام فارسی (
۰-۹) و عربی (٠-٩) در ۲۴ نویسهٔ عددی پس ازIRوجود دارد؛ این بخش شامل ۲ رقم کنترلی و BBAN بیستودورقمی است. نگاشت درDetection.valueبر اساس متن ورودی حفظ شده و مقدار یکپارچه درDetection.normalized_valueقرار میگیرد. - قالب فشردهٔ الکترونیکی: صرفاً فرمت الکترونیکی فشرده بدون فاصله، خط تیره یا جداکننده پذیرفته میشود. پیشوند
irبا حروف کوچک نیز پذیرفته نمیشود. - سلب مسئولیت بانکی و حریم خصوصی: تابع
is_valid_iranian_ibanصرفاً صحت ساختار ریاضی چکسام را به صورت محلی و آفلاین بررسی میکند. این تابع هیچگونه استعلام بانکی، بررسی صحت شماره حساب، اتصال به شبکهٔ شتاب یا پایا انجام نمیدهد و فعال بودن حساب بانکی را تایید نمیکند.
۶. اعتبارسنجی و تشخیص آدرس ایمیل اسکی (اختیاری)
کتابخانهٔ fa-redact تابع اعتبارسنجی مستقل is_valid_email و تشخیصدهندهٔ EmailDetector را برای آدرسهای ایمیل استاندارد اسکی به صورت بدون وابستگی ارائه میدهد:
from fa_redact import (
EmailDetector,
PseudonymizationSession,
detect,
is_valid_email,
redact,
)
# ۱. اعتبارسنجی ساختاری مستقل:
is_valid_email("user.name@example.com") # True (معتبر)
is_valid_email("doctor@sub.hospital.ir") # True (معتبر)
is_valid_email("user@localhost") # False (رد دامنههای تکبخشی)
is_valid_email("user+tag@invalid_domain.com") # False (رد آندرلاین در دامنه)
is_valid_email("user@مثال.ایران") # False (عدم پشتیبانی از ایمیلهای غیر اسکی)
# ۲. تشخیص با فعالسازی اختیاری (Opt-in):
text = "مکاتبه با دکتر احمدی: dr.ahmadi@hospital.ir و تماس 09123456789"
detections = detect(text, detectors=[EmailDetector()])
# خروجی: [Detection(type='EMAIL', value='dr.ahmadi@hospital.ir', ...)]
# ۳. پنهانسازی و ناممستعارسازی اختیاری:
redacted_text = redact(text, detectors=[EmailDetector()])
# خروجی: "مکاتبه با دکتر احمدی: [EMAIL_1] و تماس 09123456789"
session = PseudonymizationSession()
pseudonymized = session.pseudonymize(
"تماس با info@clinic.ir یا dr.ahmadi@hospital.ir انجام شد.",
detectors=[EmailDetector()],
)
# خروجی: "تماس با [EMAIL_1] یا [EMAIL_2] انجام شد."
restored = session.restore("پیام به [EMAIL_1] ارسال شد.")
# خروجی: "پیام به info@clinic.ir ارسال شد."
- معماری اختیاری (Opt-in): کلاس
EmailDetectorدر فاز ۱۲ به صورت اختیاری ارائه شده و در مجموعهٔ پیشفرض قرار ندارد. ایمیلهایی با بخش محلی عددی (مانند09123456789@example.com) ممکن است با تشخیصدهندههای موبایل یا کد ملی همپوشانی ایجاد کنند. از آنجا کهredact()وpseudonymize()در صورت وجود همپوشانی خطا میدهند، فعالسازی ایمیل تا زمان پیادهسازی مکانیزم حل تعارض به صورت صریح و اختیاری خواهد بود. - پیکربندی تشخیصدهندهها در هر فراخوانی (Per-Call Detectors): کلاس
PseudonymizationSessionوضعیت نگاشت و شمارندههای ناممستعارسازی را نگه میدارد، اما مجموعهٔ تشخیصدهندهها را در خود ذخیره نمیکند. تشخیصدهندهها در سازندهٔPseudonymizationSessionتنظیم نمیشوند و برای هر فراخوانیpseudonymize()باید در صورت نیاز آرگومانdetectors=[...]بهصورت صریح ارسال شود. - قالب استاندارد اسکی (Conservative ASCII): اعتبارسنجی بخش محلی dot-atom (حداکثر ۶۴ کاراکتر)، نام دامنهٔ ساختاریافته مطابق DNS (حداکثر ۲۵۳ کاراکتر دامنه، ۱ تا ۶۳ کاراکتر برای هر برچسب، حداقل ۲ کاراکتر برای TLD و حداکثر ۲۵۴ کاراکتر برای کل آدرس).
- قالبهای پشتیبانینشده: ساختارهای پیچیده یا منسوخ مانند رشتههای کوتیشندار (
"john doe"@example.com)، دامنههای لیترال IP (user@[192.168.1.1])، کامنتها، فاصلههای شکستهشده (folding whitespace)، دامنههای تکبخشی (user@localhost) و ایمیلهای بینالمللی غیر اسکی (EAI / RFC 6530+) پذیرفته نمیشوند. - سلب مسئولیت و حریم خصوصی: تابع
is_valid_emailصرفاً ساختار نگارشی را به صورت محلی و آفلاین بررسی میکند. این تابع هیچگونه درخواست شبکه، استعلام DNS یا بررسی وجود صندوق پستی (Mailbox) انجام نمیدهد و هیچ دادهای را لاگ نمیکند. صحت ساختاری به منزلهٔ وجود واقعی آدرس ایمیل نیست.
۷. اعتبارسنجی و تشخیص شماره کارت بانکی / PAN (اختیاری)
کتابخانهٔ fa-redact تابع اعتبارسنجی مستقل is_valid_bank_card_number و تشخیصدهندهٔ BankCardDetector را برای شمارههای ۱۶ رقمی کارتهای پرداخت بانکی (Primary Account Number / PAN) به صورت کاملاً آفلاین و قطعی ارائه میدهد:
from fa_redact import (
BankCardDetector,
PseudonymizationSession,
detect,
is_valid_bank_card_number,
redact,
)
# ۱. اعتبارسنجی مستقل (دقیقاً ۱۶ رقم، الگوریتم چکسام Luhn):
is_valid_bank_card_number("1234567890123452") # True (کارت معتبر آزمایشی)
is_valid_bank_card_number("۱۲۳۴۵۶۷۸۹۰۱۲۳۴۵۲") # True (پشتیبانی از ارقام فارسی)
is_valid_bank_card_number("١٢٣٤٥٦٧٨٩٠١٢٣٤٥٢") # True (پشتیبانی از ارقام عربی)
is_valid_bank_card_number("1234 5678 9012 3452") # False (رد فاصله)
is_valid_bank_card_number("1234-5678-9012-3452") # False (رد خط تیره)
is_valid_bank_card_number("0000000000000000") # False (رد رشتههای تماماً یکسان)
is_valid_bank_card_number("1234567890123453") # False (عدم تطابق چکسام)
# ۲. تشخیص با فعالسازی اختیاری (Opt-in):
text = "شماره کارت: 1234567890123452"
detections = detect(text, detectors=[BankCardDetector()])
# خروجی: [Detection(type='BANK_CARD', value='1234567890123452', ...)]
# ۳. پنهانسازی و ناممستعارسازی اختیاری:
redacted_text = redact(text, detectors=[BankCardDetector()])
# خروجی: "شماره کارت: [BANK_CARD_1]"
session = PseudonymizationSession()
pseudonymized = session.pseudonymize(
"واریز به کارت ۱۲۳۴۵۶۷۸۹۰۱۲۳۴۵۲ انجام شد.",
detectors=[BankCardDetector()],
)
# خروجی: "واریز به کارت [BANK_CARD_1] انجام شد."
restored = session.restore("تایید واریز به [BANK_CARD_1]")
# خروجی: "تایید واریز به ۱۲۳۴۵۶۷۸۹۰۱۲۳۴۵۲"
- معماری اختیاری (Opt-in): کلاس
BankCardDetectorبه صورت اختیاری ارائه شده و در مجموعهٔ پیشفرض (_DEFAULT_DETECTORS) قرار ندارد. - الگوریتم چکسام Luhn (MOD-10): بررسی صحت شمارههای ۱۶ رقمی کارت با الگوریتم استاندارد Luhn (دوبرابر کردن ارقام در جایگاههای فرد از راست، کسر ۹ در صورت بزرگتر شدن از ۹ و بررسی
sum % 10 == 0). - نرمسازی با حفظ موقعیت کاراکتری: پشتیبانی از ارقام لاتین (
0-9)، فارسی (۰-۹) و عربی (٠-٩). حفظ شکل ظاهری درDetection.valueو تبدیل به ارقام استاندارد اسکی درDetection.normalized_value. - فیلتر دفاعی رشتههای تکراری: رشتههای ۱۶ رقمی تماماً تکراری (
0000000000000000تا9999999999999999) حتی در صورت تطابق ریاضی چکسام، رد میشوند. - قالب فشردهٔ الکترونیکی: صرفاً رشتههای ۱۶ رقمی پیوسته بدون فاصله، خط تیره یا جداکننده پذیرفته میشوند.
- بیطرفی نسبت به صادرکننده (Issuer Neutrality): شناسهها و نامگذاریها به صورت خنثی (
BANK_CARD) طراحی شدهاند. این کتابخانه پایگاه داده BIN/IIN بانکی نگهداری نمیکند و تعلقی به یک بانک ایرانی یا شبکهٔ خاص را تایید نمیکند. - سلب مسئولیت بانکی و امنیتی: معتبر بودن الگوریتم Luhn به معنی واقعی بودن کارت، فعال بودن آن، تعلق آن به بانک ایرانی، مالکیت آن توسط شخص مشخص یا وجود حساب مرتبط با آن نیست. این تابع صرفاً اعتبارسنجی محلی و آفلاین ریاضی انجام میدهد و هیچگونه اتصال به درگاه پرداخت، استعلام CVV2 یا بررسی وضعیت حساب انجام نمیدهد.
۸. شناسههای سازمانی / درمانی قابل پیکربندی (اختیاری)
شماره پرونده پزشکی (MRN)، شناسه بیمار، شناسه پذیرش، شناسه مراجعه / Encounter، شناسه پرونده و سایر شناسههای درمانی و سازمانی کاملاً وابسته به سازمان یا بیمارستان مربوطه هستند. برای این شناسهها قالب واحد و جهانی وجود ندارد و کتابخانهٔ fa-redact الگوهای حدسی یا پیشفرض برای آنها تعریف نمیکند.
به جای آن، لایهٔ پیکربندی سبک، تغییرناپذیر و مستقلی (PatternRule و PatternDetector) ارائه شده است تا سازمان یا بیمارستان بتواند الگوهای منظم (Regex) اختصاصی خود را تعریف کند:
import re
from fa_redact import (
PatternDetector,
PatternRule,
PseudonymizationSession,
detect,
redact,
)
# ۱. تعریف قواعد اختصاصی بیمارستان / سازمان
hospital_detector = PatternDetector(
[
# شماره پرونده پزشکی (MRN)
PatternRule(
type="MRN",
pattern=r"(?<!\w)MRN-[0-9]{6}(?!\w)",
),
# شناسه بیمار با استفاده از گروه نامدار جهت حفظ برچسب 'Patient ID: '
PatternRule(
type="PATIENT_ID",
pattern=r"Patient\s*ID\s*:\s*(?P<id>PAT-[A-Z]{2}-[0-9]{8})",
group="id",
flags=re.IGNORECASE,
),
# شناسه پذیرش
PatternRule(
type="ADMISSION_ID",
pattern=r"(?<!\w)ADM-20[0-9]{2}-[0-9]{6}(?!\w)",
),
# شناسه مراجعه / Encounter
PatternRule(
type="ENCOUNTER_ID",
pattern=r"(?<!\w)ENC-[0-9]{10}(?!\w)",
),
]
)
# ۲. تشخیص بر روی متن با پشتیبانی از ارقام فارسی در متن اصلی
text = "پرونده: MRN-۱۲۳۴۵۶ و Patient ID: PAT-TE-12345678"
detections = detect(text, detectors=[hospital_detector])
# خروجی شامل:
# - Detection(type='MRN', value='MRN-۱۲۳۴۵۶', normalized_value='MRN-123456', span=[8:18])
# - Detection(type='PATIENT_ID', value='PAT-TE-12345678', normalized_value='PAT-TE-12345678', span=[33:48])
# ۳. پنهانسازی (حفظ برچسب متن 'Patient ID: ' و جایگزینی فقط بخش شناسه)
redacted = redact(text, detectors=[hospital_detector])
# خروجی: "پرونده: [MRN_1] و Patient ID: [PATIENT_ID_1]"
# ۴. ناممستعارسازی چندمرحلهای با ارقام فارسی و انگلیسی
session = PseudonymizationSession()
# مرحله اول: با ارقام فارسی
turn1 = session.pseudonymize("پرونده: MRN-۱۲۳۴۵۶", detectors=[hospital_detector])
# خروجی: "پرونده: [MRN_1]"
# مرحله دوم: با ارقام لاتین (به همان [MRN_1] متصل میشود)
turn2 = session.pseudonymize("پیگیری پرونده MRN-123456", detectors=[hospital_detector])
# خروجی: "پیگیری پرونده [MRN_1]"
# بازگردانی به اولین شکل دیدهشده (ارقام فارسی):
restored = session.restore("پاسخ به [MRN_1]")
# خروجی: "پاسخ به MRN-۱۲۳۴۵۶"
- معماری اختیاری (Opt-in): کلاس
PatternDetectorکاملاً اختیاری است و در_DEFAULT_DETECTORSقرار ندارد. ارسالdetectors=[hospital_detector]تشخیصدهندههای پیشفرض را برای آن فراخوانی جایگزین میکند. - تطابق روی متن نرمالشده (پیشفرض): در حالت پیشفرض (
source="normalized"), الگوها با متن نرمالشده مطابقت داده میشوند؛ بنابراین یک رجکس ساده اسکی مانندMRN-[0-9]{6}میتواند ارقام فارسی (MRN-۱۲۳۴۵۶)، عربی (MRN-١٢٣٤٥٦) و لاتین (MRN-123456) را تشخیص دهد و فرم ظاهری را درDetection.valueو مقدار نرمالشده را درDetection.normalized_valueحفظ نماید. - حالت تطابق متن خام (source="original"): در صورت نیاز به بررسی دقیق بر روی متن دستنخورده، حالت
source="original"نیز پشتیبانی میشود. - پشتیبانی از گروههای انتخابی (Capture Groups): امکان استفاده از اندیس عددی (
group=1) یا گروه نامدار (group="id") برای جدا کردن شناسه از کلمات مجاور یا برچسبها. عدم شرکت گروه در تطابق باValueErrorمواجه خواهد شد. - مالکیت مرزهای رجکس (Boundary Ownership): کتابخانه رجکسهای تعریفشده توسط کاربر را در مرزهای کلمه مانند
\bمحصور نمیکند؛ کنترل کامل رفتار رجکس بر عهدهٔ سازمان پیکربندیکننده است. - کامپایل یکباره و تغییرناپذیری: کلاس
PatternRuleیک dataclass منجمد (frozen) و تغییرناپذیر است و الگوها در زمان ساختPatternDetectorیکبار کامپایل و کش میشوند.
۹. حل صریح تعارض تشخیصها (اختیاری)
تابع detect() همواره شواهد خام تشخیصدهندهها را بازمیگرداند و همپوشانیها و رکوردهای تکراری را دقیقاً حفظ میکند تا امکان ممیزی و بازبینی شفاف وجود داشته باشد.
بهصورت پیشفرض، توابع redact() و session.pseudonymize() با سیاست محافظهکارانهٔ conflict_policy="reject" اجرا میشوند و در صورت وجود هرگونه تداخل، همپوشانی یا تکرار، با خطای ValueError متوقف میشوند:
from fa_redact import BankCardDetector, EmailDetector, redact
text = "ایمیل: 1234567890123452@example.com"
detectors = [EmailDetector(), BankCardDetector()]
# سیاست پیشفرض reject: به دلیل تداخل spanهای EMAIL و BANK_CARD خطا میدهد
# redact(text, detectors=detectors) # -> ValueError
در صورت نیاز، سازمان یا برنامه میتواند با انتخاب یکی از سیاستهای صریح، نحوهٔ حل تعارض را مشخص کند:
- سیاست طولانیترین بازه (
conflict_policy="longest"): بهصورت حریصانه طولانیترین span را انتخاب کرده و تشخیصهای کوتاهتر همپوشان را حذف میکند. موارد کاملاً تکراری در یکدیگر ادغام میشوند. در صورت وجود همپوشانی مبهم با طول مساوی،ValueErrorصادر میشود. - سیاست اولویت نوع موجودیت (
conflict_policy="priority"): اولویتبندی بر اساس فهرست مشخصشده درtype_priority=[...]انجام میشود و موجودیت با اولویت بالاتر برنده خواهد بود (حتی اگر طول span آن کوتاهتر باشد). تمامی انواع موجودیتهای درگیر در تعارض باید درtype_priorityذکر شده باشند.
# انتخاب EMAIL به دلیل طول بیشتر
redacted_longest = redact(text, detectors=detectors, conflict_policy="longest")
# خروجی: "ایمیل: [EMAIL_1]"
# اولویتدهی به کارت بانکی بر روی ایمیل
redacted_priority = redact(
text,
detectors=detectors,
conflict_policy="priority",
type_priority=["BANK_CARD", "EMAIL"],
)
# خروجی: "ایمیل: [BANK_CARD_1]@example.com"
۱۰. بازسازی دقیق بر اساس span در پنهانسازی
خروجی بر اساس بازههای دقیق Detection از متن اصلی ساخته میشود؛ فقط همان spanهای تشخیصدادهشده جایگزین میشوند و بخشهای دیگر متن بدون تغییر کپی میشوند. پیادهسازی از str.replace() سراسری بر اساس مقدار استفاده نمیکند.
۱۱. ویژگیهای امنیتی و رفتاری نشست ناممستعارسازی
کلاس PseudonymizationSession رفتارهای امنیتی زیر را تضمین میکند:
- ایزولهسازی نشستها (Mapping Isolation): هر نشست دارای حافظه و نگاشت مستقل است.
- یکپارچگی هویت در چند مرحله (Cross-Call Identity): هویت یک موجودیت بر اساس
(type, normalized_value)ردیابی میشود. بنابراین۰۹۱۲۳۴۵۶۷۸۹در نوبت اول و09123456789در نوبت دوم هر دو به[IR_MOBILE_1]متصل میشوند. - محدودیت تفاوت فرمت داخلی و بینالمللی: مقادیر
09123456789و+989123456789دارای رشتههای نرمالشدهٔ متفاوتی هستند و در نسخهٔ v0.2.0 به یک هویت یکسان تبدیل نمیشوند. - بازگردانی به اولین نمایش دیدهشده (First-Observed Representative): تابع
restore()یک بازگردانی معنایی بر اساس placeholder انجام میدهد و تضمین نمیکند که شکل نویسهای دقیق تکتک occurrenceهای متن اولیه بهصورت byte-for-byte بازسازی شود. - بازگردانی غیرآبشاری (Non-Cascading Restore): بازگردانی در یک مرحله و با فرار کاراکترهای خاص انجام میشود تا اگر مقدار بازگرداندهشده شبیه به نشانگذار باشد، بازگردانی بازگشتی و ناخواسته رخ ندهد.
- بهروزرسانی اتمیک (Atomic Updates): اگر در حین پردازش خطایی رخ دهد، وضعیت نشست و شمارندهها دستنخورده باقی میمانند.
- محافظت در برابر تداخل تاریخی با نشانگذارهای متنی: اگر در متون قبلی عبارتی مانند
[IR_MOBILE_2]به عنوان متن عادی وجود داشته باشد، سیستم آن شماره را رزرو کرده و برای شناسههای واقعی جدید اختصاص نمیدهد. - عدم دستکاری نشانگذارهای ناشناخته: نشانگذارهایی که در نگاشت نشست ثبت نشدهاند (مانند
[IR_MOBILE_99]) بدون خطا و دستنخورده باقی میمانند.
تشخیصدهندههای سفارشی (Custom Detectors)
معماری fa-redact مبتنی بر پروتکلهای ساختاری پایتون (Duck Typing) است. شما میتوانید کلاسی با متد دوآرگومانی پیادهسازی کنید:
import re
from collections.abc import Sequence
from fa_redact import Detection, detect
class MedicalRecordNumberDetector:
"""نمونه تشخیصدهنده سفارشی برای شماره پرونده پزشکی بیمارستانی (MRN)."""
def detect(
self,
original_text: str,
normalized_text: str,
) -> Sequence[Detection]:
detections: list[Detection] = []
for match in re.finditer(r"\bMRN-\d{6}\b", normalized_text):
detections.append(
Detection.from_texts(
type="MRN",
original_text=original_text,
normalized_text=normalized_text,
start=match.start(),
end=match.end(),
)
)
return detections
# توجه: پاس دادن فهرست اختصاصی تشخیصدهندهها، مجموعهٔ پیشفرض را برای آن فراخوانی جایگزین میکند.
text = "پرونده با MRN-123456 ثبت شد."
detections = detect(text, detectors=[MedicalRecordNumberDetector()])
در این پروتکل:
original_text: متن اصلی و دستنخورده است.normalized_text: نسخهٔ نرمالشده با طول دقیقاً برابر است.- تشخیصدهنده باید spanها را طوری برگرداند که offsetها در هر دو رشته یکسان باشند (استفاده از
Detection.from_texts()این موضوع را تضمین میکند). - پاس دادن
detectors=[...]مجموعهٔ تشخیصدهندههای پیشفرض را برای آن فراخوانی جایگزین میکند و بهطور خودکار به آنها افزوده نمیشود.
کاربرد در حوزهٔ سلامت و هوش مصنوعی (Healthcare & AI/LLM)
محیط محلی و امن بیمارستان / سازمان
│
├── ۱. متن پرونده حاوی شناسههای حساس بیمار
│ ↓
├── ۲. session.pseudonymize(raw_text)
│ ↓
├── ۳. پرامپت ناممستعارسازیشده (فقط شناسههای پشتیبانیشده جایگزین شدهاند)
│
▼ (ارسال صرفاً متن پالایششده به هوش مصنوعی خارج از سازمان)
سرویس مدل زبانی بزرگ (LLM API)
▲
│ (دریافت پاسخ شامل نشانگذارها)
▼
محیط محلی و امن بیمارستان / سازمان
│
└── ۴. session.restore(llm_response)
↓
۵. گزارش نهایی با شناسههای واقعی و بازگردانیشده
جدول پوشش و قابلیتها
| نوع شناسه هویتی / قابلیت | v0.1.0 | v0.2.0 | توضیحات |
|---|---|---|---|
| کد ملی ایران | ✅ پشتیبانی میشود | ✅ پیشفرض | اعتبارسنجی دقیق ۱۰ رقمی با قاعدهٔ چکسام Modulo-11 |
| شماره تلفن همراه ایران | ✅ پشتیبانی میشود | ✅ پیشفرض | اعتبارسنجی پیششمارههای مصوب رگولاتوری ایران (CRA 2026) |
| شماره شبا (IBAN) | ❌ پشتیبانی نمیشود | ✅ پیشفرض | اعتبارسنجی دقیق ۲۶ کاراکتری با قاعدهٔ چکسام MOD-97 (IR + ۲۴ رقم) |
| آدرس ایمیل | ❌ پشتیبانی نمیشود | 🧪 اختیاری | اعتبارسنجی و تشخیص ایمیلهای اسکی محافظهکارانه (detectors=[EmailDetector()]) |
| شماره کارت بانکی (PAN) | ❌ پشتیبانی نمیشود | 🧪 اختیاری | فرمت فشردهٔ ۱۶ رقمی + Luhn؛ بدون استعلام BIN/IIN یا صادرکننده (detectors=[BankCardDetector()]) |
| شناسههای سازمانی / درمانی (MRN و بیمار) | ❌ پشتیبانی نمیشود | 🧪 اختیاری | قابل پیکربندی اختصاصی توسط کاربر با PatternRule و PatternDetector |
| حل صریح تعارض تشخیصها | ❌ پشتیبانی نمیشود | 🧪 سیاست اختیاری | حل همپوشانیها و تکرارها با سیاست "longest" یا "priority" |
| نام اشخاص | ❌ پشتیبانی نمیشود | ❌ پشتیبانی نمیشود | نیازمند مدلهای پردازش زبان طبیعی و بازشناسی موجودیتهای نامدار (NER) |
| آدرس پستی و موقعیت مکانی | ❌ پشتیبانی نمیشود | ❌ پشتیبانی نمیشود | موجودیتهای غیرساختاریافته |
| تاریخ تولد و زمانها | ❌ پشتیبانی نمیشود | ❌ پشتیبانی نمیشود | برنامهریزیشده برای نسخههای آتی |
| شماره بیمه درمانی | ❌ پشتیبانی نمیشود | ❌ پشتیبانی نمیشود | فرمت سازمانی |
مدل حریم خصوصی و سلب مسئولیتهای امنیتی
سیاست دادههای آزمایشی امن
کلیهٔ مثالها، داکها و تستهای موجود در این مخزن بر پایهٔ دادههای مصنوعی، الگوهای الگوریتمی و بردارهای تستی غیرشخصی تولید شدهاند. هیچگونه اطلاعات واقعی هویتی، پرونده پزشکی، یا دادههای محرمانهٔ کاربران در این مخزن استفاده یا ذخیره نشده است.
توسعه و کنترل کیفیت
اجرای تستهای خودکار:
python -m pytest
بررسی سبک کدنویسی و لینتینگ:
ruff check .
ruff format --check .
بررسی ایستا و تایپچکینگ:
mypy src
mypy tests
بیلد پکیج و بررسی صحت توزیعها:
python -m build
python -m twine check dist/*
برای آگاهی از نحوهٔ مشارکت در پروژه، راهنماهای CONTRIBUTING.md و SECURITY.md را مطالعه فرمایید.
مجوز (License)
این پروژه تحت مجوز MIT منتشر شده است.
Metadata
Release files for fa-redact 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fa_redact-0.2.0.tar.gz | 136.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fa_redact-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 190.8 kB
Release files / fa_redact-0.2.0.tar.gz
| Download URL | fa_redact-0.2.0.tar.gz |
|---|---|
| Size | 136.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a1fc96f808386522d41c6332d96d4ca80a6f3f23de118aeb67d970f75a1521a7
|
|
BLAKE2b-256 checksum How to use checksums |
45ac0631b386efc858fa8c823dfb912c524c1980be2b997d53e11f9c1cd75238
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.
Transparency logRelease files / fa_redact-0.2.0-py3-none-any.whl
| Download URL | fa_redact-0.2.0-py3-none-any.whl |
|---|---|
| Size | 54.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b0af860dd7d96583f7d7794ea704152ecd9b1dc50d1a15aabde4e88785ab0c53
|
|
BLAKE2b-256 checksum How to use checksums |
339653c3de6ec5cdfcd491a07da09edb36946ac61469d038474d835ff7e5d0b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.
Transparency log