leilaghomashchi commited on
Commit
f1c41c6
·
verified ·
1 Parent(s): a6733f0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +229 -327
app.py CHANGED
@@ -19,21 +19,6 @@ class UniversalAnonymizer:
19
  'percent': 0
20
  }
21
  self.api_key = os.getenv("OPENAI_API_KEY", "")
22
-
23
- # لیست عبارات عمومی که نباید ناشناس‌سازی شوند
24
- self.common_phrases = [
25
- 'مجمع عمومی عادی سالیانه',
26
- 'مجمع عمومی فوق‌العاده',
27
- 'هیئت مدیره',
28
- 'سهامداران محترم',
29
- 'صورت‌های مالی',
30
- 'شرکت اصلی',
31
- 'درآمد عملیاتی',
32
- 'سود عملیاتی',
33
- 'زیان انباشته',
34
- 'محصولات گرم',
35
- 'محصولات سرد',
36
- ]
37
 
38
  def anonymize_text(self, original_text, lang='fa'):
39
  """ناشناس‌سازی جامع با تشخیص خودکار الگوها"""
@@ -47,17 +32,24 @@ class UniversalAnonymizer:
47
 
48
  anonymized = original_text
49
 
50
- # مرحله 1: درصدها (اول از همه چون ممکن است در توضیحات باشند)
51
- anonymized = self._anonymize_percentages(anonymized)
 
 
 
 
52
 
53
- # مرحله 2: مبالغ مالی (قبل از شرکت‌ها تا اعداد درون نام شرکت‌ها تغییر نکند)
54
  anonymized = self._anonymize_amounts(anonymized)
55
 
56
- # مرحله 3: نام‌های اشخاص
57
- anonymized = self._anonymize_persons(anonymized)
 
 
 
58
 
59
- # مرحله 4: نام‌های شرکت‌ها
60
- anonymized = self._anonymize_companies(anonymized)
61
 
62
  logger.info(f"✅ Anonymization completed. Found {len(self.mapping_table)} entities.")
63
  return anonymized
@@ -65,329 +57,200 @@ class UniversalAnonymizer:
65
  except Exception as e:
66
  return f"⚠ Error in anonymization: {str(e)}" if lang == 'en' else f"⚠ خطا در ناشناس‌سازی: {str(e)}"
67
 
68
- def _anonymize_percentages(self, text):
69
- """تشخیص و ناشناس‌سازی همه انواع درصدها"""
 
 
 
 
 
70
 
71
- # الگوهای جامع درصدها
72
- percent_patterns = [
73
- # درصدهای منفی
74
- r'منفی \d+\.?\d* درصد',
75
-
76
- # درصدهای با کلمه درصدی
77
- r'\d+\.?\d* درصدی',
78
-
79
- # درصدهای ساده
80
- r'\d+\.?\d* درصد',
81
-
82
- # بازه‌های درصدی
83
- r'\d+\.?\d* تا \d+\.?\d* درصد',
84
- r'\d+\.?\d*–\d+\.?\d*٪',
85
- r'\d+\.?\d*-\d+\.?\d*٪',
86
-
87
- # درصدهای با توضیحات
88
- r'بیش از \d+\.?\d* درصد',
89
- r'حدود \d+\.?\d* درصد',
90
- r'نزدیک به \d+\.?\d* درصد',
91
- r'کمتر از \d+\.?\d* درصد',
92
- r'بالای \d+\.?\d* درصد',
93
- r'زیر \d+\.?\d* درصد',
94
- r'حداقل \d+\.?\d* درصد',
95
- r'حداکثر \d+\.?\d* درصد',
96
-
97
- # نمادهای درصد
98
- r'\d+\.?\d*%',
99
- r'\d+\.?\d*٪',
100
-
101
- # درصدهای دهگانی
102
- r'\d+\.\d+ درصد',
103
-
104
- # بازه‌هایی با خط فاصله
105
- r'\d+–\d+٪',
106
  ]
107
 
108
- return self._apply_patterns(text, percent_patterns, 'percent')
 
 
 
 
 
 
 
 
 
109
 
110
  def _anonymize_amounts(self, text):
111
- """تشخیص و ناشناس‌سازی همه انواع مبالغ مالی"""
112
-
113
- # الگوهای جامع مبالغ مالی
114
  amount_patterns = [
115
- # مبالغ با پسوند "ی" - مهم: باید قبل از الگوهای بدون "ی" باشد
116
- r'\d+ میلیارد تومانی',
117
- r'\d+ میلیون تومانی',
118
- r'\d+ هزار تومانی',
119
- r'\d+\.?\d* میلیارد تومانی',
120
- r'\d+\.?\d* میلیون تومانی',
121
-
122
- # مبالغ با کاما و نقطه
123
- r'\d+,\d{3},\d{3} میلیون ریال',
124
- r'\d+\.\d{3}\.\d{3} میلیون ریال',
125
- r'\d+,\d{3} میلیارد ریال',
126
- r'\d+\.\d{3} میلیارد ریال',
127
-
128
- # مبالغ با واحد همت - با و بدون اعشار
129
- r'\d+ همت',
130
- r'\d+\.\d+ همت',
131
- r'\d+\.?\d* همت',
132
- r'بیش از \d+\.?\d* همت',
133
- r'نزدیک به \d+\.?\d* همت',
134
- r'حدود \d+\.?\d* همت',
135
- r'کمتر از \d+\.?\d* همت',
136
- r'بالغ بر \d+\.?\d* همت',
137
-
138
- # مبالغ ترکیبی با هزار
139
- r'\d+ هزار و \d+ میلیارد تومان',
140
- r'\d+ هزار و \d+ میلیارد ریال',
141
- r'\d+ هزار و \d+ میلیون تومان',
142
- r'\d+ هزار و \d+ میلیون ریال',
143
- r'\d+ هزار و \d+ دستگاه',
144
- r'\d+ هزار و \d+ تن',
145
- r'\d+ هزار و \d+ نفر',
146
-
147
- # مبالغ با توضیحات
148
- r'بیش از \d+ هزار میلیارد تومان',
149
- r'نزدیک به \d+ هزار میلیارد تومان',
150
- r'حدود \d+ هزار میلیارد تومان',
151
- r'کمتر از \d+ هزار میلیارد تومان',
152
- r'بالغ بر \d+ هزار میلیارد تومان',
153
-
154
- r'بیش از \d+ میلیارد تومان',
155
- r'نزدیک به \d+ میلیارد تومان',
156
- r'حدود \d+ میلیارد تومان',
157
- r'کمتر از \d+ میلیارد تومان',
158
- r'بالغ بر \d+ میلیارد تومان',
159
-
160
- r'بیش از \d+ میلیون تومان',
161
- r'حدود \d+ میلیون تومان',
162
- r'نزدیک به \d+ میلیون تومان',
163
-
164
- # مبالغ ارزی
165
- r'حدود \d+ میلیون دلار',
166
- r'بیش از \d+ میلیون دلار',
167
- r'نزدیک به \d+ میلیون دلار',
168
- r'\d+ میلیون دلار',
169
- r'\d+ میلیون یورو',
170
-
171
- # مبالغ خاص
172
- r'یک‌هزار میلیارد تومان',
173
- r'یکهزار میلیارد تومان',
174
- r'دویست میلیارد تومان',
175
- r'سیصد میلیارد تومان',
176
- r'چهارصد میلیارد تومان',
177
- r'پانصد میلیارد تومان',
178
-
179
- # مبالغ با کاما
180
- r'\d+,\d+ میلیارد تومان',
181
- r'\d+,\d+ میلیارد ریال',
182
- r'\d+,\d+ میلیون تومان',
183
- r'\d+,\d+ میلیون ریال',
184
- r'\d+,\d+ هزار میلیارد تومان',
185
- r'\d+,\d+ هزار میلیارد ریال',
186
 
187
  # مبالغ ساده
188
- r'\d+ میلیارد تومان',
189
- r'\d+ میلیارد ریال',
190
- r'\d+ میلیون تومان',
191
- r'\d+ میلیون ریال',
192
- r'\d+ هزار میلیارد تومان',
193
- r'\d+ هزار میلیارد ریال',
194
- r'\d+ هزارمیلیارد تومان',
195
- r'\d+ هزارمیلیون تومان',
196
- r'\d+ ریال',
197
- r'\d+ تومان',
198
-
199
- # واحدهای تولیدی و اندازه‌گیری
200
- r'\d+ هزار تن',
201
- r'\d+,\d+ هزار تن',
202
- r'\d+ میلیون تن',
203
- r'\d+ تن',
204
- r'\d+ مگا واتی',
205
- r'\d+ مگاوات',
206
- r'\d+ کیلووات',
207
- r'\d+ واحد',
208
- r'\d+ دستگاه',
209
- r'\d+ میلیون دستگاه',
210
- r'\d+ هزار دستگاه',
211
-
212
- # تعداد افراد
213
- r'\d+\.?\d* میلیون نفر',
214
- r'\d+ میلیون نفر',
215
- r'\d+ هزار نفر',
216
- r'\d+ نفر',
217
-
218
- # مبالغ اعشاری
219
- r'\d+\.\d+ میلیارد تومان',
220
- r'\d+\.\d+ میلیارد ریال',
221
- r'\d+\.\d+ میلیون تومان',
222
- r'\d+\.\d+ میلیون ریال',
223
- r'\d+\.\d+ هزار میلیارد تومان',
224
- r'\d+\.\d+ هزار میلیارد ریال',
225
- r'\d+\.\d+ همت',
226
-
227
- # بازه‌های مقداری
228
- r'\d+ تا \d+ میلیارد تومان',
229
- r'\d+ تا \d+ میلیون تومان',
230
- r'\d+ تا \d+ ماه',
231
- r'\d+ تا \d+ سال',
232
- r'\d+ الی \d+ میلیارد تومان',
233
  ]
234
 
235
- return self._apply_patterns(text, amount_patterns, 'amount')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
- def _anonymize_persons(self, text):
238
- """تشخیص و ناشناس‌سازی همه انواع نام‌های اشخاص"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
- # الگوهای جامع نام‌های اشخاص
 
 
 
241
  person_patterns = [
242
- # نام‌های خاص که در متن ذکر شده
243
- r'مهدی اخوان بهابادی',
244
-
245
- # نام‌های کامل با عناوین
246
- r'دکتر\s+[آ-ی]+\s+[آ-ی]+\s+[آ-ی]+',
247
- r'مهندس\s+[آ-ی]+\s+[آ-ی]+\s+[آ-ی]+',
248
- r'آقای\s+[آ-ی]+\s+[آ-ی]+\s+[آ-ی]+',
249
- r'خانم\s+[آ-ی]+\s+[آ-ی]+\s+[آ-ی]+',
250
-
251
- # نام‌های کامل با سید
252
- r'سید\s+[آ-ی]+\s+[آ-ی]+',
253
- r'سید\s+[آ-ی]+\s+[آ-ی]+\s+[آ-ی]+',
254
- r'سیدعلی\s+[آ-ی]+',
255
- r'سیدمحمد\s+[آ-ی]+',
256
- r'سیدحسن\s+[آ-ی]+',
257
 
258
  # نام‌هایی که با مدیرعامل همراه هستند
259
- r'[آ-ی]+\s+[آ-ی]+\s+[آ-ی]+\s*،?\s*مدیرعامل',
260
- r'[آ-ی]+\s+[آ-ی]+\s*،?\s*مدیرعامل',
261
-
262
- # نام‌هایی که با سمت همراه هستند
263
- r'[آ-ی]+\s+[آ-ی]+\s*،?\s*رئیس',
264
- r'[آ-ی]+\s+[آ-ی]+\s*،?\s*مدیر',
265
- r'[آ-ی]+\s+[آ-ی]+\s*،?\s*نایب',
266
-
267
- # نام‌های خاص (باید در آخر باشند تا اولویت کمتری داشته باشند)
268
- r'محمدرضا\s+[آ-ی]+',
269
- r'علیرضا\s+[آ-ی]+',
270
- r'محمدعلی\s+[آ-ی]+',
271
- r'حسینعلی\s+[آ-ی]+',
272
-
273
- # نام و نام خانوادگی ساده - حداقل 3 حرف برای هر کلمه
274
- r'[آ-ی]{3,}\s+[آ-ی]{3,}(?:\s+[آ-ی]{3,})?',
275
  ]
276
 
277
- return self._apply_patterns(text, person_patterns, 'person')
278
-
279
- def _anonymize_companies(self, text):
280
- """تشخیص و ناشناس‌سازی همه انواع نام‌های شرکت‌ها"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
 
282
- # الگوهای جامع نام‌های شرکت‌ها
 
 
 
283
  company_patterns = [
284
- # نام‌های خاص که در متن ذکر شده
285
- r'شرکت سرمایه گذاری پارسیان',
286
- r'شرکت سرمای��‌گذاری پارسیان',
287
- r'بانک پارسیان',
288
- r'گروه مالی پارسیان',
289
-
290
- # شرکت‌های کامل با پرانتز
291
- r'شرکت\s+[آ-ی\s]+\([آ-ی\s]+\)',
292
- r'بانک\s+[آ-ی\s]+\([آ-ی\s]+\)',
293
- r'گروه\s+[آ-ی\s]+\([آ-ی\s]+\)',
294
- r'هلدینگ\s+[آ-ی\s]+\([آ-ی\s]+\)',
295
-
296
- # شرکت‌های با کلمه شرکت
297
- r'شرکت\s+[آ-ی\s]{5,}',
298
- r'شرکت\s+گروه\s+[آ-ی\s]+',
299
- r'شرکت\s+سرمایه‌گذاری\s+[آ-ی\s]+',
300
- r'شرکت\s+سرمایه گذاری\s+[آ-ی\s]+',
301
- r'شرکت\s+بیمه\s+[آ-ی\s]+',
302
- r'شرکت\s+پتروشیمی\s+[آ-ی\s]+',
303
- r'شرکت\s+فولاد\s+[آ-ی\s]+',
304
- r'شرکت\s+پالایش\s+[آ-ی\s]+',
305
- r'شرکت\s+انرژی\s+[آ-ی\s]+',
306
- r'شرکت\s+ملی\s+[آ-ی\s]+',
307
-
308
- # بانک‌ها
309
- r'بانک\s+[آ-ی\s]{3,}',
310
- r'موسسه\s+اعتباری\s+[آ-ی\s]+',
311
- r'موسسه\s+مالی\s+[آ-ی\s]+',
312
-
313
- # گروه‌ها و هلدینگ‌ها
314
- r'گروه\s+[آ-ی\s]{3,}',
315
- r'هلدینگ\s+[آ-ی\s]{3,}',
316
- r'گروه\s+مالی\s+[آ-ی\s]+',
317
- r'گروه\s+صنعتی\s+[آ-ی\s]+',
318
-
319
- # صندوق‌ها و سازمان‌ها
320
- r'صندوق\s+[آ-ی\s]{3,}',
321
- r'سازمان\s+[آ-ی\s]{3,}',
322
- r'موسسه\s+[آ-ی\s]{3,}',
323
 
324
  # شرکت‌های خاص
325
- r'[آ-ی]+\s+خودرو',
326
- r'[آ-ی]+\s+فولاد',
327
- r'بیمه\s+[آ-ی]+',
328
- r'[آ-ی]+\s+انرژی',
329
- r'[آ-ی]+\s+پتروشیمی',
330
-
331
- # نام‌های کوتاه شرکت‌ها (باید در آخر باشند)
332
- r'همراه\s+اول',
333
- r'ایران‌خودرو',
334
- r'ایران خودرو',
335
- r'فولاد\s+مبارکه',
336
-
337
- # مجله‌ها و نشریات
338
- r'مجله\s+[آ-ی\s]+',
339
- r'نشریه\s+[آ-ی\s]+',
340
- r'روزنامه\s+[آ-ی\s]+',
341
  ]
342
 
343
- return self._apply_patterns(text, company_patterns, 'company')
344
-
345
- def _apply_patterns(self, text, patterns, category):
346
- """اعمال الگوها و جایگزینی با کدهای ناشناس"""
347
-
348
- # مرتب‌سازی الگوها بر اساس طول (طولانی‌ترین اول)
349
- patterns.sort(key=len, reverse=True)
350
 
351
- for pattern in patterns:
352
- matches = list(re.finditer(pattern, text, re.IGNORECASE))
353
-
354
- for match in matches:
355
  matched_text = match.group(0)
356
-
357
- # بررسی اینکه آیا عبارت جزو عبارات عمومی است
358
- if self._is_common_phrase(matched_text):
359
- continue
360
-
361
- # اگر قبلاً جایگزین نشده باشد
362
- if matched_text not in self.mapping_table and matched_text in text:
363
- # بررسی که کلمه کامل باشد (نه بخشی از کلمه بزرگ‌تر)
364
- if self._is_complete_word(text, match):
365
- self.counters[category] += 1
366
- code = f"{category}-{self.counters[category]:02d}"
367
- self.mapping_table[matched_text] = code
368
- text = text.replace(matched_text, code)
369
- logger.info(f"{category.title()} replaced: {matched_text} -> {code}")
370
 
371
  return text
372
-
373
- def _is_common_phrase(self, text):
374
- """بررسی اینکه آیا عبارت جزو عبارات عمومی است"""
375
- for phrase in self.common_phrases:
376
- if phrase.lower() in text.lower():
377
- return True
378
- return False
379
-
380
- def _is_complete_word(self, text, match):
381
- """بررسی اینکه آیا کلمه کامل است یا بخشی از کلمه بزرگ‌تر"""
382
- start, end = match.span()
383
-
384
- # بررسی کاراکتر قبل و بعد
385
- if start > 0 and text[start-1].isalnum():
386
- return False
387
- if end < len(text) and text[end].isalnum():
388
- return False
389
-
390
- return True
391
 
392
  def send_to_chatgpt(self, anonymized_text, lang='fa'):
393
  """ارسال به ChatGPT"""
@@ -791,18 +654,57 @@ with gr.Blocks(title="📊 Universal Anonymization System", theme=gr.themes.Soft
791
  )
792
 
793
  if __name__ == "__main__":
794
- # تست سیستم با نمونه‌های ذکر شده
795
- test_text = """مهدی اخوان بهابادی در مجمع عمومی عادی سالیانه اعلام کرد درآمد عملیاتی شرکت اصلی به 178 میلیارد تومانی رسیده است.
796
- در خودروسازان حالا از مرز 305 همت عبور کرده و در سال گذشته سود عملیاتی داشته اما زیان انباشته این شرکت 7.6 همت زیاد شده است.
797
- تولید محصولات گرم این شرکت به 1000 هزار تن و محصولات سرد به 1378 هزار تن رسید.
798
- شرکت سرمایه گذاری پارسیان سود خوبی را نشان داد. بانک پارسیان و گروه مالی پارسیان هم عملکرد مثبتی داشتند."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
799
 
800
  anonymizer_test = UniversalAnonymizer()
801
- result = anonymizer_test.anonymize_text(test_text)
802
- print("نتیجه ناشناس‌سازی جامع:")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
803
  print(result)
804
- print("\nجدول نگاشت:")
 
 
805
  for original, code in anonymizer_test.mapping_table.items():
806
- print(f"{original} -> {code}")
 
 
 
 
807
 
808
  app.launch()
 
19
  'percent': 0
20
  }
21
  self.api_key = os.getenv("OPENAI_API_KEY", "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  def anonymize_text(self, original_text, lang='fa'):
24
  """ناشناس‌سازی جامع با تشخیص خودکار الگوها"""
 
32
 
33
  anonymized = original_text
34
 
35
+ # ترتیب مهم است: از خاص به عام
36
+ # مرحله 1: نام‌های خاص اشخاص (اول از همه)
37
+ anonymized = self._anonymize_specific_persons(anonymized)
38
+
39
+ # مرحله 2: نام‌های خاص شرکت‌ها
40
+ anonymized = self._anonymize_specific_companies(anonymized)
41
 
42
+ # مرحله 3: مبالغ مالی
43
  anonymized = self._anonymize_amounts(anonymized)
44
 
45
+ # مرحله 4: درصدها
46
+ anonymized = self._anonymize_percentages(anonymized)
47
+
48
+ # مرحله 5: نام‌های عمومی اشخاص
49
+ anonymized = self._anonymize_general_persons(anonymized)
50
 
51
+ # مرحله 6: نام‌های عمومی شرکت‌ها
52
+ anonymized = self._anonymize_general_companies(anonymized)
53
 
54
  logger.info(f"✅ Anonymization completed. Found {len(self.mapping_table)} entities.")
55
  return anonymized
 
57
  except Exception as e:
58
  return f"⚠ Error in anonymization: {str(e)}" if lang == 'en' else f"⚠ خطا در ناشناس‌سازی: {str(e)}"
59
 
60
+ def _anonymize_specific_persons(self, text):
61
+ """ناشناس‌سازی نام‌های خاص اشخاص"""
62
+ # نام‌های خاص که حتماً باید ناشناس شوند
63
+ specific_names = [
64
+ 'مهدی اخوان بهابادی',
65
+ # می‌توانید نام‌های خاص دیگر را اینجا اضافه کنید
66
+ ]
67
 
68
+ for name in specific_names:
69
+ if name in text:
70
+ if name not in self.mapping_table:
71
+ self.counters['person'] += 1
72
+ code = f"person-{self.counters['person']:02d}"
73
+ self.mapping_table[name] = code
74
+ text = text.replace(name, code)
75
+ logger.info(f"Person replaced: {name} -> {code}")
76
+
77
+ return text
78
+
79
+ def _anonymize_specific_companies(self, text):
80
+ """ناشناس‌سازی نام‌های خاص شرکت‌ها"""
81
+ # نام‌های خاص شرکت‌ها
82
+ specific_companies = [
83
+ 'شرکت سرمایه گذاری پارسیان',
84
+ 'شرکت سرمایه‌گذاری پارسیان',
85
+ 'بانک پارسیان',
86
+ 'گروه مالی پارسیان',
87
+ # می‌توانید نام‌های خاص دیگر را اینجا اضافه کنید
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  ]
89
 
90
+ for company in specific_companies:
91
+ if company in text:
92
+ if company not in self.mapping_table:
93
+ self.counters['company'] += 1
94
+ code = f"company-{self.counters['company']:02d}"
95
+ self.mapping_table[company] = code
96
+ text = text.replace(company, code)
97
+ logger.info(f"Company replaced: {company} -> {code}")
98
+
99
+ return text
100
 
101
  def _anonymize_amounts(self, text):
102
+ """تشخیص و ناشناس‌سازی مبالغ مالی"""
103
+ # الگوهای مبالغ - ترتیب از خاص به عام
 
104
  amount_patterns = [
105
+ # مبالغ با "تومانی" در انتها
106
+ (r'(\d+(?:\.\d+)?)\s+(میلیارد|میلیون|��زار)\s+تومانی', 'amount'),
107
+
108
+ # مبالغ با همت
109
+ (r'(\d+(?:\.\d+)?)\s+همت', 'amount'),
110
+
111
+ # مبالغ با هزار تن
112
+ (r'(\d+(?:\.\d+)?)\s+هزار\s+تن', 'amount'),
113
+
114
+ # مبالغ عادی با میلیارد/میلیون
115
+ (r'(\d+(?:\.\d+)?)\s+(هزار\s+)?میلیارد\s+(تومان|ریال)', 'amount'),
116
+ (r'(\d+(?:\.\d+)?)\s+(هزار\s+)?میلیون\s+(تومان|ریال)', 'amount'),
117
+ (r'(\d+(?:\.\d+)?)\s+هزار\s+(تومان|ریال)', 'amount'),
118
+
119
+ # مبالغ با عبارات اضافی
120
+ (r'بیش از\s+(\d+(?:\.\d+)?)\s+(میلیارد|میلیون|هزار)\s+(تومان|ریال)', 'amount'),
121
+ (r'حدود\s+(\d+(?:\.\d+)?)\s+(میلیارد|میلیون|هزار)\s+(تومان|ریال)', 'amount'),
122
+ (r'نزدیک به\s+(\d+(?:\.\d+)?)\s+(میلیارد|میلیون|هزار)\s+(تومان|ریال)', 'amount'),
123
+
124
+ # واحدهای دیگر
125
+ (r'(\d+(?:\.\d+)?)\s+(تن|کیلوگرم|متر|لیتر|دستگاه|واحد|نفر)', 'amount'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
  # مبالغ ساده
128
+ (r'(\d+(?:\.\d+)?)\s+(تومان|ریال)(?!\w)', 'amount'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  ]
130
 
131
+ for pattern, category in amount_patterns:
132
+ matches = list(re.finditer(pattern, text))
133
+ # از آخر به اول جایگزین می‌کنیم تا موقعیت‌ها تغییر نکنند
134
+ for match in reversed(matches):
135
+ matched_text = match.group(0)
136
+ if matched_text not in self.mapping_table:
137
+ self.counters[category] += 1
138
+ code = f"{category}-{self.counters[category]:02d}"
139
+ self.mapping_table[matched_text] = code
140
+ # جایگزینی دقیق با استفاده از موقعیت
141
+ start, end = match.span()
142
+ text = text[:start] + code + text[end:]
143
+ logger.info(f"Amount replaced: {matched_text} -> {code}")
144
+
145
+ return text
146
 
147
+ def _anonymize_percentages(self, text):
148
+ """تشخیص و ناشناس‌سازی درصدها"""
149
+ percent_patterns = [
150
+ (r'(\d+(?:\.\d+)?)\s+درصدی', 'percent'),
151
+ (r'(\d+(?:\.\d+)?)\s+درصد', 'percent'),
152
+ (r'(\d+(?:\.\d+)?)\s*%', 'percent'),
153
+ (r'(\d+(?:\.\d+)?)\s*٪', 'percent'),
154
+ (r'منفی\s+(\d+(?:\.\d+)?)\s+درصد', 'percent'),
155
+ (r'بیش از\s+(\d+(?:\.\d+)?)\s+درصد', 'percent'),
156
+ (r'حدود\s+(\d+(?:\.\d+)?)\s+درصد', 'percent'),
157
+ (r'کمتر از\s+(\d+(?:\.\d+)?)\s+درصد', 'percent'),
158
+ ]
159
+
160
+ for pattern, category in percent_patterns:
161
+ matches = list(re.finditer(pattern, text))
162
+ for match in reversed(matches):
163
+ matched_text = match.group(0)
164
+ if matched_text not in self.mapping_table:
165
+ self.counters[category] += 1
166
+ code = f"{category}-{self.counters[category]:02d}"
167
+ self.mapping_table[matched_text] = code
168
+ start, end = match.span()
169
+ text = text[:start] + code + text[end:]
170
+ logger.info(f"Percent replaced: {matched_text} -> {code}")
171
 
172
+ return text
173
+
174
+ def _anonymize_general_persons(self, text):
175
+ """ناشناس‌سازی نام‌های عمومی اشخاص"""
176
  person_patterns = [
177
+ # نام با عنوان
178
+ (r'دکتر\s+[آ-ی]+\s+[آ-ی]+(?:\s+[آ-ی]+)?', 'person'),
179
+ (r'مهندس\s+[آ-ی]+\s+[آ-ی]+(?:\s+[آ-ی]+)?', 'person'),
180
+ (r'آقای\s+[آ-ی]+\s+[آ-ی]+(?:\s+[آ-ی]+)?', 'person'),
181
+ (r'خانم\s+[آ-ی]+\s+[آ-ی]+(?:\s+[آ-ی]+)?', 'person'),
182
+
183
+ # نام با سید
184
+ (r'سید\s*[آ-ی]+\s+[آ-ی]+(?:\s+[آ-ی]+)?', 'person'),
 
 
 
 
 
 
 
185
 
186
  # نام‌هایی که با مدیرعامل همراه هستند
187
+ (r'[آ-ی]+\s+[آ-ی]+(?:\s+[آ-ی]+)?\s*،?\s*مدیرعامل', 'person'),
188
+
189
+ # نام و نام خانوادگی - حداقل 3 حرف
190
+ (r'(?<!\S)[آ-ی]{3,}\s+[آ-ی]{3,}(?:\s+[آ-ی]{3,})?(?!\S)', 'person'),
 
 
 
 
 
 
 
 
 
 
 
 
191
  ]
192
 
193
+ # عباراتی که نباید به عنوان نام تشخیص داده شوند
194
+ exclude_phrases = [
195
+ 'مجمع عمومی', 'عادی سالیانه', 'شرکت اصلی', 'درآمد عملیاتی',
196
+ 'سود عملیاتی', 'زیان انباشته', 'محصولات گرم', 'محصولات سرد',
197
+ 'صورت مالی', 'سال گذشته', 'سال جاری', 'هیئت مدیره'
198
+ ]
199
+
200
+ for pattern, category in person_patterns:
201
+ matches = list(re.finditer(pattern, text))
202
+ for match in reversed(matches):
203
+ matched_text = match.group(0)
204
+ # بررسی که جزو عبارات مستثنی نباشد
205
+ is_excluded = any(phrase in matched_text for phrase in exclude_phrases)
206
+ if not is_excluded and matched_text not in self.mapping_table:
207
+ self.counters[category] += 1
208
+ code = f"{category}-{self.counters[category]:02d}"
209
+ self.mapping_table[matched_text] = code
210
+ start, end = match.span()
211
+ text = text[:start] + code + text[end:]
212
+ logger.info(f"Person replaced: {matched_text} -> {code}")
213
 
214
+ return text
215
+
216
+ def _anonymize_general_companies(self, text):
217
+ """ناشناس‌سازی نام‌های عمومی شرکت‌ها"""
218
  company_patterns = [
219
+ # شرکت‌ها با پرانتز
220
+ (r'شرکت\s+[آ-ی][آ-ی\s]+\([آ-ی\s]+\)', 'company'),
221
+ (r'بانک\s+[آ-ی][آ-ی\s]+\([آ-ی\s]+\)', 'company'),
222
+
223
+ # شرکت‌ها با انواع مختلف
224
+ (r'شرکت\s+[آ-ی][آ-ی\s]{4,}', 'company'),
225
+ (r'بانک\s+[آ-ی][آ-ی\s]{2,}', 'company'),
226
+ (r'گروه\s+[آ-ی][آ-ی\s]{2,}', 'company'),
227
+ (r'موسسه\s+[آ-ی][آ-ی\s]{2,}', 'company'),
228
+ (r'سازمان\s+[آ-ی][آ-ی\s]{2,}', 'company'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
230
  # شرکت‌های خاص
231
+ (r'[آ-ی]+\s+خودرو', 'company'),
232
+ (r'[آ-ی]+\s+فولاد', 'company'),
233
+ (r'بیمه\s+[آ-ی]+', 'company'),
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  ]
235
 
236
+ # عباراتی که نباید به عنوان شرکت تشخیص داده شوند
237
+ exclude_company_phrases = ['شرکت اصلی']
 
 
 
 
 
238
 
239
+ for pattern, category in company_patterns:
240
+ matches = list(re.finditer(pattern, text))
241
+ for match in reversed(matches):
 
242
  matched_text = match.group(0)
243
+ # بررسی که جزو عبارات مستثنی نباشد
244
+ is_excluded = any(phrase in matched_text for phrase in exclude_company_phrases)
245
+ if not is_excluded and matched_text not in self.mapping_table:
246
+ self.counters[category] += 1
247
+ code = f"{category}-{self.counters[category]:02d}"
248
+ self.mapping_table[matched_text] = code
249
+ start, end = match.span()
250
+ text = text[:start] + code + text[end:]
251
+ logger.info(f"Company replaced: {matched_text} -> {code}")
 
 
 
 
 
252
 
253
  return text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
  def send_to_chatgpt(self, anonymized_text, lang='fa'):
256
  """ارسال به ChatGPT"""
 
654
  )
655
 
656
  if __name__ == "__main__":
657
+ print("=" * 80)
658
+ print("تست سیستم ناشناس‌سازی جامع با نمونه‌های ذکر شده:")
659
+ print("=" * 80)
660
+
661
+ # نمونه‌های تست
662
+ test_samples = [
663
+ "مهدی اخوان بهابادی باید یک اسم حساب شود.",
664
+ "در مجمع عمومی عادی سالیانه اعلام کرد درآمد عملیاتی شرکت اصلی",
665
+ "به معنای درآمد روزانه 178 میلیارد تومانی این اپراتور بوده",
666
+ "در خودروسازان حالا از مرز 305 همت عبور کرده و به 305 همت رسیده است.",
667
+ "زیان انباشته این شرکت 7.6 همت زیاد شده است.",
668
+ "تولید محصولات گرم این شرکت به 1000 هزار تن و محصولات سرد به 1378 هزار تن رسید",
669
+ "شرکت سرمایه گذاری پارسیان را اعلام کرد",
670
+ "بانک پارسیان و گروه مالی پارسیان"
671
+ ]
672
+
673
+ # تست کامل
674
+ full_test = """مهدی اخوان بهابادی در مجمع عمومی عادی سالیانه اعلام کرد درآمد عملیاتی شرکت اصلی به 178 میلیارد تومانی رسیده است.
675
+ در خودروسازان حالا از مرز 305 همت عبور کرده و سود عملیاتی داشته اما زیان انباشته این شرکت 7.6 همت زیاد شده است.
676
+ تولید محصولات گرم این شرکت به 1000 هزار تن و محصولات سرد به 1378 هزار تن رسید.
677
+ شرکت سرمایه گذاری پارسیان سود خوبی را نشان داد. بانک پارسیان و گروه مالی پارسیان هم عملکرد مثبتی داشتند."""
678
 
679
  anonymizer_test = UniversalAnonymizer()
680
+
681
+ # تست نمونه‌های جداگانه
682
+ print("\n📌 تست نمونه‌های جداگانه:")
683
+ print("-" * 40)
684
+ for i, sample in enumerate(test_samples, 1):
685
+ anonymizer_test = UniversalAnonymizer() # ریست برای هر تست
686
+ result = anonymizer_test.anonymize_text(sample)
687
+ print(f"{i}. اصلی: {sample}")
688
+ print(f" ناشناس: {result}")
689
+ print()
690
+
691
+ # تست کامل
692
+ print("\n📌 تست کامل:")
693
+ print("-" * 40)
694
+ anonymizer_test = UniversalAnonymizer()
695
+ result = anonymizer_test.anonymize_text(full_test)
696
+ print("متن اصلی:")
697
+ print(full_test)
698
+ print("\nمتن ناشناس‌شده:")
699
  print(result)
700
+
701
+ print("\n📊 جدول نگاشت:")
702
+ print("-" * 40)
703
  for original, code in anonymizer_test.mapping_table.items():
704
+ print(f"{code} {original}")
705
+
706
+ print("\n" + "=" * 80)
707
+ print("✅ برنامه آماده اجراست!")
708
+ print("=" * 80)
709
 
710
  app.launch()