leilaghomashchi commited on
Commit
7e2b934
·
verified ·
1 Parent(s): 6828503

Upload persian_anonymizer_fixed.py

Browse files
Files changed (1) hide show
  1. persian_anonymizer_fixed.py +354 -0
persian_anonymizer_fixed.py ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ناشناس‌ساز پیشرفته متون مالی/خبری فارسی
3
+ قدرت‌گرفته از OpenRouter AI - Qwen3-14B
4
+ نسخه: 1.0.1 (Fixed)
5
+ """
6
+
7
+ import requests
8
+ import json
9
+ import gradio as gr
10
+ from typing import Dict, Any
11
+ import os
12
+ from dataclasses import dataclass
13
+ import re
14
+
15
+ @dataclass
16
+ class OpenRouterConfig:
17
+ """تنظیمات OpenRouter API برای Qwen3-14B"""
18
+ api_key: str
19
+ base_url: str = "https://openrouter.ai/api/v1"
20
+ # ✅ نام مدل صحیح - بدون :free
21
+ model: str = "qwen/qwen-2.5-14b-instruct"
22
+ max_tokens: int = 16384
23
+ temperature: float = 0.6
24
+ top_p: float = 0.85
25
+
26
+ class AdvancedOpenRouterAnonymizer:
27
+ """سیستم پیشرفته ناشناس‌سازی با OpenRouter"""
28
+
29
+ def __init__(self, api_key: str = None):
30
+ if api_key is None:
31
+ api_key = os.getenv("OPENROUTER_API_KEY")
32
+ if not api_key:
33
+ raise ValueError("کلید API یافت نشد - لطفاً در Settings تنظیم کنید")
34
+
35
+ self.config = OpenRouterConfig(api_key=api_key)
36
+ self.system_prompt = self._create_advanced_system_prompt()
37
+
38
+ def _create_advanced_system_prompt(self) -> str:
39
+ """ایجاد دستورالعمل سیستمی پیشرفته"""
40
+ return """شما یک «ناشناس‌ساز متون مالی/خبری فارسی» هستید.
41
+
42
+ ## قوانین اندیس‌گذاری:
43
+ 1. **ترتیب پیوسته**: company-01, company-02, ... | person-01, person-02, ... | amount-01, amount-02, ... | percent-01, percent-02, ...
44
+ 2. **ثبات**: اگر "همراه اول" → company-01 شد، در تمام متن همان باشد
45
+ 3. **نام مستعار**: "فاما" = "فولاد مبارکه" → هر دو company-01
46
+ 4. **اشاره ضمنی**: "این شرکت" اگر به company-01 اشاره دارد → company-01
47
+
48
+ ## انواع موجودیت:
49
+ - **company-XX**: شرکت‌ها، بانک‌ها، سازمان‌ها، گروه‌ها
50
+ - **person-XX**: نام و نام خانوادگی اشخاص
51
+ - **amount-XX**: مبالغ (واحد را حفظ کن)
52
+ - **percent-XX**: درصدها
53
+
54
+ ## قوانین کلیدی:
55
+ 1. بازرس = شرکت است → company-XX
56
+ 2. واحدها: "amount-01 میلیارد تومان" ✅
57
+ 3. کلمات عمومی حفظ: "سه شرکت" → حفظ
58
+ 4. تمام اعداد را شناسایی کن
59
+ 5. درصدها را شناسایی کن
60
+
61
+ ## مثال:
62
+ ورودی: ایران خودرو در اسفند 1402 حدود 23 هزار و 296 میلیارد درآمد کسب کرد که 4.58 درصد افزایش داشت.
63
+ خروجی: company-01 در اسفند 1402 حدود amount-01 درآمد کسب کرد که percent-01 افزایش داشت.
64
+
65
+ ⚠️ فقط متن ناشناس‌شده، بدون توضیح اضافی."""
66
+
67
+ def _make_api_request(self, text: str) -> Dict[str, Any]:
68
+ """ارسال درخواست به OpenRouter API"""
69
+
70
+ print(f"🔍 Debug: Using model = {self.config.model}")
71
+
72
+ # ✅ Headers ساده‌شده
73
+ headers = {
74
+ "Authorization": f"Bearer {self.config.api_key}",
75
+ "Content-Type": "application/json",
76
+ }
77
+
78
+ # ✅ Payload ساده‌شده
79
+ payload = {
80
+ "model": self.config.model,
81
+ "messages": [
82
+ {"role": "system", "content": self.system_prompt},
83
+ {"role": "user", "content": text}
84
+ ],
85
+ "temperature": self.config.temperature,
86
+ "top_p": self.config.top_p,
87
+ "max_tokens": self.config.max_tokens
88
+ }
89
+
90
+ full_url = f"{self.config.base_url}/chat/completions"
91
+
92
+ try:
93
+ response = requests.post(
94
+ full_url,
95
+ headers=headers,
96
+ json=payload,
97
+ timeout=90
98
+ )
99
+
100
+ print(f"📡 Response Status: {response.status_code}")
101
+
102
+ # ✅ نمایش پاسخ کامل در صورت خطا
103
+ if response.status_code != 200:
104
+ print(f"❌ Error Response: {response.text}")
105
+
106
+ response.raise_for_status()
107
+ return response.json()
108
+
109
+ except requests.exceptions.HTTPError as e:
110
+ error_detail = response.text if response else 'No response'
111
+ print(f"❌ HTTP Error Details: {error_detail}")
112
+
113
+ # پیام خطای بهتر
114
+ try:
115
+ error_json = json.loads(error_detail)
116
+ error_msg = error_json.get('error', {}).get('message', str(e))
117
+ except:
118
+ error_msg = str(e)
119
+
120
+ raise Exception(f"خطا در OpenRouter: {error_msg}")
121
+
122
+ except requests.exceptions.RequestException as e:
123
+ raise Exception(f"خطای شبکه: {str(e)}")
124
+
125
+ def anonymize_text(self, text: str) -> Dict[str, Any]:
126
+ """ناشناس‌سازی متن"""
127
+ if not text.strip():
128
+ return {
129
+ "success": False,
130
+ "error": "متن ورودی خالی است"
131
+ }
132
+
133
+ try:
134
+ response = self._make_api_request(text)
135
+
136
+ if "choices" not in response or not response["choices"]:
137
+ return {
138
+ "success": False,
139
+ "error": "پاسخ نامعتبر از API"
140
+ }
141
+
142
+ content = response["choices"][0]["message"]["content"]
143
+ content = self._clean_markdown(content)
144
+ content = content.strip()
145
+
146
+ analysis = self._analyze_anonymized_text(content)
147
+
148
+ return {
149
+ "success": True,
150
+ "anonymized_text": content,
151
+ "entities": analysis["entities"],
152
+ "statistics": analysis["statistics"],
153
+ "detailed_analysis": analysis["detailed_analysis"],
154
+ "usage": response.get("usage", {}),
155
+ "quality_check": self._validate_anonymized_text(content)
156
+ }
157
+
158
+ except Exception as e:
159
+ return {
160
+ "success": False,
161
+ "error": f"خطا در پردازش: {str(e)}"
162
+ }
163
+
164
+ def _clean_markdown(self, content: str) -> str:
165
+ """پاک کردن markdown از پاسخ"""
166
+ if "```" in content:
167
+ lines = content.split('\n')
168
+ clean_lines = []
169
+ skip = False
170
+ for line in lines:
171
+ if line.strip().startswith('```'):
172
+ skip = not skip
173
+ continue
174
+ if not skip:
175
+ clean_lines.append(line)
176
+ content = '\n'.join(clean_lines)
177
+ return content
178
+
179
+ def _analyze_anonymized_text(self, text: str) -> Dict[str, Any]:
180
+ """تحلیل متن ناشناس‌سازی شده"""
181
+ companies = re.findall(r'company-(\d+)', text)
182
+ persons = re.findall(r'person-(\d+)', text)
183
+ amounts = re.findall(r'amount-(\d+)', text)
184
+ percents = re.findall(r'percent-(\d+)', text)
185
+
186
+ statistics = {
187
+ "company": len(set(companies)),
188
+ "person": len(set(persons)),
189
+ "amount": len(set(amounts)),
190
+ "percent": len(set(percents)),
191
+ "total_replacements": len(companies) + len(persons) + len(amounts) + len(percents)
192
+ }
193
+
194
+ entities = {
195
+ "companies": sorted(list(set(companies)), key=lambda x: int(x)),
196
+ "persons": sorted(list(set(persons)), key=lambda x: int(x)),
197
+ "amounts": sorted(list(set(amounts)), key=lambda x: int(x)),
198
+ "percents": sorted(list(set(percents)), key=lambda x: int(x))
199
+ }
200
+
201
+ detailed_analysis = {
202
+ "preserved_dates": len(re.findall(r'\d{4}/\d{1,2}/\d{1,2}|\d{1,2}\s+\w+\s+\d{4}', text)),
203
+ "preserved_times": len(re.findall(r'\d{1,2}:\d{2}', text)),
204
+ "financial_indicators": len(re.findall(r'\b(EPS|P/E|ARPU|NPL|ROE|ROA)\b', text)),
205
+ "units_preserved": len(re.findall(r'(میلیارد|میلیون|هزار|تومان|ریال|درهم|دلار|یورو|تن|کیلوگرم)', text))
206
+ }
207
+
208
+ return {
209
+ "statistics": statistics,
210
+ "entities": entities,
211
+ "detailed_analysis": detailed_analysis
212
+ }
213
+
214
+ def _validate_anonymized_text(self, text: str) -> Dict[str, Any]:
215
+ """اعتبارسنجی"""
216
+ companies = re.findall(r'company-(\d+)', text)
217
+ persons = re.findall(r'person-(\d+)', text)
218
+ amounts = re.findall(r'amount-(\d+)', text)
219
+ percents = re.findall(r'percent-(\d+)', text)
220
+
221
+ validation_issues = []
222
+
223
+ for entity_type, indices in [
224
+ ("company", companies),
225
+ ("person", persons),
226
+ ("amount", amounts),
227
+ ("percent", percents)
228
+ ]:
229
+ if indices:
230
+ unique_indices = sorted(list(set([int(x) for x in indices])))
231
+ if unique_indices[0] != 1:
232
+ validation_issues.append(f"اندیس {entity_type} از 01 شروع نشده")
233
+
234
+ expected = list(range(1, len(unique_indices) + 1))
235
+ if unique_indices != expected:
236
+ validation_issues.append(f"اندیس‌های {entity_type} پیوسته نیستند")
237
+
238
+ return {
239
+ "is_valid": len(validation_issues) == 0,
240
+ "issues": validation_issues,
241
+ "entity_counts": {
242
+ "company": len(set(companies)),
243
+ "person": len(set(persons)),
244
+ "amount": len(set(amounts)),
245
+ "percent": len(set(percents))
246
+ }
247
+ }
248
+
249
+ def create_advanced_interface():
250
+ """ایجاد رابط کاربری"""
251
+
252
+ api_key_available = bool(os.getenv("OPENROUTER_API_KEY"))
253
+
254
+ with gr.Blocks(title="ناشناس‌ساز با Qwen3-14B", theme=gr.themes.Soft()) as interface:
255
+
256
+ gr.Markdown("""
257
+ # 🔒 ناشناس‌ساز پیشرفته متون فارسی
258
+ ### 🚀 مدل: Qwen 2.5 14B Instruct (OpenRouter)
259
+ """)
260
+
261
+ if api_key_available:
262
+ gr.Markdown("✅ **سیستم آماده است**")
263
+ else:
264
+ gr.Markdown("""
265
+ ⚠️ **کلید API تنظیم نشده**
266
+
267
+ لطفاً در Settings یک Secret با نام `OPENROUTER_API_KEY` اضافه کنید.
268
+
269
+ دریافت کلید رایگان: https://openrouter.ai/keys
270
+ """)
271
+
272
+ with gr.Row():
273
+ with gr.Column():
274
+ input_text = gr.Textbox(
275
+ label="📝 متن ورودی",
276
+ placeholder="متن خود را وارد کنید...",
277
+ lines=10,
278
+ rtl=True
279
+ )
280
+ anonymize_btn = gr.Button("🔒 ناشناس‌سازی", variant="primary")
281
+
282
+ with gr.Column():
283
+ output_text = gr.Textbox(
284
+ label="🎯 متن ناشناس‌سازی شده",
285
+ lines=10,
286
+ rtl=True
287
+ )
288
+
289
+ statistics_output = gr.Markdown(label="📊 آمار")
290
+ usage_output = gr.Markdown(label="⚡ اطلاعات پردازش")
291
+
292
+ def process_text(text: str):
293
+ """پردازش متن"""
294
+ if not text or not text.strip():
295
+ return "", "❌ متن ورودی خالی است", ""
296
+
297
+ try:
298
+ anonymizer = AdvancedOpenRouterAnonymizer()
299
+ result = anonymizer.anonymize_text(text)
300
+
301
+ if not result["success"]:
302
+ return "", f"❌ خطا: {result['error']}", ""
303
+
304
+ # آمار
305
+ stats = result.get("statistics", {})
306
+ stats_md = f"""
307
+ ## 📊 آمار کلی:
308
+ - 🏢 شرکت‌ها: {stats.get('company', 0)}
309
+ - 👤 اشخاص: {stats.get('person', 0)}
310
+ - 💰 مبالغ: {stats.get('amount', 0)}
311
+ - 📊 درصدها: {stats.get('percent', 0)}
312
+ - 🔢 کل جایگزینی‌ها: {stats.get('total_replacements', 0)}
313
+ """
314
+
315
+ # اطلاعات پردازش
316
+ usage = result.get("usage", {})
317
+ usage_md = f"""
318
+ ## ⚡ اطلاعات پردازش:
319
+ - 🤖 مدل: Qwen 2.5 14B Instruct
320
+ - 📥 ورودی: {usage.get('prompt_tokens', 0)} tokens
321
+ - 📤 خروجی: {usage.get('completion_tokens', 0)} tokens
322
+ - 📊 کل: {usage.get('total_tokens', 0)} tokens
323
+ """
324
+
325
+ return (
326
+ result["anonymized_text"],
327
+ stats_md,
328
+ usage_md
329
+ )
330
+
331
+ except Exception as e:
332
+ return "", f"❌ خطا: {str(e)}", ""
333
+
334
+ anonymize_btn.click(
335
+ fn=process_text,
336
+ inputs=[input_text],
337
+ outputs=[output_text, statistics_output, usage_output]
338
+ )
339
+
340
+ # مثال‌ها
341
+ gr.Examples(
342
+ examples=[
343
+ ["همراه اول با سهمی ۵۳ درصدی بیشترین نقش را دارد."],
344
+ ["ایران خودرو حدود 23 هزار میلیارد درآمد کسب کرد."],
345
+ ["علی محمدی مدیرعامل بانک ملت گفت که سود 15 درصدی داشته‌اند."]
346
+ ],
347
+ inputs=input_text
348
+ )
349
+
350
+ return interface
351
+
352
+ if __name__ == "__main__":
353
+ interface = create_advanced_interface()
354
+ interface.launch()