fa-redact 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. fa_redact-0.1.0/LICENSE +21 -0
  2. fa_redact-0.1.0/PKG-INFO +245 -0
  3. fa_redact-0.1.0/README.md +211 -0
  4. fa_redact-0.1.0/pyproject.toml +89 -0
  5. fa_redact-0.1.0/setup.cfg +4 -0
  6. fa_redact-0.1.0/src/fa_redact/__init__.py +37 -0
  7. fa_redact-0.1.0/src/fa_redact/detectors/__init__.py +8 -0
  8. fa_redact-0.1.0/src/fa_redact/detectors/mobile.py +62 -0
  9. fa_redact-0.1.0/src/fa_redact/detectors/national_id.py +60 -0
  10. fa_redact-0.1.0/src/fa_redact/models.py +103 -0
  11. fa_redact-0.1.0/src/fa_redact/normalization.py +102 -0
  12. fa_redact-0.1.0/src/fa_redact/pipeline.py +61 -0
  13. fa_redact-0.1.0/src/fa_redact/protocols.py +33 -0
  14. fa_redact-0.1.0/src/fa_redact/pseudonymization.py +166 -0
  15. fa_redact-0.1.0/src/fa_redact/py.typed +1 -0
  16. fa_redact-0.1.0/src/fa_redact/redaction.py +37 -0
  17. fa_redact-0.1.0/src/fa_redact/validators/__init__.py +8 -0
  18. fa_redact-0.1.0/src/fa_redact/validators/mobile.py +119 -0
  19. fa_redact-0.1.0/src/fa_redact/validators/national_id.py +47 -0
  20. fa_redact-0.1.0/src/fa_redact.egg-info/PKG-INFO +245 -0
  21. fa_redact-0.1.0/src/fa_redact.egg-info/SOURCES.txt +30 -0
  22. fa_redact-0.1.0/src/fa_redact.egg-info/dependency_links.txt +1 -0
  23. fa_redact-0.1.0/src/fa_redact.egg-info/requires.txt +7 -0
  24. fa_redact-0.1.0/src/fa_redact.egg-info/top_level.txt +1 -0
  25. fa_redact-0.1.0/tests/test_mobile.py +366 -0
  26. fa_redact-0.1.0/tests/test_models.py +286 -0
  27. fa_redact-0.1.0/tests/test_national_id.py +245 -0
  28. fa_redact-0.1.0/tests/test_normalization.py +155 -0
  29. fa_redact-0.1.0/tests/test_package.py +68 -0
  30. fa_redact-0.1.0/tests/test_pipeline.py +303 -0
  31. fa_redact-0.1.0/tests/test_pseudonymization.py +578 -0
  32. fa_redact-0.1.0/tests/test_redaction.py +429 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fa-redact contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,245 @@
1
+ Metadata-Version: 2.4
2
+ Name: fa-redact
3
+ Version: 0.1.0
4
+ Summary: A privacy-first Python toolkit for Persian/Iranian PII redaction and pseudonymization, designed especially for healthcare and AI applications.
5
+ Author: fa-redact contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mehdimt1980/fa-redact
8
+ Project-URL: Repository, https://github.com/mehdimt1980/fa-redact
9
+ Project-URL: Issues, https://github.com/mehdimt1980/fa-redact/issues
10
+ Keywords: persian,farsi,pii,redaction,anonymization,pseudonymization,privacy,healthcare
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Healthcare Industry
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Security
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Text Processing :: Linguistic
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Provides-Extra: dev
28
+ Requires-Dist: build>=1.0.0; extra == "dev"
29
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
30
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.9.0; extra == "dev"
32
+ Requires-Dist: twine>=5.0.0; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # fa-redact
36
+
37
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
+
40
+ `fa-redact` is a lightweight, privacy-first Python toolkit for Persian/Iranian Personally Identifiable Information (PII) detection, redaction, and pseudonymization, designed especially for healthcare and AI/LLM applications.
41
+
42
+ > **Status: v0.1.0 Release Candidate (Alpha)**
43
+ > This package provides position-preserving Persian text normalization, immutable `Detection` data models, strict Iranian National ID (Code Melli) and Mobile Number validators and detectors, high-level `detect()` orchestration, stateless placeholder-based `redact()`, and stateful `PseudonymizationSession` with safe restoration for AI/LLM workflows.
44
+
45
+ ---
46
+
47
+ ## Quick Start
48
+
49
+ ### 1. Detect PII
50
+ Identify sensitive spans with exact source offsets and normalized representations:
51
+
52
+ ```python
53
+ from fa_redact import detect
54
+
55
+ text = "بیمار با کد ملی ۱۲۳۴۵۶۷۸۹۱ و شماره ۰۹۱۲۳۴۵۶۷۸۹ مراجعه کرد."
56
+ detections = detect(text)
57
+
58
+ for d in detections:
59
+ print(f"Type: {d.type} | Value: {d.value} | Span: [{d.start}:{d.end}]")
60
+ ```
61
+
62
+ ### 2. Redact PII (Stateless)
63
+ Sanitize text into safe, typed placeholders with fresh counter numbering:
64
+
65
+ ```python
66
+ from fa_redact import redact
67
+
68
+ text = "کد ملی: ۱۲۳۴۵۶۷۸۹۱، تماس: ۰۹۱۲۳۴۵۶۷۸۹، تماس دوم: 09123456789"
69
+ safe_text = redact(text)
70
+ print(safe_text)
71
+ # Output: "کد ملی: [IR_NATIONAL_ID_1]، تماس: [IR_MOBILE_1]، تماس دوم: [IR_MOBILE_1]"
72
+ ```
73
+
74
+ ### 3. Stateful Pseudonymization & AI/LLM Restoration
75
+ Maintain consistent entity mappings across conversation turns and restore placeholders locally:
76
+
77
+ ```python
78
+ from fa_redact import PseudonymizationSession
79
+
80
+ session = PseudonymizationSession()
81
+
82
+ # 1. Pseudonymize prompt locally before sending to external LLM:
83
+ prompt = "کد ملی بیمار ۱۲۳۴۵۶۷۸۹۱ و شماره تماس ۰۹۱۲۳۴۵۶۷۸۹ است."
84
+ safe_prompt = session.pseudonymize(prompt)
85
+ print(safe_prompt)
86
+ # Output: "کد ملی بیمار [IR_NATIONAL_ID_1] و شماره تماس [IR_MOBILE_1] است."
87
+
88
+ # 2. Send ONLY safe_prompt to external LLM. Simulated LLM response:
89
+ llm_response = "جهت پیگیری بیمار با [IR_MOBILE_1] هماهنگ شد."
90
+
91
+ # 3. Restore placeholders locally within your trusted boundary:
92
+ restored = session.restore(llm_response)
93
+ print(restored)
94
+ # Output: "جهت پیگیری بیمار با ۰۹۱۲۳۴۵۶۷۸۹ هماهنگ شد."
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Installation
100
+
101
+ ### Future PyPI Release
102
+ Once v0.1.0 is published to PyPI:
103
+
104
+ ```bash
105
+ pip install fa-redact
106
+ ```
107
+
108
+ ### Development Installation
109
+ For development or installing from source:
110
+
111
+ ```bash
112
+ git clone https://github.com/mehdimt1980/fa-redact.git
113
+ cd fa-redact
114
+ pip install -e ".[dev]"
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Detailed Capabilities
120
+
121
+ ### 1. Stateful Pseudonymization Sessions
122
+ The `PseudonymizationSession` class manages state across multi-turn workflows:
123
+ - **Local Sensitive Mapping**: `session.mapping` holds `{placeholder: original_pii}`. Keep this mapping strictly inside your local trusted environment; never transmit it to external AI services.
124
+ - **First-Observed Representative Restoration**: For each unique identity `(type, normalized_value)`, the session records the first-observed raw string as its semantic restoration target.
125
+ - **Non-Cascading Single-Pass Restoration**: `restore()` performs an escaped single-pass substitution, preventing recursive evaluation if restored values contain placeholder-like text.
126
+ - **Cross-Call Collision Safety**: Generated placeholders automatically avoid colliding with literal placeholder-shaped tokens seen in current or previous inputs within the session.
127
+ - **Unknown Placeholders**: Unmapped placeholders (e.g., `[IR_MOBILE_999]`) are left untouched without error.
128
+
129
+ > [!WARNING]
130
+ > **Sensitive Data Notice**: `session.mapping` contains original PII. Treat it as sensitive data and protect it accordingly.
131
+ >
132
+ > **Scope Limitation**: `fa-redact` detects and redacts only the PII types supported by its enabled detectors (Iranian National IDs and Iranian Mobile Numbers in v0.1.0). It does not provide complete automated clinical de-identification.
133
+
134
+ ### 2. Position-Preserving Normalization
135
+ `fa-redact` provides pure, deterministic normalization where each Unicode character maps 1-to-1 to a normalized code point (`len(normalized) == len(original)`), guaranteeing that character offsets remain identical to the original input text:
136
+
137
+ ```python
138
+ from fa_redact import normalize_digits, normalize_letters, normalize_text
139
+
140
+ # Normalizes Persian (۰-۹) and Arabic-Indic (٠-٩) digits to ASCII (0-9)
141
+ normalize_digits("کد بیمار: ۱۲۳٤٥")
142
+ # Returns: "کد بیمار: 12345"
143
+
144
+ # Normalizes Arabic letter variants (ي -> ی, ك -> ک)
145
+ normalize_letters("پزشك و دكتر")
146
+ # Returns: "پزشک و دکتر"
147
+
148
+ # Full position-preserving normalization
149
+ normalize_text("كد ملي: ۰۰۱٢٣٤٥٦٧٨")
150
+ # Returns: "کد ملی: 0012345678"
151
+ ```
152
+
153
+ ### 3. Detection Data Model & Pipeline
154
+ The immutable `Detection` dataclass represents identified spans:
155
+
156
+ ```python
157
+ from fa_redact import Detection, IranianNationalIDDetector, detect, normalize_text
158
+
159
+ # Using custom detector list:
160
+ detections = detect("کد ملی: ۱۲۳۴۵۶۷۸۹۱", detectors=[IranianNationalIDDetector()])
161
+ for d in detections:
162
+ assert d.type == "IR_NATIONAL_ID"
163
+ assert d.value == "۱۲۳۴۵۶۷۸۹۱"
164
+ assert d.normalized_value == "1234567891"
165
+ ```
166
+
167
+ ### 4. Iranian National ID Validation & Detection
168
+ Validate and detect Iranian National IDs (Code Melli / `کد ملی`) with strict modulo-11 checksum verification:
169
+
170
+ ```python
171
+ from fa_redact import IranianNationalIDDetector, is_valid_national_id
172
+
173
+ # Algorithmic test vectors not sourced from personal data
174
+ is_valid_national_id("1234567891") # True
175
+ is_valid_national_id("۱۲۳۴۵۶۷۸۹۱") # True (Persian digits)
176
+ is_valid_national_id("1234567890") # False (invalid check digit)
177
+ is_valid_national_id("1111111111") # False (repeated digits rejected)
178
+ ```
179
+
180
+ > **Verification Notice**: Checksum validation verifies mathematical structure only without querying official registries. These values are algorithmic test vectors not sourced from personal or patient records. Checksum validity does not establish whether an identifier has been officially issued to an individual.
181
+
182
+ ### 5. Iranian Mobile Number Validation & Detection
183
+ Validate and detect Iranian mobile numbers against official Communications Regulatory Authority (CRA) mobile NDC prefixes:
184
+
185
+ ```python
186
+ from fa_redact import IranianMobileNumberDetector, is_valid_mobile_number
187
+
188
+ # Domestic, +98 international, and 0098 international formats
189
+ is_valid_mobile_number("09123456789") # True (domestic)
190
+ is_valid_mobile_number("۰۹۱۲۳۴۵۶۷۸۹") # True (Persian digits)
191
+ is_valid_mobile_number("+989123456789") # True (+98 format)
192
+ is_valid_mobile_number("00989351234567") # True (0098 format)
193
+ is_valid_mobile_number("09412345678") # False (fixed non-geographical)
194
+ ```
195
+
196
+ > **Numbering Plan Notice**: Prefix classification is based on the official Communications Regulatory Authority (CRA) National Numbering Plan (published via ITU Operational Bulletin No. 1340). Prefix validation confirms structural allocation only and does not verify subscriber ownership, active SIM status, or carrier identity.
197
+
198
+ ---
199
+
200
+ ## Privacy-Safe Test-Data Policy
201
+
202
+ All test fixtures, examples, 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.
203
+
204
+ ---
205
+
206
+ ## Important Disclaimers
207
+
208
+ - **Not Production Clinical Software**: `fa-redact` is an experimental, early-stage open-source library and is **not** certified as a medical device or approved for production clinical decision-making.
209
+ - **No Inherent Regulatory Compliance**: Use of this library does not automatically ensure compliance with HIPAA, GDPR, or local privacy regulations. Organizations remain responsible for verifying that their data pipelines meet applicable legal and privacy standards.
210
+ - **No Identity Verification**: Validation functions verify format and mathematical structure only; they do not query government registries or authenticate individuals.
211
+
212
+ ---
213
+
214
+ ## Development & Quality Checks
215
+
216
+ Run the automated test suite:
217
+ ```bash
218
+ python -m pytest
219
+ ```
220
+
221
+ Check code formatting and linting:
222
+ ```bash
223
+ ruff check .
224
+ ruff format --check .
225
+ ```
226
+
227
+ Run static type checking:
228
+ ```bash
229
+ mypy src
230
+ mypy tests
231
+ ```
232
+
233
+ Build and validate distribution packages:
234
+ ```bash
235
+ python -m build
236
+ python -m twine check dist/*
237
+ ```
238
+
239
+ For release procedures and PyPI publishing setup, see [RELEASING.md](RELEASING.md).
240
+
241
+ ---
242
+
243
+ ## License
244
+
245
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,211 @@
1
+ # fa-redact
2
+
3
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ `fa-redact` is a lightweight, privacy-first Python toolkit for Persian/Iranian Personally Identifiable Information (PII) detection, redaction, and pseudonymization, designed especially for healthcare and AI/LLM applications.
7
+
8
+ > **Status: v0.1.0 Release Candidate (Alpha)**
9
+ > This package provides position-preserving Persian text normalization, immutable `Detection` data models, strict Iranian National ID (Code Melli) and Mobile Number validators and detectors, high-level `detect()` orchestration, stateless placeholder-based `redact()`, and stateful `PseudonymizationSession` with safe restoration for AI/LLM workflows.
10
+
11
+ ---
12
+
13
+ ## Quick Start
14
+
15
+ ### 1. Detect PII
16
+ Identify sensitive spans with exact source offsets and normalized representations:
17
+
18
+ ```python
19
+ from fa_redact import detect
20
+
21
+ text = "بیمار با کد ملی ۱۲۳۴۵۶۷۸۹۱ و شماره ۰۹۱۲۳۴۵۶۷۸۹ مراجعه کرد."
22
+ detections = detect(text)
23
+
24
+ for d in detections:
25
+ print(f"Type: {d.type} | Value: {d.value} | Span: [{d.start}:{d.end}]")
26
+ ```
27
+
28
+ ### 2. Redact PII (Stateless)
29
+ Sanitize text into safe, typed placeholders with fresh counter numbering:
30
+
31
+ ```python
32
+ from fa_redact import redact
33
+
34
+ text = "کد ملی: ۱۲۳۴۵۶۷۸۹۱، تماس: ۰۹۱۲۳۴۵۶۷۸۹، تماس دوم: 09123456789"
35
+ safe_text = redact(text)
36
+ print(safe_text)
37
+ # Output: "کد ملی: [IR_NATIONAL_ID_1]، تماس: [IR_MOBILE_1]، تماس دوم: [IR_MOBILE_1]"
38
+ ```
39
+
40
+ ### 3. Stateful Pseudonymization & AI/LLM Restoration
41
+ Maintain consistent entity mappings across conversation turns and restore placeholders locally:
42
+
43
+ ```python
44
+ from fa_redact import PseudonymizationSession
45
+
46
+ session = PseudonymizationSession()
47
+
48
+ # 1. Pseudonymize prompt locally before sending to external LLM:
49
+ prompt = "کد ملی بیمار ۱۲۳۴۵۶۷۸۹۱ و شماره تماس ۰۹۱۲۳۴۵۶۷۸۹ است."
50
+ safe_prompt = session.pseudonymize(prompt)
51
+ print(safe_prompt)
52
+ # Output: "کد ملی بیمار [IR_NATIONAL_ID_1] و شماره تماس [IR_MOBILE_1] است."
53
+
54
+ # 2. Send ONLY safe_prompt to external LLM. Simulated LLM response:
55
+ llm_response = "جهت پیگیری بیمار با [IR_MOBILE_1] هماهنگ شد."
56
+
57
+ # 3. Restore placeholders locally within your trusted boundary:
58
+ restored = session.restore(llm_response)
59
+ print(restored)
60
+ # Output: "جهت پیگیری بیمار با ۰۹۱۲۳۴۵۶۷۸۹ هماهنگ شد."
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Installation
66
+
67
+ ### Future PyPI Release
68
+ Once v0.1.0 is published to PyPI:
69
+
70
+ ```bash
71
+ pip install fa-redact
72
+ ```
73
+
74
+ ### Development Installation
75
+ For development or installing from source:
76
+
77
+ ```bash
78
+ git clone https://github.com/mehdimt1980/fa-redact.git
79
+ cd fa-redact
80
+ pip install -e ".[dev]"
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Detailed Capabilities
86
+
87
+ ### 1. Stateful Pseudonymization Sessions
88
+ The `PseudonymizationSession` class manages state across multi-turn workflows:
89
+ - **Local Sensitive Mapping**: `session.mapping` holds `{placeholder: original_pii}`. Keep this mapping strictly inside your local trusted environment; never transmit it to external AI services.
90
+ - **First-Observed Representative Restoration**: For each unique identity `(type, normalized_value)`, the session records the first-observed raw string as its semantic restoration target.
91
+ - **Non-Cascading Single-Pass Restoration**: `restore()` performs an escaped single-pass substitution, preventing recursive evaluation if restored values contain placeholder-like text.
92
+ - **Cross-Call Collision Safety**: Generated placeholders automatically avoid colliding with literal placeholder-shaped tokens seen in current or previous inputs within the session.
93
+ - **Unknown Placeholders**: Unmapped placeholders (e.g., `[IR_MOBILE_999]`) are left untouched without error.
94
+
95
+ > [!WARNING]
96
+ > **Sensitive Data Notice**: `session.mapping` contains original PII. Treat it as sensitive data and protect it accordingly.
97
+ >
98
+ > **Scope Limitation**: `fa-redact` detects and redacts only the PII types supported by its enabled detectors (Iranian National IDs and Iranian Mobile Numbers in v0.1.0). It does not provide complete automated clinical de-identification.
99
+
100
+ ### 2. Position-Preserving Normalization
101
+ `fa-redact` provides pure, deterministic normalization where each Unicode character maps 1-to-1 to a normalized code point (`len(normalized) == len(original)`), guaranteeing that character offsets remain identical to the original input text:
102
+
103
+ ```python
104
+ from fa_redact import normalize_digits, normalize_letters, normalize_text
105
+
106
+ # Normalizes Persian (۰-۹) and Arabic-Indic (٠-٩) digits to ASCII (0-9)
107
+ normalize_digits("کد بیمار: ۱۲۳٤٥")
108
+ # Returns: "کد بیمار: 12345"
109
+
110
+ # Normalizes Arabic letter variants (ي -> ی, ك -> ک)
111
+ normalize_letters("پزشك و دكتر")
112
+ # Returns: "پزشک و دکتر"
113
+
114
+ # Full position-preserving normalization
115
+ normalize_text("كد ملي: ۰۰۱٢٣٤٥٦٧٨")
116
+ # Returns: "کد ملی: 0012345678"
117
+ ```
118
+
119
+ ### 3. Detection Data Model & Pipeline
120
+ The immutable `Detection` dataclass represents identified spans:
121
+
122
+ ```python
123
+ from fa_redact import Detection, IranianNationalIDDetector, detect, normalize_text
124
+
125
+ # Using custom detector list:
126
+ detections = detect("کد ملی: ۱۲۳۴۵۶۷۸۹۱", detectors=[IranianNationalIDDetector()])
127
+ for d in detections:
128
+ assert d.type == "IR_NATIONAL_ID"
129
+ assert d.value == "۱۲۳۴۵۶۷۸۹۱"
130
+ assert d.normalized_value == "1234567891"
131
+ ```
132
+
133
+ ### 4. Iranian National ID Validation & Detection
134
+ Validate and detect Iranian National IDs (Code Melli / `کد ملی`) with strict modulo-11 checksum verification:
135
+
136
+ ```python
137
+ from fa_redact import IranianNationalIDDetector, is_valid_national_id
138
+
139
+ # Algorithmic test vectors not sourced from personal data
140
+ is_valid_national_id("1234567891") # True
141
+ is_valid_national_id("۱۲۳۴۵۶۷۸۹۱") # True (Persian digits)
142
+ is_valid_national_id("1234567890") # False (invalid check digit)
143
+ is_valid_national_id("1111111111") # False (repeated digits rejected)
144
+ ```
145
+
146
+ > **Verification Notice**: Checksum validation verifies mathematical structure only without querying official registries. These values are algorithmic test vectors not sourced from personal or patient records. Checksum validity does not establish whether an identifier has been officially issued to an individual.
147
+
148
+ ### 5. Iranian Mobile Number Validation & Detection
149
+ Validate and detect Iranian mobile numbers against official Communications Regulatory Authority (CRA) mobile NDC prefixes:
150
+
151
+ ```python
152
+ from fa_redact import IranianMobileNumberDetector, is_valid_mobile_number
153
+
154
+ # Domestic, +98 international, and 0098 international formats
155
+ is_valid_mobile_number("09123456789") # True (domestic)
156
+ is_valid_mobile_number("۰۹۱۲۳۴۵۶۷۸۹") # True (Persian digits)
157
+ is_valid_mobile_number("+989123456789") # True (+98 format)
158
+ is_valid_mobile_number("00989351234567") # True (0098 format)
159
+ is_valid_mobile_number("09412345678") # False (fixed non-geographical)
160
+ ```
161
+
162
+ > **Numbering Plan Notice**: Prefix classification is based on the official Communications Regulatory Authority (CRA) National Numbering Plan (published via ITU Operational Bulletin No. 1340). Prefix validation confirms structural allocation only and does not verify subscriber ownership, active SIM status, or carrier identity.
163
+
164
+ ---
165
+
166
+ ## Privacy-Safe Test-Data Policy
167
+
168
+ All test fixtures, examples, 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.
169
+
170
+ ---
171
+
172
+ ## Important Disclaimers
173
+
174
+ - **Not Production Clinical Software**: `fa-redact` is an experimental, early-stage open-source library and is **not** certified as a medical device or approved for production clinical decision-making.
175
+ - **No Inherent Regulatory Compliance**: Use of this library does not automatically ensure compliance with HIPAA, GDPR, or local privacy regulations. Organizations remain responsible for verifying that their data pipelines meet applicable legal and privacy standards.
176
+ - **No Identity Verification**: Validation functions verify format and mathematical structure only; they do not query government registries or authenticate individuals.
177
+
178
+ ---
179
+
180
+ ## Development & Quality Checks
181
+
182
+ Run the automated test suite:
183
+ ```bash
184
+ python -m pytest
185
+ ```
186
+
187
+ Check code formatting and linting:
188
+ ```bash
189
+ ruff check .
190
+ ruff format --check .
191
+ ```
192
+
193
+ Run static type checking:
194
+ ```bash
195
+ mypy src
196
+ mypy tests
197
+ ```
198
+
199
+ Build and validate distribution packages:
200
+ ```bash
201
+ python -m build
202
+ python -m twine check dist/*
203
+ ```
204
+
205
+ For release procedures and PyPI publishing setup, see [RELEASING.md](RELEASING.md).
206
+
207
+ ---
208
+
209
+ ## License
210
+
211
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,89 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fa-redact"
7
+ version = "0.1.0"
8
+ description = "A privacy-first Python toolkit for Persian/Iranian PII redaction and pseudonymization, designed especially for healthcare and AI applications."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "fa-redact contributors" }
14
+ ]
15
+ keywords = [
16
+ "persian",
17
+ "farsi",
18
+ "pii",
19
+ "redaction",
20
+ "anonymization",
21
+ "pseudonymization",
22
+ "privacy",
23
+ "healthcare",
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 3 - Alpha",
27
+ "Intended Audience :: Developers",
28
+ "Intended Audience :: Healthcare Industry",
29
+ "Operating System :: OS Independent",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3.10",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Programming Language :: Python :: 3.13",
35
+ "Topic :: Security",
36
+ "Topic :: Software Development :: Libraries :: Python Modules",
37
+ "Topic :: Text Processing :: Linguistic",
38
+ "Typing :: Typed",
39
+ ]
40
+ dependencies = []
41
+
42
+ [project.optional-dependencies]
43
+ dev = [
44
+ "build>=1.0.0",
45
+ "mypy>=1.0.0",
46
+ "pytest>=7.0.0",
47
+ "ruff>=0.9.0",
48
+ "twine>=5.0.0",
49
+ ]
50
+
51
+ [project.urls]
52
+ Homepage = "https://github.com/mehdimt1980/fa-redact"
53
+ Repository = "https://github.com/mehdimt1980/fa-redact"
54
+ Issues = "https://github.com/mehdimt1980/fa-redact/issues"
55
+
56
+ [tool.setuptools.packages.find]
57
+ where = ["src"]
58
+
59
+ [tool.setuptools.package-data]
60
+ fa_redact = ["py.typed"]
61
+
62
+ [tool.pytest.ini_options]
63
+ minversion = "7.0"
64
+ testpaths = ["tests"]
65
+ pythonpath = ["src"]
66
+
67
+ [tool.ruff]
68
+ target-version = "py310"
69
+ line-length = 88
70
+ src = ["src", "tests"]
71
+
72
+ [tool.ruff.lint]
73
+ select = [
74
+ "E", # pycodestyle errors
75
+ "W", # pycodestyle warnings
76
+ "F", # pyflakes
77
+ "I", # isort
78
+ "B", # flake8-bugbear
79
+ "UP", # pyupgrade
80
+ ]
81
+ ignore = []
82
+
83
+ [tool.mypy]
84
+ python_version = "3.10"
85
+ mypy_path = "src"
86
+ strict = true
87
+ warn_return_any = true
88
+ warn_unused_configs = true
89
+ disallow_untyped_defs = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,37 @@
1
+ """fa-redact: Privacy-first Persian/Iranian PII redaction and pseudonymization toolkit.
2
+
3
+ This package is currently in early development.
4
+ """
5
+
6
+ from fa_redact.detectors import (
7
+ IranianMobileNumberDetector,
8
+ IranianNationalIDDetector,
9
+ )
10
+ from fa_redact.models import Detection
11
+ from fa_redact.normalization import (
12
+ normalize_digits,
13
+ normalize_letters,
14
+ normalize_text,
15
+ )
16
+ from fa_redact.pipeline import detect
17
+ from fa_redact.protocols import Detector
18
+ from fa_redact.pseudonymization import PseudonymizationSession
19
+ from fa_redact.redaction import redact
20
+ from fa_redact.validators import is_valid_mobile_number, is_valid_national_id
21
+
22
+ __version__: str = "0.1.0"
23
+ __all__: list[str] = [
24
+ "__version__",
25
+ "Detection",
26
+ "Detector",
27
+ "IranianMobileNumberDetector",
28
+ "IranianNationalIDDetector",
29
+ "PseudonymizationSession",
30
+ "detect",
31
+ "is_valid_mobile_number",
32
+ "is_valid_national_id",
33
+ "normalize_digits",
34
+ "normalize_letters",
35
+ "normalize_text",
36
+ "redact",
37
+ ]
@@ -0,0 +1,8 @@
1
+ """Identifier detectors for fa-redact."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fa_redact.detectors.mobile import IranianMobileNumberDetector
6
+ from fa_redact.detectors.national_id import IranianNationalIDDetector
7
+
8
+ __all__ = ["IranianMobileNumberDetector", "IranianNationalIDDetector"]
@@ -0,0 +1,62 @@
1
+ """Detector for Iranian mobile phone numbers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections.abc import Sequence
7
+
8
+ from fa_redact.models import Detection
9
+ from fa_redact.validators.mobile import is_valid_mobile_number
10
+
11
+ _ENTITY_TYPE: str = "IR_MOBILE"
12
+ _CANDIDATE_PATTERN: re.Pattern[str] = re.compile(
13
+ r"(?<![0-9+])(?:\+98[0-9]{10}|0098[0-9]{10}|0[0-9]{10})(?![0-9])"
14
+ )
15
+
16
+
17
+ class IranianMobileNumberDetector:
18
+ """Detects prefix-valid Iranian mobile numbers in text.
19
+
20
+ Scans position-preserving normalized text for compact domestic and international
21
+ mobile candidates, validates them against official 2026 CRA NDC mobile prefixes,
22
+ and returns Detection instances preserving both raw and normalized representations.
23
+ """
24
+
25
+ def detect(
26
+ self,
27
+ original_text: str,
28
+ normalized_text: str,
29
+ ) -> Sequence[Detection]:
30
+ """Detect Iranian mobile numbers across source texts.
31
+
32
+ Args:
33
+ original_text: Raw input text.
34
+ normalized_text: Position-preserving normalized text of identical length.
35
+
36
+ Returns:
37
+ List of detected Detection instances in textual order.
38
+
39
+ Raises:
40
+ ValueError: If original_text and normalized_text differ in length.
41
+ """
42
+ if len(original_text) != len(normalized_text):
43
+ raise ValueError(
44
+ f"original_text length ({len(original_text)}) must equal "
45
+ f"normalized_text length ({len(normalized_text)})"
46
+ )
47
+
48
+ detections: list[Detection] = []
49
+ for match in _CANDIDATE_PATTERN.finditer(normalized_text):
50
+ candidate = match.group(0)
51
+ if is_valid_mobile_number(candidate):
52
+ detections.append(
53
+ Detection.from_texts(
54
+ type=_ENTITY_TYPE,
55
+ original_text=original_text,
56
+ normalized_text=normalized_text,
57
+ start=match.start(),
58
+ end=match.end(),
59
+ )
60
+ )
61
+
62
+ return detections