leilaghomashchi commited on
Commit
e5fc873
·
verified ·
1 Parent(s): c5e65d2

Upload updated_benchmark.py

Browse files
Files changed (1) hide show
  1. updated_benchmark.py +711 -0
updated_benchmark.py ADDED
@@ -0,0 +1,711 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ سیستم benchmark ناشناس‌سازی - ورژن آپدیت شده با الگوهای جامع و دقیق
5
+ """
6
+
7
+ import pandas as pd
8
+ import re
9
+ import json
10
+ import logging
11
+ import os
12
+ import gradio as gr
13
+ from typing import Dict, List, Tuple, Set
14
+ from collections import defaultdict
15
+ import numpy as np
16
+
17
+ # تنظیم logging
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # ===== تابع کمکی برای تبدیل numpy/pandas types =====
22
+ def convert_to_serializable(obj):
23
+ if isinstance(obj, (np.integer, np.int64, np.int32)):
24
+ return int(obj)
25
+ elif isinstance(obj, (np.floating, np.float64, np.float32)):
26
+ return float(obj)
27
+ elif isinstance(obj, np.ndarray):
28
+ return obj.tolist()
29
+ elif isinstance(obj, dict):
30
+ return {key: convert_to_serializable(value) for key, value in obj.items()}
31
+ elif isinstance(obj, list):
32
+ return [convert_to_serializable(item) for item in obj]
33
+ else:
34
+ return obj
35
+
36
+ # ===== کلاس پردازش entities با الگوهای آپدیت شده =====
37
+ class UpdatedEntityExtractor:
38
+ def __init__(self):
39
+ # الگوهای آپدیت شده براساس سیستم ناشناس‌سازی بهبود یافته
40
+ self.patterns = {
41
+ # آدرس‌های کامل - اولویت بالا با پوشش میدان و برج
42
+ 'FULL_ADDRESS': [
43
+ # الگوی آدرس کامل: شهر + میدان + برج + طبقه + واحد
44
+ r'(?:تهران|اصفهان|مشهد|شیراز|کرج|اهواز|قم|رشت|کرمان|یزد|بوشهر|ارومیه|همدان|بندر عباس|ساری|اردبیل|خرم‌آباد|ایلام|بیرجند|گرگان|زنجان|سنندج|شهرکرد|سبزوار|قزوین|زاهدان|خوی|مراغه|کاشان|نجف‌آباد|شاهین‌شهر|ملایر|آبادان|دزفول|بابل|آمل|شاهرود|گنبد کاووس|خرمشهر|جهرم|فسا|مرودشت|لار|داراب|فیروزآباد|کازرون|سپیدان|نی‌ریز|استهبان|فارسان|میانه|ورامین|قرچک|ری|پاکدشت|دماوند|فیروزکوه|شهریار|اسلام‌شهر|ملارد|قدس|بهارستان|چهاردانگه)،\s*(?:میدان|خیابان|کوچه|شهرک|بلوار|کوی|محله)\s+[آ-ی‌ّٰ-ٹ\s]+(?:،\s*(?:برج|ساختمان|مجتمع)\s+[آ-ی‌ّٰ-ٹ\s]+)?(?:،\s*(?:طبقه|واحد)\s+[آ-ی‌ّٰ-ٹ\d\s]+)?(?:،\s*واحد\s+[آ-ی‌ّٰ-ٹ\d\s]+)?',
45
+
46
+ # الگوی آدرس کامل: شهر + خیابان + کوچه + پلاک + طبقه
47
+ r'(?:تهران|اصفهان|مشهد|شیراز|کرج|اهواز|قم|رشت|کرمان|یزد|بوشهر|ارومیه|همدان|بندر عباس|ساری|اردبیل|خرم‌آباد|ایلام|بیرجند|گرگان|زنجان|سنندج|شهرکرد|سبزوار|قزوین|زاهدان|خوی|مراغه|کاشان|نجف‌آباد|شاهین‌شهر|ملایر|آبادان|دزفول|بابل|آمل|شاهرود|گنبد کاووس|خرمشهر|جهرم|فسا|مرودشت|لار|داراب|فیروزآباد|کازرون|سپیدان|نی‌ریز|استهبان|فارسان|میانه|ورامین|قرچک|ری|پاکدشت|دماوند|فیروزکوه|شهریار|اسلام‌شهر|ملارد|قدس|بهارستان|چهاردانگه)،\s*(?:خیابان|کوچه|شهرک|بلوار|میدان|کوی|محله)\s+[آ-ی‌ّٰ-ٹ\s]+(?:،\s*(?:خیابان|کوچه|بلوار|کوی)\s+[آ-ی‌ّٰ-ٹ\s]+)?(?:،\s*پلاک\s+\d+)?(?:،\s*(?:طبقه|واحد)\s+[آ-ی‌ّٰ-ٹ\d\s]+)?',
48
+
49
+ # الگوی آدرس با شهرک
50
+ r'(?:تهران|اصفهان|مشهد|شیراز|کرج|اهواز|قم|رشت|کرمان|یزد|بوشهر|ارومیه|همدان|بندر عباس|ساری|اردبیل|خرم‌آباد|ایلام|بیرجند|گرگان|زنجان|سنندج|شهرکرد|سبزوار|قزوین|زاهدان|خوی|مراغه|کاشان|نجف‌آباد|شاهین‌شهر|ملایر|آبادان|دزفول|بابل|آمل|شاهرود|گنبد کاووس|خرمشهر|جهرم|فسا|مرودشت|لار|داراب|فیروزآباد|کازرون|سپیدان|نی‌ریز|استهبان|فارسان|میانه|ورامین|قرچک|ری|پاکدشت|دماوند|فیروزکوه|شهریار|اسلام‌شهر|ملارد|قدس|بهارستان|چهاردانگه)،\s*شهرک\s+[آ-ی‌ّٰ-ٹ\s]+،\s*(?:خیابان|کوچه|بلوار)\s+[آ-ی‌ّٰ-ٹ\s]+(?:،\s*پلاک\s+\d+)?',
51
+
52
+ # الگوی ساده‌تر برای آدرس‌های کوتاه‌تر
53
+ r'خیابان\s+[آ-ی‌ّٰ-ٹ\s]+،\s*کوچه\s+[آ-ی‌ّٰ-ٹ\s]+،\s*پلاک\s+\d+(?:،\s*(?:طبقه|واحد)\s+[آ-ی‌ّٰ-ٹ\d\s]+)?',
54
+ ],
55
+
56
+ # اسامی اشخاص - الگوهای دقیق‌تر شامل خانم
57
+ 'PERSON': [
58
+ r'آقای\s+[آ-ی‌ّٰ-ٹ]+\s+[آ-ی‌ّٰ-ٹ]+(?=\s+با\s+کد|\s+مدیر|$|،|\.)',
59
+ r'خانم\s+[آ-ی‌ّٰ-ٹ]+\s+[آ-ی‌ّٰ-ٹ]+(?=\s+با\s+کد|\s+با\s+موبایل|$|،|\.)',
60
+ r'مهندس\s+[آ-ی‌ّٰ-ٹ]+\s+[آ-ی‌ّٰ-ٹ]+(?=\s+با\s+کد|$|،|\.)',
61
+ r'دکتر\s+[آ-ی‌ّٰ-ٹ]+\s+[آ-ی‌ّٰ-ٹ]+(?=\s+با\s+کد|$|،|\.)',
62
+ r'مدیر\s+مالی\s+خانم\s+[آ-ی‌ّٰ-ٹ]+\s+[آ-ی‌ّٰ-ٹ]+',
63
+ r'مدیرعامل\s+[آ-ی‌ّٰ-ٹ]+\s+[آ-ی‌ّٰ-ٹ]+',
64
+ r'Mr\.\s+[A-Z][a-z]+\s+[A-Z][a-z]+(?=\s|,|\.|$)',
65
+ r'Ms\.\s+[A-Z][a-z]+\s+[A-Z][a-z]+(?=\s|,|\.|$)',
66
+ r'Dr\.\s+[A-Z][a-z]+\s+[A-Z][a-z]+(?=\s|,|\.|$)',
67
+ ],
68
+
69
+ # کدهای ملی و شناسه‌ها - جداسازی از شماره تلفن
70
+ 'ID_NUMBER': [
71
+ r'کد\s+ملی\s+\d{10}',
72
+ r'شناسه\s+ملی\s+\d{11}',
73
+ r'(?<!09)(?<!021-)(?<![0-9])\d{10}(?![0-9])', # کد ملی 10 رقمی مستقل
74
+ r'(?<!09)(?<!021-)(?<![0-9])\d{11}(?![0-9])', # شناسه 11 رقمی مستقل
75
+ ],
76
+
77
+ # مبالغ مالی - جداسازی از شماره تلفن
78
+ 'AMOUNT': [
79
+ r'\d{6,}\s*تومان', # مبالغ 6 رقمی یا بیشتر با کلمه تومان
80
+ r'مبلغ\s+\d{6,}(?:\s*تومان)?',
81
+ r'موجودی\s+حساب\s+[^\s]+\s+حدود\s+\d{6,}\s*تومان',
82
+ r'ارزش\s+روز\s+آن\s+\d{6,}\s*تومان',
83
+ r'میانگین\s+موجودی\s+حساب\s+وی\s+حدود\s+\d{6,}\s*تومان',
84
+ r'\d+\s*درصد\s+مبلغ\s+معادل\s+\d{6,}\s*تومان',
85
+ r'\$\d+(?:,\d{3})*(?:\.\d+)?',
86
+ ],
87
+
88
+ # شماره حساب و کارت بانکی - جداسازی دقیق
89
+ 'ACCOUNT': [
90
+ r'حساب\s+جاری\s+شماره\s+[\d-]+',
91
+ r'شماره\s+[\d-]{8,}(?=\s+در|\s+بانک)', # شماره حساب
92
+ ],
93
+
94
+ 'CARD_NUMBER': [
95
+ r'شماره\s+کارت\s+مربوطه\s+\d{4}-\d{4}-\d{4}-\d{4}',
96
+ r'\d{4}-\d{4}-\d{4}-\d{4}(?=\s+می‌باشد|\s+نیز)',
97
+ ],
98
+
99
+ # شماره تلفن - فقط شماره، نه کل عبارت
100
+ 'PHONE': [
101
+ r'09\d{9}(?=\s+را)', # فقط شماره موبایل قبل از "را"
102
+ r'(?<![0-9])09\d{9}(?![0-9])(?!\s+را\s+اعلام)', # شماره موبایل مستقل
103
+ r'موبایل\s+09\d{9}', # با کلمه موبایل
104
+ r'021-\d{8}', # تلفن تهران
105
+ r'0\d{2,3}-?\d{7,8}', # تلفن‌های شهری
106
+ ],
107
+
108
+ # تاریخ
109
+ 'DATE': [
110
+ r'\d{4}/\d{1,2}/\d{1,2}',
111
+ r'[۰-۹]{1,2}\s+(?:فروردین|اردیبهشت|خرداد|تیر|مرداد|شهریور|مهر|آبان|آذر|دی|بهمن|اسفند)\s+[۰-۹]{4}',
112
+ ],
113
+
114
+ # شرکت‌ها - الگوهای گسترده‌تر اصلاح شده
115
+ 'COMPANY': [
116
+ r'شرکت\s+پردازش\s+داده‌های\s+[آ-ی‌ّٰ-ٹ\s]+',
117
+ r'شرکت\s+تولیدی\s+[آ-ی‌ّٰ-ٹ\s]+(?=\s|$|،|\.)',
118
+ r'شرکت\s+[آ-ی‌ّٰ-ٹ\s]+(?=\s|$|،|\.)',
119
+ r'(?<!همین\s)(?<!این\s)(?<!آن\s)بانک\s+[آ-ی‌ّٰ-ٹ\s]+(?=\s|$|،|\.)', # بانک فقط اگر قبلش همین/این/آن نباشد
120
+ r'دفتر\s+حسابداری\s+شرکت',
121
+ r'[A-Z][a-zA-Z\s]+(?:Inc|Corp|Corporation|Company|Ltd|Limited|LLC)',
122
+ ],
123
+
124
+ # شعب و واحدهای تجاری - دقیق‌تر شده
125
+ 'BRANCH': [
126
+ r'شعبه\s+[آ-ی‌ّٰ-ٹ\s]+\s+بانک\s+[آ-ی‌ّٰ-ٹ\s]+',
127
+ r'شعبه\s+مرکزی\s+بانک\s+[آ-ی‌ّٰ-ٹ\s]+',
128
+ ],
129
+
130
+ # مکان‌ها - شهرها (فقط نام شهرها تنها)
131
+ 'LOCATION': [
132
+ r'\b(?:تهران|اصفهان|مشهد|شیراز|کرج|اهواز|قم|رشت|کرمان|یزد|بوشهر|ارومیه|همدان|بندر عباس|ساری|اردبیل|خرم‌آباد|ایلام|بیرجند|گرگان|زنجان|سنندج|شهرکرد|سبزوار|قزوین|زاهدان|خوی|مراغه|کاشان|نجف‌آباد|شاهین‌شهر|ملایر|آبادان|دزفول|بابل|آمل|شاهرود|گنبد کاووس|خرمشهر|جهرم|فسا|مرودشت|لار|داراب|فیروزآباد|کازرون|سپیدان|نی‌ریز|استهبان|فارسان|میانه|ورامین|قرچک|ری|پاکدشت|دماوند|فیروزکوه|شهریار|اسلام‌شهر|ملارد|قدس|بهارستان|چهاردانگه)\b(?!\s*،)(?!\s+میدان)(?!\s+خیابان)', # فقط نام شهر تنها
133
+ r'استان\s+[آ-ی‌ّٰ-ٹ\s]+',
134
+ r'شهر\s+[آ-ی‌ّٰ-ٹ\s]+',
135
+ ],
136
+
137
+ # شماره فاکتور و اسناد - الگوهای جدید
138
+ 'INVOICE_NUMBER': [
139
+ r'فاکتور\s+شماره\s+[A-Z]+-\d{4}-\d{4}',
140
+ r'[A-Z]+-\d{4}-\d{4}(?=\s+را\s+برای)',
141
+ ],
142
+
143
+ # دفاتر اسناد رسمی
144
+ 'DOCUMENT_OFFICE': [
145
+ r'دفتر\s+اسناد\s+رسمی\s+شماره\s+\d+',
146
+ r'شماره\s+\d+\s+(?:تهران|اصفهان|مشهد|شیراز|کرج)',
147
+ ],
148
+
149
+ # ایمیل
150
+ 'EMAIL': [
151
+ r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
152
+ ],
153
+ }
154
+
155
+ # ترتیب اولویت براساس سیستم ناشناس‌سازی
156
+ self.priority_order = [
157
+ 'FULL_ADDRESS', # بالاترین اولویت - آدرس کامل قبل از قطعات
158
+ 'INVOICE_NUMBER', # شماره فاکتور
159
+ 'DOCUMENT_OFFICE', # دفاتر اسناد رسمی
160
+ 'EMAIL',
161
+ 'CARD_NUMBER', # کارت بانکی
162
+ 'ACCOUNT', # حساب بانکی
163
+ 'PHONE', # شماره تلفن - اولویت بالا
164
+ 'ID_NUMBER', # کد ملی
165
+ 'AMOUNT', # مبالغ مالی
166
+ 'BRANCH', # شعبه
167
+ 'COMPANY', # شرکت
168
+ 'LOCATION', # مکان
169
+ 'DATE', # تاریخ
170
+ 'PERSON', # نام اشخاص
171
+ ]
172
+
173
+ def clean_entity(self, text):
174
+ """تمیز کردن دقیق entity"""
175
+ # حذف کلمات اضافی در انتها
176
+ text = re.sub(r'\s*(در|که|با|به|از|را|و|یا|است|بوده|نموده|صادر|ارائه|معرفی|برگزار|واقع|مربوط|مطرح|شد|شده)\s*$', '', text, flags=re.IGNORECASE)
177
+ # حذف فاصله‌های اضافی
178
+ text = re.sub(r'\s+', ' ', text).strip()
179
+ return text
180
+
181
+ def is_generic_word(self, text):
182
+ """بررسی کلمات عمومی که نباید entity محسوب شوند"""
183
+ text_clean = text.strip().lower()
184
+
185
+ generic_words = {
186
+ 'همین بانک', 'این بانک', 'آن بانک', 'بانک مذکور',
187
+ 'همین شرکت', 'این شرکت', 'آن شرکت', 'شرکت مذکور',
188
+ 'همین شعبه', 'این شعبه', 'آن شعبه', 'شعبه مذکور',
189
+ 'همین شهر', 'این شهر', 'آن شهر',
190
+ 'شرکت متقاضی', 'دفتر حسابداری شرکت',
191
+ 'متقاضی', 'ایشان', 'وی', 'مشتری',
192
+ 'بانک', 'شرکت', 'شعبه' # کلمات تنها
193
+ }
194
+
195
+ # بررسی exact match
196
+ if text_clean in generic_words:
197
+ return True
198
+
199
+ # بررسی کلمات که با همین/این/آن شروع می‌شوند
200
+ if text_clean.startswith(('همین ', 'این ', 'آن ')):
201
+ return True
202
+
203
+ return len(text.strip()) < 3
204
+
205
+ def is_valid_entity(self, text, category):
206
+ """بررسی دقیق‌تر معتبر بودن entity"""
207
+ if len(text) < 3 or len(text) > 100:
208
+ return False
209
+
210
+ # بررسی کلمات عمومی
211
+ if self.is_generic_word(text):
212
+ return False
213
+
214
+ # کلمات ممنوع که نباید entity باشند
215
+ forbidden_words = [
216
+ 'شد', 'کرد', 'است', 'بود', 'در', 'که', 'با', 'از', 'به', 'را', 'و', 'یا',
217
+ 'شده', 'نموده', 'صادر', 'ارائه', 'معرفی', 'برگزار', 'مطرح', 'واقع'
218
+ ]
219
+
220
+ if text.lower().strip() in forbidden_words:
221
+ return False
222
+
223
+ # بررسی‌های خاص برای هر category
224
+ if category == 'COMPANY':
225
+ # نباید شامل فعل یا کلمات اضافی باشد
226
+ if any(word in text.lower() for word in ['برگزار', 'مطرح', 'شد', 'است', 'نموده']):
227
+ return False
228
+ # باید حداقل یک اسم خاص داشته باشد
229
+ if text.strip() in ['شرکت', 'بانک', 'شرکت در', 'بانک در']:
230
+ return False
231
+
232
+ elif category == 'LOCATION':
233
+ # نباید شامل فعل باشد
234
+ if any(word in text.lower() for word in ['برگزار', 'شد', 'است', 'واقع']):
235
+ return False
236
+ # باید نام مکان واقعی باشد
237
+ if text.strip() in ['شهر', 'بندر', 'استان']:
238
+ return False
239
+
240
+ elif category == 'DATE':
241
+ # نباید عبارات طولانی باشد
242
+ if 'سال مالی' in text:
243
+ return False
244
+
245
+ elif category == 'PERSON':
246
+ # نباید فقط عنوان باشد
247
+ if text.strip() in ['آقای', 'خانم', 'مهندس', 'دکتر']:
248
+ return False
249
+
250
+ return True
251
+
252
+ def extract_entities(self, text):
253
+ """استخراج entities با دقت بالا و اولویت‌بندی"""
254
+ if not text or text.strip() == '':
255
+ return {}
256
+
257
+ entities = {}
258
+ processed_positions = set()
259
+
260
+ # پردازش براساس اولویت
261
+ for category in self.priority_order:
262
+ if category not in self.patterns:
263
+ continue
264
+
265
+ pattern_list = self.patterns[category]
266
+ found_entities = []
267
+
268
+ for pattern_str in pattern_list:
269
+ try:
270
+ pattern = re.compile(pattern_str, re.IGNORECASE | re.MULTILINE)
271
+ matches = pattern.finditer(text)
272
+
273
+ for match in matches:
274
+ # بررسی تداخل با entities قبلی
275
+ match_start, match_end = match.span()
276
+ overlaps = False
277
+
278
+ for proc_start, proc_end in processed_positions:
279
+ if not (match_end <= proc_start or match_start >= proc_end):
280
+ overlaps = True
281
+ break
282
+
283
+ if not overlaps:
284
+ entity = self.clean_entity(match.group(0))
285
+ if self.is_valid_entity(entity, category):
286
+ found_entities.append(entity)
287
+ processed_positions.add((match_start, match_end))
288
+
289
+ except re.error as e:
290
+ logger.error(f"Regex error in pattern {pattern_str}: {e}")
291
+ continue
292
+
293
+ # حذف تکراری‌ها و مرتب‌سازی
294
+ if found_entities:
295
+ # حذف entities که زیرمجموعه entities دیگر هستند
296
+ unique_entities = []
297
+ for entity in found_entities:
298
+ is_subset = False
299
+ for other in found_entities:
300
+ if entity != other and entity in other:
301
+ is_subset = True
302
+ break
303
+ if not is_subset:
304
+ unique_entities.append(entity)
305
+
306
+ entities[category] = sorted(list(set(unique_entities)))
307
+
308
+ return entities
309
+
310
+ def extract_anonymized_codes(self, text):
311
+ """استخراج کدهای ناشناس‌سازی"""
312
+ if not text or text.strip() == '':
313
+ return {}
314
+
315
+ codes = {}
316
+ # الگو براساس سیستم ناشناس‌سازی: category_number
317
+ pattern = r'([a-zA-Z_]+)_(\d{3})'
318
+
319
+ try:
320
+ matches = re.finditer(pattern, text, re.IGNORECASE)
321
+ for match in matches:
322
+ category = match.group(1).upper()
323
+ code = match.group(0)
324
+
325
+ if category not in codes:
326
+ codes[category] = []
327
+ codes[category].append(code)
328
+
329
+ except Exception as e:
330
+ logger.error(f"Error extracting codes: {e}")
331
+
332
+ # حذف تکراری‌ها
333
+ for category in codes:
334
+ codes[category] = sorted(list(set(codes[category])))
335
+
336
+ return codes
337
+
338
+ # ===== کلاس Benchmark آپدیت شده =====
339
+ class UpdatedAnonymizationBenchmark:
340
+ def __init__(self):
341
+ self.extractor = UpdatedEntityExtractor()
342
+
343
+ def analyze_single_row(self, original_text, anonymized_text, row_number):
344
+ """تحلیل دقیق یک ردیف"""
345
+ print(f"\n{'='*80}")
346
+ print(f"تحلیل دقیق ردیف {row_number} (ورژن آپدیت شده)")
347
+ print(f"{'='*80}")
348
+
349
+ print(f"\n📝 متن اصلی ({len(original_text)} کاراکتر):")
350
+ print(f"'{original_text[:150]}{'...' if len(original_text) > 150 else ''}'")
351
+
352
+ print(f"\n🔒 متن ناشناس‌سازی شده ({len(anonymized_text)} کاراکتر):")
353
+ print(f"'{anonymized_text[:150]}{'...' if len(anonymized_text) > 150 else ''}'")
354
+
355
+ # استخراج entities از متن اصلی
356
+ print(f"\n🔍 Entities دقیق استخراج شده از متن اصلی (الگوهای آپدیت شده):")
357
+ original_entities = self.extractor.extract_entities(original_text)
358
+
359
+ total_original_entities = 0
360
+ for category, entities in original_entities.items():
361
+ print(f"\n 📊 {category} ({len(entities)} عدد):")
362
+ for i, entity in enumerate(entities, 1):
363
+ print(f" {i}. '{entity}'")
364
+ total_original_entities += len(entities)
365
+
366
+ if not original_entities:
367
+ print(" ❌ هیچ entity ای یافت نشد!")
368
+ else:
369
+ print(f"\n✅ مجموع entities یافت شده: {total_original_entities}")
370
+
371
+ # استخراج کدهای ناشناس‌سازی
372
+ print(f"\n🔒 کدهای ناشناس‌سازی:")
373
+ anonymized_codes = self.extractor.extract_anonymized_codes(anonymized_text)
374
+
375
+ total_anonymized_codes = 0
376
+ for category, codes in anonymized_codes.items():
377
+ print(f"\n 🔑 {category} ({len(codes)} عدد):")
378
+ for i, code in enumerate(codes, 1):
379
+ print(f" {i}. '{code}'")
380
+ total_anonymized_codes += len(codes)
381
+
382
+ if not anonymized_codes:
383
+ print(" ❌ هیچ کد ناشناس‌سازی یافت نشد!")
384
+ else:
385
+ print(f"\n✅ مجموع کدهای ناشناس‌سازی: {total_anonymized_codes}")
386
+
387
+ # تطبیق دقیق entities و codes
388
+ print(f"\n📄 تطبیق Entities با کدهای ناشناس‌سازی:")
389
+ all_categories = set(original_entities.keys()) | set(anonymized_codes.keys())
390
+
391
+ for category in sorted(all_categories):
392
+ orig_count = len(original_entities.get(category, []))
393
+ anon_count = len(anonymized_codes.get(category, []))
394
+
395
+ print(f"\n 📈 {category}:")
396
+ print(f" Entities اصلی: {orig_count}")
397
+ print(f" کدهای ناشناس‌سازی: {anon_count}")
398
+
399
+ if orig_count > 0:
400
+ print(f" لیست اصلی: {original_entities[category]}")
401
+ if anon_count > 0:
402
+ print(f" لیست کدها: {anonymized_codes[category]}")
403
+
404
+ # وضعیت تطبیق
405
+ if orig_count == anon_count:
406
+ print(f" وضعیت: ✅ تطبیق کامل")
407
+ elif orig_count > anon_count:
408
+ print(f" وضعیت: ⚠️ {orig_count - anon_count} entity از دست رفته")
409
+ else:
410
+ print(f" وضعیت: ⚠️ {anon_count - orig_count} کد اضافی")
411
+
412
+ # محاسبه متریک‌ها
413
+ category_metrics = {}
414
+ total_tp, total_fp, total_fn = 0, 0, 0
415
+
416
+ for category in all_categories:
417
+ original_count = len(original_entities.get(category, []))
418
+ anonymized_count = len(anonymized_codes.get(category, []))
419
+
420
+ tp = min(original_count, anonymized_count)
421
+ fp = max(0, anonymized_count - original_count)
422
+ fn = max(0, original_count - anonymized_count)
423
+
424
+ precision = tp / (tp + fp) if (tp + fp) > 0 else 0
425
+ recall = tp / (tp + fn) if (tp + fn) > 0 else 0
426
+ f1_score = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
427
+
428
+ category_metrics[category] = {
429
+ 'original_count': original_count, 'anonymized_count': anonymized_count,
430
+ 'tp': tp, 'fp': fp, 'fn': fn,
431
+ 'precision': precision, 'recall': recall, 'f1_score': f1_score
432
+ }
433
+
434
+ total_tp += tp
435
+ total_fp += fp
436
+ total_fn += fn
437
+
438
+ # متریک‌های کلی
439
+ overall_precision = total_tp / (total_tp + total_fp) if (total_tp + total_fp) > 0 else 0
440
+ overall_recall = total_tp / (total_tp + total_fn) if (total_tp + total_fn) > 0 else 0
441
+ overall_f1 = 2 * (overall_precision * overall_recall) / (overall_precision + overall_recall) if (overall_precision + overall_recall) > 0 else 0
442
+ accuracy = total_tp / total_original_entities if total_original_entities > 0 else 0
443
+
444
+ print(f"\n🎯 متریک‌های نهایی ردیف {row_number} (الگوهای آپدیت شده):")
445
+ print(f" TP: {total_tp}, FP: {total_fp}, FN: {total_fn}")
446
+ print(f" Precision: {overall_precision:.4f}")
447
+ print(f" Recall: {overall_recall:.4f}")
448
+ print(f" F1-Score: {overall_f1:.4f}")
449
+ print(f" Accuracy: {accuracy:.4f}")
450
+
451
+ return {
452
+ 'original_entities': original_entities,
453
+ 'anonymized_codes': anonymized_codes,
454
+ 'category_metrics': category_metrics,
455
+ 'overall_metrics': {
456
+ 'total_original_entities': total_original_entities,
457
+ 'total_anonymized_entities': total_anonymized_codes,
458
+ 'total_tp': total_tp, 'total_fp': total_fp, 'total_fn': total_fn,
459
+ 'precision': overall_precision, 'recall': overall_recall,
460
+ 'f1_score': overall_f1, 'accuracy': accuracy
461
+ }
462
+ }
463
+
464
+ def process_csv(self, csv_file_path):
465
+ """پردازش فایل CSV"""
466
+ try:
467
+ # خواندن فایل
468
+ df = None
469
+ for encoding in ['utf-8', 'utf-8-sig', 'cp1256', 'windows-1256']:
470
+ try:
471
+ df = pd.read_csv(csv_file_path, encoding=encoding)
472
+ print(f"✅ فایل با encoding {encoding} خوانده شد")
473
+ break
474
+ except UnicodeDecodeError:
475
+ continue
476
+
477
+ if df is None:
478
+ return "❌ خطا: نمی‌توان فایل را خواند"
479
+
480
+ print(f"\n📋 اطلاعات فایل CSV:")
481
+ print(f" تعداد ردیف‌ها: {len(df)}")
482
+ print(f" ستون‌ها: {df.columns.tolist()}")
483
+
484
+ # بررسی ستون‌ها
485
+ if 'original_text' not in df.columns or 'anonymized_text' not in df.columns:
486
+ return f"❌ خطا: فایل باید شامل ستون‌های 'original_text' و 'anonymized_text' باشد"
487
+
488
+ if len(df) == 0:
489
+ return "❌ خطا: فایل خالی است"
490
+
491
+ # پردازش هر ردیف
492
+ results = []
493
+ all_analysis = []
494
+
495
+ for index, row in df.iterrows():
496
+ print(f"\n📄 پردازش ردیف {index + 1} از {len(df)}")
497
+
498
+ original_text = str(row['original_text']) if pd.notna(row['original_text']) else ""
499
+ anonymized_text = str(row['anonymized_text']) if pd.notna(row['anonymized_text']) else ""
500
+
501
+ if original_text.strip() == "" and anonymized_text.strip() == "":
502
+ print("⚠️ ردیف خالی است، رد می‌شود")
503
+ continue
504
+
505
+ # تحلیل دقیق
506
+ analysis = self.analyze_single_row(original_text, anonymized_text, index + 1)
507
+ all_analysis.append(analysis)
508
+
509
+ # ذخیره نتیجه
510
+ result = {
511
+ 'row_id': int(index),
512
+ 'original_text': original_text,
513
+ 'anonymized_text': anonymized_text,
514
+ **{k: convert_to_serializable(v) for k, v in analysis['overall_metrics'].items()}
515
+ }
516
+
517
+ # اضافه کردن متریک‌های category
518
+ for category, metrics in analysis['category_metrics'].items():
519
+ for metric_name, value in metrics.items():
520
+ result[f'{category.lower()}_{metric_name}'] = convert_to_serializable(value)
521
+
522
+ results.append(result)
523
+
524
+ if not results:
525
+ return "❌ خطا: هیچ ردیف معتبری برای پردازش یافت نشد"
526
+
527
+ return pd.DataFrame(results), all_analysis
528
+
529
+ except Exception as e:
530
+ return f"❌ خطا در پردازش: {str(e)}"
531
+
532
+ # ===== رابط Gradio آپدیت شده =====
533
+ def process_uploaded_file(file):
534
+ """پردازش فایل آپلود شده"""
535
+ if file is None:
536
+ return "❌ لطفاً ابتدا فایل CSV را آپلود کنید.", None
537
+
538
+ print(f"\n🚀 شروع تحلیل دقیق فایل (ورژن آپدیت شده): {file.name}")
539
+
540
+ benchmark = UpdatedAnonymizationBenchmark()
541
+ result = benchmark.process_csv(file.name)
542
+
543
+ if isinstance(result, str):
544
+ return result, None
545
+
546
+ results_df, all_analysis = result
547
+
548
+ # تولید گزارش نهایی
549
+ total_rows = len(results_df)
550
+
551
+ # محاسبه آمار کلی
552
+ avg_precision = results_df['precision'].mean() if 'precision' in results_df.columns else 0
553
+ avg_recall = results_df['recall'].mean() if 'recall' in results_df.columns else 0
554
+ avg_f1 = results_df['f1_score'].mean() if 'f1_score' in results_df.columns else 0
555
+ avg_accuracy = results_df['accuracy'].mean() if 'accuracy' in results_df.columns else 0
556
+
557
+ total_original = results_df['total_original_entities'].sum() if 'total_original_entities' in results_df.columns else 0
558
+ total_anonymized = results_df['total_anonymized_entities'].sum() if 'total_anonymized_entities' in results_df.columns else 0
559
+ total_tp = results_df['total_tp'].sum() if 'total_tp' in results_df.columns else 0
560
+ total_fp = results_df['total_fp'].sum() if 'total_fp' in results_df.columns else 0
561
+ total_fn = results_df['total_fn'].sum() if 'total_fn' in results_df.columns else 0
562
+
563
+ # گزارش نهایی
564
+ report = f"""
565
+ {'='*80}
566
+ 🎯 گزارش نهایی Benchmark آپدیت شده - براساس سیستم ناشناس‌سازی بهبود یافته
567
+ {'='*80}
568
+
569
+ 📈 آمار کلی (الگوهای آپدیت شده براساس سیستم جامع):
570
+ • تعداد ردیف‌های پردازش شده: {total_rows}
571
+ • مجموع Entities دقیق در همه ردیف‌ها: {total_original}
572
+ • مجموع کدهای ناشناس‌سازی: {total_anonymized}
573
+ • True Positives (درست شناسایی شده): {total_tp}
574
+ • False Positives (اشتباه شناسایی شده): {total_fp}
575
+ • False Negatives (از دست رفته): {total_fn}
576
+
577
+ 🎯 متریک‌های میانگین:
578
+ • Precision: {avg_precision:.4f}
579
+ • Recall: {avg_recall:.4f}
580
+ • F1-Score: {avg_f1:.4f}
581
+ • Accuracy: {avg_accuracy:.4f}
582
+
583
+ 🆕 بهبودهای آپدیت شده:
584
+ • آدرس کامل شامل میدان، برج، طبقه و واحد
585
+ • نام اشخاص با عنوان خانم و مدیر مالی
586
+ • شرکت‌های پیچیده (پردازش داده‌های ایرانیان)
587
+ • شماره فاکتور و اسناد رسمی
588
+ • تلفن ثابت شهری (021-)
589
+ • فیلتر کلمات عمومی ("همین بانک", "شرکت متقاضی")
590
+ • اولویت‌بندی بهتر پردازش entities
591
+ • جداسازی دقیق کد ملی از شماره تلفن
592
+
593
+ ✅ الگوهای regex دقیق‌تر و جامع‌تر شده‌اند!
594
+ ✅ entities اضافی و غلط حذف شده‌اند
595
+ ✅ فقط entities واقعی و معنادار شناسایی می‌شوند
596
+ ✅ تطبیق بهتر با سیستم ناشناس‌سازی پیشرفته
597
+ """
598
+
599
+ # ذخیره نتایج
600
+ try:
601
+ results_df.to_csv("updated_benchmark_results.csv", index=False, encoding='utf-8-sig')
602
+ print("✅ نتایج آپدیت شده در فایل updated_benchmark_results.csv ذخیره شد")
603
+ except Exception as e:
604
+ print(f"⚠️ خطا در ذخیره فایل: {e}")
605
+
606
+ # ستون‌های مهم برای نمایش
607
+ display_columns = ['row_id', 'total_original_entities', 'total_anonymized_entities',
608
+ 'total_tp', 'total_fp', 'total_fn', 'precision', 'recall', 'f1_score', 'accuracy']
609
+
610
+ display_df = results_df[[col for col in display_columns if col in results_df.columns]]
611
+
612
+ return report, display_df
613
+
614
+ def download_results():
615
+ """دانلود نتایج"""
616
+ if os.path.exists("updated_benchmark_results.csv"):
617
+ return "updated_benchmark_results.csv"
618
+ return None
619
+
620
+ # ===== رابط اصلی =====
621
+ def main():
622
+ with gr.Blocks(title="آپدیت شده: Ultra Precise Benchmark", theme=gr.themes.Soft()) as demo:
623
+
624
+ gr.HTML("""
625
+ <h1 style='text-align: center; color: #2E86AB; margin-bottom: 30px;'>
626
+ 🔄 سیستم Benchmark آپدیت شده - براساس الگوهای ناشناس‌سازی پیشرفته
627
+ </h1>
628
+ """)
629
+
630
+ with gr.Row():
631
+ with gr.Column():
632
+ gr.HTML("""
633
+ <div style='background: #e8f5e8; padding: 15px; border-radius: 10px; margin-bottom: 15px;'>
634
+ <h3>🆕 ویژگی‌های آپدیت شده:</h3>
635
+ <ul>
636
+ <li><b>آدرس کامل:</b> تهران، میدان ونک، برج میلاد، طبقه 15، واحد 1503</li>
637
+ <li><b>اسامی کامل:</b> خانم زهرا احمدی، مدیر مالی خانم احمدی</li>
638
+ <li><b>شرکت‌های پیچیده:</b> شرکت پردازش داده‌های ایرانیان</li>
639
+ <li><b>شماره فاکتور:</b> INV-2024-0156</li>
640
+ <li><b>دفاتر اسناد:</b> دفتر اسناد رسمی شماره 1247 تهران</li>
641
+ <li><b>تلفن ثابت:</b> 021-88776655</li>
642
+ <li><b>فیلتر هوشمند:</b> حذف "همین بانک"، "شرکت متقاضی"</li>
643
+ <li><b>اولویت‌بندی:</b> آدرس کامل قبل از قطعات</li>
644
+ <li><b>جداسازی دقیق:</b> کد ملی از شماره تلفن</li>
645
+ </ul>
646
+ </div>
647
+ """)
648
+
649
+ file_input = gr.File(
650
+ label="📁 فایل CSV خود را آپلود کنید",
651
+ file_types=[".csv"],
652
+ file_count="single"
653
+ )
654
+
655
+ process_btn = gr.Button("🔄 تحلیل با الگوهای آپدیت شده", variant="primary", size="lg")
656
+
657
+ with gr.Row():
658
+ with gr.Column():
659
+ gr.HTML("<h3>📊 گزارش آپدیت شده + لیست Entities صحیح</h3>")
660
+
661
+ results_output = gr.Textbox(
662
+ label="گزارش کامل تحلیل خطاها و مشکلات",
663
+ lines=30,
664
+ max_lines=35,
665
+ interactive=False
666
+ )
667
+
668
+ with gr.Row():
669
+ with gr.Column():
670
+ gr.HTML("<h3>📋 جدول نتایج آپدیت شده</h3>")
671
+
672
+ results_table = gr.Dataframe(
673
+ label="متریک‌های دقیق هر ردیف (ورژن جدید)",
674
+ interactive=False,
675
+ wrap=True
676
+ )
677
+
678
+ with gr.Row():
679
+ with gr.Column():
680
+ download_btn = gr.Button("💾 دانلود نتایج آپدیت شده", variant="secondary")
681
+ download_file = gr.File(label="فایل نتایج آپدیت شده", visible=False)
682
+
683
+ # Event handlers
684
+ process_btn.click(
685
+ fn=process_uploaded_file,
686
+ inputs=[file_input],
687
+ outputs=[results_output, results_table]
688
+ )
689
+
690
+ download_btn.click(
691
+ fn=download_results,
692
+ outputs=[download_file]
693
+ )
694
+
695
+ download_btn.click(
696
+ fn=lambda: gr.update(visible=True),
697
+ outputs=[download_file]
698
+ )
699
+
700
+ return demo
701
+
702
+ demo = main()
703
+
704
+ if __name__ == "__main__":
705
+ port = int(os.getenv("PORT", "7860"))
706
+ demo.launch(
707
+ share=False,
708
+ server_name="0.0.0.0",
709
+ server_port=port,
710
+ show_error=True
711
+ )