Skip to main content

fa-redact

PyPI version Python Version License: MIT CI

Languages: English | فارسی


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.3.0.
Published releases are available on PyPI and GitHub Releases. The PyPI badge above reflects the latest published PyPI version.


Table of Contents


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:

  1. Position-Preserving Normalization: Maps Persian and Arabic digits and letters 1-to-1 to canonical representations without altering string length or offset positions.
  2. Algorithmic Validation: Implements checksum rules and prefix checks for Iranian identifiers (National IDs and mobile numbers) rather than relying on loose regular expressions.
  3. Safe Placeholder Redaction: Deterministically substitutes detected spans with typed placeholders.
  4. 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-redact ensures len(normalized) == len(original), guaranteeing that slice offsets text[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 0000000000 or 1111111111.
  • 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) and 09950 (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

[!NOTE] Introduced in v0.2.0: Iranian IBAN validation and detection (IranianIBANDetector and is_valid_iranian_iban) are included in fa-redact v0.2.0 as part of the default detector set.

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) (representing 22-digit BBAN + 1827 + 2 check digits) and verifying remainder == 1.
  • Default Detector Inclusion: Unlike EmailDetector, IranianIBANDetector is included in _DEFAULT_DETECTORS because its structured boundary (IR followed 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 following IR (the 2 check digits plus the 22-digit BBAN), preserving the raw surface script in Detection.value and mapping to canonical ASCII in Detection.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_iban performs 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)

[!NOTE] Introduced in v0.2.0: Email validation and detection (EmailDetector and is_valid_email) are introduced in fa-redact v0.2.0 as an opt-in detector.

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: EmailDetector is intentionally opt-in in Phase 12 and is not included in the default detector set. Numeric-looking email local parts (such as 09123456789@example.com or 1234567891@example.com) can produce overlapping spans with mobile number or National ID detectors; while detect() permits overlaps, downstream redaction and pseudonymization fail-loud on overlaps until general conflict resolution is implemented.
  • Per-Call Detector Configuration: PseudonymizationSession stores pseudonym mappings and counters, not a persistent detector configuration. The detector set is selected independently for each pseudonymize() call.
  • Conservative ASCII Specification: Validates dot-atom local parts (1-64 chars) and DNS-style domain names (1-253 total domain chars, 1-63 chars per label, 2-63 chars for TLD, total address <= 254 characters).
  • 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_email performs 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)

[!NOTE] Introduced in v0.2.0: Bank card validation and detection (BankCardDetector and is_valid_bank_card_number) are introduced in fa-redact v0.2.0 as an opt-in detector.

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: BankCardDetector is 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 in Detection.value and normalizes to canonical ASCII in Detection.normalized_value.
  • Defensive Sequence Filtering: Trivial all-identical sequences (such as 0000000000000000 through 9999999999999999) 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: BankCardDetector uses 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_number performs 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)

[!NOTE] Introduced in v0.2.0: Configurable institutional identifier detection (PatternRule and PatternDetector) is introduced in fa-redact v0.2.0 as an opt-in detector.

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: PatternDetector is strictly opt-in and is not included in _DEFAULT_DETECTORS. Explicit detectors=[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 as MRN-[0-9]{6} matches Persian (MRN-۱۲۳۴۵۶), Arabic-Indic (MRN-١٢٣٤٥٦), and ASCII (MRN-123456) forms, preserving surface script in Detection.value and 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 a ValueError.
  • Regex Boundary Ownership: PatternDetector does not wrap configured patterns with word boundaries (\b or (?<!\w)). Institutions own full regex semantics.
  • Pre-Compiled & Immutable: PatternRule is an immutable frozen dataclass. Rules are compiled once during PatternDetector construction.

[!WARNING]

  • Synthetic Demonstration Patterns Only: The example patterns shown above (MRN-[0-9]{6}, PAT-AB-...) are purely synthetic demonstration examples. They are not healthcare standards.
  • Trusted Configuration Security Notice: Pattern rules are trusted application configuration. Python's standard re engine does not provide a built-in match timeout. Do not execute arbitrary unreviewed regexes supplied by untrusted users, tenants, LLMs, or external configuration sources.
  • Offline Syntactic Matching Only: A regex match proves only that configured syntax matched. It does not perform HIS/FHIR lookups, database queries, or network requests, and does not verify that a patient, encounter, admission, or hospital record exists.

9. Explicit Detection Conflict Resolution (Opt-in)

[!NOTE] Introduced in v0.2.0: Explicit conflict resolution (ConflictPolicy and resolve_detection_conflicts) is introduced in fa-redact v0.2.0.

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

[!WARNING]

  • Heuristic Policy Disclaimer: Conflict resolution is heuristic policy, not entity verification. Resolving a conflict does not verify the real-world semantic identity of a string.
  • Substring Exposure Risk: Selecting longest or priority may discard a detection that covers characters extending outside the winning detection. For example, selecting a shorter BANK_CARD over an EMAIL leaves @example.com unredacted in the output.
  • Conservative Recommendation: In cases of uncertainty or high-risk privacy requirements, keep the default reject policy.

10. Privacy-Safe Detection Reports (v0.3.0)

[!NOTE] Introduced in v0.3.0: Privacy-safe detection reporting (DetectionReport, detection_report, and report_detections) is introduced in fa-redact v0.3.0.

fa-redact provides a dedicated value-free reporting layer that summarizes detection evidence without retaining, storing, or returning detected PII values, normalized values, source text, character offsets, spans, snippets, or PII hashes.

1. Basic Detection Report

Generate a value-free summary directly from text using built-in or custom detectors:

from fa_redact import detection_report

text = "کد ملی: ۱۲۳۴۵۶۷۸۹۱، همراه: ۰۹۱۲۳۴۵۶۷۸۹"
report = detection_report(text)

print(report.total_detections)  # 2
print(dict(report.counts))  # {'IR_MOBILE': 1, 'IR_NATIONAL_ID': 1}
print(report.distinct_types)  # 2
print(report.has_conflicts)  # False
print(report.conflict_pairs)  # 0
print(report.conflicting_detections)  # 0
print(report.duplicate_groups)  # 0
2. Raw Evidence & Conflict Observability

detection_report() runs raw detector evidence without automatic conflict resolution. This exposes whether ambiguities or conflicts exist across active detectors:

from fa_redact import (
    BankCardDetector,
    EmailDetector,
    detection_report,
)

text = "ایمیل: 1234567890123452@example.com"
report = detection_report(
    text,
    detectors=[
        EmailDetector(),
        BankCardDetector(),
    ],
)

print(report.total_detections)  # 2
print(dict(report.counts))  # {'BANK_CARD': 1, 'EMAIL': 1}
print(report.has_conflicts)  # True
print(report.conflict_pairs)  # 1
print(report.conflicting_detections)  # 2
3. Standalone Report Before & After Resolution

Callers can summarize already-produced Detection sequences using report_detections(), enabling transparent pre- and post-resolution comparison:

from fa_redact import (
    detect,
    report_detections,
    resolve_detection_conflicts,
)

# 1. Raw detector evidence
raw_detections = detect(text, detectors=detectors)
raw_report = report_detections(raw_detections)
# raw_report.has_conflicts is True, raw_report.total_detections == 2

# 2. Resolved detections under explicit policy
resolved_detections = resolve_detection_conflicts(raw_detections, policy="longest")
resolved_report = report_detections(resolved_detections)
# resolved_report.has_conflicts is False, resolved_report.total_detections == 1

[!IMPORTANT]

  • Value-Free Design: DetectionReport is value-free by design: it does not retain source text, raw values, normalized values, offsets, snippets, or PII hashes.
  • Aggregate Metadata Scope: Entity-type labels (e.g. IR_NATIONAL_ID, MRN) and aggregate counts are still metadata. Applications must decide whether those metrics themselves require access control, retention limits, or restricted logging in their operational context.
  • Custom Entity Type Label Notice: Custom detector authors must keep Detection.type schema-level and non-sensitive. Use names such as MRN, PATIENT_ID, or CUSTOM_ID; never place a patient's actual name, identifier, or source value inside the type label.
  • No Compliance Claims: DetectionReport does not establish automated de-identification, anonymization, GDPR compliance, HIPAA compliance, or safe unrestricted telemetry.

11. 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-based str.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 to redact(), it raises a ValueError.

12. Stateful Pseudonymization Sessions

PseudonymizationSession manages persistent mappings across multi-turn AI interactions:

  • Local Sensitive Mapping: session.mapping contains {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 and 09123456789 in turn 2 both resolve to [IR_MOBILE_1].
  • Domestic vs. International Limitation: 09123456789 and +989123456789 have 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.

13. Command-Line Interface (CLI) (v0.3.0)

[!NOTE] Introduced in v0.3.0: The command-line interface (fa-redact and python -m fa_redact) is introduced in fa-redact v0.3.0.

fa-redact provides a conservative, privacy-conscious command-line interface using only the Python standard library (argparse). The CLI exposes detection, redaction, and aggregate reporting without modifying core library semantics.

1. Command Help & Version
# View general CLI help
fa-redact --help

# View installed CLI version
fa-redact --version
2. Stdin and File Redaction
# Redact from standard input (streaming)
echo "کد ملی بیمار ۱۲۳۴۵۶۷۸۹۱ و موبایل ۰۹۱۲۳۴۵۶۷۸۹ است." | fa-redact redact
# Output: "کد ملی بیمار [IR_NATIONAL_ID_1] و موبایل [IR_MOBILE_1] است."

# Redact from input file to stdout
fa-redact redact clinical_note.txt

# Redact from input file to an explicit output file
fa-redact redact clinical_note.txt -o sanitized_note.txt
3. Privacy-Safe Aggregate Detection Report

Generate a value-free JSON report containing entity counts and conflict indicators:

# Generate safe aggregate report from file
fa-redact report clinical_note.txt

# Pipe report output directly to JSON tools
fa-redact report clinical_note.txt | jq .
4. Detection Metadata (Value-Free)

Output structured detection slice metadata (type, start, end) as JSON:

fa-redact detect clinical_note.txt

[!IMPORTANT] fa-redact detect outputs only structural offsets (type, start, end) and intentionally never emits detected identifier values, normalized values, source text, context snippets, or PII hashes.

5. Explicit Detector Selection

Explicitly passing --detectors replaces the default detector set:

# Run only the opt-in email and bank card detectors
fa-redact redact input.txt --detectors email,bank_card

# Run no detectors (passes text through untouched)
fa-redact redact input.txt --detectors none
6. Redaction Conflict Resolution Policies

Configure how overlapping or duplicate detections are handled during redaction:

# Default 'reject' policy (fails loud with privacy-safe error on conflicts)
fa-redact redact input.txt --conflict-policy reject

# 'longest' policy (greedily prefers longer candidate spans)
fa-redact redact input.txt --detectors email,bank_card --conflict-policy longest

# 'priority' policy (resolves conflicts by explicit entity type hierarchy)
fa-redact redact input.txt --detectors email,bank_card --conflict-policy priority --priority BANK_CARD,EMAIL

[!WARNING]

  • In-Place File Safety: fa-redact rejects overwriting the source file in-place (input and output cannot point to the same file).
  • Privacy-Safe Diagnostics: Error messages sent to stderr never leak source text, detected values, or snippets.
  • No Complete De-Identification: Supported CLI detectors do NOT constitute complete clinical de-identification, free-text name extraction, or address removal.
  • No Compliance Guarantees: Using the CLI does NOT guarantee automated GDPR, HIPAA, or healthcare regulatory compliance.

14. Structured Data Helpers (v0.3.0)

[!NOTE] Introduced in v0.3.0: Structured data helpers (detect_fields, redact_fields, and report_fields) are introduced in fa-redact v0.3.0.

fa-redact provides conservative, non-destructive helpers for scanning, redacting, and summarizing explicitly selected string fields inside Python mappings and JSON-like dictionaries:

  • redact_fields(record, fields, ...): Returns a transformed dictionary copy with detected PII replaced by typed placeholders.
  • detect_fields(record, fields, ...): Returns a dictionary mapping each path to its list of Detection instances.
  • report_fields(record, fields, ...): Returns a dictionary mapping each path to a privacy-safe DetectionReport.
Key Architectural Guarantees
  1. Explicit Field Targeting: Only paths explicitly listed in fields are inspected or modified. fa-redact never performs blind recursive scanning of whole objects and never infers sensitive fields from schema key names. Unselected fields are never scanned, redacted, or reserved.
  2. Record-Wide Referential Consistency: A single redact_fields() call maintains consistent entity mappings across all selected fields in the record. If the same entity (matching type and normalized value) appears in multiple selected fields, it receives the exact same placeholder. Placeholder allocation follows deterministic selected-field order.
  3. Cross-Field Literal Collision Avoidance: Placeholder-like tokens (e.g. [EMAIL_1]) present as literal text in earlier selected fields are reserved so subsequent real entity occurrences receive safe, non-colliding placeholder numbers.
  4. Immutability & Non-Destructive Copying: Input mappings and nested objects are never mutated in place. Transformed copies are returned as plain Python dict instances.
  5. Type Preservation: All non-target values (integers, floats, booleans, None, lists, unaffected sub-mappings, and unselected strings) are preserved untouched.
  6. Dot-Separated Path Syntax: Target fields are specified using simple dot-separated keys (e.g. "note", "metadata.contact").
  7. Fail-Loud Conservative Semantics: Missing paths, non-mapping intermediate containers, non-string leaf values, or duplicate field paths fail loudly with clear exceptions.
  8. Error Privacy: Exception messages never echo or leak sensitive field contents or surrounding record data.
Example Usage
from fa_redact import EmailDetector, detect_fields, redact_fields, report_fields

record = {
    "patient_id": "کد ملی ۱۲۳۴۵۶۷۸۹۱",
    "note": "تماس با ۰۹۱۲۳۴۵۶۷۸۹ جهت هماهنگی",
    "summary": "بیمار ۰۹۱۲۳۴۵۶۷۸۹ مراجعه مجدد داشت.",
    "age": 42,
    "active": True,
    "metadata": {
        "contact_iban": "شبا IR641234567890123456789012",
        "visit_count": 3,
    },
}

# 1. Redact explicitly selected fields with record-wide referential consistency:
redacted = redact_fields(record, ["note", "summary", "metadata.contact_iban"])
print(redacted["note"])
# Output: "تماس با [IR_MOBILE_1] جهت هماهنگی"
print(redacted["summary"])
# Output: "بیمار [IR_MOBILE_1] مراجعه مجدد داشت." (same placeholder across fields)
print(redacted["metadata"]["contact_iban"])
# Output: "شبا [IR_IBAN_1]"

# Non-target fields and original record remain completely untouched:
assert record["note"] == "تماس با ۰۹۱۲۳۴۵۶۷۸۹ جهت هماهنگی"
assert redacted["age"] == 42
assert redacted["active"] is True

# 2. Detect PII in selected fields (raw evidence layer):
detections = detect_fields(record, ["patient_id", "note"])
for path, dets in detections.items():
    print(f"Path: {path} -> Found {len(dets)} detections")

# 3. Privacy-safe aggregate reporting per field:
reports = report_fields(record, ["note", "metadata.contact_iban"])
print(reports["note"].counts)
# Output: {'IR_MOBILE': 1}

[!WARNING]

  • Explicit Targeting as Safety Boundary: Structured helpers do not automatically scan arbitrary schemas. The application caller must explicitly decide which fields are candidate text fields.
  • Path Names as Metadata: report_fields() keys summaries by caller-supplied field paths. While DetectionReport values are strictly value-free, path names themselves are metadata; avoid encoding sensitive identifiers into path/field names.
  • List Indexing & Wildcards: List element traversal (e.g. items.0.note) and wildcards (*, **) are not supported in this phase.
  • No De-identification / Compliance Claims: Using structured helpers does not guarantee HIPAA Safe Harbor or GDPR de-identification compliance.

15. Experimental Opt-in Persian PERSON NER (v0.3.0)

[!NOTE] Introduced in v0.3.0: Experimental opt-in Persian personal name NER (PersianNERDetector) and the optional fa-redact[ner] extra are introduced in fa-redact v0.3.0.

fa-redact provides an experimental, strictly opt-in detector PersianNERDetector for local Persian personal name (PERSON) named-entity recognition using offline Hugging Face-compatible token-classification model checkpoints.

Installation Extra

To enable the optional ML backend dependencies without inflating base package size:

pip install "fa-redact[ner]"
Usage Concept
from fa_redact import PersianNERDetector, detect, redact

# Point strictly to a trusted local model directory (offline only)
ner = PersianNERDetector("/path/to/trusted/local/model")

text = "بیمار علی رضایی به درمانگاه مراجعه کرد."

# Explicit detectors list replaces default detectors:
detections = detect(text, detectors=[ner])
# Illustrative output (with a compatible tested model):
# Type: PERSON | Value: علی رضایی | Span: [6:15]

# Redaction with standard typed placeholder:
redacted = redact(text, detectors=[ner])
print(redacted)
# Illustrative output (with a compatible tested model):
# "بیمار [PERSON_1] به درمانگاه مراجعه کرد."
Combining NER with Built-in Direct Identifiers

Because explicit detectors=[...] replaces defaults, if you wish to run both Persian personal name NER and standard Iranian direct identifiers (National ID, Mobile, IBAN), explicitly include all desired detectors:

from fa_redact import (
    IranianIBANDetector,
    IranianMobileNumberDetector,
    IranianNationalIDDetector,
    PersianNERDetector,
    redact,
)

ner = PersianNERDetector("/path/to/trusted/local/model")
all_detectors = [
    IranianNationalIDDetector(),
    IranianMobileNumberDetector(),
    IranianIBANDetector(),
    ner,
]

text = "بیمار علی رضایی با کد ملی 0012345679 مراجعه کرد."
redacted = redact(text, detectors=all_detectors)
print(redacted)
# Illustrative output (with a compatible tested model):
# "بیمار [PERSON_1] با کد ملی [IR_NATIONAL_ID_1] مراجعه کرد."

[!WARNING]

  • Strictly Opt-in & Offline: PersianNERDetector is not included in default detectors (_DEFAULT_DETECTORS). It requires an explicitly supplied local directory path (local_files_only=True, trust_remote_code=False) and never downloads models automatically.
  • Model Trust Boundary: Local model checkpoints are executable/deserialization-adjacent assets. Only load models from trusted sources.
  • PERSON Entity Type: The detector emits PERSON entities and makes no assumptions regarding clinical roles (patient, physician, relative, or other individual).
  • Fail-Loud Long-Text Policy: Documents exceeding the model's configured sequence length (max_length) fail loudly with a privacy-safe ValueError without silent truncation.
  • No Universal Accuracy or Clinical Guarantee: PEYMA benchmark results (99.19% exact-span F1 on news domain) do not guarantee universal accuracy or clinical de-identification.

16. Clinical Redaction Profiles (v0.3.0)

[!NOTE] Introduced in v0.3.0: Clinical redaction profiles (ClinicalRedactionProfile and clinical_profile) are introduced in fa-redact v0.3.0.

fa-redact provides a small, immutable high-level composition layer for Persian healthcare text workflows through ClinicalRedactionProfile and the clinical_profile(...) builder. This layer composes existing direct identifier detectors, institutional regular expression rules, and optional person detectors without creating a duplicate detection engine or altering global default detectors.

1. Supported Document Workflow Templates

clinical_profile supports three standardized workflow labels (ClinicalTextTemplate):

  • "outpatient_note" (یادداشت سرپایی)
  • "discharge_summary" (خلاصه ترخیص)
  • "referral_letter" (برگه ارجاع)

All three templates start from the same conservative direct-identifier composition base. The workflow names provide structured configuration labels without imposing hidden regexes or different regulatory assumptions.

2. Detector Composition & Defaults

When creating a clinical profile:

  • Default Direct Identifiers: IranianNationalIDDetector, IranianMobileNumberDetector, IranianIBANDetector, and EmailDetector (include_email=True by default).
  • Bank Card Detection (Opt-in): include_bank_card=False by default. Set include_bank_card=True if payment card redaction is required.
  • Institutional Identifiers: Supply institutional_rules=[PatternRule(...)] to include institution-specific identifier formats (e.g., MRN, Patient ID, Encounter ID).
  • Personal Names (Opt-in): Pass an explicitly instantiated Detector (such as PersianNERDetector) as person_detector. Without this parameter, personal names are not detected.
  • Conflict Policy: Defaults to conflict_policy="reject". Pass conflict_policy="longest" or "priority" (with type_priority=[...]) for explicit conflict resolution.
3. Synthetic Examples
Example A: Outpatient Note (Standard Direct Identifiers)
from fa_redact import clinical_profile

# Conservative profile covering National ID, Mobile, IBAN, and Email
profile = clinical_profile("outpatient_note")

text = "بیمار با کد ملی ۱۲۳۴۵۶۷۸۹۱ و شماره تماس ۰۹۱۲۳۴۵۶۷۸۹ و ایمیل patient@clinic.ir جهت ویزیت مراجعه کرد."
redacted = profile.redact(text)
print(redacted)
# Output: "بیمار با کد ملی [IR_NATIONAL_ID_1] و شماره تماس [IR_MOBILE_1] و ایمیل [EMAIL_1] جهت ویزیت مراجعه کرد."
Example B: Discharge Summary with Institutional MRN Rule
from fa_redact import PatternRule, clinical_profile

# Add institution-specific MRN rule
mrn_rule = PatternRule(type="MRN", pattern=r"(?<!\w)MRN-[0-9]{6}(?!\w)")
profile = clinical_profile(
    "discharge_summary",
    institutional_rules=[mrn_rule],
    include_bank_card=True,
)

text = "خلاصه ترخیص پرونده MRN-123456: پرداخت با کارت ۱۲۳۴۵۶۷۸۹۰۱۲۳۴۵۲ انجام شد و با تماس ۰۹۱۲۳۴۵۶۷۸۹ هماهنگ گردید."
redacted = profile.redact(text)
print(redacted)
# Output: "خلاصه ترخیص پرونده [MRN_1]: پرداخت با کارت [BANK_CARD_1] انجام شد و با تماس [IR_MOBILE_1] هماهنگ گردید."
Example C: Referral Letter with Optional PERSON NER Detector
from fa_redact import PatternRule, PersianNERDetector, clinical_profile

# Explicitly supply a local offline NER model (requires fa-redact[ner])
ner = PersianNERDetector("/path/to/trusted/local/model")
ref_rule = PatternRule(
    type="REFERRAL_ID", pattern=r"(?<!\w)REF-[A-Z]{2}-[0-9]{4}(?!\w)"
)

profile = clinical_profile(
    "referral_letter",
    institutional_rules=[ref_rule],
    person_detector=ner,
)

text = "بیمار سارا محمدی با ارجاع REF-TE-9876 و شماره شبا IR641234567890123456789012 معرفی می‌شود."
redacted = profile.redact(text)
print(redacted)
# Illustrative output (with a compatible tested model):
# "بیمار [PERSON_1] با ارجاع [REFERRAL_ID_1] و شماره شبا [IR_IBAN_1] معرفی می‌شود."
4. Structured Data Helper Integration

ClinicalRedactionProfile provides convenience methods delegating to structured helpers:

record = {
    "patient": {"note": "مراجعه بیمار با کد ملی ۱۲۳۴۵۶۷۸۹۱"},
    "contact": "کد ملی ۱۲۳۴۵۶۷۸۹۱ و ایمیل info@hospital.ir",
    "unselected": 42,
}

profile = clinical_profile("outpatient_note")
redacted_record = profile.redact_fields(record, ["patient.note", "contact"])
# Maintains record-wide referential consistency across all selected fields:
# redacted_record["patient"]["note"] -> "مراجعه بیمار با کد ملی [IR_NATIONAL_ID_1]"
# redacted_record["contact"] -> "کد ملی [IR_NATIONAL_ID_1] و ایمیل [EMAIL_1]"

[!WARNING]

  • No Guaranteed Clinical De-Identification: ClinicalRedactionProfile is a convenience composition layer over supported detectors. It does not guarantee complete de-identification, HIPAA Safe Harbor compliance, GDPR compliance, or absence of residual identifiers.
  • Human & Institutional Review Required: Automated redaction is an aid; clinical text requires institutional validation before release or external transmission.
  • No Automatic Personal Name Detection: Without an explicitly configured person_detector, personal names are not detected.
  • No Built-in Universal MRN Patterns: Healthcare identifiers (MRN, Patient ID, Encounter ID) vary across institutions and require application-supplied PatternRule definitions.
  • No Generic Date, Address, or Medical Entity Detection: Dates, postal addresses, and clinical concepts (diagnoses, symptoms, medications) are not detected in Phase 22.
  • No Role Inference: Detected names remain PERSON; the library does not infer whether an entity is a patient, doctor, nurse, or relative.

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()])

[!NOTE] Passing detectors=[...] explicitly replaces the default detector list for that call. If you wish to use built-in detectors alongside custom ones, pass all desired detectors explicitly.


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

[!WARNING] This is not complete clinical de-identification. fa-redact detects only supported direct identifiers (National IDs, mobile numbers, and IBANs by default, plus opt-in detectors). It does not detect personal names, free-text addresses, dates, or other protected health categories by default.


Current Coverage & Limitations

Identifier / Capability v0.1.0 v0.2.0 v0.3.0 Notes
Iranian National ID (کد ملی) ✅ Supported ✅ Default ✅ Default Strict 10-digit modulo-11 checksum validation
Iranian Mobile Number ✅ Supported ✅ Default ✅ Default Prefix-aware validation against 2026 CRA numbering plan
Iranian IBAN / Sheba (شبا) ❌ Not Supported ✅ Default ✅ Default Strict 26-char MOD-97 checksum validation (IR + 24 digits)
Email Addresses ❌ Not Supported 🧪 Opt-in 🧪 Opt-in Conservative ASCII email validation and detection (detectors=[EmailDetector()])
16-digit Bank Card (PAN) ❌ Not Supported 🧪 Opt-in 🧪 Opt-in 16-digit compact PAN + Luhn checksum validation (detectors=[BankCardDetector()])
Institutional / Healthcare IDs (MRN, Patient ID) ❌ Not Supported 🧪 Opt-in 🧪 Opt-in Configurable via user-defined PatternRule / PatternDetector
Explicit Conflict Resolution ❌ Not Supported 🧪 Opt-in Policy 🧪 Opt-in Policy Resolves overlaps/duplicates via "longest" or "priority" policy
Privacy-Safe Detection Reports ❌ Not Supported ❌ Not Supported ✅ Supported Value-free aggregate reporting (DetectionReport, detection_report)
Command-Line Interface (CLI) ❌ Not Supported ❌ Not Supported ✅ Supported Console command fa-redact with detect, report, redact subcommands
Structured Data Helpers ❌ Not Supported ❌ Not Supported ✅ Supported Explicit field targeting (detect_fields, redact_fields, report_fields)
Personal Names (NER) ❌ Not Supported 🔬 Research 🧪 Opt-in Experimental opt-in PersianNERDetector with caller-supplied local ML model
Clinical Redaction Profiles ❌ Not Supported ❌ Not Supported 🧪 Supported High-level presets (ClinicalRedactionProfile, clinical_profile)
Postal Addresses ❌ Not Supported ❌ Not Supported ❌ Not Supported Unstructured spatial entities
Dates of Birth / Timestamps ❌ Not Supported ❌ Not Supported ❌ Not Supported Planned for future versions
Health Insurance Numbers ❌ Not Supported ❌ Not Supported ❌ Not Supported Institution-specific

Privacy and Security Model

[!WARNING]

  • Scope Limitation: fa-redact detects and redacts only the specific PII types supported by its enabled detectors (National IDs, mobile numbers, and IBANs by default, plus explicitly configured opt-in detectors). It does not provide complete automated clinical de-identification.
  • Sensitive Mapping Notice: session.mapping contains raw, sensitive PII. Treat session instances as sensitive in-memory objects and never transmit mappings to external services.
  • Not Production Clinical Software: fa-redact is an open-source library and is not certified as a medical device.
  • No Inherent Regulatory Compliance: Using this package does not by itself establish compliance with HIPAA, GDPR, or other privacy regulations.
  • No Identity Verification: Validation checks confirm format and checksum validity only; they do not authenticate individuals or check registry records.
  • Imperfect Detection: Heuristic and rule-based detectors may yield false positives or false negatives on malformed or atypical inputs.

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. For current development status and planned phases, see PROJECT_STATUS.md and ROADMAP.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.3.0
نسخه‌های منتشرشده در PyPI و گیت‌هاب در دسترس هستند. نشان (Badge) بالای صفحه آخرین نسخهٔ منتشرشده در PyPI را نمایش می‌دهد.


فهرست مطالب


نمای کلی (Overview)

در سیستم‌های درمانی، سازمانی و کاربردهای نوین هوش مصنوعی، ارسال متن‌های حاوی اطلاعات شناسایی‌کنندهٔ شخصی (Personally Identifiable Information یا به اختصار PII) به مدل‌های زبانی بزرگ (LLM) یا سرویس‌های ابری خارجی می‌تواند حریم خصوصی بیماران و کاربران را به خطر بیندازد.

کتابخانهٔ fa-redact با ارائهٔ ابزارهای سبک و کارآمد، متن را پیش از خروج از محیط امن شما پردازش کرده و شناسه‌های حساس پشتیبانی‌شده را با نشان‌گذارهای جایگزین (Placeholders) تعویض می‌کند. پس از دریافت پاسخ از هوش مصنوعی، می‌توان نشان‌گذارها را در محیط محلی و امن خود به مقادیر اصلی بازگرداند (Restoration).


چرا fa-redact؟ (Motivation)

پردازش متون فارسی در زمینهٔ امنیت داده و پالایش متون، چالش‌های منحصربه‌فردی دارد:

  1. تنوع ارقام و حروف: در متون فارسی، شماره‌ها ممکن است با ارقام فارسی (۰-۹)، ارقام عربی (٠-٩) یا ارقام لاتین (0-9) نوشته شده باشند. همچنین تفاوت حروف مانند «ي» و «ك» عربی با «ی» و «ک» فارسی مانع عملکرد صحیح الگوهای ساده می‌شود.
  2. حفظ موقعیت کاراکترها (Offsets): روش‌های مرسوم نرم‌سازی متن اغلب طول رشته را تغییر می‌دهند (مثلاً با حذف فاصله‌ها یا اعراب)؛ این کار باعث جابه‌جایی ایندکس‌های کاراکتری شده و جایگزینی دقیق در متن اصلی را غیرممکن می‌سازد. fa-redact تضمین می‌کند که طول رشته قبل و بعد از نرم‌سازی یکسان باشد (len(normalized) == len(original)).
  3. اعتبارسنجی الگوریتمی: به‌جای استفاده از رجکس‌های حدسی، شناسه‌های ملی و شماره‌های موبایل با قواعد کنترلی و پیش‌شماره‌های رگولاتوری ایران بررسی می‌شوند.
  4. حفظ یکپارچگی ارجاعات در هوش مصنوعی: در مکالمات چندمرحله‌ای با LLMها، یک موجودیت مشخص همواره به یک نام‌مستعار یکسان نگاشت می‌شود تا ساختار معنایی متن برای مدل حفظ شود.
  5. بدون وابستگی و کاملاً تایپ‌شده: بدون نیاز به بسته‌های جانبی سنگین، کاملاً با پایتون خالص (+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)
# خروجی: "جهت هماهنگی با بیمار با ۰۹۱۲۳۴۵۶۷۸۹ تماس حاصل فرمایید."

[!WARNING] هشدار امنیتی: شیء session.mapping حاوی جدول نگاشت شناسه‌های اصلی و اطلاعات هویتی است. این نگاشت یک دادهٔ کاملاً حساس است؛ آن را در حافظهٔ محلی امن نگه دارید و هرگز همراه پرامپت به سرویس‌های خارجی یا LLM ارسال نکنید.


قابلیت‌های تفصیلی

۱. نرم‌سازی با حفظ موقعیت کاراکترها (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 ایران

[!NOTE] معرفی‌شده در نسخهٔ v0.2.0: اعتبارسنجی و تشخیص شماره شبا (IranianIBANDetector و is_valid_iranian_iban) در نسخهٔ v0.2.0 به مجموعهٔ تشخیص‌دهنده‌های پیش‌فرض اضافه شده است.

کتابخانهٔ 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 صرفاً صحت ساختار ریاضی چکسام را به صورت محلی و آفلاین بررسی می‌کند. این تابع هیچ‌گونه استعلام بانکی، بررسی صحت شماره حساب، اتصال به شبکهٔ شتاب یا پایا انجام نمی‌دهد و فعال بودن حساب بانکی را تایید نمی‌کند.

۶. اعتبارسنجی و تشخیص آدرس ایمیل اسکی (اختیاری)

[!NOTE] معرفی‌شده در نسخهٔ v0.2.0: اعتبارسنجی و تشخیص آدرس ایمیل (EmailDetector و is_valid_email) در نسخهٔ v0.2.0 به صورت اختیاری (Opt-in) ارائه شده است.

کتابخانهٔ 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 (اختیاری)

[!NOTE] معرفی‌شده در نسخهٔ v0.2.0: اعتبارسنجی و تشخیص شماره کارت بانکی (BankCardDetector و is_valid_bank_card_number) در نسخهٔ v0.2.0 به صورت اختیاری (Opt-in) ارائه شده است.

کتابخانهٔ 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 یا بررسی وضعیت حساب انجام نمی‌دهد.

۸. شناسه‌های سازمانی / درمانی قابل پیکربندی (اختیاری)

[!NOTE] معرفی‌شده در نسخهٔ v0.2.0: تشخیص شناسه‌های سازمانی و درمانی قابل پیکربندی (PatternRule و PatternDetector) در نسخهٔ v0.2.0 به صورت اختیاری (Opt-in) ارائه شده است.

شماره پرونده پزشکی (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 یک‌بار کامپایل و کش می‌شوند.

[!WARNING]

  • الگوهای تستی و نمایشی: الگوهای مثال ارائه‌شده (MRN-[0-9]{6} و ...) صرفاً مثال‌های نمایشی و ساختگی هستند و استاندارد درمانی یا قانونی محسوب نمی‌شوند.
  • هشدار امنیتی اعتماد به رجکس: قواعد PatternRule باید بخشی از پیکربندی مورد اعتماد برنامه باشند. موتور استاندارد re پایتون timeout داخلی برای اجرای regex ندارد. نباید regex تأییدنشده یا تولیدشده توسط کاربران ناشناس یا LLM بدون بازبینی اجرا شود.
  • سلب مسئولیت استعلام و پرونده: تطابق رجکس صرفاً صحت ساختار متنی را نشان می‌دهد؛ هیچ‌گونه استعلام از سامانه‌های اطلاعات بیمارستانی (HIS)، سرورهای FHIR یا پایگاه‌های داده انجام نمی‌شود و وجود خارجی بیمار یا پرونده تایید نمی‌گردد.

۹. حل صریح تعارض تشخیص‌ها (اختیاری)

[!NOTE] معرفی‌شده در نسخهٔ v0.2.0: حل صریح تعارض تشخیص‌ها (ConflictPolicy و resolve_detection_conflicts) در نسخهٔ v0.2.0 ارائه شده است.

تابع 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"

[!WARNING]

  • عدم قطعیت ماهیت شناسه: حل تعارض صرفاً یک قاعدهٔ پالایش اکتشافی است و به معنی تشخیص قطعی ماهیت واقعی شناسه نیست.
  • خطر باقی‌ماندن بخشی از شناسه در متن: انتخاب longest یا priority ممکن است یک تشخیص همپوشان دیگر را حذف کند و بخشی از span آن تشخیص (مانند پسوند ایمیل) در متن باقی بماند.
  • توصیهٔ امنیتی: در صورت هرگونه تردید، استفاده از سیاست پیش‌فرض reject توصیه می‌شود.

۱۰. گزارش امن‌تر از نظر حریم خصوصی برای تشخیص‌ها (v0.3.0)

[!NOTE] معرفی‌شده در نسخهٔ v0.3.0: قابلیت گزارش‌گیری امن‌تر از نظر حریم خصوصی (DetectionReport، detection_report و report_detections) در نسخهٔ v0.3.0 ارائه شده است.

کتابخانهٔ fa-redact یک لایهٔ گزارش‌گیری تجمیعی و مستقل ارائه می‌دهد که شواهد خروجی تشخیص‌دهنده‌ها را بدون ذخیره‌سازی، نگهداری یا بازگرداندن مقادیر خام PII، مقادیر نرمال‌شده، متن منبع، بازه‌های کاراکتری (spans)، بخش‌های متنی مجاور (snippets) یا هش‌های PII خلاصه می‌کند.

۱. گزارش تجمیعی پایه

تولید گزارش تجمیعی مستقیم از روی متن با استفاده از تشخیص‌دهنده‌های پیش‌فرض یا سفارشی:

from fa_redact import detection_report

text = "کد ملی: ۱۲۳۴۵۶۷۸۹۱، همراه: ۰۹۱۲۳۴۵۶۷۸۹"
report = detection_report(text)

print(report.total_detections)  # 2
print(dict(report.counts))  # {'IR_MOBILE': 1, 'IR_NATIONAL_ID': 1}
print(report.distinct_types)  # 2
print(report.has_conflicts)  # False
print(report.conflict_pairs)  # 0
print(report.conflicting_detections)  # 0
print(report.duplicate_groups)  # 0
۲. مشاهده‌پذیری شواهد خام و تعارض تشخیص‌ها

تابع detection_report() شواهد خام تشخیص‌دهنده‌ها را بدون حل خودکار تعارض اجرا می‌کند. این امر به برنامه‌ها امکان می‌دهد وجود هرگونه تداخل یا تعارض بین تشخیص‌دهنده‌ها را شناسایی و ارزیابی نمایند:

from fa_redact import (
    BankCardDetector,
    EmailDetector,
    detection_report,
)

text = "ایمیل: 1234567890123452@example.com"
report = detection_report(
    text,
    detectors=[
        EmailDetector(),
        BankCardDetector(),
    ],
)

print(report.total_detections)  # 2
print(dict(report.counts))  # {'BANK_CARD': 1, 'EMAIL': 1}
print(report.has_conflicts)  # True
print(report.conflict_pairs)  # 1
print(report.conflicting_detections)  # 2
۳. گزارش‌گیری پیش و پس از حل صریح تعارض

کاربران می‌توانند با استفاده از report_detections()، دنبالهٔ اشیاء Detection را قبل و بعد از حل تعارض ممیزی و مقایسه نمایند:

from fa_redact import (
    detect,
    report_detections,
    resolve_detection_conflicts,
)

# ۱. گزارش شواهد خام تشخیص‌دهنده‌ها
raw_detections = detect(text, detectors=detectors)
raw_report = report_detections(raw_detections)
# raw_report.has_conflicts برابر با True و raw_report.total_detections برابر با 2 است

# ۲. گزارش شواهد پس از حل صریح تعارض
resolved_detections = resolve_detection_conflicts(raw_detections, policy="longest")
resolved_report = report_detections(resolved_detections)
# resolved_report.has_conflicts برابر با False و resolved_report.total_detections برابر با 1 است

[!IMPORTANT]

  • طراحی بدون مقدار (Value-Free): ساختار DetectionReport به گونه‌ای طراحی شده که هیچ مقدار خام، مقدار نرمال‌شده، موقعیت کاراکتری، برش متنی یا هش PII را در خود نگه نمی‌دارد.
  • محدودهٔ فراداده (Metadata): نبودن مقدار خام شناسه در گزارش به این معنی نیست که خود گزارش در همهٔ محیط‌ها دادهٔ غیرحساس محسوب می‌شود. وجود نوع شناسه (مانند MRN یا IR_NATIONAL_ID) و تعداد آن‌ها نیز ممکن است در برخی محیط‌های سازمانی فرادادهٔ حساس تلقی شود و نیازمند کنترل دسترسی و محدودیت ثبت لاگ باشد.
  • هشدار برچسب نوع موجودیت: توسعه‌دهندگان تشخیص‌دهنده‌های سفارشی باید فیلد Detection.type را صرفاً به عنوان یک نام دسته یا schema (مانند MRN یا PATIENT_ID) تعریف کنند و هرگز نام بیمار، شناسه یا مقادیر متنی را درون Detection.type قرار ندهند.
  • عدم ادعای انطباق قانونی: ساختار DetectionReport به خودی خود ناشناس‌سازی کامل بالینی، انطباق با GDPR یا HIPAA یا مجوز ارسال نامحدود تله‌متری را ایجاد نمی‌کند.

۱۱. بازسازی دقیق بر اساس 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]) بدون خطا و دست‌نخورده باقی می‌مانند.

۱۳. رابط خط فرمان (CLI) (v0.3.0)

[!NOTE] معرفی‌شده در نسخهٔ v0.3.0: رابط خط فرمان (fa-redact و python -m fa_redact) در نسخهٔ v0.3.0 ارائه شده است.

کتابخانهٔ fa-redact یک رابط خط فرمان سبک، مستقل و مبتنی بر حریم خصوصی ارائه می‌دهد که بدون وابستگی خارجی (با استفاده از کتابخانهٔ استاندارد argparse)، امکان تشخیص، پنهان‌سازی و گزارش‌گیری را فراهم می‌سازد.

۱. راهنما و مشاهدهٔ نسخه
# مشاهده راهنمای کلی CLI
fa-redact --help

# مشاهده نسخه نصب‌شده
fa-redact --version
۲. پنهان‌سازی از طریق Stdin و فایل
# پنهان‌سازی از طریق ورودی استاندارد (جریان / Streaming)
echo "کد ملی بیمار ۱۲۳۴۵۶۷۸۹۱ و موبایل ۰۹۱۲۳۴۵۶۷۸۹ است." | fa-redact redact
# خروجی: "کد ملی بیمار [IR_NATIONAL_ID_1] و موبایل [IR_MOBILE_1] است."

# پنهان‌سازی فایل متنی و چاپ در خروجی استاندارد
fa-redact redact input.txt

# پنهان‌سازی فایل متنی و ذخیره در فایل مقصد
fa-redact redact input.txt -o redacted.txt
۳. گزارش آماری امن از نظر حریم خصوصی

تولید گزارش آماری با ساختار JSON بدون ذخیره یا افشای مقادیر حساس:

# تولید گزارش آماری از فایل
fa-redact report input.txt
۴. فراداده‌های تشخیص (بدون مقدار)

خروجی متادیتا و بازه‌های تشخیص (type, start, end) در قالب JSON:

fa-redact detect input.txt

[!IMPORTANT] دستور fa-redact detect ساختار بازه‌ای (type, start, end) را نمایش می‌دهد و عمداً هیچ مقدار خام، مقدار نرمال‌شده، برش متنی یا هش PII را در خروجی بازنمی‌گرداند.

۵. انتخاب صریح تشخیص‌دهنده‌ها

استفاده از گزینهٔ --detectors مجموعهٔ پیش‌فرض را جایگزین می‌کند:

# اجرای فقط تشخیص‌دهنده‌های ایمیل و کارت بانکی
fa-redact redact input.txt --detectors email,bank_card

# اجرای بدون هیچ تشخیص‌دهنده (عدم تغییر متن)
fa-redact redact input.txt --detectors none
۶. سیاست‌های حل تعارض در پنهان‌سازی
# سیاست پیش‌فرض رد تعارض (خطای صریح و امن در صورت هم‌پوشانی)
fa-redact redact input.txt --conflict-policy reject

# سیاست طولانی‌ترین span
fa-redact redact input.txt --detectors email,bank_card --conflict-policy longest

# سیاست اولویت صریح انواع موجودیت
fa-redact redact input.txt --detectors email,bank_card --conflict-policy priority --priority BANK_CARD,EMAIL

[!WARNING]

  • امنیت عدم بازنویسی فایل درجا: ابزار fa-redact بازنویسی مستقیم روی همان فایل ورودی را رد می‌کند تا از آسیب دیدن فایل منبع جلوگیری شود.
  • پیام‌های خطای بدون نشت PII: خطاهای صادرشده به stderr هرگز حاوی مقادیر حساس، متن اولیه یا برش‌های محرمانه نیستند.
  • عدم ادعای ناشناس‌سازی بالینی کامل: شناسه‌های پشتیبانی‌شده شامل نام اشخاص، آدرس‌های آزاد یا سوابق متنی کامل سلامت نمی‌شوند.
  • عدم تضمین خودکار انطباق قانونی: استفاده از CLI به خودی خود انطباق با GDPR یا HIPAA را ایجاد نمی‌کند.

۱۴. پردازش داده‌های ساخت‌یافته (v0.3.0)

[!NOTE] معرفی‌شده در نسخهٔ v0.3.0: توابع پردازش داده‌های ساخت‌یافته (detect_fields، redact_fields و report_fields) در نسخهٔ v0.3.0 ارائه شده است.

کتابخانهٔ fa-redact توابع کمکی محافظه‌کارانه و غیرمخربی را برای تشخیص، پنهان‌سازی و گزارش‌گیری از فیلدهای متنی صریح در ساختارهای داده‌ای پایتون (دیکشنری‌ها و رکوردهای شبیه JSON) ارائه می‌دهد:

  • redact_fields(record, fields, ...): بازگرداندن نسخه‌ای کپی‌شده از داده با جایگزینی شناسه‌های حساس در فیلدهای صریح با جانگهدارها (بدون تغییر شیء ورودی).
  • detect_fields(record, fields, ...): بازگرداندن نگاشتی از مسیر هر فیلد به فهرستی از اشیاء Detection.
  • report_fields(record, fields, ...): بازگرداندن نگاشتی از مسیر هر فیلد به گزارش آماری امن DetectionReport.
اصول و تضمین‌های طراحی

۱. هدف‌گیری صریح فیلدها (Explicit Field Targeting): تنها مسیرهایی که به صورت صریح در پارامتر fields مشخص شده‌اند پردازش می‌شوند. کتابخانه هرگز جستجوی بازگشتی کورکورانه روی کل شیء انجام نمی‌دهد و نام کلیدها را برای حدس زدن حساسیت فیلد تحلیل نمی‌کند. فیلدهای انتخاب‌نشده هرگز اسکن یا پنهان‌سازی نمی‌شوند. ۲. یکپارچگی و ثبات انتساب در سطح کل رکورد (Referential Consistency): در یک فراخوانی redact_fields()، هویت نهادهای یکسان (با نوع و مقدار نرمال‌شده یکسان) در تمام فیلدهای صریح انتخاب‌شده ثابت می‌ماند و جانگهدار کاملاً یکسانی دریافت می‌کنند. ترتیب تخصیص شماره جانگهدارها مطابق ترتیب فیلدها در fields است. ۳. رزرو توکن‌های جانگهدار در بین فیلدها: توکن‌های شبیه جانگهدار (مانند [EMAIL_1]) که به عنوان متن از قبل در فیلدهای قبلی وجود دارند رزرو می‌شوند تا شناسه‌های واقعی در فیلدهای بعدی دچار تصادم شماره نشوند. ۴. تغییرناپذیری و کپی غیرمخرب (Immutability): نگاشت‌ها و دیکشنری‌های ورودی کاربر هرگز درجا (in-place) دستکاری نمی‌شوند و ساختار خروجی به شکل یک dict استاندارد جدید بازگردانده می‌شود. ۵. حفظ انواع داده‌های غیرهدف: تمام داده‌های دست‌نخورده (اعداد صحیح، اعشاری، مقادیر بولی، None، لیست‌ها و رشته‌های انتخاب‌نشده) بدون تغییر باقی می‌مانند. ۶. سینتکس مسیر نقطه‌ای (Dot-Separated Path): مسیردهی فیلدها با استفاده از نقطه‌گذاری استاندارد (مانند "note" یا "metadata.contact") انجام می‌شود. ۷. رفتار صریح در صورت خطا (Fail-Loud): در صورت وجود مسیرهای نامعتبر، کلیدهای مفقود، کانتینرهای میانی غیرنگاشتی، یا فیلدهای هدف غیررشته‌ای، بلافاصله خطای صریح صادر می‌شود. ۸. حفظ حریم خصوصی در پیام‌های خطا: متن و مقادیر حساس فیلدها هرگز در پیام‌های خطا منعکس نمی‌شوند.

نمونه کد
from fa_redact import detect_fields, redact_fields, report_fields

record = {
    "patient_id": "کد ملی ۱۲۳۴۵۶۷۸۹۱",
    "note": "تماس با ۰۹۱۲۳۴۵۶۷۸۹ جهت هماهنگی",
    "summary": "بیمار ۰۹۱۲۳۴۵۶۷۸۹ مراجعه مجدد داشت.",
    "age": 42,
    "active": True,
    "metadata": {
        "contact_iban": "شبا IR641234567890123456789012",
        "visit_count": 3,
    },
}

# ۱. پنهان‌سازی فیلدهای مشخص‌شده با حفظ یکپارچگی ارجاعی در کل رکورد:
redacted = redact_fields(record, ["note", "summary", "metadata.contact_iban"])
print(redacted["note"])
# خروجی: "تماس با [IR_MOBILE_1] جهت هماهنگی"
print(redacted["summary"])
# خروجی: "بیمار [IR_MOBILE_1] مراجعه مجدد داشت." (جانگهدار یکسان در چند فیلد)
print(redacted["metadata"]["contact_iban"])
# خروجی: "شبا [IR_IBAN_1]"

# فیلدهای دیگر و شیء اولیه کاملاً دست‌نخورده باقی می‌مانند:
assert record["note"] == "تماس با ۰۹۱۲۳۴۵۶۷۸۹ جهت هماهنگی"
assert redacted["age"] == 42
assert redacted["active"] is True

# ۲. تشخیص شناسه‌ها در فیلدهای مشخص‌شده:
detections = detect_fields(record, ["patient_id", "note"])
for path, dets in detections.items():
    print(f"مسیر: {path} -> تعداد تشخیص: {len(dets)}")

# ۳. گزارش آماری امن به تفکیک هر فیلد:
reports = report_fields(record, ["note", "metadata.contact_iban"])
print(reports["note"].counts)
# خروجی: {'IR_MOBILE': 1}

[!WARNING]

  • هدف‌گیری صریح به عنوان مرز امنیتی: توابع ساخت‌یافته فیلدهای ناشناس را به صورت خودکار اسکن نمی‌کنند؛ برنامهٔ فراخواننده باید صریحاً فیلدهای متنی مورد نظر را تعیین نماید.
  • نام مسیرها به عنوان فراداده (Metadata): خروجی report_fields() بر اساس نام مسیر فیلدها کلیدگذاری می‌شود؛ اگرچه مقادیر DetectionReport کاملاً بدون مقدار حساس هستند، اما خود نام مسیرها فراداده محسوب می‌شوند و نباید شناسه یا دادهٔ محرمانه در نام فیلدها درج شود.
  • اندیس لیست‌ها و وایلدکاردها: پیمایش اندیس‌های لیست (مانند items.0.note) و الگوهای * در این فاز پشتیبانی نمی‌شوند.
  • عدم تضمین انطباق یا حذف کامل اطلاعات هویتی: استفاده از این توابع به منزلهٔ حذف قطعی و خودکار همهٔ داده‌های حساس یا انطباق قانونی با استانداردهایی چون HIPAA نیست.

۱۵. تشخیص اختیاری نام اشخاص فارسی (NER) (v0.3.0)

[!NOTE] معرفی‌شده در نسخهٔ v0.3.0: تشخیص‌دهندهٔ اختیاری نام اشخاص فارسی (PersianNERDetector) و اکسترای fa-redact[ner] در نسخهٔ v0.3.0 به صورت آزمایشی ارائه شده است.

کتابخانهٔ fa-redact تشخیص‌دهندهٔ آزمایشی و کاملاً اختیاری PersianNERDetector را برای تشخیص موجودیت‌های نام اشخاص (PERSON) با استفاده از مدل‌های آفلاین سازگار با Hugging Face فراهم می‌کند.

نصب وابستگی‌های اختیاری

برای فعال‌سازی بک‌اند هوش مصنوعی بدون سنگین کردن بستهٔ پایه:

pip install "fa-redact[ner]"
نمونه استفاده
from fa_redact import PersianNERDetector, detect, redact

# مشخص کردن مسیر پوشهٔ محلی مدل (صرفاً آفلاین و محلی)
ner = PersianNERDetector("/path/to/trusted/local/model")

text = "بیمار علی رضایی به درمانگاه مراجعه کرد."

# پاس دادن صریح تشخیص‌دهنده (جایگزین پیش‌فرض‌ها می‌شود):
detections = detect(text, detectors=[ner])
# خروجی نمونه (با یک مدل سازگارِ آزموده‌شده):
# نوع: PERSON | مقدار: علی رضایی | موقعیت: [6:15]

# پنهان‌سازی با جانگهدار استاندارد:
redacted = redact(text, detectors=[ner])
print(redacted)
# خروجی نمونه (با یک مدل سازگارِ آزموده‌شده):
# "بیمار [PERSON_1] به درمانگاه مراجعه کرد."
ترکیب NER با شناسه‌های مستقیم پیش‌فرض

از آنجا که پاس دادن detectors=[...] جایگزین تشخیص‌دهنده‌های پیش‌فرض می‌شود، در صورت تمایل به اجرای هم‌زمان تشخیص نام و شناسه‌های پیش‌فرض (کد ملی، موبایل، شبا)، همهٔ آن‌ها را صریحاً در لیست قرار دهید:

from fa_redact import (
    IranianIBANDetector,
    IranianMobileNumberDetector,
    IranianNationalIDDetector,
    PersianNERDetector,
    redact,
)

ner = PersianNERDetector("/path/to/trusted/local/model")
all_detectors = [
    IranianNationalIDDetector(),
    IranianMobileNumberDetector(),
    IranianIBANDetector(),
    ner,
]

text = "بیمار علی رضایی با کد ملی 0012345679 مراجعه کرد."
redacted = redact(text, detectors=all_detectors)
print(redacted)
# خروجی نمونه (با یک مدل سازگارِ آزموده‌شده):
# "بیمار [PERSON_1] با کد ملی [IR_NATIONAL_ID_1] مراجعه کرد."

[!WARNING]

  • صرفاً اختیاری و محلی: تشخیص‌دهندهٔ PersianNERDetector در پیش‌فرض‌ها قرار ندارد و هیچ مدلی را به صورت خودکار از اینترنت دانلود نمی‌کند؛ کاربر باید پوشهٔ محلی مدل معتبر را ارائه دهد.
  • مرز اعتماد مدل: فایل‌های مدل محلی کدهای اجرایی/محاسباتی هستند؛ فقط مدل‌های منابع کاملاً معتبر را بارگذاری کنید.
  • برچسب عمومی PERSON: این مدل نوع موجودیت PERSON را برمی‌گرداند و نقشی (بیمار، پزشک، همراه و...) استنتاج نمی‌کند.
  • سیاست عدم کوتاه کردن متن‌های طولانی (Fail-Loud): در متون طولانی‌تر از ظرفیت مدل، خطا صادر می‌شود و متن به صورت خاموش کوتاه (Truncate) نمی‌شود.
  • عدم تضمین بالینی یا دقت جهانی: نتایج بنچ‌مارک خبری PEYMA (F1 معادل ۹۹.۱۹٪) به معنی تضمین دقت در اسناد بالینی یا دی‌ایدنتیفیکیشن قطعی نیست.

۱۶. پروفایل‌های پالایش متون بالینی (v0.3.0)

[!NOTE] معرفی‌شده در نسخهٔ v0.3.0: پروفایل‌های پالایش متون بالینی (ClinicalRedactionProfile و clinical_profile) در نسخهٔ v0.3.0 ارائه شده است.

کتابخانهٔ fa-redact لایهٔ ترکیبی سبک، تغییرناپذیر و سطح‌بالایی را برای جریان‌های کاری متون درمانی و بالینی فارسی در قالب کلاس ClinicalRedactionProfile و تابع سازندهٔ clinical_profile(...) فراهم می‌کند. این لایه تشخیص‌دهنده‌های شناسه‌های مستقیم، قواعد الگوی سازمانی و تشخیص‌دهندهٔ اختیاری نام اشخاص را بدون بازنویسی موتورهای پردازش و بدون تغییر رفتار پیش‌فرض بسته ترکیب می‌کند.

۱. قالب‌های استاندارد جریان کاری

تابع clinical_profile از سه برچسب استاندارد گردش کاری (ClinicalTextTemplate) پشتیبانی می‌کند:

  • "outpatient_note" (یادداشت سرپایی)
  • "discharge_summary" (خلاصه ترخیص)
  • "referral_letter" (برگه ارجاع)

هر سه قالب بر پایهٔ ترکیب محافظه‌کارانه و یکسان شناسه‌های مستقیم شروع می‌شوند. این نام‌ها صرفاً برچسب‌های استاندارد پیکربندی هستند و فرضیات مخفی یا الگوهای نامشهود در آن‌ها تعبیه نشده است.

۲. ترکیب تشخیص‌دهنده‌ها و تنظیمات پیش‌فرض
  • شناسه‌های مستقیم پایه: کد ملی (IranianNationalIDDetector)، شماره موبایل (IranianMobileNumberDetector)، شماره شبا (IranianIBANDetector) و ایمیل (EmailDetector به صورت پیش‌فرض فعال با include_email=True).
  • کارت بانکی (اختیاری): پیش‌فرض include_bank_card=False است. در صورت نیاز به پنهان‌سازی کارت بانکی، مقدار include_bank_card=True ارسال می‌شود.
  • شناسه‌های سازمانی و بیمارستانی: از طریق institutional_rules=[PatternRule(...)] الگوهای اختصاصی پرونده پزشکی (MRN)، شناسه بیمار و شناسه ارجاع تعریف می‌شوند.
  • تشخیص نام اشخاص (اختیاری): با ارسال یک شیء Detector معتبر (نظیر PersianNERDetector) در پارامتر person_detector. بدون این پارامتر، نام اشخاص تشخیص داده نمی‌شود.
  • سیاست حل تعارض: پیش‌فرض conflict_policy="reject" است. در صورت نیاز سیاست‌های "longest" یا "priority" قابل استفاده‌اند.
۳. نمونه‌های کاربردی (داده‌های مصنوعی)
نمونه الف: یادداشت سرپایی (شناسه‌های مستقیم استاندارد)
from fa_redact import clinical_profile

# پروفایل پایه شامل کد ملی، موبایل، شبا و ایمیل
profile = clinical_profile("outpatient_note")

text = "بیمار با کد ملی ۱۲۳۴۵۶۷۸۹۱ و شماره تماس ۰۹۱۲۳۴۵۶۷۸۹ و ایمیل patient@clinic.ir جهت ویزیت مراجعه کرد."
redacted = profile.redact(text)
print(redacted)
# خروجی: "بیمار با کد ملی [IR_NATIONAL_ID_1] و شماره تماس [IR_MOBILE_1] و ایمیل [EMAIL_1] جهت ویزیت مراجعه کرد."
نمونه ب: خلاصه ترخیص با الگوی سازمانی شماره پرونده (MRN)
from fa_redact import PatternRule, clinical_profile

# تعریف الگوی اختصاصی شماره پرونده بیمارستان
mrn_rule = PatternRule(type="MRN", pattern=r"(?<!\w)MRN-[0-9]{6}(?!\w)")
profile = clinical_profile(
    "discharge_summary",
    institutional_rules=[mrn_rule],
    include_bank_card=True,
)

text = "خلاصه ترخیص پرونده MRN-123456: پرداخت با کارت ۱۲۳۴۵۶۷۸۹۰۱۲۳۴۵۲ انجام شد و با تماس ۰۹۱۲۳۴۵۶۷۸۹ هماهنگ گردید."
redacted = profile.redact(text)
print(redacted)
# خروجی: "خلاصه ترخیص پرونده [MRN_1]: پرداخت با کارت [BANK_CARD_1] انجام شد و با تماس [IR_MOBILE_1] هماهنگ گردید."
نمونه ج: برگه ارجاع با تشخیص‌دهندهٔ اختیاری نام اشخاص (NER)
from fa_redact import PatternRule, PersianNERDetector, clinical_profile

# بارگذاری مدل محلی آفلاین (نیازمند نصب fa-redact[ner])
ner = PersianNERDetector("/path/to/trusted/local/model")
ref_rule = PatternRule(
    type="REFERRAL_ID", pattern=r"(?<!\w)REF-[A-Z]{2}-[0-9]{4}(?!\w)"
)

profile = clinical_profile(
    "referral_letter",
    institutional_rules=[ref_rule],
    person_detector=ner,
)

text = "بیمار سارا محمدی با ارجاع REF-TE-9876 و شماره شبا IR641234567890123456789012 معرفی می‌شود."
redacted = profile.redact(text)
print(redacted)
# خروجی نمونه (با یک مدل سازگارِ آزموده‌شده):
# "بیمار [PERSON_1] با ارجاع [REFERRAL_ID_1] و شماره شبا [IR_IBAN_1] معرفی می‌شود."
۴. یکپارچگی با توابع داده‌های ساخت‌یافته

پروفایل بالینی متدهای مناسبی را برای اعمال پنهان‌سازی روی فیلدهای ساختاریافته با حفظ یکپارچگی ارجاعی در سطح کل رکورد فراهم می‌کند:

record = {
    "patient": {"note": "مراجعه بیمار با کد ملی ۱۲۳۴۵۶۷۸۹۱"},
    "contact": "کد ملی ۱۲۳۴۵۶۷۸۹۱ و ایمیل info@hospital.ir",
    "unselected": 42,
}

profile = clinical_profile("outpatient_note")
redacted_record = profile.redact_fields(record, ["patient.note", "contact"])
# حفظ یکپارچگی ارجاعی در تمام فیلدهای هدف:
# redacted_record["patient"]["note"] -> "مراجعه بیمار با کد ملی [IR_NATIONAL_ID_1]"
# redacted_record["contact"] -> "کد ملی [IR_NATIONAL_ID_1] و ایمیل [EMAIL_1]"

[!WARNING]

  • عدم تضمین دی‌ایدنتیفیکیشن کامل بالینی: ClinicalRedactionProfile صرفاً یک لایهٔ ترکیبی بر روی تشخیص‌دهنده‌های پشتیبانی‌شده است و ناشناس‌سازی قطعی، انطباق خودکار با HIPAA Safe Harbor یا GDPR یا عدم وجود شناسه‌های باقیمانده را تضمین نمی‌کند.
  • ضرورت بازبینی انسانی و سازمانی: پردازش خودکار صرفاً ابزار کمکی است و اسناد بالینی پیش از انتشار یا خروج از مرز امنیتی باید توسط نیروی انسانی بازبینی شوند.
  • عدم تشخیص خودکار نام اشخاص: بدون ارسال صریح person_detector، نام اشخاص شناسایی نمی‌شود.
  • عدم وجود الگوی جهانی برای شماره پرونده: شناسه‌های بیمارستانی (MRN، پرونده، پذیرش) در هر سازمان متفاوتند و نیازمند تعریف صریح PatternRule توسط کاربر هستند.
  • عدم تشخیص تاریخ، آدرس یا مفاهیم پزشکی: تاریخ‌ها، آدرس‌های مکانی، تشخیص‌های بالینی، داروها و علائم بیماری در فاز ۲۲ تحت پوشش قرار ندارند.
  • عدم استنتاج نقش افراد: موجودیت‌های نام اشخاص به عنوان PERSON ثبت می‌شوند و نقشی نظیر بیمار، پزشک، پرستار یا همراه برای آن‌ها حدس زده نمی‌شود.

تشخیص‌دهنده‌های سفارشی (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)
         ↓
      ۵. گزارش نهایی با شناسه‌های واقعی و بازگردانی‌شده

[!WARNING] این فرایند به معنی ناشناس‌سازی یا de-identification کامل متن بالینی نیست. fa-redact به صورت پیش‌فرض صرفاً کد ملی، شماره موبایل و شماره شبا (به همراه تشخیص‌دهنده‌های فعال‌شدهٔ اختیاری) را پوشش می‌دهد و داده‌هایی نظیر نام اشخاص، آدرس‌ها، تاریخ‌ها یا سایر رده‌های سلامت حفاظت‌شده را به‌طور پیش‌فرض تشخیص نمی‌دهد.


جدول پوشش و قابلیت‌ها

نوع شناسه هویتی / قابلیت v0.1.0 v0.2.0 v0.3.0 توضیحات
کد ملی ایران ✅ پشتیبانی می‌شود ✅ پیش‌فرض ✅ پیش‌فرض اعتبارسنجی دقیق ۱۰ رقمی با قاعدهٔ چکسام Modulo-11
شماره تلفن همراه ایران ✅ پشتیبانی می‌شود ✅ پیش‌فرض ✅ پیش‌فرض اعتبارسنجی پیش‌شماره‌های مصوب رگولاتوری ایران (CRA 2026)
شماره شبا (IBAN) ❌ پشتیبانی نمی‌شود ✅ پیش‌فرض ✅ پیش‌فرض اعتبارسنجی دقیق ۲۶ کاراکتری با قاعدهٔ چکسام MOD-97 (IR + ۲۴ رقم)
آدرس ایمیل ❌ پشتیبانی نمی‌شود 🧪 اختیاری 🧪 اختیاری اعتبارسنجی و تشخیص ایمیل‌های اسکی محافظه‌کارانه (detectors=[EmailDetector()])
شماره کارت بانکی (PAN) ❌ پشتیبانی نمی‌شود 🧪 اختیاری 🧪 اختیاری فرمت فشردهٔ ۱۶ رقمی + Luhn؛ بدون استعلام BIN/IIN یا صادرکننده (detectors=[BankCardDetector()])
شناسه‌های سازمانی / درمانی (MRN و بیمار) ❌ پشتیبانی نمی‌شود 🧪 اختیاری 🧪 اختیاری قابل پیکربندی اختصاصی توسط کاربر با PatternRule و PatternDetector
حل صریح تعارض تشخیص‌ها ❌ پشتیبانی نمی‌شود 🧪 سیاست اختیاری 🧪 سیاست اختیاری حل همپوشانی‌ها و تکرارها با سیاست "longest" یا "priority"
گزارش امن تشخیص‌ها ❌ پشتیبانی نمی‌شود ❌ پشتیبانی نمی‌شود ✅ پشتیبانی می‌شود گزارش‌گیری تجمیعی و بدون مقدار (DetectionReport و detection_report)
رابط خط فرمان (CLI) ❌ پشتیبانی نمی‌شود ❌ پشتیبانی نمی‌شود ✅ پشتیبانی می‌شود دستور fa-redact با زیردستورهای detect، report و redact
پردازش داده‌های ساخت‌یافته ❌ پشتیبانی نمی‌شود ❌ پشتیبانی نمی‌شود ✅ پشتیبانی می‌شود هدف‌گیری صریح مسیرهای دیکشنری (detect_fields، redact_fields، report_fields)
نام اشخاص (NER) ❌ پشتیبانی نمی‌شود 🔬 ارزیابی پژوهشی 🧪 اختیاری تشخیص‌دهندهٔ اختیاری PersianNERDetector با مدل محلی کاربر
پروفایل‌های پالایش بالینی ❌ پشتیبانی نمی‌شود ❌ پشتیبانی نمی‌شود 🧪 پشتیبانی می‌شود پروفایل‌های سطح‌بالا (ClinicalRedactionProfile و clinical_profile)
آدرس پستی و موقعیت مکانی ❌ پشتیبانی نمی‌شود ❌ پشتیبانی نمی‌شود ❌ پشتیبانی نمی‌شود موجودیت‌های غیرساختاریافته
تاریخ تولد و زمان‌ها ❌ پشتیبانی نمی‌شود ❌ پشتیبانی نمی‌شود ❌ پشتیبانی نمی‌شود برنامه‌ریزی‌شده برای نسخه‌های آتی
شماره بیمه درمانی ❌ پشتیبانی نمی‌شود ❌ پشتیبانی نمی‌شود ❌ پشتیبانی نمی‌شود فرمت سازمانی

مدل حریم خصوصی و سلب مسئولیت‌های امنیتی

[!WARNING]

  • محدودیت دامنهٔ پوشش: این بسته به صورت پیش‌فرض صرفاً کد ملی، شماره موبایل و شماره شبای ایران (به همراه تشخیص‌دهنده‌های فعال‌شدهٔ اختیاری) را پوشش می‌دهد و به هیچ وجه سیستم دی‌ایدنتیفیکیشن (De-identification) کامل پرونده‌های پزشکی بالینی نیست.
  • حساسیت نگاشت: نگاشت session.mapping حاوی PII واقعی است و باید با بالاترین تدابیر امنیتی در حافظهٔ محلی نگهداری شود.
  • عدم گواهی پزشکی: fa-redact یک نرم‌افزار تجهیزات پزشکی (Medical Device) تأییدشده نیست.
  • عدم انطباق خودکار با مقررات: استفاده از این ابزار به خودی خود انطباق کامل با قوانین بین‌المللی نظیر HIPAA یا GDPR را تضمین نمی‌کند.
  • عدم استعلام هویت: توابع اعتبارسنجی صرفاً ساختار ریاضی را بررسی می‌کنند و استعلام هویتی انجام نمی‌دهند.
  • احتمال خطای الگوریتمی: هیچ سیستم مبتنی بر قاعده بدون خطا نیست؛ ممکن است در ورودی‌های غیرمعمول با خطای مثبت یا منفی کاذب (False Positives / False Negatives) مواجه شوید.

سیاست داده‌های آزمایشی امن

کلیهٔ مثال‌ها، داک‌ها و تست‌های موجود در این مخزن بر پایهٔ داده‌های مصنوعی، الگوهای الگوریتمی و بردارهای تستی غیرشخصی تولید شده‌اند. هیچ‌گونه اطلاعات واقعی هویتی، پرونده پزشکی، یا داده‌های محرمانهٔ کاربران در این مخزن استفاده یا ذخیره نشده است.


توسعه و کنترل کیفیت

اجرای تست‌های خودکار:

python -m pytest

بررسی سبک کدنویسی و لینتینگ:

ruff check .
ruff format --check .

بررسی ایستا و تایپ‌چکینگ:

mypy src
mypy tests

بیلد پکیج و بررسی صحت توزیع‌ها:

python -m build
python -m twine check dist/*

برای آگاهی از نحوهٔ مشارکت در پروژه، راهنماهای CONTRIBUTING.md و SECURITY.md را مطالعه فرمایید. برای مشاهدهٔ وضعیت جاری توسعه و فازهای برنامه‌ریزی‌شده، فایل‌های PROJECT_STATUS.md و ROADMAP.md را بررسی کنید.


مجوز (License)

این پروژه تحت مجوز MIT منتشر شده است.

Metadata

Release files for fa-redact 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fa-redact 0.3.0
File Size Uploaded
fa_redact-0.3.0.tar.gz 221.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fa-redact 0.3.0
File Interpreter ABI Platform
fa_redact-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 305.8 kB

Release files / fa_redact-0.3.0.tar.gz

Download URL fa_redact-0.3.0.tar.gz
Size 221.5 kB
Tags Source
SHA-256 checksum
How to use checksums
8d5f60c81572f0f4aeaf368d2de2e79783d905271cf1c9a278a40555651966ef
BLAKE2b-256 checksum
How to use checksums
6d5b515064bad4331be3f156f49cbc0d118698a6a961b035495ba9aaef512af7
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 7, 2026.

Transparency log

Release files / fa_redact-0.3.0-py3-none-any.whl

Download URL fa_redact-0.3.0-py3-none-any.whl
Size 84.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2a16a03bd7e656e7589fc640ce2c8ed069ce432e67d91fa0aceec9163ae26442
BLAKE2b-256 checksum
How to use checksums
c964c8d752c2fac34075f0b9b85ae6145e6bcd7d0938cd6f38645a39129fac55
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 7, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.0

2 release files

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page