leilaghomashchi commited on
Commit
7b409b9
·
verified ·
1 Parent(s): 214d724

Upload lightweight_anonymizer_fixed.py

Browse files
Files changed (1) hide show
  1. lightweight_anonymizer_fixed.py +1179 -0
lightweight_anonymizer_fixed.py ADDED
@@ -0,0 +1,1179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import re
3
+ import os
4
+ import requests
5
+ import time
6
+ import logging
7
+
8
+ # تنظیم logging
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class LightweightDataAnonymizer:
13
+ def __init__(self):
14
+ self.mapping_table = {}
15
+ # دسته‌بندی‌های الگوها برای UI
16
+ self.pattern_categories = {
17
+ 'personal_identity': {
18
+ 'name_fa': 'اطلاعات شخصی و هویتی',
19
+ 'name_en': 'Personal & Identity Information',
20
+ 'patterns': ['PERSON', 'MIXED_NAMES', 'ID_NUMBER', 'ENGLISH_TITLES'],
21
+ 'icon': '👤'
22
+ },
23
+ 'financial': {
24
+ 'name_fa': 'اطلاعات مالی',
25
+ 'name_en': 'Financial Information',
26
+ 'patterns': ['AMOUNT', 'INTERNATIONAL_CURRENCIES', 'ACCOUNT', 'FINANCIAL_TERMS', 'STOCK_SYMBOL'],
27
+ 'icon': '💰'
28
+ },
29
+ 'temporal': {
30
+ 'name_fa': 'اطلاعات زمانی',
31
+ 'name_en': 'Temporal Information',
32
+ 'patterns': ['DATE', 'ADVANCED_DATE_FORMATS', 'TIME_RANGES'],
33
+ 'icon': '📅'
34
+ },
35
+ 'location': {
36
+ 'name_fa': 'اطلاعات مکانی',
37
+ 'name_en': 'Location Information',
38
+ 'patterns': ['LOCATION', 'COMPLEX_ADDRESSES'],
39
+ 'icon': '📍'
40
+ },
41
+ 'technical': {
42
+ 'name_fa': 'اطلاعات فنی و تکنولوژیکی',
43
+ 'name_en': 'Technical & Technological',
44
+ 'patterns': ['TECHNICAL_CODES', 'NETWORK_ADDRESSES', 'TECHNICAL_UNITS', 'ACRONYMS_ABBREVIATIONS'],
45
+ 'icon': '⚙️'
46
+ },
47
+ 'business': {
48
+ 'name_fa': 'اطلاعات کسب‌وکار',
49
+ 'name_en': 'Business Information',
50
+ 'patterns': ['COMPANY', 'BUSINESS_TERMS', 'PRODUCT', 'PETROCHEMICAL'],
51
+ 'icon': '🏢'
52
+ },
53
+ 'quantity': {
54
+ 'name_fa': 'اطلاعات کمیت و واحد',
55
+ 'name_en': 'Quantity & Unit Information',
56
+ 'patterns': ['PERCENTAGE', 'VOLUME', 'RATIOS'],
57
+ 'icon': '📊'
58
+ },
59
+ 'communication': {
60
+ 'name_fa': 'اطلاعات ارتباطی',
61
+ 'name_en': 'Communication Information',
62
+ 'patterns': ['PHONE', 'EMAIL'],
63
+ 'icon': '📞'
64
+ }
65
+ }
66
+
67
+ # counters
68
+ self.counters = {
69
+ 'PERSON': 0, 'MIXED_NAMES': 0, 'ID_NUMBER': 0, 'ENGLISH_TITLES': 0,
70
+ 'AMOUNT': 0, 'INTERNATIONAL_CURRENCIES': 0, 'ACCOUNT': 0,
71
+ 'FINANCIAL_TERMS': 0, 'STOCK_SYMBOL': 0,
72
+ 'DATE': 0, 'ADVANCED_DATE_FORMATS': 0, 'TIME_RANGES': 0,
73
+ 'LOCATION': 0, 'COMPLEX_ADDRESSES': 0,
74
+ 'TECHNICAL_CODES': 0, 'NETWORK_ADDRESSES': 0, 'TECHNICAL_UNITS': 0,
75
+ 'ACRONYMS_ABBREVIATIONS': 0,
76
+ 'COMPANY': 0, 'BUSINESS_TERMS': 0, 'PRODUCT': 0, 'PETROCHEMICAL': 0,
77
+ 'PERCENTAGE': 0, 'VOLUME': 0, 'RATIOS': 0,
78
+ 'PHONE': 0, 'EMAIL': 0
79
+ }
80
+
81
+ self.api_key = os.getenv("OPENAI_API_KEY", "")
82
+
83
+ def get_category_choices(self, language='fa'):
84
+ """دریافت لیست دسته‌بندی‌ها برای چک‌باکس"""
85
+ choices = []
86
+ for cat_key, cat_info in self.pattern_categories.items():
87
+ name = cat_info['name_fa'] if language == 'fa' else cat_info['name_en']
88
+ icon = cat_info['icon']
89
+ choices.append(f"{icon} {name}")
90
+ return choices
91
+
92
+ def get_selected_patterns(self, selected_categories, language='fa'):
93
+ """تبدیل دسته‌بندی‌های انتخاب شده به لیست الگوها"""
94
+ selected_patterns = []
95
+
96
+ for cat_key, cat_info in self.pattern_categories.items():
97
+ name = cat_info['name_fa'] if language == 'fa' else cat_info['name_en']
98
+ icon = cat_info['icon']
99
+ category_display = f"{icon} {name}"
100
+
101
+ if category_display in selected_categories:
102
+ selected_patterns.extend(cat_info['patterns'])
103
+
104
+ return selected_patterns
105
+
106
+ def detect_language(self, text):
107
+ """تشخیص زبان متن"""
108
+ if not text:
109
+ return 'fa'
110
+
111
+ persian_chars = len(re.findall(r'[\u0600-\u06FF]', text))
112
+ english_chars = len(re.findall(r'[a-zA-Z]', text))
113
+ total = persian_chars + english_chars
114
+
115
+ if total == 0:
116
+ return 'fa'
117
+
118
+ if persian_chars / total > 0.6:
119
+ return 'fa'
120
+ elif english_chars / total > 0.6:
121
+ return 'en'
122
+ else:
123
+ return 'mixed'
124
+
125
+ def get_comprehensive_patterns(self):
126
+ """الگوهای جامع ��اشناس‌سازی - نسخه کامل"""
127
+ return {
128
+ 'PERSON': [
129
+ r'آقای\s+([آ-ی۰-۹a-zA-Z]+(?:\s+[آ-ی۰-۹a-zA-Z]+)*)',
130
+ r'خانم\s+([آ-ی۰-۹a-zA-Z]+(?:\s+[آ-ی۰-۹a-zA-Z]+)*)',
131
+ r'مهندس\s+([آ-ی۰-۹a-zA-Z]+(?:\s+[آ-ی۰-۹a-zA-Z]+)*)',
132
+ r'دکتر\s+([آ-ی۰-۹a-zA-Z]+(?:\s+[آ-ی۰-۹a-zA-Z]+)*)',
133
+ r'استاد\s+([آ-ی۰-۹a-zA-Z]+(?:\s+[آ-ی۰-۹a-zA-Z]+)*)',
134
+ r'Mr\.\s+([a-zA-Z]+(?:\s+[a-zA-Z]+)*)',
135
+ r'Ms\.\s+([a-zA-Z]+(?:\s+[a-zA-Z]+)*)',
136
+ r'Dr\.\s+([a-zA-Z]+(?:\s+[a-zA-Z]+)*)',
137
+ r'([آ-ی۰-۹a-zA-Z]+\s+[آ-ی۰-۹a-zA-Z]+)(?:، مدیرعامل|\s+مدیرعامل|\s+رئیس)',
138
+ r'مدیرعامل(?=\s|$|،|\.)',
139
+ r'سرپرست(?=\s+و|\s|$|،|\.)',
140
+ r'رئیس\s+هیأت‌مدیره'
141
+ ],
142
+
143
+ 'MIXED_NAMES': [
144
+ r'([آ-ی۰-۹a-zA-Z]{2,}\s+[آ-ی۰-۹a-zA-Z]{2,})',
145
+ r'([A-Z][a-z]+-[A-Z][a-z]+)',
146
+ r"([A-Z]'[A-Z][a-z]+)",
147
+ r'Dr\.\s+([آ-ی۰-۹a-zA-Z\s‌]+)'
148
+ ],
149
+
150
+ 'ID_NUMBER': [
151
+ r'IR[۰-۹0-9]{24}',
152
+ r'شبا[\s:]*IR[۰-۹0-9]{24}',
153
+ r'IBAN[\s:]*IR[۰-۹0-9]{24}',
154
+ r'شماره[\s]*شبا[\s:]*IR[۰-۹0-9]{24}',
155
+ r'(?:کد[\s]*)?(?:ملی[\s:]*)?[۰-۹0-9]{10}',
156
+ r'(?:شناسه[\s]*)?(?:ملی[\s:]*)?[۰-۹0-9]{10}',
157
+ r'National[\s]*(?:ID[\s:]*)?[0-9]{10}',
158
+ r'(?:پاسپورت[\s:]*)?[A-Z][0-9]{8}',
159
+ r'(?:Passport[\s:]*)?[A-Z][0-9]{8}',
160
+ r'(?:کارت[\s:]*)?(?:[۰-۹0-9]{4}[-\s]?){3}[۰-۹0-9]{4}',
161
+ r'(?:Card[\s:]*)?(?:[0-9]{4}[-\s]?){3}[0-9]{4}',
162
+ r'SSN[\s:]*[0-9]{3}-[0-9]{2}-[0-9]{4}',
163
+ r'FICO[\s]*(?:score[\s:]*)?[0-9]{3}',
164
+ r'EIN[\s:]*[0-9]{2}-[0-9]{7}',
165
+ r'Meeting[\s]*ID[\s:]*[0-9]{9,11}'
166
+ ],
167
+
168
+ 'ENGLISH_TITLES': [
169
+ r'business\s+partner',
170
+ r'team\s+lead',
171
+ r'head\s+of\s+production',
172
+ r'senior\s+architect',
173
+ r'civil\s+engineer',
174
+ r'system\s+administrator',
175
+ r'network\s+engineer',
176
+ r'environmental\s+consultant',
177
+ r'senior\s+loan\s+officer',
178
+ r'facility\s+manager',
179
+ r'project\s+team',
180
+ r'technical\s+support'
181
+ ],
182
+
183
+ 'AMOUNT': [
184
+ r'\d+(?:,\d{3})*\s*(?:میلیون|میلیارد|هزار)\s*تومان',
185
+ r'مبلغ\s+\d+(?:,\d{3})*\s*(?:میلیون|میلیارد|هزار)?\s*تومان',
186
+ r'\d+\s*تومان(?=\s+به\s+ازای|\s+فروش|،)',
187
+ r'رقم\s+فعلی\s+\d+(?:,\d{3})*\s*(?:میلیون|میلیارد)\s*تومان',
188
+ r'رقم\s+\d+(?:,\d{3})*\s*(?:میلیون|میلیارد)\s*تومان',
189
+ r'به\s+\d+(?:,\d{3})*\s*(?:میلیون|میلیارد|هزار)\s*تومان',
190
+ r'\$\d+(?:,\d{3})*(?:\.\d+)?\s*(?:million|billion|thousand|M|B|K)?',
191
+ r'\d+(?:,\d{3})*\s*ریال',
192
+ r'€\d+(?:,\d{3})*(?:\.\d+)?',
193
+ r'\d+(?:,\d{3})*\s*AED',
194
+ r'\$\d+(?:\.\d+)?[KMB]',
195
+ r'€\d+(?:\.\d+)?[KM]'
196
+ ],
197
+
198
+ 'INTERNATIONAL_CURRENCIES': [
199
+ r'\d+(?:,\d{3})*\s+euro',
200
+ r'€\d+(?:\.\d+)?M',
201
+ r'\d+\s+EUR',
202
+ r'\d+(?:,\d{3})*\s+AED',
203
+ r'\d+(?:\.\d+)?M\s+AED',
204
+ r'\$\d+(?:\.\d+)?M',
205
+ r'\$\d+(?:\.\d+)?K',
206
+ r'£\d+(?:,\d{3})*(?:\.\d+)?',
207
+ r'\d+\s+GBP',
208
+ r'\d+\s+CHF',
209
+ r'¥\d+(?:,\d{3})*',
210
+ r'\d+\s+JPY'
211
+ ],
212
+
213
+ 'ACCOUNT': [
214
+ r'(?:شماره[\s]*)?(?:حساب[\s]*)?(?:بانکی[\s:]*)?(?:[۰-۹0-9]{1,3}[-\s]?)*[۰-۹0-9]{8,20}',
215
+ r'حساب[\s]*(?:شماره[\s:]*)?(?:[۰-۹0-9]{1,3}[-\s]?)*[۰-۹0-9]{8,20}',
216
+ r'شماره[\s]*حساب[\s:]*(?:[۰-۹0-9]{1,3}[-\s]?)*[۰-۹0-9]{8,20}',
217
+ r'Account[\s]*(?:Number[\s:]*)?(?:[0-9]{1,3}[-\s]?)*[0-9]{8,20}',
218
+ r'[۰-۹0-9]{3}[-\s]?[۰-۹0-9]{3}[-\s]?[۰-۹0-9]{6,12}',
219
+ r'[۰-۹0-9]{2,4}[-\s]?[۰-۹0-9]{6,12}[-\s]?[۰-۹0-9]{2,4}',
220
+ r'واریز[\s]*(?:سود[\s:]*)?(?:[۰-۹0-9]{1,3}[-\s]?)*[۰-۹0-9]{8,20}',
221
+ r'سود[\s:]*(?:[۰-۹0-9]{1,3}[-\s]?)*[۰-۹0-9]{8,20}'
222
+ ],
223
+
224
+ 'FINANCIAL_TERMS': [
225
+ r'فروش\s+(?:ماهانه|تجمیعی|صادراتی)',
226
+ r'درآمد\s+شرکت',
227
+ r'سود\s+(?:خالص|نقدی)',
228
+ r'صورت‌های\s+مالی',
229
+ r'بهای\s+تمام‌شده',
230
+ r'سودآوری',
231
+ r'عملکرد\s+مالی',
232
+ r'میانگین\s+فروش',
233
+ r'بالاترین\s+رقم\s+فروش',
234
+ r'رقم\s+فروش',
235
+ r'درآمدهای\s+عملیاتی'
236
+ ],
237
+
238
+ 'STOCK_SYMBOL': [
239
+ r'نماد\s+([آ-یa-zA-Z0-9]+)',
240
+ r'(سبهان|غدیر|شتران|شپنا|پترول|فارس|خارک|پلاسکو|جم|کرمان|مارون|اراک|رازی|شازند|کاوه|بندر|پارس|خوزستان|ماهشهر|عسلویه)(?=\s|$|،|\.|\s+)',
241
+ r'شرکت\s+([آ-یa-zA-Z\s]+?)(?=\s+در|\s+که|\s+با|،|\.|\s+$|\s+را|\s+به)',
242
+ r'پتروشیمی\s+([آ-یa-zA-Z\s]+?)(?=\s+در|\s+که|\s+با|،|\.|\s+$|\s+توان)',
243
+ r'(AAPL|GOOGL|MSFT|AMZN|TSLA|META|NVDA|SABIC)(?=\s|$|,|\.)'
244
+ ],
245
+
246
+ 'DATE': [
247
+ r'[۰-۹0-9]{4}[/-][۰-۹0-9]{1,2}[/-][۰-۹0-9]{1,2}',
248
+ r'[۰-۹0-9]{1,2}[/-][۰-۹0-9]{1,2}[/-][۰-۹0-9]{4}',
249
+ r'(?:[۰-۹0-9]{1,2})\s*(?:فروردین|اردیبهشت|خرداد|تیر|مرداد|شهریور|مهر|آبان|آذر|دی|بهمن|اسفند)\s*(?:[۰-۹0-9]{4})',
250
+ r'(?:فروردین|اردیبهشت|خرداد|تیر|مرداد|شهریور|مهر|آبان|آذر|دی|بهمن|اسفند)\s+[۰-۹0-9]{4}',
251
+ r'(?:[0-9]{1,2})\s*(?:January|February|March|April|May|June|July|August|September|October|November|December)\s*(?:[0-9]{4})',
252
+ r'(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s*[0-9]{1,2},?\s*[0-9]{4}',
253
+ r'سال\s+گذشته',
254
+ r'سال\s+جاری',
255
+ r'این\s+سال',
256
+ r'ماه\s+قبل',
257
+ r'ماه\s+اخیر',
258
+ r'(?:13[0-9]{2}|14[0-9]{2}|20[0-9]{2}|19[0-9]{2})(?=\s|$|،|\.)'
259
+ ],
260
+
261
+ 'ADVANCED_DATE_FORMATS': [
262
+ r'(?:March|April|May|June|July|August|September|October|November|December)\s+\d{1,2}(?:st|nd|rd|th),?\s+\d{4}',
263
+ r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z',
264
+ r'(?:PST|EST|GMT|UTC)(?:[+-]\d{1,2}:\d{2})?',
265
+ r'Eastern\s+Time',
266
+ r'GMT[+-]\d{1,2}:\d{2}',
267
+ r'end\s+of\s+fiscal\s+year\s+\d{4}/\d{2}/\d{2}'
268
+ ],
269
+
270
+ 'TIME_RANGES': [
271
+ r'\d{2}:\d{2}-\d{2}:\d{2}',
272
+ r'\d{2}:\d{2}\s+تا\s+\d{2}:\d{2}',
273
+ r'\d{1,2}:\d{2}\s+(?:AM|PM)\s+(?:PST|EST|GMT|UTC)',
274
+ r'\d{2}:\d{2}:\d{2}\s+(?:AM|PM)',
275
+ r'COB\s*\(Close\s+of\s+Business\)',
276
+ r'\d{1,3}\s+(?:business\s+days|روز\s+کاری)'
277
+ ],
278
+
279
+ 'LOCATION': [
280
+ r'(تهران|اصفهان|ماهشهر|عسلویه|بندرعباس|اهواز|شیراز|مشهد|تبریز|کرج|قم|رشت|کرمان|یزد|زاهدان|بوشهر|خرمشهر|آبادان|اراک|قزوین)',
281
+ r'استان\s+([آ-ی\s]+)',
282
+ r'شهر\s+([آ-ی\s]+)',
283
+ r'(ایران|عراق|کویت|عربستان|امارات|قطر|عمان|بحرین|ترکیه|پاکستان|افغانستان)',
284
+ r'داخلی|بازار\s+داخلی',
285
+ r'خارجی|بازارهای\s+خارجی',
286
+ r'(London|Paris|Tokyo|New\s+York|Dubai|Singapore|Hong\s+Kong|Shanghai|Mumbai|Frankfurt|Amsterdam)'
287
+ ],
288
+
289
+ 'COMPLEX_ADDRESSES': [
290
+ r'کیلومتر\s+\d+\s+جاده\s+[آ-ی\s]+-[آ-ی\s]+',
291
+ r'روبروی\s+(?:پمپ\s+بنزین|بانک|پارک|مسجد|بیمارستان)\s+[آ-یa-zA-Z\s]+',
292
+ r'Building-[A-Z],?\s+Floor-\d+,?\s+Unit-[A-Z0-9]+',
293
+ r'rack\s+number\s+R-\d+,?\s+slot\s+\d+',
294
+ r'phase\s+\d+\s+development,?\s+block\s+[A-Z],?\s+plot\s+\d+-[A-Z]',
295
+ r'\d{2,5}\s+[A-Z][a-z]+\s+(?:Street|Avenue|Boulevard|Road|Drive),?\s+Floor\s+\d+,?\s+Building\s+[A-Z]',
296
+ r'شهرک\s+صنعتی\s+[آ-ی\s]+،?\s+محور\s+[آ-ی\s]+'
297
+ ],
298
+
299
+ 'TECHNICAL_CODES': [
300
+ r'SN-\d{4}-[A-Z]{3}-\d{4}',
301
+ r'Serial\s+Number[\s:]*[A-Z0-9-]+',
302
+ r'REF-[A-Z]{3}-\d{4}-\d{3}',
303
+ r'DOC-[A-Z]{2}-\d{4}-\d{4}',
304
+ r'INF-\d{4}-\d{4}',
305
+ r'CTR/\d{4}/\d{3}',
306
+ r'HVAC-\d{7}',
307
+ r'Generator-Model-[A-Z0-9]+',
308
+ r'LOI-\d{4}-[A-Z]{4}-\d{3}',
309
+ r'BOQ-\d{4}-[A-Z]{3}-\d{3}',
310
+ r'#INV-\d{4}-Q\d-\d{4}',
311
+ r'ESC-\d{4}-[A-Z]{3}-\d{3}',
312
+ r'BN-\d{6}-[A-Z]\d+'
313
+ ],
314
+
315
+ 'NETWORK_ADDRESSES': [
316
+ r'\b(?:\d{1,3}\.){3}\d{1,3}\b',
317
+ r'xxx\.xxx\.xxx\.xxx',
318
+ r'[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}',
319
+ r'srv-[a-z]+-[a-z]+-\d{2}',
320
+ r'[a-z]+-[a-z]+\d*\.[a-z]+\.[a-z]+',
321
+ r'[a-zA-Z0-9-]+\.[a-zA-Z]{2,4}(?:\.[a-zA-Z]{2,4})?'
322
+ ],
323
+
324
+ 'TECHNICAL_UNITS': [
325
+ r'\d+(?:\.\d+)?\s*MW',
326
+ r'\d+(?:\.\d+)?\s*kWh?',
327
+ r'\d+(?:,\d{3})*\s*cubic\s+meters',
328
+ r'\d+(?:,\d{3})*\s*m³',
329
+ r'\d+(?:,\d{3})*\s*sq\s+ft',
330
+ r'\d+(?:\.\d+)?\s*ppm',
331
+ r'\d+(?:\.\d+)?\s*mg/m³',
332
+ r'\b(?:CO2|NOx|SO2)\b',
333
+ r'\d+(?:\.\d+)?\s*TB',
334
+ r'\d+(?:\.\d+)?\s*GB',
335
+ r'\d+(?:,\d{3})*\s*square\s+meters',
336
+ r'\d+(?:\.\d+)?\%\s*efficiency',
337
+ r'FICO\s+score:\s*\d{3}',
338
+ r'\d+(?:\.\d+)?\s*(?:bar|psi)',
339
+ r'\d+(?:\.\d+)?\s*°[CF]',
340
+ r'\d+(?:\.\d+)?\s*(?:rpm|m/s)'
341
+ ],
342
+
343
+ 'ACRONYMS_ABBREVIATIONS': [
344
+ r'\b(?:HVAC|IT|HSE|BOQ|LC|COB)\b',
345
+ r'\b(?:YTD|NNN|EIN|SSN|FICO)\b',
346
+ r'\bIP\s+Address\b',
347
+ r'\bMAC\s+Address\b',
348
+ r'\bURL\b',
349
+ r'\b(?:LLC|Corp|Inc|Ltd)\b',
350
+ r'\b(?:PST|GMT|UTC|EST)\b',
351
+ r'\b(?:CO2|NOx|pH|UV)\b',
352
+ r'\b(?:SCADA|PLC|HMI)\b',
353
+ r'\b(?:GDP|CPI|ROI|NPV)\b',
354
+ r'\b(?:FOB|CIF|DDP)\b',
355
+ r'\b(?:ABA|SWIFT|IBAN)\b'
356
+ ],
357
+
358
+ 'COMPANY': [
359
+ r'شرکت(?=\s+در|\s+که|\s+با|\s+را|\s+به|\s+طی)',
360
+ r'([آ-یa-zA-Z\s]+)\s+شرکت',
361
+ r'این\s+شرکت(?=\s|$|،|\.)',
362
+ r'(بانک\s+[آ-یa-zA-Z\s]+)',
363
+ r'([A-Z][a-zA-Z\s]+(?:Inc|Corp|Corporation|Company|Ltd|Limited|LLC))'
364
+ ],
365
+
366
+ 'BUSINESS_TERMS': [
367
+ r'تحلیل\s+عملکرد',
368
+ r'گزارش\s+(?:فعالیت|عملکرد)\s+ماهانه',
369
+ r'وضعیت\s+فروش',
370
+ r'تولید\s+پایدار',
371
+ r'سهم\s+بازار',
372
+ r'صادرات\s+هدفمند',
373
+ r'بهره‌وری',
374
+ r'ظرفیت‌های\s+داخلی',
375
+ r'شرکت‌های\s+پیشرو',
376
+ r'صنعت\s+پتروشیمی',
377
+ r'سرمایه‌گذاران\s+بنیادی',
378
+ r'شاخص‌های\s+عملیاتی',
379
+ r'برنامه‌ریزی\s+مناسب',
380
+ r'واحد\s+فروش',
381
+ r'موجودی\s+انبار',
382
+ r'فاز\s+رشد\s+جدید',
383
+ r'ترکیب\s+فروش',
384
+ r'سهم\s+صادراتی',
385
+ r'روند\s+عملکرد',
386
+ r'اعداد\s+اعلام‌شده',
387
+ r'داده‌های\s+ثبت‌شده'
388
+ ],
389
+
390
+ 'PRODUCT': [
391
+ r'\b(?:VCM|PVC|PE|PP|PS|ABS|SAN|PC|PMMA|PET|PBT|PA|POM|TPU)\b',
392
+ r'پلی\s*(?:اتیلن|پروپیلن|استایرن|کربنات|متیل)',
393
+ r'\b(?:اتیلن|پروپیلن|بنزن|تولوئن|زایلن|متانول|اتانول|استون|فنول)\b',
394
+ r'\b(?:کلر|هیدروژن|اکسیژن|نیتروژن|آمونیاک|اتان|پروپان|بوتان)\b',
395
+ r'محصول(?:ات)?',
396
+ r'تولیدات\s+شرکت'
397
+ ],
398
+
399
+ 'PETROCHEMICAL': [
400
+ r'\b(?:LDPE|HDPE|LLDPE|PP|PS|EPS|ABS|SAN|PC|PMMA|PET|PBT|PA6|PA66|POM|TPU|EVA|EAA)\b',
401
+ r'(?:Ethylene\s+Vinyl\s+Acetate|Ethyl\s+Acrylate|Methyl\s+Methacrylate|Polyethylene\s+Terephthalate)'
402
+ ],
403
+
404
+ 'PERCENTAGE': [
405
+ r'\d+(?:\.\d+)?\s*درصد(?:\s+افزایش|\s+رشد|\s+کاهش|\s+بالاتر|\s+پایین‌تر)?',
406
+ r'\d+(?:\.\d+)?\s*%',
407
+ r'معادل\s+\d+(?:\.\d+)?\s*درصد',
408
+ r'حدود\s+\d+(?:\.\d+)?\s*درصد',
409
+ r'با\s+\d+(?:\.\d+)?\s*درصد\s+افزایش',
410
+ r'رشد\s+\d+(?:\.\d+)?\s*درصدی',
411
+ r'\d+(?:\.\d+)?\s*درصدی(?=\s+همراه|\s+بوده)',
412
+ r'میزان\s+رشد(?=\s+نسبت|\s+معادل)',
413
+ r'افزایش\s+قابل‌توجهی',
414
+ r'بهبود\s+نسبی',
415
+ r'\d+(?:\.\d+)?\%\s*(?:increase|decrease|growth|improvement)',
416
+ r'(?:approximately|about)\s+\d+(?:\.\d+)?\%'
417
+ ],
418
+
419
+ 'VOLUME': [
420
+ r'\d+(?:,\d{3})*\s*تن',
421
+ r'\d+(?:,\d{3})*\s*(?:کیلوگرم|لیتر|بشکه)',
422
+ r'میزان\s+\d+(?:,\d{3})*\s*تن',
423
+ r'مقدار\s+تولید',
424
+ r'حجم\s+فروش',
425
+ r'ظرفیت\s+(?:تولید|اسمی)',
426
+ r'\d+(?:,\d{3})*\s*(?:tons|kg|liters|barrels)',
427
+ r'\d+(?:,\d{3})*\s*(?:metric\s+tons|MT)',
428
+ r'\d+(?:,\d{3})*\s*(?:thousand\s+tons|KT)'
429
+ ],
430
+
431
+ 'RATIOS': [
432
+ r'نسبت\s+(?:فروش|تولید)\s+به\s+[آ-ی\s]+',
433
+ r'\d+(?:\.\d+)?\s*نزدیک',
434
+ r'برابر\s+با\s+\d+(?:\.\d+)?',
435
+ r'معادل\s+\d+(?:\.\d+)?',
436
+ r'میزان\s+(?:رشد|افزایش)',
437
+ r'شاخص\s+(?:مهم|عملیاتی)',
438
+ r'\d+(?:\.\d+)?\s*درصد\s+کل\s+تولید'
439
+ ],
440
+
441
+ 'PHONE': [
442
+ r'(?:تلفن[\s:]*)?(?:شماره[\s:]*)?(?:0)?(?:[۰-۹0-9]{2,3}[-\s]?)?[۰-۹0-9]{7,8}',
443
+ r'(?:تماس[\s:]*)?(?:شماره[\s:]*)?(?:با[\s]*)?(?:0)?(?:[۰-۹0-9]{2,3}[-\s]?)?[۰-۹0-9]{7,8}',
444
+ r'(?:موبایل[\s:]*)?(?:شماره[\s:]*)?(?:0)?9[۰-۹0-9]{9}',
445
+ r'[۰-۹0-9]{3,4}[-\s][۰-۹0-9]{7,8}',
446
+ r'[۰-۹0-9]{11}(?!\d)',
447
+ r'(?:\+98|0098)?[۰-۹0-9]{10}',
448
+ r'[۰-۹0-9]{3,4}[-\s]?[۰-۹0-9]{3,4}[-\s]?[۰-۹0-9]{3,4}',
449
+ r'\+[0-9]{1,3}-[0-9]{3}-[0-9]{3}-[0-9]{4}(?:\s+ext\.\s+[0-9]{3,4})?',
450
+ r'\([0-9]{3}\)\s+[0-9]{3}-[0-9]{4}'
451
+ ],
452
+
453
+ 'EMAIL': [
454
+ r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
455
+ r'ایمیل[\s:]*[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
456
+ r'email[\s:]*[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
457
+ r'نشانی[\s]*الکترونیکی[\s:]*[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
458
+ r'آدرس[\s]*ایمیل[\s:]*[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
459
+ r'facility\.manager@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
460
+ ]
461
+ }
462
+
463
+ def anonymize_text(self, original_text, lang='fa', selected_categories=None):
464
+ """گام 1: ناشناس‌سازی متن با الگوهای انتخاب شده - نسخه Lightweight"""
465
+ try:
466
+ if not original_text or not original_text.strip():
467
+ return "❌ Please enter input text!" if lang == 'en' else "❌ لطفاً متن ورودی را وارد کنید!"
468
+
469
+ # ریست متغیرها
470
+ self.mapping_table = {}
471
+ self.counters = {key: 0 for key in self.counters.keys()}
472
+
473
+ anonymized = original_text
474
+ found_entities = set()
475
+
476
+ # تشخیص زبان
477
+ detected_lang = self.detect_language(original_text)
478
+ logger.info(f"Detected language: {detected_lang}")
479
+
480
+ # استخراج با الگوهای Regex - Lightweight mode
481
+ all_patterns = self.get_comprehensive_patterns()
482
+
483
+ # فیلتر کردن الگوها بر اساس انتخاب کاربر
484
+ if selected_categories:
485
+ selected_pattern_types = self.get_selected_patterns(selected_categories, lang)
486
+ patterns = {k: v for k, v in all_patterns.items() if k in selected_pattern_types}
487
+ logger.info(f"📋 Using selected pattern categories: {len(patterns)} types")
488
+ else:
489
+ patterns = all_patterns
490
+ logger.info("📋 Using all available pattern categories")
491
+
492
+ # پردازش patterns
493
+ logger.info("🔍 Running lightweight regex extraction...")
494
+
495
+ processed_entities = set()
496
+
497
+ # اولویت‌بندی دسته‌ها
498
+ priority_order = [
499
+ 'ID_NUMBER', 'EMAIL', 'PHONE', 'ACCOUNT', 'TECHNICAL_CODES',
500
+ 'NETWORK_ADDRESSES', 'INTERNATIONAL_CURRENCIES', 'AMOUNT',
501
+ 'TECHNICAL_UNITS', 'ACRONYMS_ABBREVIATIONS', 'ADVANCED_DATE_FORMATS',
502
+ 'TIME_RANGES', 'COMPLEX_ADDRESSES', 'MIXED_NAMES', 'ENGLISH_TITLES',
503
+ 'STOCK_SYMBOL', 'COMPANY', 'PERSON', 'PERCENTAGE', 'VOLUME',
504
+ 'RATIOS', 'LOCATION', 'DATE', 'FINANCIAL_TERMS', 'BUSINESS_TERMS',
505
+ 'PRODUCT', 'PETROCHEMICAL'
506
+ ]
507
+
508
+ for category in priority_order:
509
+ if category in patterns:
510
+ pattern_list = patterns[category]
511
+ for pattern in pattern_list:
512
+ try:
513
+ matches = re.finditer(pattern, original_text, re.IGNORECASE | re.MULTILINE)
514
+ for match in matches:
515
+ if match.groups():
516
+ item = match.group(1).strip()
517
+ full_match = match.group(0).strip()
518
+ else:
519
+ item = match.group(0).strip()
520
+ full_match = item
521
+
522
+ # بررسی تداخل
523
+ overlaps = False
524
+ match_start, match_end = match.span()
525
+
526
+ for proc_start, proc_end in processed_entities:
527
+ if not (match_end <= proc_start or match_start >= proc_end):
528
+ overlaps = True
529
+ break
530
+
531
+ if (not overlaps and
532
+ full_match not in found_entities and
533
+ full_match not in self.mapping_table and
534
+ len(full_match) >= 2):
535
+
536
+ self.counters[category] += 1
537
+ code = f"{category}_{self.counters[category]:03d}_REGEX"
538
+ self.mapping_table[full_match] = code
539
+ found_entities.add(full_match)
540
+ processed_entities.add((match_start, match_end))
541
+ except re.error as e:
542
+ logger.error(f"Regex error in pattern {pattern}: {e}")
543
+ continue
544
+
545
+ # جایگزینی در متن
546
+ sorted_items = sorted(self.mapping_table.items(), key=lambda x: len(x[0]), reverse=True)
547
+ for original_item, code in sorted_items:
548
+ anonymized = anonymized.replace(original_item, code)
549
+
550
+ logger.info(f"✅ Lightweight anonymization completed. Found {len(self.mapping_table)} entities.")
551
+ return anonymized
552
+
553
+ except Exception as e:
554
+ logger.error(f"Anonymization error: {e}")
555
+ return f"❌ Error in anonymization: {str(e)}" if lang == 'en' else f"❌ خطا در ناشناس‌سازی: {str(e)}"
556
+
557
+ def send_to_chatgpt(self, anonymized_text, lang='fa'):
558
+ """گام 2: ارسال به ChatGPT"""
559
+ try:
560
+ if not anonymized_text or not anonymized_text.strip():
561
+ return "❌ Anonymized text is empty!" if lang == 'en' else "❌ متن ناشناس‌شده خالی است!"
562
+
563
+ if not self.api_key:
564
+ return "❌ API Key not configured! Please set OPENAI_API_KEY environment variable." if lang == 'en' else "❌ کلید API تنظیم نشده است!"
565
+
566
+ system_msg = "You are a professional analyst. Answer questions accurately." if lang == 'en' else "شما یک تحلیلگر حرفه‌ای هستید. به سوالات با دقت پاسخ دهید."
567
+
568
+ headers = {
569
+ "Authorization": f"Bearer {self.api_key}",
570
+ "Content-Type": "application/json"
571
+ }
572
+
573
+ data = {
574
+ "model": "gpt-4o-mini",
575
+ "messages": [
576
+ {"role": "system", "content": system_msg},
577
+ {"role": "user", "content": anonymized_text}
578
+ ],
579
+ "max_tokens": 2000,
580
+ "temperature": 0.7
581
+ }
582
+
583
+ response = requests.post(
584
+ "https://api.openai.com/v1/chat/completions",
585
+ headers=headers,
586
+ json=data,
587
+ timeout=30
588
+ )
589
+
590
+ if response.status_code == 200:
591
+ result = response.json()
592
+ return result['choices'][0]['message']['content']
593
+ else:
594
+ error_data = response.json() if response.content else {}
595
+ error_message = error_data.get('error', {}).get('message', response.text)
596
+ return f"❌ API Error: {error_message}"
597
+
598
+ except Exception as e:
599
+ return f"❌ Error connecting to ChatGPT: {str(e)}" if lang == 'en' else f"❌ خطا در ارتباط با ChatGPT: {str(e)}"
600
+
601
+ def deanonymize_response(self, gpt_response, lang='fa'):
602
+ """گام 3: بازگردانی"""
603
+ try:
604
+ if not gpt_response or not gpt_response.strip():
605
+ return "❌ ChatGPT response is empty!" if lang == 'en' else "❌ پاسخ ChatGPT خالی است!"
606
+
607
+ if not self.mapping_table:
608
+ return "❌ Mapping table is empty!" if lang == 'en' else "❌ جدول نگاشت خالی است!"
609
+
610
+ final_result = gpt_response
611
+ reverse_mapping = {code: original for original, code in self.mapping_table.items()}
612
+
613
+ sorted_codes = sorted(reverse_mapping.items(), key=lambda x: len(x[0]), reverse=True)
614
+ for code, original in sorted_codes:
615
+ final_result = final_result.replace(code, original)
616
+
617
+ return final_result
618
+
619
+ except Exception as e:
620
+ return f"❌ Deanonymization error: {str(e)}" if lang == 'en' else f"❌ خطا در بازگردانی: {str(e)}"
621
+
622
+ def get_model_status(self):
623
+ """وضعیت سیستم"""
624
+ status = "🚀 **Lightweight Anonymization System Status (No Dependencies):**\n\n"
625
+
626
+ status += "• **Mode**: Pure Regex Processing (No torch/transformers required)\n"
627
+ status += "• **Performance**: High-speed lightweight processing\n"
628
+ status += "• **Compatibility**: Works on any Python environment\n"
629
+ status += "• **Memory Usage**: Minimal (< 100MB)\n"
630
+
631
+ status += f"\n🎯 **Available Pattern Categories:**"
632
+ for cat_key, cat_info in self.pattern_categories.items():
633
+ icon = cat_info['icon']
634
+ name_fa = cat_info['name_fa']
635
+ pattern_count = len(cat_info['patterns'])
636
+ status += f"\n {icon} {name_fa}: {pattern_count} patterns"
637
+
638
+ status += f"\n\n✨ **Lightweight Features:**"
639
+ status += f"\n 🎯 User-controlled category selection"
640
+ status += f"\n 🛡️ High-precision regex patterns (221 total)"
641
+ status += f"\n 📊 Efficient targeted processing"
642
+ status += f"\n ⚡ Zero external model dependencies"
643
+ status += f"\n 🔥 Works perfectly in HuggingFace Spaces"
644
+
645
+ status += f"\n\n💡 **Advantages of Lightweight Mode:**"
646
+ status += f"\n ✅ No dependency issues"
647
+ status += f"\n ✅ Fast processing speed"
648
+ status += f"\n ✅ Low memory usage"
649
+ status += f"\n ✅ Consistent results"
650
+ status += f"\n ✅ Easy deployment anywhere"
651
+
652
+ return status
653
+
654
+ # ایجاد instance
655
+ anonymizer = LightweightDataAnonymizer()
656
+
657
+ def process_all_steps(input_text, language, selected_categories):
658
+ """پردازش خودکار تمام مراحل - نسخه Lightweight"""
659
+ lang = 'en' if language == 'English' else 'fa'
660
+
661
+ if not input_text.strip():
662
+ error_msg = "❌ Please enter input text!" if lang == 'en' else "❌ لطفاً متن ورودی را وارد کنید!"
663
+ return error_msg, "", "", ""
664
+
665
+ try:
666
+ start_time = time.time()
667
+
668
+ anonymized_text = anonymizer.anonymize_text(input_text, lang, selected_categories)
669
+ if anonymized_text.startswith("❌"):
670
+ return anonymized_text, "", "", ""
671
+
672
+ gpt_response = anonymizer.send_to_chatgpt(anonymized_text, lang)
673
+ if gpt_response.startswith("❌"):
674
+ entities_found = len(anonymizer.mapping_table)
675
+
676
+ selected_count = len(selected_categories) if selected_categories else 0
677
+
678
+ success_msg = (f"✅ Lightweight anonymization completed successfully!\n"
679
+ f"📋 Selected categories: {selected_count} | 🔍 Pure Regex Processing\n"
680
+ f"📊 Total protected entities: {entities_found} | ⚡ High-speed lightweight mode")
681
+ return success_msg, anonymized_text, gpt_response, ""
682
+
683
+ final_result = anonymizer.deanonymize_response(gpt_response, lang)
684
+
685
+ total_time = time.time() - start_time
686
+ entities_found = len(anonymizer.mapping_table)
687
+
688
+ selected_count = len(selected_categories) if selected_categories else 8
689
+
690
+ success_msg = (f"🎉 Complete lightweight anonymization & restoration successful!\n"
691
+ f"🔧 Method: Pure Regex Processing | 📋 Categories: {selected_count}/8\n"
692
+ f"📊 Total: {entities_found} entities | ⏱️ Time: {total_time:.2f}s\n"
693
+ f"⚡ Zero dependencies - Maximum compatibility!")
694
+
695
+ return success_msg, anonymized_text, gpt_response, final_result
696
+
697
+ except Exception as e:
698
+ error_msg = f"❌ Processing error: {str(e)}" if lang == 'en' else f"❌ خطا در پردازش: {str(e)}"
699
+ return error_msg, "", "", ""
700
+
701
+ def get_mapping_table(language):
702
+ """نمایش جدول نگاشت"""
703
+ lang = 'en' if language == 'English' else 'fa'
704
+
705
+ if not anonymizer.mapping_table:
706
+ return "❌ Mapping table is empty!" if lang == 'en' else "❌ جدول نگاشت خالی است!"
707
+
708
+ result = "📋 **Lightweight Mapping Table (Pure Regex):**\n\n"
709
+
710
+ # نمایش آمار کلی
711
+ result += f"📊 **Statistics**: {len(anonymizer.mapping_table)} total entities\n"
712
+ result += f"🔍 **Method**: Pure Regex Processing (221 patterns)\n"
713
+ result += f"⚡ **Mode**: Lightweight (No external dependencies)\n\n"
714
+
715
+ # دسته‌بندی نتایج
716
+ category_stats = {}
717
+ for original, code in anonymizer.mapping_table.items():
718
+ category = code.split('_')[0]
719
+ if category not in category_stats:
720
+ category_stats[category] = []
721
+ category_stats[category].append((original, code))
722
+
723
+ # نمایش نتایج بر اساس دسته‌بندی
724
+ for category, items in category_stats.items():
725
+ if len(items) > 0:
726
+ result += f"🔍 **{category}** ({len(items)} items):\n"
727
+ for original, code in items[:3]: # نمایش 3 نمونه اول
728
+ result += f" • `{original}` → `{code}`\n"
729
+ if len(items) > 3:
730
+ result += f" ... و {len(items) - 3} مورد دیگر\n"
731
+ result += "\n"
732
+
733
+ result += "✨ **Lightweight System**: Maximum efficiency with minimal dependencies!"
734
+
735
+ return result
736
+
737
+ def clear_all():
738
+ """پاک کردن همه"""
739
+ anonymizer.mapping_table = {}
740
+ anonymizer.counters = {key: 0 for key in anonymizer.counters.keys()}
741
+ return "", "", "", "", ""
742
+
743
+ def update_ui_text(language):
744
+ """به‌روزرسانی متن‌های رابط کاربری"""
745
+ if language == 'English':
746
+ return {
747
+ 'title': 'Lightweight High-Precision Data Anonymization System',
748
+ 'step1': 'Input Text & Category Selection',
749
+ 'step2': 'Anonymized Text',
750
+ 'step3': 'Raw ChatGPT Response',
751
+ 'step4': 'Final Restored Response',
752
+ 'input_placeholder': 'Enter your original text here...\nExample: Company reports, person names, financial amounts, phone numbers, emails, IBAN codes, bank accounts, etc.\n\n✨ Lightweight system with category-based control!',
753
+ 'process_btn': 'Process with Selected Categories',
754
+ 'clear_btn': 'Clear All',
755
+ 'mapping_btn': 'Show Lightweight Mapping Table',
756
+ 'status_btn': 'Show Lightweight System Status',
757
+ 'categories_label': 'Select Pattern Categories:',
758
+ 'direction': 'ltr'
759
+ }
760
+ else:
761
+ return {
762
+ 'title': 'سیستم ناشناس‌سازی سبک و دقیق داده‌ها',
763
+ 'step1': 'متن ورودی و انتخاب دسته‌بندی',
764
+ 'step2': 'متن ناشناس‌شده',
765
+ 'step3': 'پاسخ خام ChatGPT',
766
+ 'step4': 'پاسخ نهایی بازگردانده شده',
767
+ 'input_placeholder': 'متن اصلی خود را اینجا وارد کنید...\nمثال: گزارش‌های شرکت، نام اشخاص، مبالغ مالی، شماره تلفن، ایمیل، شماره شبا، حساب بانکی و غیره\n\n✨ سیستم سبک با کنترل دسته‌بندی!',
768
+ 'process_btn': 'پردازش با دسته‌بندی‌های انتخاب شده',
769
+ 'clear_btn': 'پاک کردن همه',
770
+ 'mapping_btn': 'نمایش جدول نگاشت سبک',
771
+ 'status_btn': 'نمایش وضعیت سیستم سبک',
772
+ 'categories_label': 'انتخاب دسته‌بندی‌های الگو:',
773
+ 'direction': 'rtl'
774
+ }
775
+
776
+ def update_interface(language):
777
+ """تغییر رابط کاربری بر اساس زبان"""
778
+ ui_text = update_ui_text(language)
779
+ is_english = (language == 'English')
780
+
781
+ # تغییر direction برای workflow
782
+ workflow_css = "workflow ltr" if is_english else "workflow rtl"
783
+
784
+ # دریافت دسته‌بندی‌ها بر اساس زبان
785
+ category_choices = anonymizer.get_category_choices('en' if is_english else 'fa')
786
+
787
+ return [
788
+ gr.update(value=f"<h1 style='text-align: center; color: #FFD700; font-size: 3.5em; font-weight: bold; text-shadow: 3px 3px 6px rgba(0,0,0,0.5); margin: 20px 0; background: linear-gradient(45deg, #FFD700, #FFA500); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;'>📊 {ui_text['title']}</h1>"),
789
+ gr.update(value=f"<h2 style='direction: {ui_text['direction']};'>🔍 {ui_text['step1']}</h2>"),
790
+ gr.update(placeholder=ui_text['input_placeholder'], rtl=not is_english),
791
+ gr.update(value=f"🚀 {ui_text['process_btn']}"),
792
+ gr.update(value=f"🗑️ {ui_text['clear_btn']}"),
793
+ gr.update(rtl=not is_english),
794
+ gr.update(value=f"<h2 style='direction: {ui_text['direction']};'>🎭 {ui_text['step2']}</h2>"),
795
+ gr.update(rtl=not is_english),
796
+ gr.update(value=f"<h2 style='direction: {ui_text['direction']};'>🤖 {ui_text['step3']}</h2>"),
797
+ gr.update(rtl=not is_english),
798
+ gr.update(value=f"<h2 style='direction: {ui_text['direction']};'>✅ {ui_text['step4']}</h2>"),
799
+ gr.update(rtl=not is_english),
800
+ gr.update(value=f"📋 {ui_text['mapping_btn']}"),
801
+ gr.update(value=f"📊 {ui_text['status_btn']}"),
802
+ gr.update(rtl=not is_english),
803
+ gr.update(elem_classes=workflow_css),
804
+ gr.update(label=ui_text['categories_label'], choices=category_choices, value=category_choices) # انتخاب همه به طور پیش‌فرض
805
+ ]
806
+
807
+ # CSS اصلاح ش��ه برای تراز‌بندی عمودی مناسب
808
+ custom_css = """
809
+ body, .gradio-container {
810
+ font-family: 'Segoe UI', Tahoma, Arial, sans-serif !important;
811
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
812
+ min-height: 100vh !important;
813
+ padding: 20px !important;
814
+ }
815
+
816
+ .rtl {
817
+ direction: rtl !important;
818
+ text-align: right !important;
819
+ }
820
+
821
+ .ltr {
822
+ direction: ltr !important;
823
+ text-align: left !important;
824
+ }
825
+
826
+ .workflow {
827
+ display: grid !important;
828
+ grid-template-columns: 1fr 1fr 1fr 1fr !important;
829
+ gap: 25px !important;
830
+ padding: 30px !important;
831
+ align-items: start !important;
832
+ align-content: start !important;
833
+ grid-auto-rows: auto !important;
834
+ }
835
+
836
+ .workflow > * {
837
+ align-self: start !important;
838
+ vertical-align: top !important;
839
+ margin-top: 0 !important;
840
+ }
841
+
842
+ .workflow .gradio-column,
843
+ .workflow-column {
844
+ display: flex !important;
845
+ flex-direction: column !important;
846
+ align-items: stretch !important;
847
+ justify-content: flex-start !important;
848
+ height: auto !important;
849
+ min-height: 0 !important;
850
+ margin-top: 0 !important;
851
+ padding-top: 0 !important;
852
+ }
853
+
854
+ .gradio-textbox {
855
+ border-radius: 10px !important;
856
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1) !important;
857
+ flex-grow: 1 !important;
858
+ min-height: 380px !important;
859
+ max-height: 380px !important;
860
+ height: 380px !important;
861
+ }
862
+
863
+ .gradio-textbox textarea {
864
+ min-height: 350px !important;
865
+ max-height: 350px !important;
866
+ height: 350px !important;
867
+ resize: vertical !important;
868
+ }
869
+
870
+ .workflow.rtl {
871
+ direction: rtl !important;
872
+ }
873
+
874
+ .workflow.ltr {
875
+ direction: ltr !important;
876
+ }
877
+
878
+ h1, h2, h3 {
879
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3) !important;
880
+ margin-top: 0 !important;
881
+ margin-bottom: 10px !important;
882
+ padding-top: 0 !important;
883
+ line-height: 1.2 !important;
884
+ }
885
+
886
+ h2 {
887
+ min-height: 40px !important;
888
+ max-height: 40px !important;
889
+ display: flex !important;
890
+ align-items: center !important;
891
+ margin-bottom: 15px !important;
892
+ }
893
+
894
+ .status-box {
895
+ background: linear-gradient(135deg, #4CAF50, #45a049) !important;
896
+ border: 3px solid #2E7D32 !important;
897
+ border-radius: 15px !important;
898
+ padding: 15px !important;
899
+ margin: 10px 0 !important;
900
+ box-shadow: 0 8px 32px rgba(76, 175, 80, 0.3) !important;
901
+ animation: pulse 2s infinite !important;
902
+ min-height: 120px !important;
903
+ max-height: 120px !important;
904
+ }
905
+
906
+ .status-box textarea {
907
+ background: rgba(255, 255, 255, 0.95) !important;
908
+ border: none !important;
909
+ border-radius: 10px !important;
910
+ font-weight: bold !important;
911
+ font-size: 1.1em !important;
912
+ color: #1B5E20 !important;
913
+ text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8) !important;
914
+ min-height: 80px !important;
915
+ max-height: 80px !important;
916
+ }
917
+
918
+ .category-selection {
919
+ background: linear-gradient(135deg, #E3F2FD, #BBDEFB) !important;
920
+ border: 2px solid #1976D2 !important;
921
+ border-radius: 15px !important;
922
+ padding: 20px !important;
923
+ margin: 15px 0 !important;
924
+ box-shadow: 0 6px 20px rgba(25, 118, 210, 0.2) !important;
925
+ }
926
+
927
+ .gradio-checkboxgroup {
928
+ background: rgba(255, 255, 255, 0.9) !important;
929
+ border-radius: 10px !important;
930
+ padding: 15px !important;
931
+ margin: 10px 0 !important;
932
+ }
933
+
934
+ @keyframes pulse {
935
+ 0% { box-shadow: 0 8px 32px rgba(76, 175, 80, 0.3); }
936
+ 50% { box-shadow: 0 8px 40px rgba(76, 175, 80, 0.6); }
937
+ 100% { box-shadow: 0 8px 32px rgba(76, 175, 80, 0.3); }
938
+ }
939
+
940
+ .gradio-button {
941
+ border-radius: 25px !important;
942
+ font-weight: bold !important;
943
+ transition: all 0.3s ease !important;
944
+ margin: 5px 0 !important;
945
+ min-height: 50px !important;
946
+ max-height: 50px !important;
947
+ }
948
+
949
+ .gradio-button:hover {
950
+ transform: translateY(-2px) !important;
951
+ box-shadow: 0 6px 20px rgba(0,0,0,0.2) !important;
952
+ }
953
+
954
+ h1 {
955
+ background: linear-gradient(45deg, #FFD700, #FFA500) !important;
956
+ -webkit-background-clip: text !important;
957
+ -webkit-text-fill-color: transparent !important;
958
+ background-clip: text !important;
959
+ min-height: 80px !important;
960
+ }
961
+
962
+ @media (max-width: 1200px) {
963
+ .workflow {
964
+ grid-template-columns: 1fr 1fr !important;
965
+ gap: 20px !important;
966
+ }
967
+ }
968
+
969
+ @media (max-width: 768px) {
970
+ .workflow {
971
+ grid-template-columns: 1fr !important;
972
+ gap: 15px !important;
973
+ }
974
+
975
+ .gradio-textbox {
976
+ min-height: 300px !important;
977
+ max-height: 300px !important;
978
+ height: 300px !important;
979
+ }
980
+ }
981
+
982
+ [data-testid="textbox"]:dir(rtl) {
983
+ text-align: right !important;
984
+ direction: rtl !important;
985
+ }
986
+
987
+ [data-testid="textbox"]:dir(ltr) {
988
+ text-align: left !important;
989
+ direction: ltr !important;
990
+ }
991
+
992
+ .gradio-container .gradio-column {
993
+ align-self: start !important;
994
+ vertical-align: top !important;
995
+ }
996
+
997
+ .gradio-container .gradio-row {
998
+ align-items: flex-start !important;
999
+ }
1000
+
1001
+ * {
1002
+ box-sizing: border-box !important;
1003
+ }
1004
+
1005
+ .gradio-container {
1006
+ align-items: start !important;
1007
+ justify-content: start !important;
1008
+ }
1009
+ """
1010
+
1011
+ # رابط کاربری Gradio با ترازبندی اصلاح شده مطابق enhanced_anonymization_selective.py
1012
+ with gr.Blocks(title="⚡ Lightweight Anonymization System", theme=gr.themes.Soft(), css=custom_css) as app:
1013
+
1014
+ with gr.Row():
1015
+ language_selector = gr.Radio(
1016
+ choices=["فارسی", "English"],
1017
+ value="فارسی",
1018
+ label="Language / زبان",
1019
+ interactive=True
1020
+ )
1021
+
1022
+ with gr.Column():
1023
+ title = gr.HTML("<h1 style='text-align: center; color: #FFD700; font-size: 3.5em; font-weight: bold; text-shadow: 3px 3px 6px rgba(0,0,0,0.5); margin: 20px 0; background: linear-gradient(45deg, #FFD700, #FFA500); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;'>📊 سیستم ناشناس‌سازی سبک و دقیق داده‌ها</h1>")
1024
+
1025
+ # بخش انتخاب دسته‌بندی‌ها
1026
+ with gr.Row(elem_classes="category-selection"):
1027
+ with gr.Column():
1028
+ category_title = gr.HTML("<h3 style='text-align: center; color: #1976D2; margin-bottom: 15px;'>🎯 انتخاب دسته‌بندی‌های الگوی ناشناس‌سازی</h3>")
1029
+
1030
+ pattern_categories = gr.CheckboxGroup(
1031
+ choices=anonymizer.get_category_choices('fa'),
1032
+ value=anonymizer.get_category_choices('fa'), # انتخاب همه به طور پیش‌فرض
1033
+ label="انتخاب دسته‌بندی‌های الگو:",
1034
+ interactive=True,
1035
+ elem_classes=["gradio-checkboxgroup"]
1036
+ )
1037
+
1038
+ category_info = gr.HTML("""
1039
+ <div style='background: rgba(255, 255, 255, 0.9); padding: 15px; border-radius: 10px; margin-top: 10px;'>
1040
+ <p style='margin: 0; color: #666; font-size: 0.9em; text-align: center;'>
1041
+ 💡 <strong>راهنمایی:</strong> فقط دسته‌بندی‌هایی که نیاز دارید را انتخاب کنید تا ناشناس‌سازی دقیق‌تر و سریع‌تر انجام شود
1042
+ </p>
1043
+ </div>
1044
+ """)
1045
+
1046
+ with gr.Row(elem_classes="workflow rtl") as workflow_row:
1047
+ with gr.Column(elem_classes="workflow-column"):
1048
+ step1_title = gr.HTML('<h2 style="direction: rtl;">🔍 متن ورودی و انتخاب دسته‌بندی</h2>')
1049
+
1050
+ input_text = gr.Textbox(
1051
+ lines=15,
1052
+ placeholder="متن اصلی خود را اینجا وارد کنید...\nمثال: گزارش‌های شرکت، نام اشخاص، مبالغ مالی، شماره تلفن، ایمیل، شماره شبا، حساب بانکی و غیره\n\n✨ سیستم سبک با کنترل دسته‌بندی!",
1053
+ label="",
1054
+ rtl=True
1055
+ )
1056
+
1057
+ process_btn = gr.Button("🚀 پردازش با دسته‌بندی‌های انتخاب شده", variant="primary")
1058
+ clear_btn = gr.Button("🗑️ پاک کردن همه", variant="stop")
1059
+
1060
+ status = gr.Textbox(
1061
+ label="وضعیت",
1062
+ lines=4,
1063
+ interactive=False,
1064
+ rtl=True,
1065
+ elem_classes=["status-box"]
1066
+ )
1067
+
1068
+ with gr.Column(elem_classes="workflow-column"):
1069
+ step2_title = gr.HTML('<h2 style="direction: rtl;">🎭 متن ناشناس‌شده</h2>')
1070
+
1071
+ anonymized_output = gr.Textbox(
1072
+ lines=15,
1073
+ placeholder="متن ناشناس‌شده اینجا نمایش داده می‌شود...",
1074
+ label="",
1075
+ interactive=False,
1076
+ rtl=True
1077
+ )
1078
+
1079
+ with gr.Column(elem_classes="workflow-column"):
1080
+ step3_title = gr.HTML('<h2 style="direction: rtl;">🤖 پاسخ خام ChatGPT</h2>')
1081
+
1082
+ gpt_output = gr.Textbox(
1083
+ lines=15,
1084
+ placeholder="پاسخ خام ChatGPT اینجا نمایش داده می‌شود...",
1085
+ label="",
1086
+ interactive=False,
1087
+ rtl=True
1088
+ )
1089
+
1090
+ with gr.Column(elem_classes="workflow-column"):
1091
+ step4_title = gr.HTML('<h2 style="direction: rtl;">✅ پاسخ نهایی بازگردانده شده</h2>')
1092
+
1093
+ final_output = gr.Textbox(
1094
+ lines=15,
1095
+ placeholder="پاسخ نهایی اینجا نمایش داده می‌شود...",
1096
+ label="",
1097
+ interactive=False,
1098
+ rtl=True
1099
+ )
1100
+
1101
+ with gr.Row():
1102
+ with gr.Column():
1103
+ mapping_title = gr.HTML('<h2>🗂️ جدول نگاشت سبک</h2>')
1104
+ mapping_btn = gr.Button("📋 نمایش جدول نگاشت سبک")
1105
+
1106
+ mapping_output = gr.Textbox(
1107
+ lines=15,
1108
+ label="جدول نگاشت اطلاعات",
1109
+ interactive=False,
1110
+ visible=False,
1111
+ rtl=True
1112
+ )
1113
+
1114
+ with gr.Row():
1115
+ with gr.Column():
1116
+ status_title = gr.HTML('<h2>⚙️ وضعیت سیستم و قابلیت‌ها</h2>')
1117
+ system_status_btn = gr.Button("📊 نمایش وضعیت سیستم سبک")
1118
+
1119
+ system_status_output = gr.Textbox(
1120
+ lines=20,
1121
+ label="وضعیت سیستم",
1122
+ interactive=False,
1123
+ visible=False,
1124
+ rtl=True
1125
+ )
1126
+
1127
+ # Event handlers
1128
+ language_selector.change(
1129
+ fn=update_interface,
1130
+ inputs=[language_selector],
1131
+ outputs=[title, step1_title, input_text, process_btn, clear_btn,
1132
+ status, step2_title, anonymized_output, step3_title, gpt_output,
1133
+ step4_title, final_output, mapping_btn, system_status_btn,
1134
+ mapping_output, workflow_row, pattern_categories]
1135
+ )
1136
+
1137
+ process_btn.click(
1138
+ fn=process_all_steps,
1139
+ inputs=[input_text, language_selector, pattern_categories],
1140
+ outputs=[status, anonymized_output, gpt_output, final_output]
1141
+ )
1142
+
1143
+ clear_btn.click(
1144
+ fn=clear_all,
1145
+ outputs=[input_text, anonymized_output, gpt_output, final_output, status]
1146
+ )
1147
+
1148
+ mapping_btn.click(
1149
+ fn=get_mapping_table,
1150
+ inputs=[language_selector],
1151
+ outputs=[mapping_output]
1152
+ )
1153
+
1154
+ mapping_btn.click(
1155
+ fn=lambda: gr.update(visible=True),
1156
+ outputs=[mapping_output]
1157
+ )
1158
+
1159
+ system_status_btn.click(
1160
+ fn=lambda: anonymizer.get_model_status(),
1161
+ outputs=[system_status_output]
1162
+ )
1163
+
1164
+ system_status_btn.click(
1165
+ fn=lambda: gr.update(visible=True),
1166
+ outputs=[system_status_output]
1167
+ )
1168
+
1169
+ if __name__ == "__main__":
1170
+ logger.info("⚡ Starting Lightweight Anonymization System...")
1171
+ logger.info("🔥 No torch/transformers dependencies required!")
1172
+ logger.info("✅ Ready for any environment including HuggingFace Spaces!")
1173
+
1174
+ app.launch(
1175
+ share=False,
1176
+ server_name="0.0.0.0",
1177
+ server_port=7860,
1178
+ show_error=True
1179
+ )