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.
- fa_redact-0.1.0/LICENSE +21 -0
- fa_redact-0.1.0/PKG-INFO +245 -0
- fa_redact-0.1.0/README.md +211 -0
- fa_redact-0.1.0/pyproject.toml +89 -0
- fa_redact-0.1.0/setup.cfg +4 -0
- fa_redact-0.1.0/src/fa_redact/__init__.py +37 -0
- fa_redact-0.1.0/src/fa_redact/detectors/__init__.py +8 -0
- fa_redact-0.1.0/src/fa_redact/detectors/mobile.py +62 -0
- fa_redact-0.1.0/src/fa_redact/detectors/national_id.py +60 -0
- fa_redact-0.1.0/src/fa_redact/models.py +103 -0
- fa_redact-0.1.0/src/fa_redact/normalization.py +102 -0
- fa_redact-0.1.0/src/fa_redact/pipeline.py +61 -0
- fa_redact-0.1.0/src/fa_redact/protocols.py +33 -0
- fa_redact-0.1.0/src/fa_redact/pseudonymization.py +166 -0
- fa_redact-0.1.0/src/fa_redact/py.typed +1 -0
- fa_redact-0.1.0/src/fa_redact/redaction.py +37 -0
- fa_redact-0.1.0/src/fa_redact/validators/__init__.py +8 -0
- fa_redact-0.1.0/src/fa_redact/validators/mobile.py +119 -0
- fa_redact-0.1.0/src/fa_redact/validators/national_id.py +47 -0
- fa_redact-0.1.0/src/fa_redact.egg-info/PKG-INFO +245 -0
- fa_redact-0.1.0/src/fa_redact.egg-info/SOURCES.txt +30 -0
- fa_redact-0.1.0/src/fa_redact.egg-info/dependency_links.txt +1 -0
- fa_redact-0.1.0/src/fa_redact.egg-info/requires.txt +7 -0
- fa_redact-0.1.0/src/fa_redact.egg-info/top_level.txt +1 -0
- fa_redact-0.1.0/tests/test_mobile.py +366 -0
- fa_redact-0.1.0/tests/test_models.py +286 -0
- fa_redact-0.1.0/tests/test_national_id.py +245 -0
- fa_redact-0.1.0/tests/test_normalization.py +155 -0
- fa_redact-0.1.0/tests/test_package.py +68 -0
- fa_redact-0.1.0/tests/test_pipeline.py +303 -0
- fa_redact-0.1.0/tests/test_pseudonymization.py +578 -0
- fa_redact-0.1.0/tests/test_redaction.py +429 -0
fa_redact-0.1.0/LICENSE
ADDED
|
@@ -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.
|
fa_redact-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://www.python.org/downloads/)
|
|
38
|
+
[](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
|
+
[](https://www.python.org/downloads/)
|
|
4
|
+
[](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,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
|