| | import gradio as gr |
| | import pandas as pd |
| | import re |
| |
|
| | class AnonymizationEvaluator: |
| | def __init__(self): |
| | |
| | self.iranian_banks = [ |
| | 'بانک ملی', 'بانک صادرات', 'بانک پاسارگاد', 'بانک کشاورزی', |
| | 'بانک ملت', 'بانک تجارت', 'بانک صنعت و معدن', 'بانک رسالت', |
| | 'بانک دی', 'بانک پارسیان', 'بانک کارآفرین', 'بانک سامان', |
| | 'بانک اقتصاد نوین', 'بانک مهر اقتصاد', 'بانک آینده', 'بانک کشاورزی' |
| | ] |
| | |
| | |
| | self.government_orgs = [ |
| | 'بانک مرکزی جمهوری اسلامی ایران', |
| | 'دفتر اسناد رسمی', |
| | 'اداره کل مالیات', |
| | 'تامین اجتماعی', |
| | 'وزارت دادگستری' |
| | ] |
| | |
| | self.patterns = { |
| | 'person_names': { |
| | 'pattern': re.compile(r'(?:آقای|خانم)\s+[\u0600-\u06FF\s]+?(?=\s+با|$)', re.UNICODE), |
| | 'replacement': re.compile(r'person_\d+'), |
| | 'name': 'اسامی اشخاص' |
| | }, |
| | 'national_ids': { |
| | 'pattern': re.compile(r'\b\d{10,11}\b'), |
| | 'replacement': re.compile(r'id_number_\d+'), |
| | 'name': 'کدهای ملی' |
| | }, |
| | 'phone_numbers': { |
| | 'pattern': re.compile(r'(?:09\d{9}|021-\d{8}|0\d{2,3}-\d{8})'), |
| | 'replacement': re.compile(r'phone_\d+'), |
| | 'name': 'شماره تلفنها' |
| | }, |
| | 'account_numbers': { |
| | 'pattern': re.compile(r'\b\d{3}-\d{3}-\d{3}-\d{1}\b'), |
| | 'replacement': re.compile(r'account_\d+'), |
| | 'name': 'شماره حسابها' |
| | }, |
| | 'card_numbers': { |
| | 'pattern': re.compile(r'\b\d{4}-\d{4}-\d{4}-\d{4}\b'), |
| | 'replacement': re.compile(r'card_number_\d+'), |
| | 'name': 'شماره کارتها' |
| | }, |
| | 'amounts': { |
| | 'pattern': re.compile(r'\b\d+\s*تومان'), |
| | 'replacement': re.compile(r'amount_\d+'), |
| | 'name': 'مبالغ مالی' |
| | }, |
| | 'dates': { |
| | 'pattern': re.compile(r'(?:\d{4}\/\d{2}\/\d{2}|۳۰\s*اسفند\s*۱۴۰۳|\b\d{4}\b(?=\s*سال))'), |
| | 'replacement': re.compile(r'date_\d+'), |
| | 'name': 'تاریخها' |
| | }, |
| | 'iranian_banks': { |
| | 'pattern': re.compile(f"({'|'.join(self.iranian_banks)})", re.UNICODE), |
| | 'replacement': re.compile(r'company_\d+'), |
| | 'name': 'بانکهای ایران' |
| | }, |
| | 'government_orgs': { |
| | 'pattern': re.compile(f"({'|'.join(self.government_orgs)})", re.UNICODE), |
| | 'replacement': re.compile(r'company_\d+'), |
| | 'name': 'سازمانهای دولتی' |
| | }, |
| | 'other_companies': { |
| | 'pattern': re.compile(r'شرکت\s+[\u0600-\u06FF\s]+?(?=\s|$|،|\.)', re.UNICODE), |
| | 'replacement': re.compile(r'company_\d+'), |
| | 'name': 'سایر شرکتها' |
| | }, |
| | 'addresses': { |
| | 'pattern': re.compile(r'(?:تهران|کرج|اصفهان|بندر\s*ماهشهر)[،\s]+[^،\.]+', re.UNICODE), |
| | 'replacement': re.compile(r'(?:full_address_\d+|location_\d+)'), |
| | 'name': 'آدرسها' |
| | }, |
| | 'documents': { |
| | 'pattern': re.compile(r'(?:INV-\d{4}-\d{4}|RPT-\d{4}-\d{4}|\b\d{4}(?=\s+تهران)|\b\d{7}\b)'), |
| | 'replacement': re.compile(r'(?:invoice_\d+|report_\d+|contract_\d+|cheque_\d+)'), |
| | 'name': 'شماره اسناد' |
| | } |
| | } |
| | |
| | def analyze_entities(self, original_text, anonymized_text): |
| | results = {} |
| | for entity_type, config in self.patterns.items(): |
| | original_matches = config['pattern'].findall(original_text) |
| | replacement_matches = config['replacement'].findall(anonymized_text) |
| | |
| | anonymized_count = 0 |
| | for match in original_matches: |
| | if not anonymized_text.count(match.strip()): |
| | anonymized_count += 1 |
| | |
| | if len(replacement_matches) > anonymized_count: |
| | anonymized_count = min(len(replacement_matches), len(original_matches)) |
| | |
| | percentage = (anonymized_count / len(original_matches) * 100) if original_matches else 0 |
| | |
| | results[entity_type] = { |
| | 'name': config['name'], |
| | 'total': len(original_matches), |
| | 'anonymized': anonymized_count, |
| | 'percentage': round(percentage, 1), |
| | 'samples': original_matches[:3] if original_matches else [] |
| | } |
| | return results |
| | |
| | def evaluate_csv(self, csv_file): |
| | try: |
| | if csv_file is None: |
| | return "لطفاً یک فایل CSV آپلود کنید." |
| | |
| | try: |
| | df = pd.read_csv(csv_file.name, encoding='utf-8') |
| | except: |
| | df = pd.read_csv(csv_file.name, encoding='utf-8', sep='\t') |
| | |
| | if 'original_text' not in df.columns or 'anonymized_text' not in df.columns: |
| | return "فایل CSV باید شامل ستونهای 'original_text' و 'anonymized_text' باشد." |
| | |
| | overall_stats = {} |
| | total_entities = 0 |
| | total_anonymized = 0 |
| | |
| | for _, row in df.iterrows(): |
| | if pd.isna(row['original_text']) or pd.isna(row['anonymized_text']): |
| | continue |
| | |
| | row_analysis = self.analyze_entities(str(row['original_text']), str(row['anonymized_text'])) |
| | |
| | for entity_type, data in row_analysis.items(): |
| | if entity_type not in overall_stats: |
| | overall_stats[entity_type] = { |
| | 'name': data['name'], |
| | 'total': 0, |
| | 'anonymized': 0, |
| | 'samples': [] |
| | } |
| | |
| | overall_stats[entity_type]['total'] += data['total'] |
| | overall_stats[entity_type]['anonymized'] += data['anonymized'] |
| | overall_stats[entity_type]['samples'].extend(data['samples']) |
| | |
| | total_entities += data['total'] |
| | total_anonymized += data['anonymized'] |
| | |
| | for entity_type in overall_stats: |
| | stats = overall_stats[entity_type] |
| | stats['percentage'] = round((stats['anonymized'] / stats['total'] * 100) if stats['total'] > 0 else 0, 1) |
| | stats['samples'] = list(set(stats['samples']))[:3] |
| | |
| | return self.generate_report(overall_stats, total_entities, total_anonymized, len(df)) |
| | |
| | except Exception as e: |
| | return f"خطا در پردازش فایل: {str(e)}" |
| | |
| | def generate_report(self, stats, total_entities, total_anonymized, total_rows): |
| | report = f"""# گزارش ارزیابی ناشناسسازی متن |
| | |
| | ## خلاصه کلی |
| | - **تعداد ردیفهای پردازش شده**: {total_rows:,} ردیف |
| | - **تعداد موجودیتهای حساس شناسایی شده**: {total_entities:,} مورد |
| | - **تعداد موجودیتهای ناشناس شده**: {total_anonymized:,} مورد |
| | - **درصد پوشش کلی**: {(total_anonymized/total_entities*100) if total_entities > 0 else 0:.1f}% |
| | |
| | ## تحلیل تفصیلی دستهبندی موجودیتها |
| | |
| | """ |
| | |
| | excellent = [] |
| | good = [] |
| | poor = [] |
| | not_found = [] |
| | |
| | for entity_type, data in stats.items(): |
| | if data['total'] == 0: |
| | not_found.append((entity_type, data)) |
| | elif data['percentage'] == 100: |
| | excellent.append((entity_type, data)) |
| | elif data['percentage'] >= 80: |
| | good.append((entity_type, data)) |
| | else: |
| | poor.append((entity_type, data)) |
| | |
| | if excellent: |
| | report += "### ✅ عملکرد عالی (100% موفقیت)\n" |
| | for entity_type, data in excellent: |
| | report += f"- **{data['name']}**: {data['anonymized']}/{data['total']} (100%)\n" |
| | report += "\n" |
| | |
| | if good: |
| | report += "### 🟡 عملکرد خوب (80-99% موفقیت)\n" |
| | for entity_type, data in good: |
| | report += f"- **{data['name']}**: {data['anonymized']}/{data['total']} ({data['percentage']}%)\n" |
| | report += "\n" |
| | |
| | if poor: |
| | report += "### 🔴 عملکرد ضعیف (<80% موفقیت)\n" |
| | for entity_type, data in poor: |
| | missed = data['total'] - data['anonymized'] |
| | report += f"- **{data['name']}**: {data['anonymized']}/{data['total']} ({data['percentage']}%) - {missed} مورد جا مانده\n" |
| | report += "\n" |
| | |
| | if not_found: |
| | report += "### ⚪ موجودیتهای یافت نشده\n" |
| | for entity_type, data in not_found: |
| | report += f"- **{data['name']}**: هیچ موجودیتی یافت نشد\n" |
| | report += "\n" |
| | |
| | report += "## جدول خلاصه متریکها\n\n" |
| | report += "| دسته موجودیت | یافته شده | ناشناس شده | درصد موفقیت | موارد جا مانده |\n" |
| | report += "|---------------|-----------|-------------|-------------|----------------|\n" |
| | |
| | for entity_type, data in stats.items(): |
| | if data['total'] > 0: |
| | missed = data['total'] - data['anonymized'] |
| | report += f"| {data['name']} | {data['total']} | {data['anonymized']} | {data['percentage']}% | {missed} |\n" |
| | |
| | major_issues = [(k, v) for k, v in stats.items() if v['total'] > 0 and v['percentage'] < 80] |
| | major_issues.sort(key=lambda x: x[1]['total'] - x[1]['anonymized'], reverse=True) |
| | |
| | if major_issues: |
| | report += "\n## 🚨 مشکلات اصلی شناسایی شده\n\n" |
| | for i, (entity_type, data) in enumerate(major_issues, 1): |
| | missed = data['total'] - data['anonymized'] |
| | impact = round(missed / total_entities * 100, 1) if total_entities > 0 else 0 |
| | report += f"### {i}. {data['name']}\n" |
| | report += f"- **وضعیت**: {data['percentage']}% موفقیت\n" |
| | report += f"- **موارد جا مانده**: {missed} مورد از {data['total']} مورد\n" |
| | report += f"- **تاثیر بر کل**: {impact}% از کل موجودیتها\n\n" |
| | |
| | precision = round((total_anonymized / total_entities * 100) if total_entities > 0 else 0, 1) |
| | |
| | report += f"""## 📊 آمار نهایی |
| | |
| | - **کل موجودیتهای شناسایی شده**: {total_entities:,} |
| | - **کل موجودیتهای ناشناس شده**: {total_anonymized:,} |
| | - **موجودیتهای جا مانده**: {total_entities - total_anonymized:,} |
| | - **دقت (Precision)**: {precision}% |
| | - **پوشش (Recall)**: {precision}% |
| | - **امتیاز F1**: {precision}% |
| | """ |
| | |
| | return report |
| |
|
| | def create_interface(): |
| | evaluator = AnonymizationEvaluator() |
| | |
| | def process_file(csv_file): |
| | if csv_file is None: |
| | return "لطفاً یک فایل CSV آپلود کنید." |
| | return evaluator.evaluate_csv(csv_file) |
| | |
| | with gr.Blocks(title="ارزیاب متریکهای ناشناسسازی") as demo: |
| | gr.Markdown("# ارزیاب متریکهای ناشناسسازی متن فارسی") |
| | |
| | file_input = gr.File(label="آپلود فایل CSV", file_types=[".csv"]) |
| | analyze_btn = gr.Button("محاسبه متریکها", variant="primary") |
| | output = gr.Markdown(value="فایل CSV خود را آپلود کنید.") |
| | |
| | analyze_btn.click(fn=process_file, inputs=[file_input], outputs=[output]) |
| | |
| | return demo |
| |
|
| | if __name__ == "__main__": |
| | app = create_interface() |
| | app.launch() |