leilaghomashchi commited on
Commit
884f8d5
·
verified ·
1 Parent(s): 8c49672

Upload app (24).py

Browse files
Files changed (1) hide show
  1. app (24).py +402 -0
app (24).py ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import gradio as gr
5
+ import pandas as pd
6
+ import re
7
+
8
+ class ImprovedAnonymizationEvaluator:
9
+ def __init__(self):
10
+ # فهرست بانک‌های ایران
11
+ self.iranian_banks = [
12
+ 'بانک ملی', 'بانک صادرات', 'بانک پاسارگاد', 'بانک کشاورزی',
13
+ 'بانک ملت', 'بانک تجارت', 'بانک صنعت و معدن', 'بانک رسالت',
14
+ 'بانک دی', 'بانک پارسیان', 'بانک کارآفرین', 'بانک سامان',
15
+ 'بانک اقتصاد نوین', 'بانک مهر اقتصاد', 'بانک آینده'
16
+ ]
17
+
18
+ # فهرست سازمان‌های دولتی
19
+ self.government_orgs = [
20
+ 'بانک مرکزی جمهوری اسلامی ایران',
21
+ 'دفتر اسناد رسمی',
22
+ 'اداره کل مالیات',
23
+ 'تامین اجتماعی',
24
+ 'وزارت دادگستری'
25
+ ]
26
+
27
+ # کلمات عمومی که نباید entity محسوب شوند
28
+ self.generic_words = {
29
+ 'همین بانک', 'این بانک', 'آن بانک', 'بانک مذکور',
30
+ 'همین شرکت', 'این شرکت', 'آن شرکت', 'شرکت مذکور',
31
+ 'همین شعبه', 'این شعبه', 'آن شعبه', 'شعبه مذکور',
32
+ 'شرکت متقاضی', 'دفتر حسابداری شرکت',
33
+ 'متقاضی', 'ایشان', 'وی', 'مشتری'
34
+ }
35
+
36
+ # کلمات حذفی
37
+ self.remove_words = [
38
+ 'در', 'که', 'با', 'به', 'از', 'را', 'و', 'یا', 'است', 'بوده',
39
+ 'نموده', 'صادر', 'ارائه', 'معرفی', 'برگزار', 'واقع', 'مربوط',
40
+ 'مطرح', 'شد', 'شده'
41
+ ]
42
+
43
+ self.patterns = {
44
+ 'person_names': {
45
+ 'pattern': re.compile(r'(?:آقای|خانم|مهندس|دکتر)\s+[\u0600-\u06FF\s]+?(?=\s+با\s+کد|\s+مدیر|$|،|\.)', re.UNICODE),
46
+ 'replacement': re.compile(r'person_\d+'),
47
+ 'name': 'اسامی اشخاص'
48
+ },
49
+ 'national_ids': {
50
+ 'pattern': re.compile(r'(?<!09)(?<!021-)(?<![0-9])\d{10,11}(?![0-9])'),
51
+ 'replacement': re.compile(r'id_number_\d+'),
52
+ 'name': 'کدهای ملی'
53
+ },
54
+ 'phone_numbers': {
55
+ 'pattern': re.compile(r'(?:09\d{9}|021-\d{8}|0\d{2,3}-?\d{7,8})'),
56
+ 'replacement': re.compile(r'phone_\d+'),
57
+ 'name': 'شماره تلفن‌ها'
58
+ },
59
+ 'account_numbers': {
60
+ 'pattern': re.compile(r'\b\d{3}-\d{3}-\d{3}-\d{1}\b'),
61
+ 'replacement': re.compile(r'account_\d+'),
62
+ 'name': 'شماره حساب‌ها'
63
+ },
64
+ 'card_numbers': {
65
+ 'pattern': re.compile(r'\b\d{4}-\d{4}-\d{4}-\d{4}\b'),
66
+ 'replacement': re.compile(r'card_number_\d+'),
67
+ 'name': 'شماره کارت‌ها'
68
+ },
69
+ 'amounts': {
70
+ 'pattern': re.compile(r'\d{6,}\s*تومان'),
71
+ 'replacement': re.compile(r'amount_\d+'),
72
+ 'name': 'مبالغ مالی'
73
+ },
74
+ 'dates': {
75
+ 'pattern': re.compile(r'(?:\d{4}\/\d{2}\/\d{2}|۳۰\s*اسفند\s*۱۴۰۳|\b\d{4}\b(?=\s*سال))'),
76
+ 'replacement': re.compile(r'date_\d+'),
77
+ 'name': 'تاریخ‌ها'
78
+ },
79
+ 'full_addresses': {
80
+ 'pattern': re.compile(r'(?:تهران|اصفهان|مشهد|شیراز|کرج|اهواز|قم|رشت|کرمان|یزد|بوشهر|ارومیه|همدان|بندر عباس|ساری|اردبیل|خرم‌آباد|ایلام|بیرجند|گرگان|زنجان|سنندج|شهرکرد|سبزوار|قزوین|زاهدان|خوی|مراغه|کاشان|نجف‌آباد|شاهین‌شهر|ملایر|آبادان|دزفول|بابل|آمل|شاهرود|گنبد کاووس|خرمشهر|جهرم|فسا|مرودشت|لار|داراب|فیروزآباد|کازرون|سپیدان|نی‌ریز|استهبان|فارسان|میانه|ورامین|قرچک|ری|پاکدشت|دماوند|فیروزکوه|شهریار|اسلام‌شهر|ملارد|قدس|بهارستان|چهاردانگه),\s*(?:میدان|خیابان|کوچه|شهرک|بلوار|کوی|محله)\s+[\u0600-\u06FF\u200C\u0621\u06F0-\u06F9\s]+(?:،\s*(?:برج|ساختمان|مجتمع)\s+[\u0600-\u06FF\u200C\u0621\u06F0-\u06F9\s]+)?(?:،\s*(?:طبقه|واحد)\s+[\u0600-\u06FF\u200C\u0621\u06F0-\u06F9\d\s]+)?', re.UNICODE),
81
+ 'replacement': re.compile(r'(?:full_address_\d+|location_\d+)'),
82
+ 'name': 'آدرس‌های کامل'
83
+ },
84
+ 'iranian_banks': {
85
+ 'pattern': re.compile(f"({'|'.join(re.escape(bank) for bank in self.iranian_banks)})", re.UNICODE),
86
+ 'replacement': re.compile(r'company_\d+'),
87
+ 'name': 'بانک‌های ایران'
88
+ },
89
+ 'government_orgs': {
90
+ 'pattern': re.compile(f"({'|'.join(re.escape(org) for org in self.government_orgs)})", re.UNICODE),
91
+ 'replacement': re.compile(r'company_\d+'),
92
+ 'name': 'سازمان‌های دولتی'
93
+ },
94
+ 'other_companies': {
95
+ 'pattern': re.compile(r'شرکت\s+[\u0600-\u06FF\u200C\u0621\u06F0-\u06F9\s]+?(?=\s|$|،|\.)', re.UNICODE),
96
+ 'replacement': re.compile(r'company_\d+'),
97
+ 'name': 'سایر شرکت‌ها'
98
+ },
99
+ 'invoice_numbers': {
100
+ 'pattern': re.compile(r'(?:INV-\d{4}-\d{4}|RPT-\d{4}-\d{4})'),
101
+ 'replacement': re.compile(r'(?:invoice_\d+|report_\d+)'),
102
+ 'name': 'شماره فاکتور و گزارش'
103
+ },
104
+ 'document_offices': {
105
+ 'pattern': re.compile(r'دفتر\s+اسناد\s+رسمی\s+شماره\s+\d+'),
106
+ 'replacement': re.compile(r'(?:contract_\d+|office_\d+)'),
107
+ 'name': 'دفاتر اسناد رسمی'
108
+ },
109
+ 'cheque_numbers': {
110
+ 'pattern': re.compile(r'چک\s+شماره\s+\d+'),
111
+ 'replacement': re.compile(r'cheque_\d+'),
112
+ 'name': 'شماره چک‌ها'
113
+ }
114
+ }
115
+
116
+ def is_generic_word(self, text):
117
+ """بررسی کلمات عمومی که نباید entity محسوب شوند"""
118
+ text_clean = text.strip().lower()
119
+
120
+ if text_clean in self.generic_words:
121
+ return True
122
+
123
+ if text_clean.startswith(('همین ', 'این ', 'آن ')):
124
+ return True
125
+
126
+ return len(text.strip()) < 3
127
+
128
+ def clean_entity(self, text):
129
+ """تمیز کردن دقیق entity"""
130
+ # ساخت الگو برای حذف کلمات اضافی
131
+ pattern = r'\s*(' + '|'.join(re.escape(word) for word in self.remove_words) + r')\s*$'
132
+ text = re.sub(pattern, '', text, flags=re.IGNORECASE)
133
+ text = re.sub(r'\s+', ' ', text).strip()
134
+ return text
135
+
136
+ def is_valid_entity(self, text, category):
137
+ """بررسی دقیق‌تر معتبر بودن entity"""
138
+ if len(text) < 3 or len(text) > 100:
139
+ return False
140
+
141
+ if self.is_generic_word(text):
142
+ return False
143
+
144
+ forbidden_words = [
145
+ 'شد', 'کرد', 'است', 'بود', 'در', 'که', 'با', 'از', 'به', 'را', 'و', 'یا',
146
+ 'شده', 'نموده', 'صادر', 'ارائه', 'معرفی', 'برگزار', 'مطرح', 'واقع'
147
+ ]
148
+
149
+ if text.lower().strip() in forbidden_words:
150
+ return False
151
+
152
+ if category in ['iranian_banks', 'other_companies']:
153
+ if any(word in text.lower() for word in ['برگزار', 'مطرح', 'شد', 'است', 'نموده']):
154
+ return False
155
+ if text.strip() in ['شرکت', 'بانک', 'شرکت در', 'بانک در']:
156
+ return False
157
+
158
+ elif category == 'person_names':
159
+ if text.strip() in ['آقای', 'خانم', 'مهندس', 'دکتر']:
160
+ return False
161
+
162
+ return True
163
+
164
+ def analyze_entities(self, original_text, anonymized_text):
165
+ """تحلیل موجودیت‌های شناسایی شده و ناشناس شده"""
166
+ results = {}
167
+
168
+ for entity_type, config in self.patterns.items():
169
+ original_matches = config['pattern'].findall(original_text)
170
+ replacement_matches = config['replacement'].findall(anonymized_text)
171
+
172
+ # تمیز کردن و فیلتر کردن entities
173
+ clean_original = []
174
+ for match in original_matches:
175
+ cleaned = self.clean_entity(match)
176
+ if self.is_valid_entity(cleaned, entity_type):
177
+ clean_original.append(cleaned)
178
+
179
+ # محاسبه تعداد موجودیت‌های ناشناس شده
180
+ anonymized_count = 0
181
+ for entity in clean_original:
182
+ if not anonymized_text.count(entity.strip()):
183
+ anonymized_count += 1
184
+
185
+ # اگر تعداد جایگزین‌ها بیشتر باشد
186
+ if len(replacement_matches) > anonymized_count:
187
+ anonymized_count = min(len(replacement_matches), len(clean_original))
188
+
189
+ percentage = (anonymized_count / len(clean_original) * 100) if clean_original else 0
190
+
191
+ results[entity_type] = {
192
+ 'name': config['name'],
193
+ 'total': len(clean_original),
194
+ 'anonymized': anonymized_count,
195
+ 'percentage': round(percentage, 1),
196
+ 'samples': clean_original[:3] if clean_original else []
197
+ }
198
+
199
+ return results
200
+
201
+ def evaluate_csv(self, csv_file):
202
+ """ارزیابی فایل CSV و تولید گزارش"""
203
+ try:
204
+ if csv_file is None:
205
+ return "لطفاً یک فایل CSV آپلود کنید."
206
+
207
+ try:
208
+ df = pd.read_csv(csv_file.name, encoding='utf-8')
209
+ except:
210
+ try:
211
+ df = pd.read_csv(csv_file.name, encoding='utf-8', sep='\t')
212
+ except:
213
+ df = pd.read_csv(csv_file.name, encoding='utf-8-sig')
214
+
215
+ if 'original_text' not in df.columns or 'anonymized_text' not in df.columns:
216
+ return "فایل CSV باید شامل ستون‌های 'original_text' و 'anonymized_text' باشد."
217
+
218
+ overall_stats = {}
219
+ total_entities = 0
220
+ total_anonymized = 0
221
+
222
+ for _, row in df.iterrows():
223
+ if pd.isna(row['original_text']) or pd.isna(row['anonymized_text']):
224
+ continue
225
+
226
+ row_analysis = self.analyze_entities(str(row['original_text']), str(row['anonymized_text']))
227
+
228
+ for entity_type, data in row_analysis.items():
229
+ if entity_type not in overall_stats:
230
+ overall_stats[entity_type] = {
231
+ 'name': data['name'],
232
+ 'total': 0,
233
+ 'anonymized': 0,
234
+ 'samples': []
235
+ }
236
+
237
+ overall_stats[entity_type]['total'] += data['total']
238
+ overall_stats[entity_type]['anonymized'] += data['anonymized']
239
+ overall_stats[entity_type]['samples'].extend(data['samples'])
240
+
241
+ total_entities += data['total']
242
+ total_anonymized += data['anonymized']
243
+
244
+ for entity_type in overall_stats:
245
+ stats = overall_stats[entity_type]
246
+ stats['percentage'] = round((stats['anonymized'] / stats['total'] * 100) if stats['total'] > 0 else 0, 1)
247
+ stats['samples'] = list(set(stats['samples']))[:3]
248
+
249
+ return self.generate_report(overall_stats, total_entities, total_anonymized, len(df))
250
+
251
+ except Exception as e:
252
+ return f"خطا در پردازش فایل: {str(e)}"
253
+
254
+ def generate_report(self, stats, total_entities, total_anonymized, total_rows):
255
+ """تولید گزارش کامل متریک‌ها"""
256
+
257
+ report = f"""# گزارش ارزیابی ناشناس‌سازی متن (نسخه بهبود یافته)
258
+
259
+ ## خلاصه کلی
260
+ - **تعداد ردیف‌های پردازش شده**: {total_rows:,} ردیف
261
+ - **تعداد موجودیت‌های حساس شناسایی شده**: {total_entities:,} مورد
262
+ - **تعداد موجودیت‌های ناشناس شده**: {total_anonymized:,} مورد
263
+ - **درصد پوشش کلی**: {(total_anonymized/total_entities*100) if total_entities > 0 else 0:.1f}%
264
+
265
+ ## بهبودهای اعمال شده
266
+ ✅ **آدرس‌های کامل**: شناسایی دقیق آدرس‌ها شامل میدان، برج، طبقه و واحد
267
+ ✅ **اسامی کامل**: پشتیبانی از خانم، مهندس، دکتر
268
+ ✅ **شرکت‌های پیچیده**: شناسایی "شرکت پردازش داده‌های ایرانیان"
269
+ ✅ **جداسازی دقیق**: کد ملی از شماره تلفن جدا شده
270
+ ✅ **فیلتر هوشمند**: حذف "همین بانک"، "شرکت متقاضی"
271
+ ✅ **بانک‌های ایران**: فهرست کامل 15 بانک اصلی
272
+ ✅ **سازمان‌های دولتی**: شناسایی نهادهای رسمی
273
+ ✅ **اسناد و فاکتورها**: شماره فاکتور، چک، دفتر اسناد
274
+
275
+ ## تحلیل تفصیلی دسته‌بندی موجودیت‌ها
276
+
277
+ """
278
+
279
+ excellent = []
280
+ good = []
281
+ poor = []
282
+ not_found = []
283
+
284
+ for entity_type, data in stats.items():
285
+ if data['total'] == 0:
286
+ not_found.append((entity_type, data))
287
+ elif data['percentage'] == 100:
288
+ excellent.append((entity_type, data))
289
+ elif data['percentage'] >= 80:
290
+ good.append((entity_type, data))
291
+ else:
292
+ poor.append((entity_type, data))
293
+
294
+ if excellent:
295
+ report += "### ✅ عملکرد عالی (100% موفقیت)\n"
296
+ for entity_type, data in excellent:
297
+ report += f"- **{data['name']}**: {data['anonymized']}/{data['total']} (100%)\n"
298
+ report += "\n"
299
+
300
+ if good:
301
+ report += "### 🟡 عملکرد خو�� (80-99% موفقیت)\n"
302
+ for entity_type, data in good:
303
+ report += f"- **{data['name']}**: {data['anonymized']}/{data['total']} ({data['percentage']}%)\n"
304
+ report += "\n"
305
+
306
+ if poor:
307
+ report += "### 🔴 عملکرد ضعیف (<80% موفقیت)\n"
308
+ for entity_type, data in poor:
309
+ missed = data['total'] - data['anonymized']
310
+ report += f"- **{data['name']}**: {data['anonymized']}/{data['total']} ({data['percentage']}%) - {missed} مورد جا مانده\n"
311
+ if data['samples']:
312
+ report += f" نمونه‌های شناسایی شده: {', '.join(data['samples'][:2])}\n"
313
+ report += "\n"
314
+
315
+ if not_found:
316
+ report += "### ⚪ موجودیت‌های یافت نشده\n"
317
+ for entity_type, data in not_found:
318
+ report += f"- **{data['name']}**: هیچ موجودیتی یافت نشد\n"
319
+ report += "\n"
320
+
321
+ report += "## جدول خلاصه متریک‌ها\n\n"
322
+ report += "| دسته موجودیت | یافته شده | ناشناس شده | درصد موفقیت | موارد جا مانده |\n"
323
+ report += "|---------------|-----------|-------------|-------------|----------------|\n"
324
+
325
+ for entity_type, data in stats.items():
326
+ if data['total'] > 0:
327
+ missed = data['total'] - data['anonymized']
328
+ report += f"| {data['name']} | {data['total']} | {data['anonymized']} | {data['percentage']}% | {missed} |\n"
329
+
330
+ major_issues = [(k, v) for k, v in stats.items() if v['total'] > 0 and v['percentage'] < 80]
331
+ major_issues.sort(key=lambda x: x[1]['total'] - x[1]['anonymized'], reverse=True)
332
+
333
+ if major_issues:
334
+ report += "\n## 🚨 مشکلات اصلی شناسایی شده\n\n"
335
+ for i, (entity_type, data) in enumerate(major_issues, 1):
336
+ missed = data['total'] - data['anonymized']
337
+ impact = round(missed / total_entities * 100, 1) if total_entities > 0 else 0
338
+ report += f"### {i}. {data['name']}\n"
339
+ report += f"- **وضعیت**: {data['percentage']}% موفقیت\n"
340
+ report += f"- **موارد جا مانده**: {missed} مورد از {data['total']} مورد\n"
341
+ report += f"- **تاثیر بر کل**: {impact}% از کل موجودیت‌ها\n"
342
+ if data['samples']:
343
+ report += f"- **نمونه‌ها**: {', '.join(data['samples'][:3])}\n"
344
+ report += "\n"
345
+
346
+ precision = round((total_anonymized / total_entities * 100) if total_entities > 0 else 0, 1)
347
+
348
+ report += f"""## 📊 آمار نهایی
349
+
350
+ - **کل موجودیت‌های شناسایی شده**: {total_entities:,}
351
+ - **کل موجودیت‌های ناشناس شده**: {total_anonymized:,}
352
+ - **موجودیت‌های جا مانده**: {total_entities - total_anonymized:,}
353
+ - **دقت (Precision)**: {precision}%
354
+ - **پوشش (Recall)**: {precision}%
355
+ - **امتیاز F1**: {precision}%
356
+
357
+ ## 🆕 ویژگی‌های جدید اعمال شده
358
+ - الگوهای regex بهبود یافته و دقیق‌تر
359
+ - فیلتر کلمات عمومی و غیرمعنادار
360
+ - شناسایی آدرس‌های کامل با جزئیات
361
+ - تشخیص اسامی با عناوین مختلف
362
+ - جداسازی دقیق انواع موجودیت‌ها
363
+ - پشتیبانی از شرکت‌های پیچیده
364
+ - شناسایی اسناد و فاکتورها
365
+ """
366
+
367
+ return report
368
+
369
+ def create_interface():
370
+ evaluator = ImprovedAnonymizationEvaluator()
371
+
372
+ def process_file(csv_file):
373
+ if csv_file is None:
374
+ return "لطفاً یک فایل CSV آپلود کنید."
375
+ return evaluator.evaluate_csv(csv_file)
376
+
377
+ with gr.Blocks(title="ارزیاب ناشناس‌سازی بهبود یافته") as demo:
378
+ gr.Markdown("""
379
+ # 📊 ارزیاب ناشناس‌سازی متن فارسی (نسخه بهبود یافته)
380
+
381
+ ## 🆕 ویژگی‌های جدید:
382
+ - **آدرس‌های کامل**: تهران، میدان ونک، برج میلاد، طبقه 15، واحد 1503
383
+ - **اسامی کامل**: خانم زهرا احمدی، مدیر مالی خانم احمدی
384
+ - **شرکت‌های پیچیده**: شرکت پردازش داده‌های ایرانیان
385
+ - **فیلتر هوشمند**: حذف "همین بانک"، "شرکت متقاضی"
386
+ - **جداسازی دقیق**: کد ملی از شماره تلفن
387
+ - **بانک‌های ایران**: 15 بانک اصلی شناسایی می‌شود
388
+ - **سازمان‌های دولتی**: بانک مرکزی، دفتر اسناد رسمی
389
+ - **اسناد**: شماره فاکتور، چک، دفتر اسناد
390
+ """)
391
+
392
+ file_input = gr.File(label="آپلود فایل CSV", file_types=[".csv"])
393
+ analyze_btn = gr.Button("محاسبه متریک‌ها (نسخه بهبود یافته)", variant="primary")
394
+ output = gr.Markdown(value="فایل CSV خود را آپلود کنید.")
395
+
396
+ analyze_btn.click(fn=process_file, inputs=[file_input], outputs=[output])
397
+
398
+ return demo
399
+
400
+ if __name__ == "__main__":
401
+ app = create_interface()
402
+ app.launch()