leilaghomashchi commited on
Commit
b14b4a7
·
verified ·
1 Parent(s): b61e729

Upload 2 files

Browse files
huggingface_setup (3).py ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ import gradio as gr
4
+ from typing import Dict, Any
5
+ import os
6
+ from dataclasses import dataclass
7
+
8
+ @dataclass
9
+ class GroqConfig:
10
+ """تنظیمات Groq API"""
11
+ api_key: str
12
+ base_url: str = "https://api.groq.com/openai/v1"
13
+ model: str = "llama-3.1-8b-instant"
14
+ max_tokens: int = 1000
15
+ temperature: float = 0.1
16
+
17
+ class GroqAnonymizer:
18
+ """سیستم ناشناس‌سازی با استفاده از Groq"""
19
+
20
+ def __init__(self, api_key: str = None):
21
+ if api_key is None:
22
+ api_key = os.getenv("GROQ_API_KEY")
23
+ if not api_key:
24
+ raise ValueError("کلید API یافت نشد")
25
+
26
+ self.config = GroqConfig(api_key=api_key)
27
+ self.system_prompt = self._create_system_prompt()
28
+
29
+ def _create_system_prompt(self) -> str:
30
+ """ایجاد دستورالعمل سیستمی برای Groq"""
31
+ return """Replace these in Persian text:
32
+ - Company names with: company-01, company-02, etc.
33
+ - Person names with: person-01, person-02, etc.
34
+ - Numbers/amounts with: amount-01, amount-02, etc.
35
+ - Percentages with: percent-01, percent-02, etc.
36
+
37
+ Only return the anonymized text, nothing else."""
38
+
39
+ def _make_api_request(self, text: str) -> Dict[str, Any]:
40
+ """ارسال درخواست به Groq API"""
41
+ headers = {
42
+ "Authorization": f"Bearer {self.config.api_key}",
43
+ "Content-Type": "application/json"
44
+ }
45
+
46
+ payload = {
47
+ "messages": [
48
+ {
49
+ "role": "system",
50
+ "content": self.system_prompt
51
+ },
52
+ {
53
+ "role": "user",
54
+ "content": text
55
+ }
56
+ ],
57
+ "model": self.config.model,
58
+ "temperature": self.config.temperature,
59
+ "max_tokens": self.config.max_tokens
60
+ }
61
+
62
+ try:
63
+ response = requests.post(
64
+ f"{self.config.base_url}/chat/completions",
65
+ headers=headers,
66
+ json=payload,
67
+ timeout=30
68
+ )
69
+ response.raise_for_status()
70
+ return response.json()
71
+
72
+ except requests.exceptions.RequestException as e:
73
+ raise Exception(f"خطا در ارتباط با Groq API: {str(e)}")
74
+
75
+ def anonymize_text(self, text: str) -> Dict[str, Any]:
76
+ """ناشناس‌سازی متن با استفاده از Groq"""
77
+ if not text.strip():
78
+ return {
79
+ "success": False,
80
+ "error": "متن ورودی خالی است"
81
+ }
82
+
83
+ try:
84
+ response = self._make_api_request(text)
85
+
86
+ if "choices" not in response or not response["choices"]:
87
+ return {
88
+ "success": False,
89
+ "error": "پاسخ نامعتبر از API"
90
+ }
91
+
92
+ content = response["choices"][0]["message"]["content"]
93
+
94
+ # پاک کردن markdown اگر وجود دارد
95
+ if "```" in content:
96
+ lines = content.split('\n')
97
+ clean_lines = []
98
+ skip = False
99
+ for line in lines:
100
+ if line.strip().startswith('```'):
101
+ skip = not skip
102
+ continue
103
+ if not skip:
104
+ clean_lines.append(line)
105
+ content = '\n'.join(clean_lines)
106
+
107
+ # حذف خطوط اضافی و فضاهای خالی
108
+ content = content.strip()
109
+
110
+ return {
111
+ "success": True,
112
+ "anonymized_text": content,
113
+ "entities": [], # در حالت ساده entities نداریم
114
+ "statistics": self._count_entities(content),
115
+ "usage": response.get("usage", {})
116
+ }
117
+
118
+ except Exception as e:
119
+ return {
120
+ "success": False,
121
+ "error": f"خطا در پردازش: {str(e)}"
122
+ }
123
+
124
+ def _count_entities(self, text: str) -> Dict[str, int]:
125
+ """شمارش موجودیت‌ها در متن ناشناس‌سازی شده"""
126
+ import re
127
+
128
+ company_count = len(re.findall(r'company-\d+', text))
129
+ person_count = len(re.findall(r'person-\d+', text))
130
+ amount_count = len(re.findall(r'amount-\d+', text))
131
+ percent_count = len(re.findall(r'percent-\d+', text))
132
+ group_count = len(re.findall(r'group-\d+', text))
133
+
134
+ return {
135
+ "company": company_count,
136
+ "person": person_count,
137
+ "amount": amount_count,
138
+ "percent": percent_count,
139
+ "group": group_count
140
+ }
141
+
142
+ def create_interface():
143
+ """ایجاد رابط کاربری"""
144
+
145
+ # بررسی وجود کلید API
146
+ api_key_available = bool(os.getenv("GROQ_API_KEY"))
147
+
148
+ # CSS سفارشی
149
+ custom_css = """
150
+ .gradio-container {
151
+ font-family: 'Tahoma', 'Arial', sans-serif !important;
152
+ direction: rtl;
153
+ max-width: 1200px;
154
+ margin: 0 auto;
155
+ }
156
+ .result-box {
157
+ background-color: #f0f8ff;
158
+ border: 1px solid #ddd;
159
+ border-radius: 8px;
160
+ padding: 15px;
161
+ }
162
+ .warning-box {
163
+ background-color: #fff3cd;
164
+ border: 1px solid #ffeaa7;
165
+ border-radius: 8px;
166
+ padding: 10px;
167
+ color: #856404;
168
+ }
169
+ .success-box {
170
+ background-color: #d4edda;
171
+ border: 1px solid #c3e6cb;
172
+ border-radius: 8px;
173
+ padding: 10px;
174
+ color: #155724;
175
+ }
176
+ """
177
+
178
+ with gr.Blocks(css=custom_css, title="ناشناس‌سازی متن فارسی", theme=gr.themes.Soft()) as interface:
179
+
180
+ # عنوان
181
+ gr.Markdown("""
182
+ # 🔒 سیستم ناشناس‌سازی متن فارسی
183
+ ### قدرت‌گرفته از Groq AI برای تشخیص و حفاظت از اطلاعات حساس
184
+ """)
185
+
186
+ # نمایش وضعیت API
187
+ if api_key_available:
188
+ gr.Markdown("""
189
+ <div class="success-box">
190
+ ✅ <strong>سیستم آماده است</strong> - کلید API تنظیم شده
191
+ </div>
192
+ """)
193
+ api_key_input = gr.Textbox(visible=False, value="")
194
+ else:
195
+ gr.Markdown("""
196
+ <div class="warning-box">
197
+ ⚠️ <strong>کلید API تنظیم نشده</strong><br>
198
+ لطفاً کلید Groq API خود را در زیر وارد کنید
199
+ </div>
200
+ """)
201
+ api_key_input = gr.Textbox(
202
+ label="🔑 کلید Groq API",
203
+ placeholder="gsk_...",
204
+ type="password",
205
+ value="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"
206
+ )
207
+
208
+ with gr.Row():
209
+ with gr.Column(scale=1):
210
+ input_text = gr.Textbox(
211
+ label="📝 متن ورودی",
212
+ placeholder="متن خود را اینجا وارد کنید...",
213
+ lines=10,
214
+ max_lines=20
215
+ )
216
+
217
+ with gr.Row():
218
+ anonymize_btn = gr.Button(
219
+ "🔒 ناشناس‌سازی متن",
220
+ variant="primary",
221
+ size="lg"
222
+ )
223
+ clear_btn = gr.Button(
224
+ "🗑️ پاک کردن",
225
+ variant="secondary"
226
+ )
227
+
228
+ with gr.Column(scale=1):
229
+ output_text = gr.Textbox(
230
+ label="🎯 متن ناشناس‌سازی شده",
231
+ lines=10,
232
+ max_lines=20,
233
+ elem_classes=["result-box"]
234
+ )
235
+
236
+ # دکمه کپی
237
+ copy_btn = gr.Button(
238
+ "📋 کپی متن",
239
+ variant="secondary",
240
+ size="sm"
241
+ )
242
+
243
+ # متن برای کپی
244
+ copy_output = gr.Textbox(
245
+ label="📋 متن برای کپی (Ctrl+A و Ctrl+C)",
246
+ lines=3,
247
+ max_lines=10,
248
+ visible=False,
249
+ interactive=True
250
+ )
251
+
252
+ with gr.Row():
253
+ with gr.Column():
254
+ statistics_output = gr.Markdown(label="📊 آمار تشخیص")
255
+ with gr.Column():
256
+ usage_output = gr.Markdown(label="⚡ اطلاعات پردازش")
257
+
258
+ entities_output = gr.Markdown(label="📋 جزئیات تغییرات")
259
+
260
+ def process_text(text: str, api_key_manual: str = ""):
261
+ """پردازش متن"""
262
+ # حل مشکل NoneType
263
+ if api_key_manual is None:
264
+ api_key_manual = ""
265
+
266
+ # تعیین کلید API
267
+ final_api_key = ""
268
+ if api_key_manual and api_key_manual.strip():
269
+ final_api_key = api_key_manual.strip()
270
+ elif os.getenv("GROQ_API_KEY"):
271
+ final_api_key = os.getenv("GROQ_API_KEY")
272
+
273
+ if not final_api_key:
274
+ return (
275
+ "",
276
+ "❌ کلید API وارد نشده است",
277
+ "",
278
+ ""
279
+ )
280
+
281
+ if not text or not text.strip():
282
+ return (
283
+ "",
284
+ "❌ لطفاً متن ورودی را وارد کنید",
285
+ "",
286
+ ""
287
+ )
288
+
289
+ try:
290
+ anonymizer = GroqAnonymizer(api_key=final_api_key)
291
+ result = anonymizer.anonymize_text(text)
292
+
293
+ if not result["success"]:
294
+ return (
295
+ "",
296
+ f"❌ خطا: {result['error']}",
297
+ "",
298
+ ""
299
+ )
300
+
301
+ # آمار
302
+ stats = result.get("statistics", {})
303
+ stats_md = "📊 **آمار تشخیص:**\n\n"
304
+ total = sum(stats.values()) if stats else 0
305
+ stats_md += f"🔢 **کل موارد:** {total}\n\n"
306
+
307
+ type_names = {
308
+ 'company': 'شرکت‌ها',
309
+ 'person': 'افراد',
310
+ 'group': 'گروه‌ها',
311
+ 'amount': 'مبالغ',
312
+ 'percent': 'درصدها'
313
+ }
314
+
315
+ if stats:
316
+ for key, value in stats.items():
317
+ if value > 0:
318
+ name = type_names.get(key, key)
319
+ stats_md += f"• {name}: **{value}** مورد\n"
320
+
321
+ # اطلاعات پردازش
322
+ usage = result.get("usage", {})
323
+ usage_md = "⚡ **اطلاعات پردازش:**\n\n"
324
+ if usage:
325
+ usage_md += f"• مدل: Llama 3.1\n"
326
+ usage_md += f"• Token های ورودی: {usage.get('prompt_tokens', 'نامشخص')}\n"
327
+ usage_md += f"• Token های خروجی: {usage.get('completion_tokens', 'نامشخص')}\n"
328
+ else:
329
+ usage_md += "✅ پردازش با موفقیت انجام شد"
330
+
331
+ # جزئیات موجودیت‌ها
332
+ entities_md = "📋 **جزئیات تغییرات:**\n\n"
333
+ entities_md += "ناشناس‌سازی با موفقیت انجام شد"
334
+
335
+ return (
336
+ result["anonymized_text"],
337
+ stats_md,
338
+ usage_md,
339
+ entities_md
340
+ )
341
+
342
+ except Exception as e:
343
+ return (
344
+ "",
345
+ f"❌ خطای غیرمنتظره: {str(e)}",
346
+ "",
347
+ ""
348
+ )
349
+
350
+ def copy_text(text_to_copy):
351
+ """تابع کپی متن"""
352
+ if not text_to_copy or not text_to_copy.strip():
353
+ return gr.Textbox(visible=False), "⚠️ متنی برای کپی وجود ندارد"
354
+
355
+ return gr.Textbox(value=text_to_copy, visible=True), "✅ متن در کادر زیر آماده کپی است"
356
+
357
+ def clear_all():
358
+ """پاک کردن تمام فیلدها"""
359
+ return "", "", "", "", "", gr.Textbox(visible=False)
360
+
361
+ # اتصال رویدادها
362
+ anonymize_btn.click(
363
+ fn=process_text,
364
+ inputs=[input_text, api_key_input],
365
+ outputs=[output_text, statistics_output, usage_output, entities_output]
366
+ )
367
+
368
+ copy_btn.click(
369
+ fn=copy_text,
370
+ inputs=[output_text],
371
+ outputs=[copy_output, statistics_output]
372
+ )
373
+
374
+ clear_btn.click(
375
+ fn=clear_all,
376
+ outputs=[input_text, output_text, statistics_output, usage_output, entities_output, copy_output]
377
+ )
378
+
379
+ # مثال‌های نمونه
380
+ gr.Examples(
381
+ examples=[
382
+ ["ایران خودرو در اسفندماه سال 1402 حدود 23 هزار و 296 میلیارد تومان درآمد کسب کرد که در مقایسه با بهمن 4.58 درصد افزایش داشت."],
383
+ ["مهدی اخوان بهابادی، مدیرعامل همراه اول، اعلام کرد درآمد عملیاتی شرکت با رشد 37 درصدی به 70 هزار و 677 میلیارد تومان رسیده است."],
384
+ ["بانک پاسارگاد با شناسایی سود خالص 155 هزار میلیارد ریالی در رده دوم سودآورترین بانک‌های کشور قرار گرفت."]
385
+ ],
386
+ inputs=input_text,
387
+ label="📚 مثال‌های آزمایشی"
388
+ )
389
+
390
+ # راهنمای استفاده
391
+ gr.Markdown("""
392
+ ---
393
+ ### 🎯 **ویژگی‌ها:**
394
+ - 🏢 تشخیص نام شرکت‌ها و سازمان‌ها
395
+ - 👤 تشخیص نام افراد با پیشوندهای مختلف
396
+ - 💰 تشخیص مبالغ مالی و اعداد
397
+ - 📊 تشخیص درصدها با فرمت‌های مختلف
398
+ """)
399
+
400
+ return interface
401
+
402
+ # اجرای برنامه
403
+ if __name__ == "__main__":
404
+ interface = create_interface()
405
+ interface.launch()
persian_anonymizer_advanced.py ADDED
@@ -0,0 +1,666 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ import gradio as gr
4
+ from typing import Dict, Any
5
+ import os
6
+ from dataclasses import dataclass
7
+ import re
8
+
9
+ @dataclass
10
+ class GroqConfig:
11
+ """تنظیمات Groq API"""
12
+ api_key: str
13
+ base_url: str = "https://api.groq.com/openai/v1"
14
+ model: str = "llama-3.1-8b-instant"
15
+ max_tokens: int = 2000
16
+ temperature: float = 0.1
17
+
18
+ class AdvancedGroqAnonymizer:
19
+ """سیستم پیشرفته ناشناس‌سازی متون مالی/خبری فارسی"""
20
+
21
+ def __init__(self, api_key: str = None):
22
+ if api_key is None:
23
+ api_key = os.getenv("GROQ_API_KEY")
24
+ if not api_key:
25
+ raise ValueError("کلید API یافت نشد")
26
+
27
+ self.config = GroqConfig(api_key=api_key)
28
+ self.system_prompt = self._create_advanced_system_prompt()
29
+
30
+ def _create_advanced_system_prompt(self) -> str:
31
+ """ایجاد دستورالعمل سیستمی پیشرفته برای Groq"""
32
+ return """شما یک «ناشناس‌ساز متون مالی/خبری فارسی» هستید. وظیفه‌تان جایگزینی اسامی خاص و مقادیر عددی با شناسه‌های بی‌معناست، بی‌آنکه ساختار جمله و معنای تحلیلی متن مخدوش شود.
33
+
34
+ ## طرح برچسب‌ها (Tagging Scheme)
35
+ * شرکت/نهاد/برند/سازمان/سامانه → `company-XX`
36
+ * شخص حقیقی (نام و نام‌خانوادگی، القابِ مختص فرد) → `person-XX`
37
+ * گروه/هلدینگ/کنسرسیوم → `group-XX`
38
+ * هر عدد پولی/تعدادی/حجمی/زمانی/شناسه ثبت/شماره/… → `amount-XX` (واحد دقیق بلافاصله پس از برچسب حفظ شود: «amount-03 میلیارد تومان»)
39
+ * درصدها (٪) و بازه‌های درصد → `percent-XX` (مثال بازه: `percent-05–percent-07`)
40
+ * شاخص‌ها/علائم مالی (EPS, P/E, ARPU, NPL و…) باقی بمانند؛ فقط مقدارشان ناشناس شود (`EPS amount-01 ریال`, `P/E amount-02`)
41
+
42
+ ## قواعد اندیس‌گذاری
43
+ * برای هر نوع برچسب، از `-01` شروع و افزایشی تخصیص دهید (شمارنده‌های هر نوع از هم مستقل‌اند).
44
+ * پایداری «درون همان قطعه»: یک موجودیت در یک قطعه همیشه همان شناسه را نگه دارد.
45
+ * عدم نیاز به پایداری بین قطعات/پاراگراف‌های مستقل.
46
+
47
+ ## چه چیزهایی حفظ می‌شود؟
48
+ * تاریخ‌ها/ساعت‌ها (مثل 1404/04/29، 31 اردیبهشت 1404) دست‌نخورده.
49
+ * عناوین شغلی/نقش‌ها (مدیرعامل، بازرس قانونی، …).
50
+ * واحدها و اصطلاحات فنی (همت، میلیارد ریال، تُن، TEU، EPS، P/E، ARPU، NPL، …).
51
+ * نام مکان‌ها/آدرس‌ها/محل برگزاری رویدادها اصولاً حفظ می‌شوند (مگر صریحاً سیاست دیگری داده شود).
52
+ * ساختار جمله، روابط علّی/مقایسه‌ای، نتیجه‌گیری‌ها و لحن.
53
+
54
+ ## قواعد جایگزینی
55
+ 1. هر اسم خاص سازمانی/برندی/رسانه‌ای → `company-XX` (بانک مرکزی، سامانه کدال، بیمه مرکزی، … نیز سازمان‌اند.)
56
+ 2. هر نام شخص → `person-XX` (عنوان شغلی حفظ شود).
57
+ 3. هر عدد مستقل یا همراه واحد → `amount-XX` + واحد.
58
+ 4. هر درصد → `percent-XX`؛ در بازه‌ها با خط تیره بدون فاصله.
59
+ 5. اعداد چسبیده به واژه («5هزار») → بخش عددی ناشناس شود: `amount-01 هزار`.
60
+ 6. مدل/سری محصول (مثل G10) معمولاً حفظ می‌شود؛ اگر سیاست پوشش کامل ارقام سری لازم بود: «Gamount-XX».
61
+ 7. اگر درباره‌ی نوع برچسب مردد بودید، اولویت با `company-XX` (برای موجودیت‌های حقوقی) است.
62
+
63
+ ## کنترل کیفیت (برای هر قطعه)
64
+ * هر موجودیت در همان قطعه شناسه ثابت دارد.
65
+ * نوع برچسب با ماهیت موجودیت سازگار است (شخص≠شرکت).
66
+ * همه‌ی اعداد و درصدها پوشانده و واحدها حفظ شده‌اند.
67
+ * تاریخ/ساعت حفظ شده است.
68
+ * اندیس‌ها از 01 شروع و پیوسته‌اند (به‌تفکیک نوع).
69
+ * هیچ توضیح یا کروشه اضافه به متن ناشناس‌شده افزوده نشده است.
70
+
71
+ ## قالب خروجی
72
+ برای هر ورودی، **صرفاً متن ناشناس‌شده** را برگردان. هیچ توضیح، سربرگ، یا نشانه‌گذاری اضافه ننویس. زبان و علائم نگارشی اصلی را حفظ کن.
73
+
74
+ ## مثال‌های کوتاه
75
+ **مثال 1:**
76
+ ورودی: «بانک پاسارگاد با شناسایی سود خالص 155 هزار میلیارد ریالی…»
77
+ خروجی: «company-01 با شناسایی سود خالص amount-01 هزار میلیارد ریالی…»
78
+
79
+ **مثال 2:**
80
+ ورودی: «مهدی اخوان بهابادی، مدیرعامل همراه اول، گفت سود خالص 7101 میلیارد تومان شد و EPS به 936 ریال رسید.»
81
+ خروجی: «person-01، مدیرعامل company-01، گفت سود خالص amount-01 میلیارد تومان شد و EPS به amount-02 ریال رسید.»
82
+
83
+ **مثال 3:**
84
+ ورودی: «هزینه لجستیکی بوعلی حدود 100 میلیون دلار بود؛ 40٪ خوراک از خط لوله و 60٪ معادل 1 تا 1.5 میلیون تن.»
85
+ خروجی: «هزینه لجستیکی company-01 حدود amount-01 میلیون دلار بود؛ percent-01 خوراک از خط لوله و percent-02 معادل amount-02 تا amount-03 میلیون تن.»
86
+
87
+ ## نکات لبه‌ای
88
+ * بازه‌های عددی/درصدی: `amount-01–amount-02` / `percent-01–percent-02`.
89
+ * اعداد سریالی سند/ثبت/شماره ملی/شماره ثبت: عدد → `amount-XX`، عنوان/واژه‌ها حفظ.
90
+ * اگر یک نام هم‌زمان برند محصول و شرکت است، در متن مالی/حاکمیتی آن را شرکت فرض کنید (`company-XX`).
91
+
92
+ فقط متن ناشناس‌شده را برگردان، بدون هیچ توضیح اضافی."""
93
+
94
+ def _make_api_request(self, text: str) -> Dict[str, Any]:
95
+ """ارسال درخواست به Groq API"""
96
+ headers = {
97
+ "Authorization": f"Bearer {self.config.api_key}",
98
+ "Content-Type": "application/json"
99
+ }
100
+
101
+ payload = {
102
+ "messages": [
103
+ {
104
+ "role": "system",
105
+ "content": self.system_prompt
106
+ },
107
+ {
108
+ "role": "user",
109
+ "content": text
110
+ }
111
+ ],
112
+ "model": self.config.model,
113
+ "temperature": self.config.temperature,
114
+ "max_tokens": self.config.max_tokens
115
+ }
116
+
117
+ try:
118
+ response = requests.post(
119
+ f"{self.config.base_url}/chat/completions",
120
+ headers=headers,
121
+ json=payload,
122
+ timeout=45
123
+ )
124
+ response.raise_for_status()
125
+ return response.json()
126
+
127
+ except requests.exceptions.RequestException as e:
128
+ raise Exception(f"خطا در ارتباط با Groq API: {str(e)}")
129
+
130
+ def anonymize_text(self, text: str) -> Dict[str, Any]:
131
+ """ناشناس‌سازی متن با استفاده از Groq"""
132
+ if not text.strip():
133
+ return {
134
+ "success": False,
135
+ "error": "متن ورودی خالی است"
136
+ }
137
+
138
+ try:
139
+ response = self._make_api_request(text)
140
+
141
+ if "choices" not in response or not response["choices"]:
142
+ return {
143
+ "success": False,
144
+ "error": "پاسخ نامعتبر از API"
145
+ }
146
+
147
+ content = response["choices"][0]["message"]["content"]
148
+
149
+ # پاک کردن markdown اگر وجود دارد
150
+ content = self._clean_markdown(content)
151
+
152
+ # حذف خطوط اضافی و فضاهای خالی
153
+ content = content.strip()
154
+
155
+ # تحلیل نتایج
156
+ analysis = self._analyze_anonymized_text(content)
157
+
158
+ return {
159
+ "success": True,
160
+ "anonymized_text": content,
161
+ "entities": analysis["entities"],
162
+ "statistics": analysis["statistics"],
163
+ "detailed_analysis": analysis["detailed_analysis"],
164
+ "usage": response.get("usage", {}),
165
+ "quality_check": self._quality_check(content)
166
+ }
167
+
168
+ except Exception as e:
169
+ return {
170
+ "success": False,
171
+ "error": f"خطا در پردازش: {str(e)}"
172
+ }
173
+
174
+ def _clean_markdown(self, content: str) -> str:
175
+ """پاک کردن markdown از پاسخ"""
176
+ if "```" in content:
177
+ lines = content.split('\n')
178
+ clean_lines = []
179
+ skip = False
180
+ for line in lines:
181
+ if line.strip().startswith('```'):
182
+ skip = not skip
183
+ continue
184
+ if not skip:
185
+ clean_lines.append(line)
186
+ content = '\n'.join(clean_lines)
187
+ return content
188
+
189
+ def _analyze_anonymized_text(self, text: str) -> Dict[str, Any]:
190
+ """تحلیل متن ناشناس‌سازی شده"""
191
+ import re
192
+
193
+ # شمارش موجودیت‌ها
194
+ companies = re.findall(r'company-(\d+)', text)
195
+ persons = re.findall(r'person-(\d+)', text)
196
+ amounts = re.findall(r'amount-(\d+)', text)
197
+ percents = re.findall(r'percent-(\d+)', text)
198
+ groups = re.findall(r'group-(\d+)', text)
199
+
200
+ # آمار کلی
201
+ statistics = {
202
+ "company": len(set(companies)),
203
+ "person": len(set(persons)),
204
+ "amount": len(set(amounts)),
205
+ "percent": len(set(percents)),
206
+ "group": len(set(groups)),
207
+ "total_replacements": len(companies) + len(persons) + len(amounts) + len(percents) + len(groups)
208
+ }
209
+
210
+ # جزئیات موجودیت‌ها
211
+ entities = {
212
+ "companies": sorted(list(set(companies)), key=lambda x: int(x)),
213
+ "persons": sorted(list(set(persons)), key=lambda x: int(x)),
214
+ "amounts": sorted(list(set(amounts)), key=lambda x: int(x)),
215
+ "percents": sorted(list(set(percents)), key=lambda x: int(x)),
216
+ "groups": sorted(list(set(groups)), key=lambda x: int(x))
217
+ }
218
+
219
+ # تحلیل دقیق‌تر
220
+ detailed_analysis = {
221
+ "preserved_dates": len(re.findall(r'\d{4}/\d{1,2}/\d{1,2}|\d{1,2}\s+\w+\s+\d{4}', text)),
222
+ "preserved_times": len(re.findall(r'\d{1,2}:\d{2}', text)),
223
+ "financial_indicators": len(re.findall(r'\b(EPS|P/E|ARPU|NPL|ROE|ROA)\b', text)),
224
+ "units_preserved": len(re.findall(r'(میلیارد|میلیون|هزار|تومان|ریال|درهم|دلار|یورو|تن|کیلوگرم)', text))
225
+ }
226
+
227
+ return {
228
+ "statistics": statistics,
229
+ "entities": entities,
230
+ "detailed_analysis": detailed_analysis
231
+ }
232
+
233
+ def _quality_check(self, text: str) -> Dict[str, Any]:
234
+ """بررسی کیفیت ناشناس‌سازی"""
235
+ import re
236
+
237
+ # بررسی شروع اندیس‌ها از 01
238
+ company_indices = [int(x) for x in re.findall(r'company-(\d+)', text)]
239
+ person_indices = [int(x) for x in re.findall(r'person-(\d+)', text)]
240
+ amount_indices = [int(x) for x in re.findall(r'amount-(\d+)', text)]
241
+ percent_indices = [int(x) for x in re.findall(r'percent-(\d+)', text)]
242
+
243
+ issues = []
244
+
245
+ # بررسی شروع از 01
246
+ for entity_type, indices in [
247
+ ("company", company_indices),
248
+ ("person", person_indices),
249
+ ("amount", amount_indices),
250
+ ("percent", percent_indices)
251
+ ]:
252
+ if indices and min(indices) != 1:
253
+ issues.append(f"اندیس {entity_type} از 01 شروع نشده")
254
+
255
+ # بررسی پیوستگی اندیس‌ها
256
+ for entity_type, indices in [
257
+ ("company", company_indices),
258
+ ("person", person_indices),
259
+ ("amount", amount_indices),
260
+ ("percent", percent_indices)
261
+ ]:
262
+ if indices:
263
+ unique_indices = sorted(list(set(indices)))
264
+ expected = list(range(1, len(unique_indices) + 1))
265
+ if unique_indices != expected:
266
+ issues.append(f"اندیس‌های {entity_type} پیوسته نیستند")
267
+
268
+ return {
269
+ "passed": len(issues) == 0,
270
+ "issues": issues,
271
+ "total_checks": 8,
272
+ "passed_checks": 8 - len(issues)
273
+ }
274
+
275
+ def create_advanced_interface():
276
+ """ایجاد رابط کاربری پیشرفته"""
277
+
278
+ # بررسی وجود کلید API
279
+ api_key_available = bool(os.getenv("GROQ_API_KEY"))
280
+
281
+ # CSS سفارشی پیشرفته
282
+ custom_css = """
283
+ .gradio-container {
284
+ font-family: 'Tahoma', 'Arial', sans-serif !important;
285
+ direction: rtl;
286
+ max-width: 1400px;
287
+ margin: 0 auto;
288
+ }
289
+ .result-box {
290
+ background-color: #f8f9fa;
291
+ border: 2px solid #e9ecef;
292
+ border-radius: 12px;
293
+ padding: 20px;
294
+ margin: 10px 0;
295
+ }
296
+ .warning-box {
297
+ background-color: #fff3cd;
298
+ border: 2px solid #ffeaa7;
299
+ border-radius: 12px;
300
+ padding: 15px;
301
+ color: #856404;
302
+ margin: 10px 0;
303
+ }
304
+ .success-box {
305
+ background-color: #d4edda;
306
+ border: 2px solid #c3e6cb;
307
+ border-radius: 12px;
308
+ padding: 15px;
309
+ color: #155724;
310
+ margin: 10px 0;
311
+ }
312
+ .stats-grid {
313
+ display: grid;
314
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
315
+ gap: 15px;
316
+ margin: 15px 0;
317
+ }
318
+ .stat-card {
319
+ background-color: #ffffff;
320
+ border: 1px solid #dee2e6;
321
+ border-radius: 8px;
322
+ padding: 15px;
323
+ text-align: center;
324
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
325
+ }
326
+ .quality-badge {
327
+ display: inline-block;
328
+ padding: 5px 10px;
329
+ border-radius: 20px;
330
+ font-weight: bold;
331
+ margin: 5px;
332
+ }
333
+ .quality-pass {
334
+ background-color: #28a745;
335
+ color: white;
336
+ }
337
+ .quality-fail {
338
+ background-color: #dc3545;
339
+ color: white;
340
+ }
341
+ """
342
+
343
+ with gr.Blocks(css=custom_css, title="ناشناس‌ساز پیشرفته متن فارسی", theme=gr.themes.Soft()) as interface:
344
+
345
+ # عنوان
346
+ gr.Markdown("""
347
+ # 🔒 سیستم پیشرفته ناشناس‌سازی متون مالی/خبری فارسی
348
+ ### قدرت‌گرفته از Groq AI با الگوریتم‌های هوشمند تشخیص و جایگزینی موجودیت‌ها
349
+ """)
350
+
351
+ # نمایش وضعیت API
352
+ if api_key_available:
353
+ gr.Markdown("""
354
+ <div class="success-box">
355
+ ✅ <strong>سیستم آماده است</strong> - کلید API تنظیم شده
356
+ </div>
357
+ """)
358
+ api_key_input = gr.Textbox(visible=False, value="")
359
+ else:
360
+ gr.Markdown("""
361
+ <div class="warning-box">
362
+ ⚠️ <strong>کلید API تنظیم نشده</strong><br>
363
+ لطفاً کلید Groq API خود را در زیر وارد کنید
364
+ </div>
365
+ """)
366
+ api_key_input = gr.Textbox(
367
+ label="🔑 کلید Groq API",
368
+ placeholder="gsk_...",
369
+ type="password",
370
+ value="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"
371
+ )
372
+
373
+ with gr.Row():
374
+ with gr.Column(scale=1):
375
+ input_text = gr.Textbox(
376
+ label="📝 متن ورودی",
377
+ placeholder="متن مالی یا خبری خود را اینجا وارد کنید...",
378
+ lines=12,
379
+ max_lines=25
380
+ )
381
+
382
+ with gr.Row():
383
+ anonymize_btn = gr.Button(
384
+ "🔒 ناشناس‌سازی پیشرفته",
385
+ variant="primary",
386
+ size="lg"
387
+ )
388
+ clear_btn = gr.Button(
389
+ "🗑️ پاک کردن",
390
+ variant="secondary"
391
+ )
392
+
393
+ with gr.Column(scale=1):
394
+ output_text = gr.Textbox(
395
+ label="🎯 متن ناشناس‌سازی شده",
396
+ lines=12,
397
+ max_lines=25,
398
+ elem_classes=["result-box"]
399
+ )
400
+
401
+ # دکمه کپی
402
+ copy_btn = gr.Button(
403
+ "📋 کپی متن",
404
+ variant="secondary",
405
+ size="sm"
406
+ )
407
+
408
+ # متن برای کپی
409
+ copy_output = gr.Textbox(
410
+ label="📋 متن برای کپی (Ctrl+A و Ctrl+C)",
411
+ lines=3,
412
+ max_lines=10,
413
+ visible=False,
414
+ interactive=True
415
+ )
416
+
417
+ # نمایش آمار پیشرفته
418
+ with gr.Row():
419
+ with gr.Column():
420
+ statistics_output = gr.Markdown(label="📊 آمار کلی")
421
+ with gr.Column():
422
+ quality_output = gr.Markdown(label="✅ کنترل کیفیت")
423
+
424
+ with gr.Row():
425
+ with gr.Column():
426
+ entities_output = gr.Markdown(label="🏷️ موجودیت‌های شناسایی شده")
427
+ with gr.Column():
428
+ detailed_analysis_output = gr.Markdown(label="🔍 تحلیل دقیق")
429
+
430
+ usage_output = gr.Markdown(label="⚡ اطلاعات پردازش")
431
+
432
+ def process_advanced_text(text: str, api_key_manual: str = ""):
433
+ """پردازش پیشرفته متن"""
434
+ # حل مشکل NoneType
435
+ if api_key_manual is None:
436
+ api_key_manual = ""
437
+
438
+ # تعیین کلید API
439
+ final_api_key = ""
440
+ if api_key_manual and api_key_manual.strip():
441
+ final_api_key = api_key_manual.strip()
442
+ elif os.getenv("GROQ_API_KEY"):
443
+ final_api_key = os.getenv("GROQ_API_KEY")
444
+
445
+ if not final_api_key:
446
+ return (
447
+ "",
448
+ "❌ کلید API وارد نشده است",
449
+ "",
450
+ "",
451
+ "",
452
+ ""
453
+ )
454
+
455
+ if not text or not text.strip():
456
+ return (
457
+ "",
458
+ "❌ لطفاً متن ورودی را وارد کنید",
459
+ "",
460
+ "",
461
+ "",
462
+ ""
463
+ )
464
+
465
+ try:
466
+ anonymizer = AdvancedGroqAnonymizer(api_key=final_api_key)
467
+ result = anonymizer.anonymize_text(text)
468
+
469
+ if not result["success"]:
470
+ return (
471
+ "",
472
+ f"❌ خطا: {result['error']}",
473
+ "",
474
+ "",
475
+ "",
476
+ ""
477
+ )
478
+
479
+ # آمار کلی
480
+ stats = result.get("statistics", {})
481
+ stats_md = "📊 **آمار کلی:**\n\n"
482
+
483
+ stats_md += f"""
484
+ <div class="stats-grid">
485
+ <div class="stat-card">
486
+ <h3>🏢 شرکت‌ها</h3>
487
+ <h2>{stats.get('company', 0)}</h2>
488
+ </div>
489
+ <div class="stat-card">
490
+ <h3>👤 اشخاص</h3>
491
+ <h2>{stats.get('person', 0)}</h2>
492
+ </div>
493
+ <div class="stat-card">
494
+ <h3>💰 مبالغ</h3>
495
+ <h2>{stats.get('amount', 0)}</h2>
496
+ </div>
497
+ <div class="stat-card">
498
+ <h3>📊 درصدها</h3>
499
+ <h2>{stats.get('percent', 0)}</h2>
500
+ </div>
501
+ <div class="stat-card">
502
+ <h3>👥 گروه‌ها</h3>
503
+ <h2>{stats.get('group', 0)}</h2>
504
+ </div>
505
+ <div class="stat-card">
506
+ <h3>🔢 کل تغییرات</h3>
507
+ <h2>{stats.get('total_replacements', 0)}</h2>
508
+ </div>
509
+ </div>
510
+ """
511
+
512
+ # کنترل کیفیت
513
+ quality = result.get("quality_check", {})
514
+ quality_md = "✅ **کنترل کیفیت:**\n\n"
515
+
516
+ if quality.get("passed", False):
517
+ quality_md += '<span class="quality-badge quality-pass">✅ تمام بررسی‌ها موفق</span>\n\n'
518
+ else:
519
+ quality_md += '<span class="quality-badge quality-fail">❌ مشکلاتی یافت شد</span>\n\n'
520
+ issues = quality.get("issues", [])
521
+ if issues:
522
+ quality_md += "**مشکلات:**\n"
523
+ for issue in issues:
524
+ quality_md += f"• {issue}\n"
525
+
526
+ quality_md += f"\n**نتیجه:** {quality.get('passed_checks', 0)}/{quality.get('total_checks', 0)} بررسی موفق"
527
+
528
+ # موجودیت‌های شناسایی شده
529
+ entities = result.get("entities", {})
530
+ entities_md = "🏷️ **موجودیت‌های شناسایی شده:**\n\n"
531
+
532
+ if entities.get("companies"):
533
+ entities_md += f"🏢 **شرکت‌ها:** company-{', company-'.join(entities['companies'])}\n\n"
534
+ if entities.get("persons"):
535
+ entities_md += f"👤 **اشخاص:** person-{', person-'.join(entities['persons'])}\n\n"
536
+ if entities.get("amounts"):
537
+ entities_md += f"💰 **مبالغ:** amount-{', amount-'.join(entities['amounts'])}\n\n"
538
+ if entities.get("percents"):
539
+ entities_md += f"📊 **درصدها:** percent-{', percent-'.join(entities['percents'])}\n\n"
540
+ if entities.get("groups"):
541
+ entities_md += f"👥 **گروه‌ها:** group-{', group-'.join(entities['groups'])}\n\n"
542
+
543
+ # تحلیل دقیق
544
+ detailed = result.get("detailed_analysis", {})
545
+ detailed_md = "🔍 **تحلیل دقیق:**\n\n"
546
+ detailed_md += f"📅 **تاریخ‌های حفظ شده:** {detailed.get('preserved_dates', 0)}\n"
547
+ detailed_md += f"🕐 **ساعت‌های حفظ شده:** {detailed.get('preserved_times', 0)}\n"
548
+ detailed_md += f"📈 **شاخص‌های مالی:** {detailed.get('financial_indicators', 0)}\n"
549
+ detailed_md += f"📏 **واحدهای حفظ شده:** {detailed.get('units_preserved', 0)}\n"
550
+
551
+ # اطلاعات پردازش
552
+ usage = result.get("usage", {})
553
+ usage_md = "⚡ **اطلاعات پردازش:**\n\n"
554
+ if usage:
555
+ usage_md += f"🤖 **مدل:** {anonymizer.config.model}\n"
556
+ usage_md += f"📥 **Token های ورودی:** {usage.get('prompt_tokens', 'نامشخص')}\n"
557
+ usage_md += f"📤 **Token های خروجی:** {usage.get('completion_tokens', 'نامشخص')}\n"
558
+ usage_md += f"📊 **کل Token ها:** {usage.get('total_tokens', 'نامشخص')}\n"
559
+ else:
560
+ usage_md += "✅ پردازش با موفقیت انجام شد"
561
+
562
+ return (
563
+ result["anonymized_text"],
564
+ stats_md,
565
+ quality_md,
566
+ entities_md,
567
+ detailed_md,
568
+ usage_md
569
+ )
570
+
571
+ except Exception as e:
572
+ return (
573
+ "",
574
+ f"❌ خطای غیرمنتظره: {str(e)}",
575
+ "",
576
+ "",
577
+ "",
578
+ ""
579
+ )
580
+
581
+ def copy_text(text_to_copy):
582
+ """تابع کپی متن"""
583
+ if not text_to_copy or not text_to_copy.strip():
584
+ return gr.Textbox(visible=False), "⚠️ متنی برای کپی وجود ندارد"
585
+
586
+ return gr.Textbox(value=text_to_copy, visible=True), "✅ متن در کادر زیر آماده کپی است"
587
+
588
+ def clear_all():
589
+ """پاک کردن تمام فیلدها"""
590
+ return "", "", "", "", "", "", gr.Textbox(visible=False)
591
+
592
+ # اتصال رویدادها
593
+ anonymize_btn.click(
594
+ fn=process_advanced_text,
595
+ inputs=[input_text, api_key_input],
596
+ outputs=[output_text, statistics_output, quality_output, entities_output, detailed_analysis_output, usage_output]
597
+ )
598
+
599
+ copy_btn.click(
600
+ fn=copy_text,
601
+ inputs=[output_text],
602
+ outputs=[copy_output, statistics_output]
603
+ )
604
+
605
+ clear_btn.click(
606
+ fn=clear_all,
607
+ outputs=[input_text, output_text, statistics_output, quality_output, entities_output, detailed_analysis_output, usage_output, copy_output]
608
+ )
609
+
610
+ # مثال‌های پیشرفته
611
+ gr.Examples(
612
+ examples=[
613
+ ["بانک پاسارگاد با شناسایی سود خالص 155 هزار میلیارد ریالی در ردۀ دوم سودآورترین بانک‌های کشور قرار گرفت."],
614
+ ["مهدی اخوان بهابادی، مدیرعامل همراه اول، اعلام کرد درآمد عملیاتی شرکت با رشد 37 درصدی به 70 هزار و 677 میلیارد تومان رسیده است."],
615
+ ["هزینه لجستیکی بوعلی حدود 100 میلیون دلار بود؛ 40٪ خوراک از خط لوله و 60٪ معادل 1 تا 1.5 میلیون تن در سال."],
616
+ ["مجمع عمومی در تاریخ 1404/04/29 ساعت 10:00 در هتل بزرگ نخل کنگان برگزار شد و علی محمدی، رئیس هیئت مدیره، گزارش عملکرد ارائه داد."],
617
+ ["گروه دتا با سود خالص 45.3 میلیارد تومان و EPS برابر 2850 ریال، رشد 15.7 درصدی نسبت به مدت مشابه سال قبل داشته است."]
618
+ ],
619
+ inputs=input_text,
620
+ label="📚 مثال‌های پیشرفته آزمایشی"
621
+ )
622
+
623
+ # راهنمای کامل
624
+ with gr.Accordion("📖 راهنمای کامل استفاده", open=False):
625
+ gr.Markdown("""
626
+ ## 🎯 ویژگی‌های سیستم پیشرفته:
627
+
628
+ ### 🏷️ انواع برچسب‌ها:
629
+ - **company-XX:** شرکت‌ها، سازمان‌ها، برندها، نهادها
630
+ - **person-XX:** اشخاص حقیقی (نام و نام‌خانوادگی)
631
+ - **group-XX:** گروه‌ها، هلدینگ‌ها، کنسرسیوم‌ها
632
+ - **amount-XX:** تمام اعداد (پولی، تعدادی، حجمی، زمانی)
633
+ - **percent-XX:** درصدها و بازه‌های درصدی
634
+
635
+ ### ✅ موارد حفظ شده:
636
+ - 📅 تاریخ‌ها و ساعت‌ها
637
+ - 🏢 عناوین شغلی و نقش‌ها
638
+ - 📏 واحدها (تومان، ریال، میلیارد، تن، ...)
639
+ - 📈 شاخص‌های مالی (EPS, P/E, ARPU, NPL)
640
+ - 🗺️ نام مکان‌ها و آدرس‌ها
641
+ - 📝 ساختار جمله و لحن
642
+
643
+ ### 🔍 کنترل کیفیت:
644
+ - بررسی شروع اندیس‌ها از 01
645
+ - بررسی پیوستگی اندیس‌ها
646
+ - تضمین ثبات شناسه‌ها در یک متن
647
+ - حفظ واحدها و شاخص‌های مالی
648
+
649
+ ### 💡 نکات مهم:
650
+ - هر نوع موجودیت شماره‌گذاری مستقل دارد
651
+ - در بازه‌های عددی: amount-01–amount-02
652
+ - برای درصدها: percent-01–percent-02
653
+ - اعداد چسبیده: "5هزار" → "amount-01 هزار"
654
+ """)
655
+
656
+ return interface
657
+
658
+ # اجرای برنامه
659
+ if __name__ == "__main__":
660
+ interface = create_advanced_interface()
661
+ interface.launch(
662
+ server_name="0.0.0.0",
663
+ server_port=7860,
664
+ share=True,
665
+ show_error=True
666
+ )