leilaghomashchi commited on
Commit
6291d8a
·
verified ·
1 Parent(s): e453a66

Upload app_batch_fixed (1).py

Browse files
Files changed (1) hide show
  1. app_batch_fixed (1).py +740 -0
app_batch_fixed (1).py ADDED
@@ -0,0 +1,740 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ import gradio as gr
4
+ from typing import Dict, Any, List, Optional
5
+ import os
6
+ from dataclasses import dataclass
7
+ import re
8
+ import pandas as pd
9
+ import time
10
+ from io import StringIO
11
+
12
+ @dataclass
13
+ class CerebrasConfig:
14
+ """تنظیمات Cerebras API"""
15
+ api_key: str
16
+ base_url: str = "https://api.cerebras.ai/v1"
17
+ model: str = "llama-3.3-70b"
18
+ max_tokens: int = 2000
19
+ temperature: float = 0.1
20
+
21
+ class AdvancedCerebrasAnonymizer:
22
+ """سیستم پیشرفته ناشناس‌سازی متون مالی/خبری فارسی"""
23
+
24
+ def __init__(self, api_key: str = None):
25
+ if api_key is None:
26
+ api_key = os.getenv("CEREBRAS_API_KEY")
27
+ if not api_key:
28
+ raise ValueError("کلید API یافت نشد")
29
+
30
+ self.config = CerebrasConfig(api_key=api_key)
31
+ self.system_prompt = self._create_advanced_system_prompt()
32
+
33
+ def _create_advanced_system_prompt(self) -> str:
34
+ """ایجاد دستورالعمل سیستمی پیشرفته برای Cerebras"""
35
+ return """شما یک «ناشناس‌ساز متون مالی/خبری فارسی» هستید. وظیفه‌تان جایگزینی اسامی خاص و مقادیر عددی با شناسه‌های بی‌معناست.
36
+
37
+ ## **قوانین اندیس‌گذاری - CRITICAL**
38
+ ### **1. ترتیب شماره‌گذاری الزامی:**
39
+ - شرکت‌ها: company-01, company-02, company-03, company-04, ... (پیوسته و بدون گپ)
40
+ - اشخاص: person-01, person-02, person-03, ... (پیوسته و بدون گپ)
41
+ - اعداد: amount-01, amount-02, amount-03, ... (پیوسته و بدون گپ)
42
+ - درصدها: percent-01, percent-02, percent-03, ... (پیوسته و بدون گپ)
43
+
44
+ ### **2. ثبات شناسه‌ها در متن:**
45
+ - اگر "همراه اول" اول‌بار company-01 شد، در تمام متن همان باشد
46
+ - اگر "مهدی احمدی" اول‌بار person-01 شد، در تمام متن همان باشد
47
+
48
+ ### **3. تشخیص صحیح انواع:**
49
+ **شرکت/سازمان:** همراه اول، بانک ملی، ایران‌خودرو، سایپا، بانک مرکزی، سامانه کدال، وزارت نفت، سازمان تنظیم مقررات رادیویی، سازمان تامین اجتماعی
50
+ **⚠️ CRITICAL - گروه‌ها:** "گروه همراه اول"، "گروه اقتصادی آزادگان"، "گروه مالی صبا" → همه company-XX هستند (نه group-XX)
51
+ **⚠️ CRITICAL - کلمات عمومی:** "سه شرکت دارویی"، "چند بانک"، "یک شرکت" → کلمات عمومی هستند، موجودیت نیستند (حفظ شوند)
52
+ **⚠️ CRITICAL - نام‌های مستعار:** "فاما" همان "فولاد مبارکه اصفهان" است → هر دو company-01
53
+ **شخص:** مهدی اخوان بهابادی، محمدرضا فرزین، ابوالفضل نجارزاده
54
+ **عدد:** 37، 70، 677، 73.7، 178 (هر عددی)
55
+ **درصد:** 37 درصدی، 15 درصدی، 53 درصد، 43%
56
+
57
+ ## **مثال‌های صحیح:**
58
+
59
+ ### **مثال 1 (الگوی کامل):**
60
+ **ورودی:** مهدی اخوان بهابادی، مدیرعامل همراه اول، اعلام کرد درآمد عملیاتی شرکت با رشد 37 درصدی به 70 هزار و 677 میلیارد تومان رسیده است. سود خالص 7101 میلیارد تومان و تلفیقی گروه همراه اول 8003 میلیارد تومان شد.
61
+ **خروجی صحیح:** person-01، مدیرعامل company-01، اعلام کرد درآمد عملیاتی شرکت با رشد percent-01 به amount-01 رسیده است. سود خالص amount-02 و تلفیقی گروه company-01 amount-03 شد.
62
+
63
+ ### **مثال 2:**
64
+ **ورودی:** بانک مرکزی و بانک ملی با همکاری محمدرضا فرزین، 60 درصد سپرده‌ها را مدیریت کردند.
65
+ **خروجی:** company-01 و company-02 با همکاری person-01، percent-01 سپرده‌ها را مدیریت کردند.
66
+
67
+ ## **موارد حفظ شده:**
68
+ - تاریخ‌ها: 1404/04/23، 30 آذر 1403، پاییز 1401
69
+ - فصل‌های سال: پاییز، بهار، تابستان، زمستان
70
+ - عناوین شغلی: مدیرعامل، رئیس کل، مدیرکل
71
+ - واحدها: میلیارد تومان، همت، ریال، ماه، سال
72
+ - مکان‌ها: تهران، اصفهان، ایران
73
+ - کلمات عمومی: "سه شرکت دارویی"، "چند بانک"، "یک شرکت"، "مراکز درمانی"
74
+ - دوره‌های زمانی: "۵ ماهه سال"، "۹ ماهه"، "۳ ماهه اول"
75
+
76
+ ## **ممنوع:**
77
+ - کلمات انگلیسی اضافی
78
+ - تغییر ساختار جمله
79
+ - حذف یا اضافه کردن کلمات
80
+ - استفاده از group-XX - همه گروه‌ها باید company-XX باشند
81
+
82
+ **فقط متن ناشناس‌شده را برگردان - هیچ توضیح اضافی نیاز نیست.**
83
+ """
84
+
85
+ def _make_api_request(self, text: str, max_retries: int = 5) -> Dict[str, Any]:
86
+ """ارسال درخواست به Cerebras API با retry logic"""
87
+ headers = {
88
+ "Authorization": f"Bearer {self.config.api_key}",
89
+ "Content-Type": "application/json"
90
+ }
91
+
92
+ payload = {
93
+ "messages": [
94
+ {"role": "system", "content": self.system_prompt},
95
+ {"role": "user", "content": text}
96
+ ],
97
+ "model": self.config.model,
98
+ "temperature": self.config.temperature,
99
+ "max_tokens": self.config.max_tokens
100
+ }
101
+
102
+ for attempt in range(max_retries):
103
+ try:
104
+ response = requests.post(
105
+ f"{self.config.base_url}/chat/completions",
106
+ headers=headers,
107
+ json=payload,
108
+ timeout=60
109
+ )
110
+ response.raise_for_status()
111
+ return response.json()
112
+
113
+ except requests.exceptions.RequestException as e:
114
+ if "429" in str(e):
115
+ # Rate limit - exponential backoff
116
+ wait_time = (2 ** attempt) * 10 # 10, 20, 40, 80, 160 ثانیه
117
+ print(f"⚠️ Rate limit! تلاش {attempt + 1}/{max_retries}. صبر {wait_time} ثانیه...")
118
+ time.sleep(wait_time)
119
+ if attempt == max_retries - 1:
120
+ raise Exception(f"خطا در ارتباط با Cerebras API پس از {max_retries} تلاش: {str(e)}")
121
+ else:
122
+ raise Exception(f"خطا در ارتباط با Cerebras API: {str(e)}")
123
+
124
+ def _clean_markdown(self, content: str) -> str:
125
+ """پاک کردن markdown از پاسخ"""
126
+ if "```" in content:
127
+ lines = content.split('\n')
128
+ clean_lines = []
129
+ skip = False
130
+ for line in lines:
131
+ if line.strip().startswith('```'):
132
+ skip = not skip
133
+ continue
134
+ if not skip:
135
+ clean_lines.append(line)
136
+ content = '\n'.join(clean_lines)
137
+ return content
138
+
139
+ def _analyze_anonymized_text(self, text: str) -> Dict[str, Any]:
140
+ """تحلیل متن ناشناس‌سازی شده"""
141
+ companies = re.findall(r'company-(\d+)', text)
142
+ persons = re.findall(r'person-(\d+)', text)
143
+ amounts = re.findall(r'amount-(\d+)', text)
144
+ percents = re.findall(r'percent-(\d+)', text)
145
+
146
+ statistics = {
147
+ "company": len(set(companies)),
148
+ "person": len(set(persons)),
149
+ "amount": len(set(amounts)),
150
+ "percent": len(set(percents)),
151
+ "total_replacements": len(companies) + len(persons) + len(amounts) + len(percents)
152
+ }
153
+
154
+ entities = {
155
+ "companies": sorted(list(set(companies)), key=lambda x: int(x)),
156
+ "persons": sorted(list(set(persons)), key=lambda x: int(x)),
157
+ "amounts": sorted(list(set(amounts)), key=lambda x: int(x)),
158
+ "percents": sorted(list(set(percents)), key=lambda x: int(x))
159
+ }
160
+
161
+ detailed_analysis = {
162
+ "preserved_dates": len(re.findall(r'\d{4}/\d{1,2}/\d{1,2}|\d{1,2}\s+\w+\s+\d{4}', text)),
163
+ "preserved_times": len(re.findall(r'\d{1,2}:\d{2}', text)),
164
+ "financial_indicators": len(re.findall(r'\b(EPS|P/E|ARPU|NPL|ROE|ROA)\b', text)),
165
+ "units_preserved": len(re.findall(r'(میلیارد|میلیون|هزار|تومان|ریال|درهم|دلار|یورو|تن|کیلوگرم)', text))
166
+ }
167
+
168
+ return {
169
+ "statistics": statistics,
170
+ "entities": entities,
171
+ "detailed_analysis": detailed_analysis
172
+ }
173
+
174
+ def _validate_anonymized_text(self, text: str) -> Dict[str, Any]:
175
+ """اعتبارسنجی پیشرفته متن ناشناس‌شده"""
176
+ companies = re.findall(r'company-(\d+)', text)
177
+ persons = re.findall(r'person-(\d+)', text)
178
+ amounts = re.findall(r'amount-(\d+)', text)
179
+ percents = re.findall(r'percent-(\d+)', text)
180
+
181
+ validation_issues = []
182
+
183
+ for entity_type, indices in [
184
+ ("company", companies),
185
+ ("person", persons),
186
+ ("amount", amounts),
187
+ ("percent", percents)
188
+ ]:
189
+ if indices:
190
+ unique_indices = sorted(list(set([int(x) for x in indices])))
191
+ if unique_indices[0] != 1:
192
+ validation_issues.append(f"اندیس {entity_type} از 01 شروع نشده")
193
+ expected = list(range(1, len(unique_indices) + 1))
194
+ if unique_indices != expected:
195
+ validation_issues.append(f"اندیس‌های {entity_type} پیوسته نیستند")
196
+
197
+ return {
198
+ "is_valid": len(validation_issues) == 0,
199
+ "issues": validation_issues,
200
+ "entity_counts": {
201
+ "company": len(set(companies)),
202
+ "person": len(set(persons)),
203
+ "amount": len(set(amounts)),
204
+ "percent": len(set(percents))
205
+ }
206
+ }
207
+
208
+ def anonymize_text(self, text: str) -> Dict[str, Any]:
209
+ """ناشناس‌سازی متن با استفاده از Cerebras"""
210
+ if not text or not text.strip():
211
+ return {
212
+ "success": False,
213
+ "error": "متن ورودی خالی است"
214
+ }
215
+
216
+ try:
217
+ response = self._make_api_request(text)
218
+
219
+ if "choices" not in response or not response["choices"]:
220
+ return {
221
+ "success": False,
222
+ "error": "پاسخ نامعتبر از API"
223
+ }
224
+
225
+ content = response["choices"][0]["message"]["content"]
226
+ content = self._clean_markdown(content)
227
+ content = content.strip()
228
+
229
+ analysis = self._analyze_anonymized_text(content)
230
+
231
+ return {
232
+ "success": True,
233
+ "anonymized_text": content,
234
+ "entities": analysis["entities"],
235
+ "statistics": analysis["statistics"],
236
+ "detailed_analysis": analysis["detailed_analysis"],
237
+ "usage": response.get("usage", {}),
238
+ "quality_check": self._validate_anonymized_text(content)
239
+ }
240
+
241
+ except Exception as e:
242
+ return {
243
+ "success": False,
244
+ "error": f"خطا در پردازش: {str(e)}"
245
+ }
246
+
247
+ def anonymize_batch(self, texts: List[str], progress_callback=None) -> List[Dict[str, Any]]:
248
+ """ناشناس‌سازی دسته‌ای متون"""
249
+ results = []
250
+ total = len(texts)
251
+
252
+ for idx, text in enumerate(texts):
253
+ if progress_callback:
254
+ progress_callback((idx + 1) / total, f"پردازش سطر {idx + 1} از {total}")
255
+
256
+ result = self.anonymize_text(text)
257
+ results.append(result)
258
+
259
+ # تاخیر برای جلوگیری از rate limiting
260
+ if idx < total - 1:
261
+ time.sleep(3) # افزایش به 3 ثانیه برای جلوگیری از rate limit
262
+
263
+ return results
264
+
265
+
266
+ def create_advanced_interface():
267
+ """ایجاد رابط کاربری پیشرفته با قابلیت پردازش دسته‌ای"""
268
+
269
+ api_key_available = bool(os.getenv("CEREBRAS_API_KEY"))
270
+
271
+ custom_css = """
272
+ .gradio-container {
273
+ font-family: 'Tahoma', 'Arial', sans-serif !important;
274
+ direction: rtl;
275
+ max-width: 1400px;
276
+ margin: 0 auto;
277
+ }
278
+ .result-box {
279
+ background-color: #f8f9fa;
280
+ border: 2px solid #e9ecef;
281
+ border-radius: 12px;
282
+ padding: 20px;
283
+ margin: 10px 0;
284
+ }
285
+ .warning-box {
286
+ background-color: #fff3cd;
287
+ border: 2px solid #ffeaa7;
288
+ border-radius: 12px;
289
+ padding: 15px;
290
+ color: #856404;
291
+ margin: 10px 0;
292
+ }
293
+ .success-box {
294
+ background-color: #d4edda;
295
+ border: 2px solid #c3e6cb;
296
+ border-radius: 12px;
297
+ padding: 15px;
298
+ color: #155724;
299
+ margin: 10px 0;
300
+ }
301
+ .batch-progress {
302
+ background-color: #e3f2fd;
303
+ border: 2px solid #90caf9;
304
+ border-radius: 12px;
305
+ padding: 15px;
306
+ margin: 10px 0;
307
+ }
308
+ """
309
+
310
+ with gr.Blocks(css=custom_css, title="ناشناس‌ساز پیشرفته متن فارسی با Cerebras", theme=gr.themes.Soft()) as interface:
311
+
312
+ gr.Markdown("""
313
+ # 🔒 سیستم پیشرفته ناشناس‌سازی متون مالی/خبری فارسی
314
+ ### ⚡ قدرت‌گرفته از Cerebras AI - سریع‌ترین استنباط LLM در جهان!
315
+ """)
316
+
317
+ # API Key input
318
+ if api_key_available:
319
+ gr.Markdown("""
320
+ <div class="success-box">
321
+ ✅ <strong>سیستم آماده است</strong> - کلید API تنظیم شده
322
+ </div>
323
+ """)
324
+ api_key_input = gr.Textbox(visible=False, value="")
325
+ else:
326
+ gr.Markdown("""
327
+ <div class="warning-box">
328
+ ⚠️ <strong>کلید API تنظیم نشده</strong><br>
329
+ لطفاً کلید Cerebras API خود را در زیر وارد کنید
330
+ </div>
331
+ """)
332
+ api_key_input = gr.Textbox(
333
+ label="🔑 کلید Cerebras API",
334
+ placeholder="csk-...",
335
+ type="password"
336
+ )
337
+
338
+ # تب‌های اصلی
339
+ with gr.Tabs() as tabs:
340
+
341
+ # ===============================
342
+ # تب 1: پردازش تکی
343
+ # ===============================
344
+ with gr.TabItem("📝 پردازش تکی"):
345
+ with gr.Row():
346
+ with gr.Column(scale=1):
347
+ input_text = gr.Textbox(
348
+ label="📝 متن ورودی",
349
+ placeholder="متن مالی یا خبری خود را اینجا وارد کنید...",
350
+ lines=12,
351
+ max_lines=25
352
+ )
353
+
354
+ with gr.Row():
355
+ anonymize_btn = gr.Button("🔒 ناشناس‌سازی", variant="primary", size="lg")
356
+ clear_btn = gr.Button("🗑️ پاک کردن", variant="secondary")
357
+
358
+ with gr.Column(scale=1):
359
+ output_text = gr.Textbox(
360
+ label="🎯 متن ناشناس‌سازی شده",
361
+ lines=12,
362
+ max_lines=25,
363
+ elem_classes=["result-box"]
364
+ )
365
+ copy_btn = gr.Button("📋 کپی متن", variant="secondary", size="sm")
366
+
367
+ with gr.Row():
368
+ with gr.Column(scale=1):
369
+ statistics_output = gr.Markdown(label="📊 آمار")
370
+ with gr.Column(scale=1):
371
+ quality_output = gr.Markdown(label="✅ کنترل کیفیت")
372
+
373
+ with gr.Accordion("📋 جزئیات موجودیت‌ها", open=False):
374
+ entities_output = gr.Markdown()
375
+
376
+ with gr.Accordion("📈 تحلیل تفصیلی", open=False):
377
+ detailed_analysis_output = gr.Markdown()
378
+
379
+ usage_output = gr.Markdown()
380
+ copy_output = gr.Textbox(visible=False, label="متن کپی شده")
381
+
382
+ # ===============================
383
+ # تب 2: پردازش دسته‌ای
384
+ # ===============================
385
+ with gr.TabItem("📁 پردازش دسته‌ای CSV"):
386
+ gr.Markdown("""
387
+ ### 📁 پردازش دسته‌ای فایل CSV
388
+ فایل CSV خود را آپلود کنید، ستون متن را انتخاب کنید و تمام سطرها به صورت خودکار ناشناس‌سازی می‌شوند.
389
+ """)
390
+
391
+ with gr.Row():
392
+ with gr.Column(scale=1):
393
+ csv_file = gr.File(
394
+ label="📤 آپلود فایل CSV",
395
+ file_types=[".csv"],
396
+ type="filepath"
397
+ )
398
+
399
+ text_column = gr.Dropdown(
400
+ label="📋 انتخاب ستون متن",
401
+ choices=[],
402
+ interactive=True
403
+ )
404
+
405
+ output_column_name = gr.Textbox(
406
+ label="🏷️ نام ستون خروجی",
407
+ value="anonymized_text",
408
+ placeholder="نام ستون جدید برای متن ناشناس‌شده"
409
+ )
410
+
411
+ with gr.Row():
412
+ batch_btn = gr.Button("🚀 شروع پردازش دسته‌ای", variant="primary", size="lg")
413
+ cancel_btn = gr.Button("❌ لغو", variant="stop", size="sm")
414
+
415
+ with gr.Column(scale=1):
416
+ batch_progress = gr.Markdown(
417
+ value="⏳ در انتظار آپلود فایل...",
418
+ elem_classes=["batch-progress"]
419
+ )
420
+
421
+ batch_stats = gr.Markdown(label="📊 آمار پردازش")
422
+
423
+ # پیش‌نمایش داده‌ها
424
+ with gr.Accordion("👁️ پیش‌نمایش داده‌ها", open=True):
425
+ preview_df = gr.Dataframe(
426
+ label="پیش‌نمایش 5 سطر اول",
427
+ headers=["ستون‌ها"],
428
+ interactive=False
429
+ )
430
+
431
+ # نتایج و دانلود
432
+ with gr.Row():
433
+ result_df = gr.Dataframe(
434
+ label="📊 نتایج پردازش (10 سطر اول)",
435
+ interactive=False,
436
+ visible=False
437
+ )
438
+
439
+ download_btn = gr.File(
440
+ label="📥 دانلود فایل خروجی",
441
+ visible=False
442
+ )
443
+
444
+ # لاگ خطاها
445
+ with gr.Accordion("⚠️ گزارش خطاها", open=False):
446
+ error_log = gr.Markdown()
447
+
448
+ # ===============================
449
+ # راهنما
450
+ # ===============================
451
+ with gr.Accordion("📖 راهنمای استفاده", open=False):
452
+ gr.Markdown("""
453
+ ## 🎯 ویژگی‌های سیستم:
454
+
455
+ ### 📝 پردازش تکی:
456
+ - متن خود را وارد کنید و روی دکمه ناشناس‌سازی کلیک کنید
457
+ - نتایج شامل آمار، کنترل کیفیت و جزئیات موجودیت‌ها است
458
+
459
+ ### 📁 پردازش دسته‌ای CSV:
460
+ 1. فایل CSV خود را آپلود کنید
461
+ 2. ستون حاوی متن را انتخاب کنید
462
+ 3. نام ستون خروجی را مشخص کنید
463
+ 4. روی «شروع پردازش دسته‌ای» کلیک کنید
464
+ 5. پس از اتمام، فایل خروجی را دانلود کنید
465
+
466
+ ### 🏷️ انواع برچسب‌ها:
467
+ - **company-XX:** شرکت‌ها، سازمان‌ها، برندها
468
+ - **person-XX:** اشخاص حقیقی
469
+ - **amount-XX:** اعداد و مبالغ
470
+ - **percent-XX:** درصدها
471
+
472
+ ### ⚡ نکات مهم:
473
+ - حداکثر 1000 سطر در هر فایل پشتیبانی می‌شود
474
+ - فایل CSV باید با encoding UTF-8 ذخیره شده باشد
475
+ - پردازش هر سطر حدود 2-3 ثانیه زمان می‌برد
476
+ """)
477
+
478
+ # ===============================
479
+ # توابع کمکی
480
+ # ===============================
481
+
482
+ def load_csv_columns(file_path):
483
+ """بارگذاری ستون‌های فایل CSV"""
484
+ if file_path is None:
485
+ return gr.Dropdown(choices=[]), None, "⏳ در انتظار آپلود فایل..."
486
+
487
+ try:
488
+ df = pd.read_csv(file_path, encoding='utf-8')
489
+ except:
490
+ try:
491
+ df = pd.read_csv(file_path, encoding='utf-8-sig')
492
+ except:
493
+ df = pd.read_csv(file_path, encoding='cp1256')
494
+
495
+ columns = df.columns.tolist()
496
+ preview = df.head(5)
497
+
498
+ status = f"✅ فایل بارگذاری شد | **{len(df)}** سطر | **{len(columns)}** ستون"
499
+
500
+ return gr.Dropdown(choices=columns, value=columns[0] if columns else None), preview, status
501
+
502
+ def process_single_text(text, api_key):
503
+ """پردازش تکی متن"""
504
+ if not text or not text.strip():
505
+ return "", "⚠️ لطفاً متن وارد کنید", "", "", "", ""
506
+
507
+ try:
508
+ key = api_key if api_key else os.getenv("CEREBRAS_API_KEY")
509
+ anonymizer = AdvancedCerebrasAnonymizer(api_key=key)
510
+ result = anonymizer.anonymize_text(text)
511
+
512
+ if not result["success"]:
513
+ return "", f"❌ خطا: {result['error']}", "", "", "", ""
514
+
515
+ # آمار
516
+ stats = result["statistics"]
517
+ stats_md = f"""
518
+ ### 📊 آمار موجودیت‌ها:
519
+ | نوع | تعداد |
520
+ |-----|-------|
521
+ | 🏢 شرکت/سازمان | {stats['company']} |
522
+ | 👤 شخص | {stats['person']} |
523
+ | 💰 مبلغ/عدد | {stats['amount']} |
524
+ | 📈 درصد | {stats['percent']} |
525
+ | **کل جایگزینی‌ها** | **{stats['total_replacements']}** |
526
+ """
527
+
528
+ # کنترل کیفیت
529
+ quality = result["quality_check"]
530
+ if quality["is_valid"]:
531
+ quality_md = "✅ **کیفیت تأیید شد** - بدون مشکل"
532
+ else:
533
+ issues = "\n".join([f"- {issue}" for issue in quality["issues"]])
534
+ quality_md = f"⚠️ **مشکلات شناسایی شده:**\n{issues}"
535
+
536
+ # موجودیت‌ها
537
+ entities = result["entities"]
538
+ entities_md = f"""
539
+ **شرکت‌ها:** {', '.join([f'company-{x}' for x in entities['companies']]) or 'ندارد'}
540
+ **اشخاص:** {', '.join([f'person-{x}' for x in entities['persons']]) or 'ندارد'}
541
+ **مبالغ:** {', '.join([f'amount-{x}' for x in entities['amounts']]) or 'ندارد'}
542
+ **درصدها:** {', '.join([f'percent-{x}' for x in entities['percents']]) or 'ندارد'}
543
+ """
544
+
545
+ # تحلیل تفصیلی
546
+ detailed = result["detailed_analysis"]
547
+ detailed_md = f"""
548
+ | شاخص | مقدار |
549
+ |------|-------|
550
+ | تاریخ‌های حفظ شده | {detailed['preserved_dates']} |
551
+ | شاخص‌های مالی | {detailed['financial_indicators']} |
552
+ | واحدهای حفظ شده | {detailed['units_preserved']} |
553
+ """
554
+
555
+ # مصرف
556
+ usage = result.get("usage", {})
557
+ usage_md = f"⚡ **توکن‌ها:** ورودی: {usage.get('prompt_tokens', '-')} | خروجی: {usage.get('completion_tokens', '-')}"
558
+
559
+ return (
560
+ result["anonymized_text"],
561
+ stats_md,
562
+ quality_md,
563
+ entities_md,
564
+ detailed_md,
565
+ usage_md
566
+ )
567
+
568
+ except Exception as e:
569
+ return "", f"❌ خطا: {str(e)}", "", "", "", ""
570
+
571
+ def process_batch_csv(file_path, text_col, output_col, api_key, progress=gr.Progress()):
572
+ """پردازش دسته‌ای فایل CSV"""
573
+ if file_path is None:
574
+ return None, "❌ لطفاً فایل CSV آپلود کنید", "", gr.File(visible=False), None
575
+
576
+ if not text_col:
577
+ return None, "❌ لطفاً ستون متن را انتخاب کنید", "", gr.File(visible=False), None
578
+
579
+ try:
580
+ # خواندن فایل
581
+ try:
582
+ df = pd.read_csv(file_path, encoding='utf-8')
583
+ except:
584
+ try:
585
+ df = pd.read_csv(file_path, encoding='utf-8-sig')
586
+ except:
587
+ df = pd.read_csv(file_path, encoding='cp1256')
588
+
589
+ if text_col not in df.columns:
590
+ return None, f"❌ ستون '{text_col}' در فایل یافت نشد", "", gr.File(visible=False), None
591
+
592
+ # محدودیت تعداد سطرها
593
+ max_rows = 1000
594
+ if len(df) > max_rows:
595
+ return None, f"❌ تعداد سطرها ({len(df)}) از حداکثر مجاز ({max_rows}) بیشتر است", "", gr.File(visible=False), None
596
+
597
+ # ایجاد anonymizer
598
+ key = api_key if api_key else os.getenv("CEREBRAS_API_KEY")
599
+ if not key:
600
+ return None, "❌ کلید API تنظیم نشده", "", gr.File(visible=False), None
601
+
602
+ anonymizer = AdvancedCerebrasAnonymizer(api_key=key)
603
+
604
+ # پردازش سطرها
605
+ total = len(df)
606
+ anonymized_texts = []
607
+ error_rows = []
608
+ success_count = 0
609
+
610
+ progress(0, desc="شروع پردازش...")
611
+
612
+ for idx, row in df.iterrows():
613
+ text = str(row[text_col])
614
+
615
+ progress((idx + 1) / total, desc=f"پردازش سطر {idx + 1} از {total}")
616
+
617
+ if not text or text.strip() == '' or text.lower() == 'nan':
618
+ anonymized_texts.append("")
619
+ continue
620
+
621
+ result = anonymizer.anonymize_text(text)
622
+
623
+ if result["success"]:
624
+ anonymized_texts.append(result["anonymized_text"])
625
+ success_count += 1
626
+ else:
627
+ anonymized_texts.append(f"[خطا: {result['error']}]")
628
+ error_rows.append(f"سطر {idx + 1}: {result['error']}")
629
+
630
+ # تاخیر برای جلوگیری از rate limit
631
+ time.sleep(3) # افزایش به 3 ثانیه
632
+
633
+ # اضافه کردن ستون جدید
634
+ output_col_name = output_col if output_col else "anonymized_text"
635
+ df[output_col_name] = anonymized_texts
636
+
637
+ # ذخیره فایل خروجی
638
+ output_path = "/tmp/anonymized_output.csv"
639
+ df.to_csv(output_path, index=False, encoding='utf-8-sig')
640
+
641
+ # آمار
642
+ stats_md = f"""
643
+ ### 📊 آمار پردازش:
644
+ | شاخص | مقدار |
645
+ |------|-------|
646
+ | کل سطرها | {total} |
647
+ | پردازش موفق | {success_count} |
648
+ | خطا | {len(error_rows)} |
649
+ | درصد موفقیت | {(success_count/total*100):.1f}% |
650
+ """
651
+
652
+ # گزارش خطاها
653
+ error_md = ""
654
+ if error_rows:
655
+ error_md = "### ⚠️ خطاهای مشاهده شده:\n" + "\n".join([f"- {e}" for e in error_rows[:20]])
656
+ if len(error_rows) > 20:
657
+ error_md += f"\n... و {len(error_rows) - 20} خطای دیگر"
658
+
659
+ # نمایش نتایج
660
+ result_preview = df[[text_col, output_col_name]].head(10)
661
+
662
+ return (
663
+ result_preview,
664
+ f"✅ **پردازش کامل شد!** | {success_count} سطر با موفقیت",
665
+ stats_md,
666
+ gr.File(value=output_path, visible=True),
667
+ error_md
668
+ )
669
+
670
+ except Exception as e:
671
+ return None, f"❌ خطا در پردازش: {str(e)}", "", gr.File(visible=False), str(e)
672
+
673
+ def copy_text(text_to_copy):
674
+ """کپی متن"""
675
+ if not text_to_copy or not text_to_copy.strip():
676
+ return gr.Textbox(visible=False), "⚠️ متنی برای کپی وجود ندارد"
677
+ return gr.Textbox(value=text_to_copy, visible=True), "✅ متن کپی شد"
678
+
679
+ def clear_all():
680
+ """پاک کردن فیلدها"""
681
+ return "", "", "", "", "", "", "", gr.Textbox(visible=False)
682
+
683
+ # ===============================
684
+ # اتصال رویدادها
685
+ # ===============================
686
+
687
+ # پردازش تکی
688
+ anonymize_btn.click(
689
+ fn=process_single_text,
690
+ inputs=[input_text, api_key_input],
691
+ outputs=[output_text, statistics_output, quality_output, entities_output, detailed_analysis_output, usage_output]
692
+ )
693
+
694
+ copy_btn.click(
695
+ fn=copy_text,
696
+ inputs=[output_text],
697
+ outputs=[copy_output, statistics_output]
698
+ )
699
+
700
+ clear_btn.click(
701
+ fn=clear_all,
702
+ outputs=[input_text, output_text, statistics_output, quality_output, entities_output, detailed_analysis_output, usage_output, copy_output]
703
+ )
704
+
705
+ # پردازش دسته‌ای
706
+ csv_file.change(
707
+ fn=load_csv_columns,
708
+ inputs=[csv_file],
709
+ outputs=[text_column, preview_df, batch_progress]
710
+ )
711
+
712
+ batch_btn.click(
713
+ fn=process_batch_csv,
714
+ inputs=[csv_file, text_column, output_column_name, api_key_input],
715
+ outputs=[result_df, batch_progress, batch_stats, download_btn, error_log]
716
+ )
717
+
718
+ # مثال‌ها
719
+ gr.Examples(
720
+ examples=[
721
+ ["مهدی اخوان بهابادی، مدیرعامل همراه اول، اعلام کرد درآمد عملیاتی شرکت با رشد 37 درصدی به 70 هزار و 677 میلیارد تومان رسیده است."],
722
+ ["بانک مرکزی و بانک ملی با همکاری محمدرضا فرزین، 60 درصد سپرده‌ها را مدیریت کردند."],
723
+ ["سازمان تامین اجتماعی دارای سه شرکت دارویی است که از مراکز درمانی وابسته به وزارت بهداشت مطالباتی دارند."]
724
+ ],
725
+ inputs=input_text,
726
+ label="📚 مثال‌ها"
727
+ )
728
+
729
+ return interface
730
+
731
+
732
+ # اجرای برنامه
733
+ if __name__ == "__main__":
734
+ interface = create_advanced_interface()
735
+ interface.launch(
736
+ server_name="0.0.0.0",
737
+ server_port=7860,
738
+ share=True,
739
+ show_error=True
740
+ )