Really-amin commited on
Commit
784d7e4
·
verified ·
1 Parent(s): 5857c68

Update app/app.py

Browse files
Files changed (1) hide show
  1. app/app.py +1498 -420
app/app.py CHANGED
@@ -1,516 +1,1594 @@
1
- import gradio as gr
2
- import logging
 
 
 
3
  import os
4
- import sqlite3
5
- import json
6
  import time
7
- from datetime import datetime
8
- from typing import Dict, List, Tuple, Optional
9
- from urllib.parse import urljoin, urlparse
10
- from urllib.robotparser import RobotFileParser
11
- import re
12
- from bs4 import BeautifulSoup
13
  import requests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- # Configure logging
16
- logging.basicConfig(
17
- level=logging.INFO,
18
- format='%(asctime)s - %(levelname)s - %(message)s',
19
- handlers=[logging.StreamHandler()]
 
 
 
 
 
20
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  logger = logging.getLogger(__name__)
22
 
23
- # Iranian legal sources
24
- IRANIAN_LEGAL_SOURCES = [
25
- "https://rc.majlis.ir",
26
- "https://dolat.ir",
27
- "https://iribnews.ir"
28
- ]
 
 
 
 
 
 
 
 
29
 
30
- class SimpleLegalDocument:
31
- """Simple document structure for demo"""
32
- def __init__(self, title: str, content: str, source_url: str,
33
- document_type: str = "web_page", importance_score: float = 0.5):
34
- self.title = title
35
- self.content = content
36
- self.source_url = source_url
37
- self.document_type = document_type
38
- self.importance_score = importance_score
39
- self.summary = content[:100] + "..." if len(content) > 100 else content
40
- self.keywords = self._extract_keywords(content)
41
- self.date_scraped = datetime.now().isoformat()
42
-
43
- def _extract_keywords(self, text: str) -> List[str]:
44
- """Simple keyword extraction for demo"""
45
- words = text.split()
46
- word_freq = {}
47
- for word in words:
48
- if len(word) > 3 and word.isalpha():
49
- word_freq[word] = word_freq.get(word, 0) + 1
50
- return sorted(word_freq.keys(), key=lambda x: word_freq[x], reverse=True)[:5]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
- class SimpleLegalScraper:
53
- """Simple scraper for demo without heavy dependencies"""
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
- def __init__(self, delay: float = 2.0):
56
- self.delay = delay
57
- self.last_request_time = 0
58
- self.session = requests.Session()
59
- self.session.headers.update({
60
- 'User-Agent': 'LegalScraperDemo/1.0',
61
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
62
- })
 
 
 
 
 
63
 
64
- def _respect_delay(self):
65
- """Respect delay between requests"""
66
- current_time = time.time()
67
- time_since_last = current_time - self.last_request_time
68
- if time_since_last < self.delay:
69
- time.sleep(self.delay - time_since_last)
70
- self.last_request_time = time.time()
71
 
72
- def fetch_page(self, url: str) -> Optional[BeautifulSoup]:
73
- """Fetch and parse a web page"""
74
  try:
75
- self._respect_delay()
76
- logger.info(f"Fetching: {url}")
77
- response = self.session.get(url, timeout=10)
78
- response.raise_for_status()
79
- return BeautifulSoup(response.text, 'html.parser')
 
 
 
 
 
 
 
 
 
80
  except Exception as e:
81
- logger.warning(f"Failed to fetch {url}: {e}")
82
- return None
83
 
84
- def scrape_demo_content(self, urls: List[str], max_docs: int = 5) -> List[SimpleLegalDocument]:
85
- """Scrape demo content from websites"""
86
- documents = []
 
 
 
 
 
87
 
88
- for url in urls[:3]: # Limit to 3 URLs for demo
89
- try:
90
- soup = self.fetch_page(url)
91
- if not soup:
92
- continue
93
-
94
- # Extract title
95
- title = soup.find('title')
96
- title_text = title.get_text(strip=True) if title else "صفحه وب"
97
-
98
- # Extract content
99
- content_elem = soup.find('body') or soup
100
- content = content_elem.get_text(strip=True)
101
- content = content[:500] # Limit content length
102
-
103
- # Determine document type based on URL
104
- doc_type = "web_page"
105
- if 'majlis' in url:
106
- doc_type = "law"
107
- elif 'news' in url:
108
- doc_type = "news"
109
-
110
- # Create document
111
- doc = SimpleLegalDocument(
112
- title=title_text,
113
- content=content,
114
- source_url=url,
115
- document_type=doc_type,
116
- importance_score=0.7 if doc_type == "law" else 0.5
117
- )
118
-
119
- documents.append(doc)
120
- logger.info(f"Scraped: {title_text[:50]}...")
121
 
122
- if len(documents) >= max_docs:
123
- break
 
 
124
 
125
- except Exception as e:
126
- logger.error(f"Error scraping {url}: {e}")
127
- continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
- return documents
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
- class LegalScraperInterface:
 
 
 
132
  def __init__(self):
133
- self.scraper = SimpleLegalScraper(delay=2.0)
134
- self.is_scraping = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
- def scrape_real_sources(self, urls_text: str, max_docs: int) -> Tuple[str, str, str]:
137
- """Scrape websites from provided URLs"""
138
- if self.is_scraping:
139
- return "❌ اسکراپینگ در حال انجام است", "", ""
140
 
141
  try:
142
- self.is_scraping = True
143
- urls = [url.strip() for url in urls_text.split('\n') if url.strip()]
144
 
145
- if not urls:
146
- urls = IRANIAN_LEGAL_SOURCES
147
 
148
- documents = self.scraper.scrape_demo_content(urls, max_docs)
 
149
 
150
- status = f"✅ اسکراپینگ کامل شد - {len(documents)} سند جمع‌آوری شد"
 
151
 
152
- # Create summary
153
- summary_lines = [
154
- f"📊 **خلاصه نتایج:**",
155
- f"- تعداد اسناد: {len(documents)}",
156
- f"- منابع پردازش شده: {len(urls)}",
157
- f"- زمان: {datetime.now().strftime('%H:%M:%S')}",
158
- "",
159
- "📋 **اسناد جمع‌آوری شده:**"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  ]
161
 
162
- for i, doc in enumerate(documents, 1):
163
- summary_lines.append(f"{i}. {doc.title[:50]}...")
 
164
 
165
- summary = "\n".join(summary_lines)
 
166
 
167
- # Create preview
168
- preview_lines = []
169
- for doc in documents[:3]:
170
- preview_lines.extend([
171
- f"**{doc.title}**",
172
- f"نوع: {doc.document_type}",
173
- f"منبع: {doc.source_url}",
174
- f"امتیاز اهمیت: {doc.importance_score:.2f}",
175
- f"خلاصه: {doc.summary}",
176
- f"کلیدواژه‌ها: {', '.join(doc.keywords[:3])}",
177
- "---"
178
- ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
- preview = "\n".join(preview_lines) if preview_lines else "هیچ سندی یافت نشد"
 
 
 
 
 
 
 
181
 
182
- return status, summary, preview
 
183
 
184
  except Exception as e:
185
- error_msg = f"خطا در اسکراپینگ: {str(e)}"
186
- logger.error(error_msg)
187
- return error_msg, "", ""
188
-
189
- finally:
190
- self.is_scraping = False
191
 
192
- def get_database_stats(self) -> Tuple[str, str]:
193
- """Get simulated statistics"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  try:
195
- stats_lines = [
196
- "📊 **آمار پایگاه داده:**",
197
- f"- کل اسناد: 15",
198
- "",
199
- "📈 **بر اساس نوع:**",
200
- "- قوانین: 6",
201
- "- اخبار: 5",
202
- "- صفحات وب: 4",
203
- "",
204
- "⭐ **توزيع اهمیت:**",
205
- "- بالا (>0.7): 8",
206
- "- متوسط (0.4-0.7): 5",
207
- "- پایین (<0.4): 2",
208
- "",
209
- "🔑 **کلیدواژه‌های برتر:**",
210
- "- قانون: 12",
211
- "- حقوق: 9",
212
- "- ایران: 7",
213
- "- مجلس: 6",
214
- "- اسلامی: 5"
215
- ]
216
 
217
- stats_text = "\n".join(stats_lines)
218
-
219
- # Simple HTML visualization
220
- viz_html = """
221
- <div style='text-align: center; padding: 20px; background: #f8f9fa; border-radius: 10px;'>
222
- <h3>📊 نمودارهای تحلیلی</h3>
223
- <div style='display: flex; justify-content: space-around; margin-top: 20px;'>
224
- <div style='text-align: center;'>
225
- <div style='width: 120px; height: 120px; background: linear-gradient(135deg, #3498db, #2c3e50);
226
- border-radius: 50%; display: flex; align-items: center; justify-content: center;
227
- color: white; font-weight: bold; font-size: 14px; margin: 0 auto;'>
228
- انواع اسناد
229
- </div>
230
- <p style='margin-top: 10px;'>توزیع بر اساس نوع</p>
231
- </div>
232
- <div style='text-align: center;'>
233
- <div style='width: 120px; height: 120px; background: linear-gradient(135deg, #e74c3c, #c0392b);
234
- border-radius: 50%; display: flex; align-items: center; justify-content: center;
235
- color: white; font-weight: bold; font-size: 14px; margin: 0 auto;'>
236
- سطح اهمیت
237
- </div>
238
- <p style='margin-top: 10px;'>امتیازبندی اسناد</p>
239
- </div>
240
- </div>
241
- </div>
242
- """
243
 
244
- return stats_text, viz_html
 
 
 
 
 
 
 
 
245
 
246
  except Exception as e:
247
- error_msg = f"خطا در تولید آمار: {str(e)}"
248
- return error_msg, ""
 
 
249
 
250
- def search_documents(self, query: str) -> str:
251
- """Simulate search functionality"""
252
- if not query.strip():
253
- return "لطفاً کلیدواژه‌ای برای جستجو وارد کنید"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
  try:
256
- # Sample search results
257
- sample_results = [
258
- {
259
- "title": "قانون اساسی جمهوری اسلامی ایران",
260
- "document_type": "law",
261
- "similarity": 0.92,
262
- "source": "https://rc.majlis.ir/fa/law/show/1",
263
- "summary": "قانون اساسی جمهوری اسلامی ایران مصوب 1358 با اصلاحات بعدی"
264
- },
265
- {
266
- "title": "آیین‌نامه داخلی مجلس شورای اسلامی",
267
- "document_type": "regulation",
268
- "similarity": 0.85,
269
- "source": "https://rc.majlis.ir/fa/regulation/show/1",
270
- "summary": "مقررات مربوط به نحوه تشکیل و اداره جلسات مجلس شورای اسلامی"
271
- }
272
  ]
273
 
274
- result_lines = [f"🔍 **نتایج جستجو برای '{query}':**\n"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
 
276
- for i, result in enumerate(sample_results, 1):
277
- result_lines.extend([
278
- f"**{i}. {result['title']}**",
279
- f" نوع: {result['document_type']}",
280
- f" امتیاز شباهت: {result['similarity']:.2f}",
281
- f" منبع: {result['source']}",
282
- f" خلاصه: {result['summary']}",
283
- "---"
284
- ])
285
 
286
- return "\n".join(result_lines)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
  except Exception as e:
289
- error_msg = f"خطا در جستجو: {str(e)}"
290
- return error_msg
291
 
292
- def export_data(self) -> Tuple[str, str]:
293
- """Create sample export file"""
294
  try:
295
- import csv
 
296
 
297
- # Create data directory if not exists
298
- os.makedirs('/app/data', exist_ok=True)
299
 
300
- filename = f"/app/data/legal_documents_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
 
301
 
302
- # Sample data
303
- sample_data = [
304
- ['title', 'type', 'score', 'source', 'keywords'],
305
- ['قانون اساسی جمهوری اسلامی ایران', 'law', '0.95', 'https://rc.majlis.ir/fa/law/show/1', 'قانون,اساسی,جمهوری'],
306
- ['آیین‌نامه داخلی مجلس', 'regulation', '0.85', 'https://rc.majlis.ir/fa/regulation/show/1', 'آیین‌نامه,مجلس,داخلی'],
307
- ['اخبار حقوقی روز', 'news', '0.65', 'https://iribnews.ir/news/456', 'اخبار,حقوقی,روز'],
308
- ['تحلیل قراردادهای بین‌المللی', 'analysis', '0.75', 'https://dolat.ir/analysis/789', 'تحلیل,قرارداد,بین‌المللی']
309
- ]
310
 
311
- with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
312
- writer = csv.writer(csvfile)
313
- writer.writerows(sample_data)
314
 
315
- return f"✅ فایل نمونه با موفقیت تولید شد: {filename}", filename
 
 
 
 
 
 
 
316
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  except Exception as e:
318
- error_msg = f"خطا در تولید فایل: {str(e)}"
319
- return error_msg, ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
- def create_interface():
322
- interface = LegalScraperInterface()
323
-
324
- css = """
325
- .gradio-container {
326
- max-width: 1200px !important;
327
- margin: auto;
328
- font-family: 'Tahoma', sans-serif;
329
- }
330
- .header {
331
- background: linear-gradient(135deg, #2c3e50, #3498db);
332
- color: white;
333
- padding: 20px;
334
- border-radius: 10px;
335
- text-align: center;
336
- margin-bottom: 20px;
337
- }
338
- .tab-content {
339
- padding: 20px;
340
- background: #f8f9fa;
341
- border-radius: 10px;
342
- margin-bottom: 20px;
343
- }
344
- """
345
-
346
- with gr.Blocks(css=css, title="اسکراپر اسناد حقوقی", theme=gr.themes.Soft()) as app:
347
-
348
- gr.HTML("""
349
- <div class="header">
350
- <h1>🕷️ اسکراپر اسناد حقوقی</h1>
351
- <p>سیستم جمع‌آوری و تحلیل اسناد حقوقی از منابع وب</p>
352
- </div>
353
- """)
354
-
355
- with gr.Tab("🕷️ اسکراپینگ"):
356
- gr.Markdown("## جمع‌آوری اسناد از منابع وب")
357
-
358
- with gr.Row():
359
- with gr.Column(scale=2):
360
- urls_input = gr.Textbox(
361
- label="📝 URL های منابع",
362
- placeholder="هر URL را در یک خط وارد کنید:",
363
- lines=5,
364
- value="https://rc.majlis.ir\nhttps://dolat.ir\nhttps://iribnews.ir"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  )
366
 
367
- max_docs = gr.Slider(
368
- label="حداکثر اسناد",
369
- minimum=1,
370
- maximum=10,
371
- value=5,
372
- step=1
373
  )
374
 
375
- scrape_btn = gr.Button("🚀 شروع اسکراپینگ", variant="primary")
376
-
377
- with gr.Column(scale=1):
378
- status_output = gr.Textbox(
379
- label="⚡ وضعیت",
380
- interactive=False,
381
- lines=3
 
 
 
 
 
382
  )
383
-
384
- with gr.Row():
385
- summary_output = gr.Textbox(
386
- label="📊 خلاصه نتایج",
387
- interactive=False,
388
- lines=8
389
- )
 
 
 
 
 
 
 
 
 
 
 
390
 
391
- preview_output = gr.Textbox(
392
- label="👁️ پیش‌نمایش اسناد",
393
- interactive=False,
394
- lines=8
395
- )
396
 
397
- scrape_btn.click(
398
- fn=interface.scrape_real_sources,
399
- inputs=[urls_input, max_docs],
400
- outputs=[status_output, summary_output, preview_output]
401
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
 
403
- with gr.Tab("🔍 جستجو"):
404
- gr.Markdown("## جستجو در اسناد جمع‌آوری شده")
 
 
 
 
 
405
 
406
- search_input = gr.Textbox(
407
- label="🔍 کلیدواژه جستجو",
408
- placeholder="مثال: قانون اساسی, آیین‌نامه, حقوق"
409
- )
410
 
411
- search_btn = gr.Button("🔍 جستجو", variant="primary")
 
412
 
413
- search_results = gr.Textbox(
414
- label="📋 نتایج جستجو",
415
- interactive=False,
416
- lines=15
417
- )
418
 
419
- search_btn.click(
420
- fn=interface.search_documents,
421
- inputs=[search_input],
422
- outputs=[search_results]
423
- )
 
 
 
 
 
 
 
424
 
425
- with gr.Tab("📊 آمار و تحلیل"):
426
- gr.Markdown("## آمار و تحلیل داده‌ها")
427
 
428
- stats_btn = gr.Button("📊 بروزرسانی آمار", variant="secondary")
 
429
 
430
- with gr.Row():
431
- stats_text = gr.Textbox(
432
- label="📈 آمار متنی",
433
- interactive=False,
434
- lines=15
435
- )
436
-
437
- stats_plot = gr.HTML(
438
- label="📊 نمودارهای تحلیلی"
439
- )
 
 
 
 
440
 
441
- stats_btn.click(
442
- fn=interface.get_database_stats,
443
- outputs=[stats_text, stats_plot]
444
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
 
446
- with gr.Tab("💾 خروجی داده‌ها"):
447
- gr.Markdown("## ذخیره‌سازی و خروجی")
 
 
 
448
 
449
- export_btn = gr.Button("💾 تولید فایل خروجی", variant="primary")
 
 
 
 
 
 
 
 
450
 
451
- export_status = gr.Textbox(
452
- label="📝 وضعیت",
453
- interactive=False,
454
- lines=2
455
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
 
457
- export_file = gr.File(
458
- label="📁 دانلود فایل",
459
- visible=False
 
 
 
 
460
  )
461
 
462
- export_btn.click(
463
- fn=interface.export_data,
464
- outputs=[export_status, export_file]
 
 
 
465
  )
466
-
467
- with gr.Tab("📚 راهنما"):
468
- gr.Markdown("""
469
- # 🕷️ راهنمای اسکراپر اسناد حقوقی
470
 
471
- ## ویژگی‌ها
 
 
 
 
472
 
473
- - جمع‌آوری اسناد از منابع وب
474
- - پردازش و تحلیل محتوا
475
- - جستجوی پیشرفته
476
- - آمار و گزارش‌های تحلیلی
477
- - خروجی در قالب CSV
 
478
 
479
- ## نحوه استفاده
 
 
 
 
 
480
 
481
- 1. در تب "اسکراپینگ" URL منابع را وارد کنید
482
- 2. دکمه "شروع اسکراپینگ" را بزنید
483
- 3. از تب "جستجو" برای یافتن اسناد استفاده کنید
484
- 4. آمار و تحلیل‌ها را مشاهده کنید
485
- 5. فایل‌های خروجی را دانلود کنید
486
 
487
- ## منابع پیشنهادی
 
 
 
488
 
489
- - مجلس شورای اسلامی (rc.majlis.ir)
490
- - پورتال دولت (dolat.ir)
491
- - خبرگزاری‌های معتبر
 
 
492
 
493
- ⚠️ **تذکر**: این ابزار برای مقاصد آموزشی و پژوهشی ا.رائه شده است.
494
- """)
495
-
496
- return app
 
 
 
497
 
 
498
  def main():
499
- """Main entry point"""
500
- print("🚀 راه اندازی اسکراپر اسناد حقوقی...")
501
-
502
- # Create required directories
503
- os.makedirs("/app/data", exist_ok=True)
504
-
505
- # Create and launch interface
506
- interface = create_interface()
507
- interface.launch(
508
- server_name="0.0.0.0",
509
- server_port=7860,
510
- share=False,
511
- show_error=True,
512
- debug=False
513
- )
 
 
 
 
 
 
514
 
515
  if __name__ == "__main__":
516
  main()
 
1
+ """
2
+ پلتفرم پیشرفته هوشمند اسناد حقوقی ایران - نسخه ارتقاء یافته
3
+ مجهز به مدل‌های SOTA فارسی، سیستم کش هوشمند و امتیازدهی پیشرفته
4
+ """
5
+
6
  import os
7
+ import gc
8
+ import sys
9
  import time
10
+ import json
11
+ import logging
12
+ import hashlib
13
+ import resource
 
 
14
  import requests
15
+ import threading
16
+ import re
17
+ import random
18
+ import sqlite3
19
+ import pickle
20
+ import tempfile
21
+ from pathlib import Path
22
+ from datetime import datetime, timedelta
23
+ from typing import List, Dict, Any, Optional, Tuple, Union
24
+ from dataclasses import dataclass, field
25
+ from concurrent.futures import ThreadPoolExecutor, as_completed, ProcessPoolExecutor
26
+ from urllib.parse import urljoin, urlparse
27
+ import warnings
28
+ import asyncio
29
+ from functools import lru_cache
30
+ import numpy as np
31
 
32
+ import gradio as gr
33
+ import pandas as pd
34
+ import torch
35
+ from bs4 import BeautifulSoup
36
+ from transformers import (
37
+ AutoTokenizer,
38
+ AutoModelForSequenceClassification,
39
+ pipeline,
40
+ logging as transformers_logging,
41
+ AutoModel
42
  )
43
+ from sentence_transformers import SentenceTransformer, util
44
+ from hazm import Normalizer, word_tokenize, Lemmatizer
45
+ import faiss
46
+
47
+ # === تنظیمات اولیه پیشرفته ===
48
+ warnings.filterwarnings('ignore')
49
+ transformers_logging.set_verbosity_error()
50
+
51
+ try:
52
+ resource.setrlimit(resource.RLIMIT_AS, (4*1024*1024*1024, 4*1024*1024*1024))
53
+ except:
54
+ pass
55
+
56
+ # ایجاد دایرکتوری‌های مورد نیاز
57
+ WORK_DIR = Path("/tmp/legal_scraper_data")
58
+ WORK_DIR.mkdir(exist_ok=True, parents=True)
59
+
60
+ os.environ.update({
61
+ 'TRANSFORMERS_CACHE': str(WORK_DIR / 'hf_cache'),
62
+ 'HF_HOME': str(WORK_DIR / 'hf_cache'),
63
+ 'TORCH_HOME': str(WORK_DIR / 'torch_cache'),
64
+ 'TOKENIZERS_PARALLELISM': 'false',
65
+ 'CUDA_VISIBLE_DEVICES': '0' if torch.cuda.is_available() else ''
66
+ })
67
+
68
+ # ایجاد دایرکتوری‌های کش
69
+ for cache_dir in [os.environ['TRANSFORMERS_CACHE'], os.environ['HF_HOME'], os.environ['TORCH_HOME']]:
70
+ os.makedirs(cache_dir, exist_ok=True)
71
+
72
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
73
  logger = logging.getLogger(__name__)
74
 
75
+ # === ثابت‌های سیستم با مسیرهای اصلاح شده ===
76
+ DB_PATH = str(WORK_DIR / "iranian_legal_archive_advanced.sqlite")
77
+ CACHE_DB_PATH = str(WORK_DIR / "cache_system.sqlite")
78
+ EMBEDDINGS_CACHE_PATH = str(WORK_DIR / "embeddings_cache.pkl")
79
+ VECTOR_INDEX_PATH = str(WORK_DIR / "faiss_index.bin")
80
+
81
+ # آیکون‌های بهبود یافته
82
+ SVG_ICONS = {
83
+ 'search': '🔍', 'document': '📄', 'analyze': '🤖', 'export': '📊',
84
+ 'settings': '⚙️', 'link': '🔗', 'success': '✅', 'error': '❌',
85
+ 'warning': '⚠️', 'database': '🗄️', 'crawler': '🔄', 'brain': '🧠',
86
+ 'cache': '⚡', 'score': '📈', 'classify': '🏷️', 'similarity': '🎯',
87
+ 'legal': '⚖️', 'home': '🏠', 'stats': '📈', 'process': '🔧'
88
+ }
89
 
90
+ # منابع حقوقی با پیکربندی پیشرفته
91
+ LEGAL_SOURCES = {
92
+ "مجلس شورای اسلامی": {
93
+ "base_url": "https://rc.majlis.ir",
94
+ "patterns": ["/fa/law/show/", "/fa/report/show/"],
95
+ "selectors": [".main-content", ".article-body", "article"],
96
+ "delay_range": (2, 5),
97
+ "priority": 1,
98
+ "reliability_score": 0.95
99
+ },
100
+ "پورتال ملی قوانین": {
101
+ "base_url": "https://www.dotic.ir",
102
+ "patterns": ["/portal/law/", "/regulation/"],
103
+ "selectors": [".content-area", ".law-content"],
104
+ "delay_range": (1, 4),
105
+ "priority": 1,
106
+ "reliability_score": 0.90
107
+ },
108
+ "قوه قضاییه": {
109
+ "base_url": "https://www.judiciary.ir",
110
+ "patterns": ["/fa/news/", "/fa/verdict/"],
111
+ "selectors": [".news-content", ".main-content"],
112
+ "delay_range": (3, 6),
113
+ "priority": 2,
114
+ "reliability_score": 0.85
115
+ },
116
+ "وزارت دادگستری": {
117
+ "base_url": "https://www.moj.ir",
118
+ "patterns": ["/fa/news/", "/fa/regulation/"],
119
+ "selectors": [".content-area", ".news-content"],
120
+ "delay_range": (2, 4),
121
+ "priority": 2,
122
+ "reliability_score": 0.80
123
+ }
124
+ }
125
+
126
+ # واژگان تخصصی حقوقی گسترده
127
+ PERSIAN_LEGAL_TERMS = {
128
+ "قوانین_اساسی": ["قانون اساسی", "مجلس شورای اسلامی", "شورای نگهبان", "ولایت فقیه", "اصول قانون اساسی"],
129
+ "قوانین_عادی": ["ماده", "تبصره", "فصل", "باب", "قانون مدنی", "قانون جزا", "قانون تجارت", "قانون کار"],
130
+ "اصطلاحات_حقوقی": ["شخص حقیقی", "شخص حقوقی", "دعوا", "خواهان", "خوانده", "مجازات", "قرارداد", "تعهد"],
131
+ "نهادهای_قضایی": ["دادگاه", "قاضی", "وکیل", "مدعی‌العموم", "رای", "حکم", "دادنامه", "قرار"],
132
+ "اصطلاحات_مالی": ["مالیات", "عوارض", "جریمه", "خسارت", "تأمین", "ضمانت", "وثیقه", "دیه"],
133
+ "جرائم": ["جنایت", "جنحه", "تخلف", "قصاص", "دیه", "تعزیر", "حدود", "قذف"]
134
+ }
135
+
136
+ # مدل‌های SOTA پیشنهادی
137
+ AVAILABLE_MODELS = {
138
+ "classification": {
139
+ "fabert": "sbunlp/fabert",
140
+ "parsbert": "HooshvareLab/bert-base-parsbert-uncased",
141
+ "legal_roberta": "lexlms/legal-roberta-base"
142
+ },
143
+ "embedding": {
144
+ "maux_gte": "xmanii/maux-gte-persian",
145
+ "sentence_transformer": "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
146
+ },
147
+ "ner": {
148
+ "parsbert_ner": "HooshvareLab/bert-fa-base-uncased-ner"
149
+ }
150
+ }
151
 
152
+ @dataclass
153
+ class ProcessingResult:
154
+ """نتیجه پردازش سند"""
155
+ url: str
156
+ title: str
157
+ content: str
158
+ source: str
159
+ quality_score: float
160
+ classification: Dict[str, float]
161
+ sentiment_score: float
162
+ legal_entities: List[str]
163
+ embeddings: Optional[np.ndarray]
164
+ processing_time: float
165
+ cache_hit: bool = False
166
 
167
+ @dataclass
168
+ class SystemMetrics:
169
+ """متریک‌های عملکرد سیستم"""
170
+ total_processed: int = 0
171
+ cache_hits: int = 0
172
+ classification_accuracy: float = 0.0
173
+ avg_processing_time: float = 0.0
174
+ memory_usage: float = 0.0
175
+ active_crawlers: int = 0
176
+
177
+ # === سیستم کش هوشمند ===
178
+ class IntelligentCacheSystem:
179
+ """سیستم کش هوشمند با TTL و اولویت‌بندی"""
180
 
181
+ def __init__(self, cache_db_path: str = CACHE_DB_PATH):
182
+ self.cache_db_path = cache_db_path
183
+ self.memory_cache = {}
184
+ self.access_count = {}
185
+ self.max_memory_items = 1000
186
+ self._init_database()
 
187
 
188
+ def _init_database(self):
189
+ """ایجاد پایگاه داده کش"""
190
  try:
191
+ with sqlite3.connect(self.cache_db_path) as conn:
192
+ conn.execute('''
193
+ CREATE TABLE IF NOT EXISTS cache_entries (
194
+ key TEXT PRIMARY KEY,
195
+ value BLOB,
196
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
197
+ access_count INTEGER DEFAULT 1,
198
+ ttl_seconds INTEGER DEFAULT 3600,
199
+ priority INTEGER DEFAULT 1
200
+ )
201
+ ''')
202
+ conn.execute('''
203
+ CREATE INDEX IF NOT EXISTS idx_created_ttl ON cache_entries(created_at, ttl_seconds)
204
+ ''')
205
  except Exception as e:
206
+ logger.error(f"خطا در ایجاد پایگاه داده کش: {e}")
 
207
 
208
+ def _generate_key(self, url: str, model_type: str = "general") -> str:
209
+ """تولید کلید یکتا برای کش"""
210
+ content = f"{url}:{model_type}"
211
+ return hashlib.md5(content.encode()).hexdigest()
212
+
213
+ def get(self, url: str, model_type: str = "general") -> Optional[Dict]:
214
+ """دریافت از کش با بررسی TTL"""
215
+ key = self._generate_key(url, model_type)
216
 
217
+ # بررسی memory cache
218
+ if key in self.memory_cache:
219
+ self.access_count[key] = self.access_count.get(key, 0) + 1
220
+ return self.memory_cache[key]
221
+
222
+ # بررسی database cache
223
+ try:
224
+ with sqlite3.connect(self.cache_db_path) as conn:
225
+ cursor = conn.execute('''
226
+ SELECT value, created_at, ttl_seconds, access_count
227
+ FROM cache_entries
228
+ WHERE key = ?
229
+ ''', (key,))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
+ row = cursor.fetchone()
232
+ if row:
233
+ value_blob, created_at, ttl_seconds, access_count = row
234
+ created_time = datetime.fromisoformat(created_at)
235
 
236
+ # بررسی انقضاء
237
+ if datetime.now() - created_time < timedelta(seconds=ttl_seconds):
238
+ # بروزرسانی شمارنده دسترسی
239
+ conn.execute(
240
+ 'UPDATE cache_entries SET access_count = access_count + 1 WHERE key = ?',
241
+ (key,)
242
+ )
243
+
244
+ # اضافه به memory cache
245
+ value = pickle.loads(value_blob)
246
+ self._add_to_memory_cache(key, value)
247
+ return value
248
+ else:
249
+ # حذف entry منقضی
250
+ conn.execute('DELETE FROM cache_entries WHERE key = ?', (key,))
251
+ except Exception as e:
252
+ logger.error(f"خطا در دریافت از کش: {e}")
253
+
254
+ return None
255
+
256
+ def set(self, url: str, value: Dict, model_type: str = "general",
257
+ ttl_seconds: int = 3600, priority: int = 1):
258
+ """ذخیره در کش"""
259
+ key = self._generate_key(url, model_type)
260
+
261
+ # ذخیره در memory cache
262
+ self._add_to_memory_cache(key, value)
263
 
264
+ # ذخیره در database
265
+ try:
266
+ with sqlite3.connect(self.cache_db_path) as conn:
267
+ value_blob = pickle.dumps(value)
268
+ conn.execute('''
269
+ INSERT OR REPLACE INTO cache_entries
270
+ (key, value, ttl_seconds, priority)
271
+ VALUES (?, ?, ?, ?)
272
+ ''', (key, value_blob, ttl_seconds, priority))
273
+ except Exception as e:
274
+ logger.error(f"خطا در ذخیره در کش: {e}")
275
+
276
+ def _add_to_memory_cache(self, key: str, value: Dict):
277
+ """اضافه کردن به memory cache با مدیریت حد"""
278
+ if len(self.memory_cache) >= self.max_memory_items:
279
+ # حذف کم‌استفاده‌ترین item
280
+ if self.access_count:
281
+ least_used_key = min(self.access_count.keys(), key=self.access_count.get)
282
+ if least_used_key in self.memory_cache:
283
+ del self.memory_cache[least_used_key]
284
+ if least_used_key in self.access_count:
285
+ del self.access_count[least_used_key]
286
+
287
+ self.memory_cache[key] = value
288
+ self.access_count[key] = self.access_count.get(key, 0) + 1
289
+
290
+ def cleanup_expired(self):
291
+ """پاکسازی entries منقضی"""
292
+ try:
293
+ with sqlite3.connect(self.cache_db_path) as conn:
294
+ conn.execute('''
295
+ DELETE FROM cache_entries
296
+ WHERE datetime(created_at, '+' || ttl_seconds || ' seconds') < datetime('now')
297
+ ''')
298
+ conn.commit()
299
+ except Exception as e:
300
+ logger.error(f"خطا در پاکسازی کش: {e}")
301
+
302
+ def get_stats(self) -> Dict:
303
+ """آمار کش"""
304
+ try:
305
+ with sqlite3.connect(self.cache_db_path) as conn:
306
+ cursor = conn.execute('''
307
+ SELECT
308
+ COUNT(*) as total_entries,
309
+ SUM(access_count) as total_accesses,
310
+ AVG(access_count) as avg_accesses
311
+ FROM cache_entries
312
+ ''')
313
+ stats = cursor.fetchone()
314
+
315
+ return {
316
+ 'memory_cache_size': len(self.memory_cache),
317
+ 'database_entries': stats[0] if stats and stats[0] else 0,
318
+ 'total_accesses': stats[1] if stats and stats[1] else 0,
319
+ 'average_accesses': round(stats[2], 2) if stats and stats[2] else 0
320
+ }
321
+ except Exception as e:
322
+ logger.error(f"خطا در دریافت آمار کش: {e}")
323
+ return {
324
+ 'memory_cache_size': len(self.memory_cache),
325
+ 'database_entries': 0,
326
+ 'total_accesses': 0,
327
+ 'average_accesses': 0
328
+ }
329
 
330
+ # === سیستم امتیازدهی پیشرفته ===
331
+ class AdvancedScoringSystem:
332
+ """سیستم امتیازدهی پیشرفته با وزن‌دهی چندگانه"""
333
+
334
  def __init__(self):
335
+ self.weights = {
336
+ 'content_length': 0.15,
337
+ 'legal_terms_density': 0.25,
338
+ 'source_reliability': 0.20,
339
+ 'structure_quality': 0.15,
340
+ 'linguistic_quality': 0.15,
341
+ 'citation_count': 0.10
342
+ }
343
+
344
+ try:
345
+ self.normalizer = Normalizer()
346
+ self.lemmatizer = Lemmatizer()
347
+ except Exception as e:
348
+ logger.warning(f"خطا در بارگذاری ابزارهای پردازش متن: {e}")
349
+ self.normalizer = None
350
+ self.lemmatizer = None
351
 
352
+ def calculate_comprehensive_score(self, content: str, source_info: Dict,
353
+ legal_entities: List[str]) -> Dict[str, float]:
354
+ """محاسبه امتیاز جامع"""
355
+ scores = {}
356
 
357
  try:
358
+ # امتیاز طول محتوا
359
+ scores['content_length'] = self._score_content_length(content)
360
 
361
+ # تراکم اصطلاحات حقوقی
362
+ scores['legal_terms_density'] = self._score_legal_terms_density(content)
363
 
364
+ # قابلیت اعتماد منبع
365
+ scores['source_reliability'] = source_info.get('reliability_score', 0.5)
366
 
367
+ # کیفیت ساختار
368
+ scores['structure_quality'] = self._score_structure_quality(content)
369
 
370
+ # کیفیت زبانی
371
+ scores['linguistic_quality'] = self._score_linguistic_quality(content)
372
+
373
+ # تعداد ارجاعات
374
+ scores['citation_count'] = self._score_citations(content, legal_entities)
375
+
376
+ # محاسبه امتیاز نهایی
377
+ final_score = sum(
378
+ scores[factor] * self.weights[factor]
379
+ for factor in scores
380
+ )
381
+
382
+ scores['final_score'] = min(100, max(0, final_score * 100))
383
+
384
+ except Exception as e:
385
+ logger.error(f"خطا در محاسبه امتیاز: {e}")
386
+ scores = {
387
+ 'content_length': 0.5,
388
+ 'legal_terms_density': 0.5,
389
+ 'source_reliability': 0.5,
390
+ 'structure_quality': 0.5,
391
+ 'linguistic_quality': 0.5,
392
+ 'citation_count': 0.5,
393
+ 'final_score': 50.0
394
+ }
395
+
396
+ return scores
397
+
398
+ def _score_content_length(self, content: str) -> float:
399
+ """امتیازدهی بر اساس طول محتوا"""
400
+ try:
401
+ word_count = len(content.split())
402
+ if word_count < 50:
403
+ return word_count / 50 * 0.5
404
+ elif word_count > 2000:
405
+ return 1.0
406
+ else:
407
+ return 0.5 + (word_count - 50) / 1950 * 0.5
408
+ except:
409
+ return 0.5
410
+
411
+ def _score_legal_terms_density(self, content: str) -> float:
412
+ """امتیازدهی تراکم اصطلاحات حقوقی"""
413
+ try:
414
+ total_terms = 0
415
+ content_lower = content.lower()
416
+
417
+ for category, terms in PERSIAN_LEGAL_TERMS.items():
418
+ for term in terms:
419
+ total_terms += content_lower.count(term.lower())
420
+
421
+ words = len(content.split())
422
+ if words == 0:
423
+ return 0.0
424
+
425
+ density = total_terms / words
426
+ return min(1.0, density * 20) # نرمال‌سازی
427
+ except:
428
+ return 0.5
429
+
430
+ def _score_structure_quality(self, content: str) -> float:
431
+ """امتیازدهی کیفیت ساختار"""
432
+ try:
433
+ score = 0.0
434
+
435
+ # بررسی وجود ساختار مواد و تبصره‌ها
436
+ if 'ماده' in content:
437
+ score += 0.3
438
+ if 'تبصره' in content:
439
+ score += 0.2
440
+ if 'فصل' in content or 'باب' in content:
441
+ score += 0.2
442
+
443
+ # بررسی پاراگراف‌بندی
444
+ paragraphs = content.split('\n')
445
+ if len(paragraphs) > 2:
446
+ score += 0.3
447
+
448
+ return min(1.0, score)
449
+ except:
450
+ return 0.5
451
+
452
+ def _score_linguistic_quality(self, content: str) -> float:
453
+ """امتیازدهی کیفیت زبانی"""
454
+ try:
455
+ score = 0.0
456
+
457
+ if not content:
458
+ return 0.0
459
+
460
+ # نسبت کاراکترهای فارسی
461
+ persian_chars = sum(1 for c in content if '\u0600' <= c <= '\u06FF')
462
+ persian_ratio = persian_chars / len(content)
463
+ score += persian_ratio * 0.5
464
+
465
+ # بررسی وجود علائم نگارشی
466
+ punctuation_count = sum(1 for c in content if c in '.,;:!؟')
467
+ words_count = len(content.split())
468
+ if words_count > 0:
469
+ punctuation_ratio = punctuation_count / words_count
470
+ score += min(0.3, punctuation_ratio * 3)
471
+
472
+ # بررسی طول متوسط جملات
473
+ sentences = re.split(r'[.؟!]', content)
474
+ if sentences:
475
+ avg_sentence_length = sum(len(s.split()) for s in sentences) / len(sentences)
476
+ if 10 <= avg_sentence_length <= 25:
477
+ score += 0.2
478
+
479
+ return min(1.0, score)
480
+ except:
481
+ return 0.5
482
+
483
+ def _score_citations(self, content: str, legal_entities: List[str]) -> float:
484
+ """امتیازدهی ارجاعات قانونی"""
485
+ try:
486
+ citation_patterns = [
487
+ r'ماده\s*\d+', r'تبصره\s*\d+', r'بند\s*\d+',
488
+ r'فصل\s*\d+', r'قانون\s+[آ-ی\s]+', r'مصوبه\s+[آ-ی\s]+'
489
  ]
490
 
491
+ total_citations = 0
492
+ for pattern in citation_patterns:
493
+ total_citations += len(re.findall(pattern, content))
494
 
495
+ # اضافه کردن موجودیت‌های قانونی
496
+ total_citations += len(legal_entities)
497
 
498
+ # نرمال‌سازی (هر 5 ارجاع = امتیاز کامل)
499
+ return min(1.0, total_citations / 5)
500
+ except:
501
+ return 0.5
502
+
503
+ # === سیستم طبقه‌بندی هوشمند ===
504
+ class IntelligentClassificationSystem:
505
+ """سیستم طبقه‌بندی هوشمند چندمرحله‌ای"""
506
+
507
+ def __init__(self, cache_system: IntelligentCacheSystem):
508
+ self.cache_system = cache_system
509
+ self.models = {}
510
+ self.is_ready = False
511
+
512
+ # دسته‌های حقوقی
513
+ self.legal_categories = {
514
+ 'قانون': ['قانون', 'مقررات', 'آیین‌نامه'],
515
+ 'دادنامه': ['دادنامه', 'رای', 'حکم'],
516
+ 'قرارداد': ['قرارداد', 'توافق‌نامه', 'پروتکل'],
517
+ 'لایحه': ['لایحه', 'طرح', 'پیشنهاد'],
518
+ 'بخشنامه': ['بخشنامه', 'دستورالعمل', 'رهنمود'],
519
+ 'نظریه': ['نظریه', 'استعلام', 'پاسخ']
520
+ }
521
+
522
+ def load_models(self):
523
+ """بارگذاری مدل‌های طبقه‌بندی"""
524
+ try:
525
+ logger.info("شروع بارگذاری مدل‌ها...")
526
 
527
+ # بارگذاری ساده مدل embedding
528
+ try:
529
+ self.models['embedder'] = SentenceTransformer(
530
+ AVAILABLE_MODELS['embedding']['sentence_transformer']
531
+ )
532
+ logger.info("مدل embedding بارگذاری شد")
533
+ except Exception as e:
534
+ logger.warning(f"خطا در بارگذاری مدل embedding: {e}")
535
 
536
+ self.is_ready = True
537
+ logger.info("سیستم طبقه‌بندی آماده است")
538
 
539
  except Exception as e:
540
+ logger.error(f"خطا در بارگذاری مدل‌ها: {e}")
541
+ self.is_ready = False
 
 
 
 
542
 
543
+ def classify_document(self, content: str, use_cache: bool = True) -> Dict:
544
+ """طبقه‌بندی هوشمند سند"""
545
+ if not content or not content.strip():
546
+ return {'error': 'محتوا خالی است'}
547
+
548
+ # بررسی کش
549
+ cache_key = hashlib.md5(content.encode()).hexdigest()
550
+ if use_cache:
551
+ cached = self.cache_system.get(cache_key, 'classification')
552
+ if cached:
553
+ cached['cache_hit'] = True
554
+ return cached
555
+
556
+ start_time = time.time()
557
+ result = {}
558
+
559
  try:
560
+ # متن نمونه برای پردازش
561
+ text_sample = ' '.join(content.split()[:400])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
 
563
+ # طبقه‌بندی بر اساس قوانین
564
+ result['rule_based_classification'] = self._rule_based_classify(content)
565
+
566
+ # استخراج موجودیت‌های حقوقی
567
+ result['legal_entities'] = self._extract_legal_entities(content)
568
+
569
+ # تولید embedding
570
+ if 'embedder' in self.models:
571
+ try:
572
+ result['embedding'] = self.models['embedder'].encode(text_sample)
573
+ except Exception as e:
574
+ logger.warning(f"خطا در تولید embedding: {e}")
575
+ result['embedding'] = None
 
 
 
 
 
 
 
 
 
 
 
 
 
576
 
577
+ # محاسبه اعتماد ترکیبی
578
+ result['confidence_score'] = self._calculate_combined_confidence(result)
579
+
580
+ result['processing_time'] = time.time() - start_time
581
+ result['cache_hit'] = False
582
+
583
+ # ذخیره در کش
584
+ if use_cache:
585
+ self.cache_system.set(cache_key, result, 'classification', ttl_seconds=7200)
586
 
587
  except Exception as e:
588
+ logger.error(f"خطا در طبقه‌بندی: {e}")
589
+ result['error'] = str(e)
590
+
591
+ return result
592
 
593
+ def _rule_based_classify(self, content: str) -> Dict[str, float]:
594
+ """طبقه‌بندی بر اساس قوانین"""
595
+ scores = {}
596
+ content_lower = content.lower()
597
+
598
+ for category, keywords in self.legal_categories.items():
599
+ score = 0.0
600
+ for keyword in keywords:
601
+ count = content_lower.count(keyword.lower())
602
+ score += count * 0.1
603
+
604
+ scores[category] = min(1.0, score)
605
+
606
+ # نرمال‌سازی امتیازها
607
+ total_score = sum(scores.values())
608
+ if total_score > 0:
609
+ scores = {k: v/total_score for k, v in scores.items()}
610
+
611
+ return scores
612
+
613
+ def _extract_legal_entities(self, content: str) -> List[str]:
614
+ """استخراج موجودیت‌های حقوقی"""
615
+ entities = []
616
 
617
  try:
618
+ # الگوهای ارجاعات قانونی
619
+ patterns = [
620
+ r'ماده\s*(\d+)', r'تبصره\s*(\d+)', r'بند\s*([الف-ی]|\d+)',
621
+ r'فصل\s*(\d+)', r'قانون\s+([آ-ی\s]{5,50})',
622
+ r'مصوبه\s+([آ-ی\s]{5,50})'
 
 
 
 
 
 
 
 
 
 
 
623
  ]
624
 
625
+ for pattern in patterns:
626
+ matches = re.findall(pattern, content)
627
+ entities.extend([str(m) for m in matches])
628
+
629
+ except Exception as e:
630
+ logger.error(f"خطا در استخراج موجودیت‌ها: {e}")
631
+
632
+ return list(set(entities))[:20]
633
+
634
+ def _calculate_combined_confidence(self, result: Dict) -> float:
635
+ """محاسبه اعتماد ترکیبی"""
636
+ try:
637
+ confidence = 0.0
638
+ weights = {'rule_based': 0.7, 'entities': 0.3}
639
+
640
+ # اعتماد طبقه‌بندی قانونی
641
+ if 'rule_based_classification' in result:
642
+ rule_conf = max(result['rule_based_classification'].values()) if result['rule_based_classification'] else 0
643
+ confidence += rule_conf * weights['rule_based']
644
 
645
+ # تعداد موجودیت‌های قانونی
646
+ entity_count = len(result.get('legal_entities', []))
647
+ entity_conf = min(1.0, entity_count / 5)
648
+ confidence += entity_conf * weights['entities']
 
 
 
 
 
649
 
650
+ return round(confidence, 3)
651
+ except:
652
+ return 0.5
653
+
654
+ # === مدیریت پایگاه داده پیشرفته ===
655
+ class AdvancedDatabaseManager:
656
+ """مدیریت پیشرفته پایگاه داده با بهینه‌سازی‌های جدید"""
657
+
658
+ def __init__(self, db_path: str = DB_PATH):
659
+ self.db_path = db_path
660
+ self._init_database()
661
+
662
+ def _init_database(self):
663
+ """ایجاد پایگاه داده با جداول پیشرفته"""
664
+ try:
665
+ with sqlite3.connect(self.db_path) as conn:
666
+ # جدول اسناد اصلی
667
+ conn.execute('''
668
+ CREATE TABLE IF NOT EXISTS documents (
669
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
670
+ url TEXT UNIQUE NOT NULL,
671
+ title TEXT,
672
+ source TEXT,
673
+ content TEXT,
674
+ word_count INTEGER,
675
+ quality_scores TEXT,
676
+ classification_result TEXT,
677
+ legal_entities TEXT,
678
+ embedding_vector BLOB,
679
+ scraped_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
680
+ last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
681
+ processing_status TEXT DEFAULT 'pending'
682
+ )
683
+ ''')
684
+
685
+ # ایجاد ایندکس‌ها برای جدول documents
686
+ conn.execute('CREATE INDEX IF NOT EXISTS idx_documents_source ON documents(source)')
687
+ conn.execute('CREATE INDEX IF NOT EXISTS idx_documents_scraped_at ON documents(scraped_at)')
688
+ conn.execute('CREATE INDEX IF NOT EXISTS idx_documents_status ON documents(processing_status)')
689
+ conn.execute('CREATE INDEX IF NOT EXISTS idx_documents_updated ON documents(last_updated)')
690
+
691
+ # جدول متریک‌های عملکرد
692
+ conn.execute('''
693
+ CREATE TABLE IF NOT EXISTS performance_metrics (
694
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
695
+ metric_name TEXT,
696
+ metric_value REAL,
697
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
698
+ )
699
+ ''')
700
+
701
+ # ایندکس‌ها برای جدول metrics
702
+ conn.execute('CREATE INDEX IF NOT EXISTS idx_metrics_name ON performance_metrics(metric_name)')
703
+ conn.execute('CREATE INDEX IF NOT EXISTS idx_metrics_timestamp ON performance_metrics(timestamp)')
704
+
705
+ # جدول لاگ‌های پردازش
706
+ conn.execute('''
707
+ CREATE TABLE IF NOT EXISTS processing_logs (
708
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
709
+ document_id INTEGER,
710
+ operation TEXT,
711
+ status TEXT,
712
+ details TEXT,
713
+ processing_time REAL,
714
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
715
+ FOREIGN KEY (document_id) REFERENCES documents (id)
716
+ )
717
+ ''')
718
+
719
+ # ایندکس‌ها برای جدول logs
720
+ conn.execute('CREATE INDEX IF NOT EXISTS idx_logs_operation ON processing_logs(operation)')
721
+ conn.execute('CREATE INDEX IF NOT EXISTS idx_logs_status ON processing_logs(status)')
722
+ conn.execute('CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON processing_logs(timestamp)')
723
+ conn.execute('CREATE INDEX IF NOT EXISTS idx_logs_document_id ON processing_logs(document_id)')
724
+
725
+ except Exception as e:
726
+ logger.error(f"خطا در ایجاد پایگاه داده: {e}")
727
+
728
+ def save_document_advanced(self, result: ProcessingResult) -> bool:
729
+ """ذخیره پیشرفته سند"""
730
+ try:
731
+ with sqlite3.connect(self.db_path) as conn:
732
+ # محاسبه embedding bytes
733
+ embedding_blob = None
734
+ if result.embeddings is not None:
735
+ embedding_blob = result.embeddings.tobytes()
736
+
737
+ conn.execute('''
738
+ INSERT OR REPLACE INTO documents
739
+ (url, title, source, content, word_count, quality_scores,
740
+ classification_result, legal_entities, embedding_vector,
741
+ processing_status, last_updated)
742
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'completed', datetime('now'))
743
+ ''', (
744
+ result.url,
745
+ result.title,
746
+ result.source,
747
+ result.content,
748
+ len(result.content.split()),
749
+ json.dumps({'quality_score': result.quality_score}, ensure_ascii=False),
750
+ json.dumps(result.classification, ensure_ascii=False),
751
+ json.dumps(result.legal_entities, ensure_ascii=False),
752
+ embedding_blob
753
+ ))
754
+
755
+ # ثبت لاگ
756
+ document_id = conn.lastrowid
757
+ conn.execute('''
758
+ INSERT INTO processing_logs
759
+ (document_id, operation, status, processing_time)
760
+ VALUES (?, 'save', 'success', ?)
761
+ ''', (document_id, result.processing_time))
762
+
763
+ return True
764
+
765
+ except Exception as e:
766
+ logger.error(f"خطا در ذخیره پیشرفته: {e}")
767
+ return False
768
+
769
+ def get_documents_with_embeddings(self) -> List[Tuple]:
770
+ """دریافت اسناد همراه با embeddings"""
771
+ try:
772
+ with sqlite3.connect(self.db_path) as conn:
773
+ return conn.execute('''
774
+ SELECT id, url, title, content, embedding_vector
775
+ FROM documents
776
+ WHERE processing_status = 'completed'
777
+ AND content IS NOT NULL
778
+ ORDER BY last_updated DESC
779
+ ''').fetchall()
780
+ except Exception as e:
781
+ logger.error(f"خطا در دریافت اسناد: {e}")
782
+ return []
783
+
784
+ def get_advanced_stats(self) -> Dict:
785
+ """آمار پیشرفته سیستم"""
786
+ try:
787
+ with sqlite3.connect(self.db_path) as conn:
788
+ # آمار کلی
789
+ total_docs = conn.execute("SELECT COUNT(*) FROM documents").fetchone()[0]
790
+
791
+ # آمار بر اساس منبع
792
+ source_stats = conn.execute('''
793
+ SELECT source, COUNT(*), AVG(word_count)
794
+ FROM documents
795
+ GROUP BY source
796
+ ORDER BY COUNT(*) DESC
797
+ ''').fetchall()
798
+
799
+ # آمار عملکرد
800
+ avg_processing_time = conn.execute('''
801
+ SELECT AVG(processing_time)
802
+ FROM processing_logs
803
+ WHERE operation = 'save' AND status = 'success'
804
+ ''').fetchone()[0] or 0
805
+
806
+ # آمار وضعیت پردازش
807
+ status_stats = conn.execute('''
808
+ SELECT processing_status, COUNT(*)
809
+ FROM documents
810
+ GROUP BY processing_status
811
+ ''').fetchall()
812
+
813
+ return {
814
+ 'total_documents': total_docs,
815
+ 'source_statistics': source_stats,
816
+ 'average_processing_time': round(avg_processing_time, 2),
817
+ 'status_statistics': status_stats,
818
+ 'last_updated': datetime.now().isoformat()
819
+ }
820
+ except Exception as e:
821
+ logger.error(f"خطا در دریافت آمار: {e}")
822
+ return {
823
+ 'total_documents': 0,
824
+ 'source_statistics': [],
825
+ 'average_processing_time': 0,
826
+ 'status_statistics': [],
827
+ 'last_updated': datetime.now().isoformat()
828
+ }
829
+
830
+ # === سیستم جستجوی معنایی پیشرفته ===
831
+ class SemanticSearchEngine:
832
+ """موتور جستجوی معنایی با ایندکس FAISS"""
833
+
834
+ def __init__(self, cache_system: IntelligentCacheSystem):
835
+ self.cache_system = cache_system
836
+ self.embedder = None
837
+ self.faiss_index = None
838
+ self.documents = []
839
+ self.document_embeddings = None
840
+ self.is_ready = False
841
+
842
+ def initialize(self):
843
+ """مقداردهی اولیه موتور جستجو"""
844
+ try:
845
+ self.embedder = SentenceTransformer(
846
+ AVAILABLE_MODELS['embedding']['sentence_transformer']
847
+ )
848
+ logger.info("مدل embedding بارگذاری شد")
849
+
850
+ # بارگذاری ایندکس موجود در صورت وجود
851
+ if os.path.exists(VECTOR_INDEX_PATH) and os.path.exists(EMBEDDINGS_CACHE_PATH):
852
+ self._load_existing_index()
853
+
854
+ self.is_ready = True
855
 
856
  except Exception as e:
857
+ logger.error(f"خطا در مقداردهی موتور جستجو: {e}")
 
858
 
859
+ def build_index(self, documents: List[Tuple], progress_callback=None):
860
+ """ساخت ایندکس FAISS"""
861
  try:
862
+ if not documents:
863
+ return False
864
 
865
+ if progress_callback:
866
+ progress_callback("استخراج متون...", 0.1)
867
 
868
+ self.documents = documents
869
+ texts = [doc[3] for doc in documents if doc[3]] # content field
870
 
871
+ if progress_callback:
872
+ progress_callback(f"تولید embedding برای {len(texts)} سند...", 0.3)
 
 
 
 
 
 
873
 
874
+ # تولید embeddings با batch processing
875
+ batch_size = 16
876
+ all_embeddings = []
877
 
878
+ for i in range(0, len(texts), batch_size):
879
+ batch = texts[i:i+batch_size]
880
+ batch_embeddings = self.embedder.encode(
881
+ batch,
882
+ convert_to_tensor=True,
883
+ show_progress_bar=False
884
+ )
885
+ all_embeddings.append(batch_embeddings)
886
 
887
+ if progress_callback:
888
+ progress = 0.3 + (i / len(texts)) * 0.6
889
+ progress_callback(f"پردازش batch {i//batch_size + 1}...", progress)
890
+
891
+ # ترکیب embeddings
892
+ self.document_embeddings = torch.cat(all_embeddings, dim=0).cpu().numpy()
893
+
894
+ if progress_callback:
895
+ progress_callback("ساخت ایندکس FAISS...", 0.9)
896
+
897
+ # ساخت ایندکس FAISS
898
+ dimension = self.document_embeddings.shape[1]
899
+ self.faiss_index = faiss.IndexFlatIP(dimension) # Inner Product for cosine similarity
900
+
901
+ # نرمال‌سازی برای cosine similarity
902
+ faiss.normalize_L2(self.document_embeddings)
903
+ self.faiss_index.add(self.document_embeddings)
904
+
905
+ # ذخیره ایندکس
906
+ self._save_index()
907
+
908
+ if progress_callback:
909
+ progress_callback("تکمیل ایندکس‌سازی", 1.0)
910
+
911
+ logger.info(f"ایندکس با {len(documents)} سند آماده شد")
912
+ return True
913
+
914
+ except Exception as e:
915
+ logger.error(f"خطا در ساخت ایندکس: {e}")
916
+ return False
917
+
918
+ def search(self, query: str, top_k: int = 10, threshold: float = 0.3) -> List[Dict]:
919
+ """جستجوی معنایی پیشرفته"""
920
+ if not self.is_ready or self.faiss_index is None:
921
+ return []
922
+
923
+ try:
924
+ # بررسی کش
925
+ cache_key = f"search:{hashlib.md5(query.encode()).hexdigest()}:{top_k}"
926
+ cached = self.cache_system.get(cache_key, 'search')
927
+ if cached:
928
+ return cached
929
+
930
+ # تولید embedding برای query
931
+ query_embedding = self.embedder.encode([query], convert_to_tensor=True)
932
+ query_embedding = query_embedding.cpu().numpy()
933
+ faiss.normalize_L2(query_embedding)
934
+
935
+ # جستجو در ایندکس
936
+ similarities, indices = self.faiss_index.search(query_embedding, top_k * 2)
937
+
938
+ results = []
939
+ for i, (similarity, idx) in enumerate(zip(similarities[0], indices[0])):
940
+ if similarity < threshold:
941
+ continue
942
+
943
+ if idx < len(self.documents):
944
+ doc = self.documents[idx]
945
+ results.append({
946
+ 'rank': i + 1,
947
+ 'document_id': doc[0],
948
+ 'url': doc[1],
949
+ 'title': doc[2],
950
+ 'content_preview': doc[3][:300] + '...' if len(doc[3]) > 300 else doc[3],
951
+ 'similarity_score': float(similarity),
952
+ 'relevance_category': self._categorize_relevance(similarity)
953
+ })
954
+
955
+ # مرتب‌سازی نهایی
956
+ results = results[:top_k]
957
+
958
+ # ذخیره در کش
959
+ self.cache_system.set(cache_key, results, 'search', ttl_seconds=1800)
960
+
961
+ return results
962
+
963
  except Exception as e:
964
+ logger.error(f"خطا در جستجو: {e}")
965
+ return []
966
+
967
+ def _categorize_relevance(self, similarity: float) -> str:
968
+ """دسته‌بندی میزان ارتباط"""
969
+ if similarity >= 0.8:
970
+ return "بسیار مرتبط"
971
+ elif similarity >= 0.6:
972
+ return "مرتبط"
973
+ elif similarity >= 0.4:
974
+ return "نسبتاً مرتبط"
975
+ else:
976
+ return "کم‌ارتباط"
977
+
978
+ def _save_index(self):
979
+ """ذخیره ایندکس و embeddings"""
980
+ try:
981
+ if self.faiss_index:
982
+ faiss.write_index(self.faiss_index, VECTOR_INDEX_PATH)
983
+
984
+ if self.document_embeddings is not None:
985
+ with open(EMBEDDINGS_CACHE_PATH, 'wb') as f:
986
+ pickle.dump({
987
+ 'embeddings': self.document_embeddings,
988
+ 'documents_info': [(doc[0], doc[1], doc[2]) for doc in self.documents]
989
+ }, f)
990
+
991
+ logger.info("ایندکس ذخیره شد")
992
+
993
+ except Exception as e:
994
+ logger.error(f"خطا در ذخیره ایندکس: {e}")
995
+
996
+ def _load_existing_index(self):
997
+ """بارگذاری ایندکس موجود"""
998
+ try:
999
+ self.faiss_index = faiss.read_index(VECTOR_INDEX_PATH)
1000
+
1001
+ with open(EMBEDDINGS_CACHE_PATH, 'rb') as f:
1002
+ cache_data = pickle.load(f)
1003
+ self.document_embeddings = cache_data['embeddings']
1004
+ # بازسازی documents از اطلاعات ذخیره شده
1005
+ # نیاز به query از دیتابیس برای محتوای کامل
1006
+
1007
+ logger.info("ایندکس موجود بارگذاری شد")
1008
+
1009
+ except Exception as e:
1010
+ logger.error(f"خطا در بارگذاری ایندکس: {e}")
1011
 
1012
+ # === سیستم اسکرپر ساده ===
1013
+ class SimpleWebScraper:
1014
+ """سیستم اسکرپر ساده برای تست"""
1015
+
1016
+ def __init__(self):
1017
+ self.session = requests.Session()
1018
+ self.session.headers.update({
1019
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
1020
+ })
1021
+
1022
+ def scrape_document(self, url: str) -> Dict:
1023
+ """اسکرپ ساده یک سند"""
1024
+ try:
1025
+ response = self.session.get(url, timeout=10)
1026
+ response.raise_for_status()
1027
+
1028
+ soup = BeautifulSoup(response.content, 'html.parser')
1029
+
1030
+ # استخراج عنوان
1031
+ title = ""
1032
+ title_tag = soup.find('title')
1033
+ if title_tag:
1034
+ title = title_tag.get_text().strip()
1035
+
1036
+ # استخراج محتوا
1037
+ content = ""
1038
+ for tag in soup.find_all(['p', 'div', 'article']):
1039
+ if tag.get_text().strip():
1040
+ content += tag.get_text().strip() + "\n"
1041
+
1042
+ return {
1043
+ 'status': 'موفق',
1044
+ 'title': title,
1045
+ 'content': content,
1046
+ 'source_info': {'name': urlparse(url).netloc},
1047
+ 'quality_assessment': {'overall_score': 0.7}
1048
+ }
1049
+
1050
+ except Exception as e:
1051
+ logger.error(f"خطا در اسکرپ {url}: {e}")
1052
+ return {
1053
+ 'status': 'ناموفق',
1054
+ 'error': str(e)
1055
+ }
1056
+
1057
+ # === اپلیکیشن اصلی پیشرفته ===
1058
+ class AdvancedLegalScrapingApp:
1059
+ """اپلیکیشن اصلی پیشرفته اسکرپینگ حقوقی"""
1060
+
1061
+ def __init__(self):
1062
+ logger.info("🚀 شروع راه‌اندازی سیستم پیشرفته...")
1063
+
1064
+ # اجزای اصلی سیستم
1065
+ self.cache_system = IntelligentCacheSystem()
1066
+ self.scoring_system = AdvancedScoringSystem()
1067
+ self.db_manager = AdvancedDatabaseManager()
1068
+ self.classification_system = IntelligentClassificationSystem(self.cache_system)
1069
+ self.search_engine = SemanticSearchEngine(self.cache_system)
1070
+ self.scraper = SimpleWebScraper()
1071
+
1072
+ # متریک‌های سیستم
1073
+ self.system_metrics = SystemMetrics()
1074
+
1075
+ logger.info("✅ سیستم آماده است")
1076
+
1077
+ def initialize_models(self, progress_callback=None) -> str:
1078
+ """مقداردهی مدل‌ها"""
1079
+ try:
1080
+ if progress_callback:
1081
+ progress_callback("بارگذاری مدل‌های طبقه‌بندی...", 0.3)
1082
+
1083
+ self.classification_system.load_models()
1084
+
1085
+ if progress_callback:
1086
+ progress_callback("مقداردهی موتور جستجو...", 0.7)
1087
+
1088
+ self.search_engine.initialize()
1089
+
1090
+ if progress_callback:
1091
+ progress_callback("تکمیل مقداردهی", 1.0)
1092
+
1093
+ return "✅ تمام مدل‌ها با موفقیت بارگذاری شدند"
1094
+
1095
+ except Exception as e:
1096
+ error_msg = f"❌ خطا در بارگذاری مدل‌ها: {str(e)}"
1097
+ logger.error(error_msg)
1098
+ return error_msg
1099
+
1100
+ def process_urls_intelligent(self, urls_text: str) -> Tuple[str, str]:
1101
+ """پردازش هوشمند URLs"""
1102
+ if not urls_text or not urls_text.strip():
1103
+ return "❌ لطفاً URLs را وارد کنید", ""
1104
+
1105
+ urls = [url.strip() for url in urls_text.split('\n') if url.strip()]
1106
+ if not urls:
1107
+ return "❌ URL معتبر یافت نشد", ""
1108
+
1109
+ results = []
1110
+ total_processing_time = 0
1111
+
1112
+ for url in urls[:5]: # محدودیت برای تست
1113
+ start_time = time.time()
1114
+
1115
+ try:
1116
+ # اسکرپ سند
1117
+ scraped_result = self.scraper.scrape_document(url)
1118
+
1119
+ if scraped_result.get('status') == 'موفق':
1120
+ # طبقه‌بندی
1121
+ classification = self.classification_system.classify_document(
1122
+ scraped_result.get('content', '')
1123
  )
1124
 
1125
+ # امتیازدهی
1126
+ quality_scores = self.scoring_system.calculate_comprehensive_score(
1127
+ scraped_result.get('content', ''),
1128
+ scraped_result.get('source_info', {}),
1129
+ classification.get('legal_entities', [])
 
1130
  )
1131
 
1132
+ # ساخت ProcessingResult
1133
+ processing_result = ProcessingResult(
1134
+ url=url,
1135
+ title=scraped_result.get('title', ''),
1136
+ content=scraped_result.get('content', ''),
1137
+ source=scraped_result.get('source_info', {}).get('name', ''),
1138
+ quality_score=quality_scores.get('final_score', 0),
1139
+ classification=classification.get('rule_based_classification', {}),
1140
+ sentiment_score=0.5,
1141
+ legal_entities=classification.get('legal_entities', []),
1142
+ embeddings=classification.get('embedding'),
1143
+ processing_time=classification.get('processing_time', 0)
1144
  )
1145
+
1146
+ # ذخیره در دیتابیس
1147
+ self.db_manager.save_document_advanced(processing_result)
1148
+
1149
+ # ذخیره در کش
1150
+ cache_data = {
1151
+ 'title': processing_result.title,
1152
+ 'quality_score': processing_result.quality_score,
1153
+ 'classification': processing_result.classification,
1154
+ 'legal_entities': processing_result.legal_entities,
1155
+ 'status': 'موفق'
1156
+ }
1157
+ self.cache_system.set(url, cache_data, 'comprehensive', ttl_seconds=7200)
1158
+
1159
+ results.append(cache_data)
1160
+
1161
+ else:
1162
+ results.append({'status': 'ناموفق', 'error': scraped_result.get('error', '')})
1163
 
1164
+ total_processing_time += time.time() - start_time
1165
+
1166
+ except Exception as e:
1167
+ logger.error(f"خطا در پردازش {url}: {e}")
1168
+ results.append({'status': 'ناموفق', 'error': str(e)})
1169
 
1170
+ # تأخیر بین درخواست‌ها
1171
+ time.sleep(1)
1172
+
1173
+ # تولید گزارش جامع
1174
+ successful = sum(1 for r in results if r.get('status') == 'موفق')
1175
+ failed = len(results) - successful
1176
+ avg_quality = sum(r.get('quality_score', 0) for r in results if r.get('quality_score')) / max(successful, 1)
1177
+
1178
+ summary = f"""
1179
+ 📊 **گزارش پردازش هوشمند**
1180
+
1181
+ ✅ **موفق**: {successful} سند
1182
+ ❌ **ناموفق**: {failed} سند
1183
+ 📈 **میانگین کیفیت**: {avg_quality:.1f}/100
1184
+ ⏱️ **زمان کل**: {total_processing_time:.2f} ثانیه
1185
+
1186
+ 🎯 **نتایج کیفیت**:
1187
+ • بالا (>80): {sum(1 for r in results if r.get('quality_score', 0) > 80)}
1188
+ • متوسط (50-80): {sum(1 for r in results if 50 <= r.get('quality_score', 0) <= 80)}
1189
+ • پایین (<50): {sum(1 for r in results if 0 < r.get('quality_score', 0) < 50)}
1190
+ """
1191
+
1192
+ # جزئیات
1193
+ details = "\n".join(
1194
+ f"📄 {r.get('title', 'بدون عنوان')[:50]}... - امتیاز: {r.get('quality_score', 0):.1f}"
1195
+ for r in results if r.get('status') == 'موفق'
1196
+ )
1197
 
1198
+ return summary, details
1199
+
1200
+ def build_intelligent_search_index(self, progress_callback=None) -> str:
1201
+ """ساخت ایندکس جستجوی هوشمند"""
1202
+ try:
1203
+ if progress_callback:
1204
+ progress_callback("دریافت اسناد از دیتابیس...", 0.1)
1205
 
1206
+ documents = self.db_manager.get_documents_with_embeddings()
 
 
 
1207
 
1208
+ if not documents:
1209
+ return "❌ هیچ سندی برای ایندکس‌سازی یافت نشد"
1210
 
1211
+ success = self.search_engine.build_index(documents, progress_callback)
 
 
 
 
1212
 
1213
+ if success:
1214
+ return f"✅ ایندکس جستجو با {len(documents)} سند آماده شد"
1215
+ else:
1216
+ return "❌ خطا در ساخت ایندکس"
1217
+
1218
+ except Exception as e:
1219
+ return f"❌ خطا در ساخت ایندکس: {str(e)}"
1220
+
1221
+ def intelligent_semantic_search(self, query: str, limit: int = 10) -> Tuple[str, pd.DataFrame]:
1222
+ """جستجوی معنایی هوشمند"""
1223
+ if not query or not query.strip():
1224
+ return "❌ لطفاً عبارت جستجو را وارد کنید", pd.DataFrame()
1225
 
1226
+ try:
1227
+ results = self.search_engine.search(query, top_k=limit)
1228
 
1229
+ if not results:
1230
+ return "❌ نتیجه‌ای یافت نشد", pd.DataFrame()
1231
 
1232
+ # تولید خلاصه
1233
+ summary = f"""
1234
+ 🔍 **نتایج جستجو برای**: "{query}"
1235
+
1236
+ 📊 **آمار**:
1237
+ • تعداد نتایج: {len(results)}
1238
+ • بهترین امتیاز: {max(r['similarity_score'] for r in results):.3f}
1239
+ میانگین امتیاز: {sum(r['similarity_score'] for r in results) / len(results):.3f}
1240
+
1241
+ 🎯 **توزیع ارتباط**:
1242
+ • بسیار مرتبط: {sum(1 for r in results if r['relevance_category'] == 'بسیار مرتبط')}
1243
+ • مرتبط: {sum(1 for r in results if r['relevance_category'] == 'مرتبط')}
1244
+ • نسبتاً مرتبط: {sum(1 for r in results if r['relevance_category'] == 'نسبتاً مرتبط')}
1245
+ """
1246
 
1247
+ # تولید DataFrame
1248
+ df_data = []
1249
+ for result in results:
1250
+ df_data.append({
1251
+ 'رتبه': result['rank'],
1252
+ 'عنوان': result['title'][:50] + '...' if len(result['title']) > 50 else result['title'],
1253
+ 'امتیاز شباهت': f"{result['similarity_score']:.3f}",
1254
+ 'میزان ارتباط': result['relevance_category'],
1255
+ 'پیش‌نمایش محتوا': result['content_preview'][:100] + '...'
1256
+ })
1257
+
1258
+ df = pd.DataFrame(df_data)
1259
+
1260
+ return summary, df
1261
+
1262
+ except Exception as e:
1263
+ return f"❌ خطا در جستجو: {str(e)}", pd.DataFrame()
1264
+
1265
+ def export_advanced_data(self) -> Tuple[str, Optional[str]]:
1266
+ """صدور داده‌های پیشرفته"""
1267
+ try:
1268
+ # دریافت آمار
1269
+ stats = self.db_manager.get_advanced_stats()
1270
+ cache_stats = self.cache_system.get_stats()
1271
+
1272
+ # تولید گزارش جامع
1273
+ report = {
1274
+ 'system_info': {
1275
+ 'export_time': datetime.now().isoformat(),
1276
+ 'total_documents': stats['total_documents'],
1277
+ 'cache_performance': cache_stats
1278
+ },
1279
+ 'database_statistics': stats,
1280
+ 'performance_metrics': {
1281
+ 'cache_hit_ratio': cache_stats['total_accesses'] / max(cache_stats['database_entries'], 1),
1282
+ 'avg_processing_time': stats['average_processing_time']
1283
+ }
1284
+ }
1285
+
1286
+ # ذخیره در فایل موقت
1287
+ temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8')
1288
+ json.dump(report, temp_file, ensure_ascii=False, indent=2)
1289
+ temp_file.close()
1290
+
1291
+ status = f"""
1292
+ 📊 **گزارش صدور داده‌ها**
1293
+
1294
+ ✅ **موفقیت آمیز**
1295
+ 📄 **تعداد اسناد**: {stats['total_documents']}
1296
+ ⚡ **کارایی کش**: {cache_stats['memory_cache_size']} items در حافظه
1297
+ 📈 **میانگین زمان پردازش**: {stats['average_processing_time']} ثانیه
1298
+ 📅 **زمان صدور**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
1299
+ """
1300
+
1301
+ return status, temp_file.name
1302
+
1303
+ except Exception as e:
1304
+ return f"❌ خطا در صدور داده‌ها: {str(e)}", None
1305
+
1306
+ def get_comprehensive_system_status(self) -> str:
1307
+ """وضعیت جامع سیستم"""
1308
+ try:
1309
+ # آمار پایگاه داده
1310
+ db_stats = self.db_manager.get_advanced_stats()
1311
+
1312
+ # آمار کش
1313
+ cache_stats = self.cache_system.get_stats()
1314
+
1315
+ # آمار مدل‌ها
1316
+ models_ready = self.classification_system.is_ready
1317
+ search_ready = self.search_engine.is_ready
1318
+
1319
+ status_parts = [
1320
+ "## 🏠 وضعیت سیستم پیشرفته اسناد حقوقی",
1321
+ "",
1322
+ "### 📊 آمار کلی",
1323
+ f"• **تعداد کل اسناد**: {db_stats['total_documents']}",
1324
+ f"• **میانگین زمان پردازش**: {db_stats['average_processing_time']} ثانیه",
1325
+ f"• **آخرین بروزرسانی**: {db_stats['last_updated'][:19]}",
1326
+ "",
1327
+ "### ⚡ عملکرد کش",
1328
+ f"• **حافظه فعال**: {cache_stats['memory_cache_size']} آیتم",
1329
+ f"• **پایگاه داده کش**: {cache_stats['database_entries']} ورودی",
1330
+ f"• **تعداد دسترسی‌ها**: {cache_stats['total_accesses']}",
1331
+ f"• **میانگین استفاده**: {cache_stats['average_accesses']}",
1332
+ "",
1333
+ "### 🧠 وضعیت مدل‌ها",
1334
+ f"• **سیستم طبقه‌بندی**: {'🟢 آماده' if models_ready else '🔴 غیرفعال'}",
1335
+ f"• **موتور جستجو**: {'🟢 آماده' if search_ready else '🔴 غیرفعال'}",
1336
+ "",
1337
+ "### 📈 آمار منابع"
1338
+ ]
1339
+
1340
+ # اضافه کردن آمار منابع
1341
+ for source, count, avg_words in db_stats.get('source_statistics', []):
1342
+ if source:
1343
+ status_parts.append(f"• **{source}**: {count} سند (میانگین {avg_words:.0f} کلمه)")
1344
+
1345
+ # وضعیت پردازش
1346
+ status_parts.extend([
1347
+ "",
1348
+ "### 🔧 وضعیت پردازش"
1349
+ ])
1350
+
1351
+ for status, count in db_stats.get('status_statistics', []):
1352
+ status_parts.append(f"• **{status}**: {count} سند")
1353
+
1354
+ return "\n".join(status_parts)
1355
+
1356
+ except Exception as e:
1357
+ return f"❌ خطا در دریافت وضعیت سیستم: {str(e)}"
1358
+
1359
+ def create_advanced_interface(self):
1360
+ """ایجاد رابط کاربری پیشرفته"""
1361
+ # تنظیمات CSS سفارشی برای بهبود ظاهر
1362
+ custom_css = """
1363
+ .rtl { direction: rtl; text-align: right; }
1364
+ .status-success { color: #10b981; font-weight: bold; }
1365
+ .status-error { color: #ef4444; font-weight: bold; }
1366
+ .metric-card {
1367
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1368
+ padding: 20px;
1369
+ border-radius: 10px;
1370
+ color: white;
1371
+ margin: 10px 0;
1372
+ }
1373
+ .gradio-container { font-family: 'Vazir', 'Tahoma', sans-serif; }
1374
+ """
1375
 
1376
+ with gr.Blocks(
1377
+ theme=gr.themes.Soft(),
1378
+ css=custom_css,
1379
+ title="🏛️ سیستم پیشرفته اسناد حقوقی ایران"
1380
+ ) as interface:
1381
 
1382
+ # هدر اصلی
1383
+ gr.HTML("""
1384
+ <div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #1e40af, #7c3aed); border-radius: 15px; margin-bottom: 20px;">
1385
+ <h1 style="color: white; margin: 0; font-size: 2.5em;">🏛️ سیستم پیشرفته اسناد حقوقی ایران</h1>
1386
+ <p style="color: #e0e7ff; margin: 10px 0 0 0; font-size: 1.2em;">
1387
+ پلتفرم هوشمند تحلیل و دسته‌بندی متون حقوقی با قابلیت‌های پیشرفته
1388
+ </p>
1389
+ </div>
1390
+ """)
1391
 
1392
+ # تب‌های اصلی
1393
+ with gr.Tabs():
1394
+
1395
+ # تب خانه و داشبورد
1396
+ with gr.Tab("🏠 داشبورد اصلی"):
1397
+ with gr.Row():
1398
+ with gr.Column(scale=2):
1399
+ gr.Markdown("### 🚀 مقداردهی سیستم")
1400
+
1401
+ initialize_btn = gr.Button(
1402
+ "⚡ بارگذاری مدل‌های هوش مصنوعی",
1403
+ variant="primary",
1404
+ size="lg"
1405
+ )
1406
+
1407
+ initialization_status = gr.Textbox(
1408
+ label="وضعیت مقداردهی",
1409
+ interactive=False,
1410
+ elem_classes=["rtl"]
1411
+ )
1412
+
1413
+ with gr.Column(scale=1):
1414
+ gr.Markdown("### 📊 آمار سریع")
1415
+ quick_stats = gr.HTML()
1416
+
1417
+ # تب پردازش هوشمند
1418
+ with gr.Tab("🤖 پردازش هوشمند اسناد"):
1419
+ gr.Markdown("### 🧠 پردازش و تحلیل هوشمند اسناد حقوقی")
1420
+
1421
+ with gr.Row():
1422
+ urls_input = gr.Textbox(
1423
+ label="📝 آدرس اسناد (هر خط یک URL)",
1424
+ placeholder="https://rc.majlis.ir/fa/law/show/123456\nhttps://www.dotic.ir/portal/law/789",
1425
+ lines=5,
1426
+ elem_classes=["rtl"]
1427
+ )
1428
+
1429
+ with gr.Row():
1430
+ process_intelligent_btn = gr.Button(
1431
+ "🚀 شروع پردازش هوشمند",
1432
+ variant="primary",
1433
+ size="lg"
1434
+ )
1435
+ clear_cache_btn = gr.Button("🗑️ پاک کردن کش", variant="secondary")
1436
+
1437
+ with gr.Row():
1438
+ intelligent_summary = gr.Textbox(
1439
+ label="📊 گزارش جامع پردازش",
1440
+ interactive=False,
1441
+ lines=8,
1442
+ elem_classes=["rtl"]
1443
+ )
1444
+ intelligent_details = gr.Textbox(
1445
+ label="📋 جزئیات و امتیازها",
1446
+ interactive=False,
1447
+ lines=8,
1448
+ elem_classes=["rtl"]
1449
+ )
1450
+
1451
+ # تب جستجوی هوشمند
1452
+ with gr.Tab("🔍 جستجوی معنایی پیشرفته"):
1453
+ gr.Markdown("### 🎯 جستجوی معنایی با الگوریتم‌های پیشرفته")
1454
+
1455
+ with gr.Row():
1456
+ build_advanced_index_btn = gr.Button("🔧 ساخت ایندکس هوشمند", variant="secondary")
1457
+ advanced_index_status = gr.Textbox(
1458
+ label="📈 وضعیت ایندکس",
1459
+ interactive=False,
1460
+ elem_classes=["rtl"]
1461
+ )
1462
+
1463
+ with gr.Row():
1464
+ search_query = gr.Textbox(
1465
+ label="🔍 عبارت جستجو",
1466
+ placeholder="مسئولیت کیفری اشخاص حقوقی",
1467
+ elem_classes=["rtl"],
1468
+ scale=3
1469
+ )
1470
+ search_limit = gr.Slider(
1471
+ minimum=5, maximum=20, value=10, step=1,
1472
+ label="📊 تعداد نتایج", scale=1
1473
+ )
1474
+
1475
+ advanced_search_btn = gr.Button("🎯 جستجوی هوشمند", variant="primary")
1476
+
1477
+ advanced_search_results_text = gr.Markdown(elem_classes=["rtl"])
1478
+ advanced_search_results_df = gr.DataFrame(
1479
+ label="📋 نتایج تفصیلی",
1480
+ interactive=False
1481
+ )
1482
+
1483
+ # تب مدیریت سیستم
1484
+ with gr.Tab("📊 مدیریت سیستم پیشرفته"):
1485
+ with gr.Row():
1486
+ with gr.Column():
1487
+ gr.Markdown("### 🏥 وضعیت جامع سیستم")
1488
+ comprehensive_status = gr.Markdown(elem_classes=["rtl"])
1489
+
1490
+ with gr.Row():
1491
+ refresh_status_btn = gr.Button("🔄 بروزرسانی")
1492
+ optimize_system_btn = gr.Button("⚡ بهینه‌سازی سیستم")
1493
+
1494
+ with gr.Column():
1495
+ gr.Markdown("### 📤 صدور و پشتیبان‌گیری")
1496
+
1497
+ advanced_export_btn = gr.Button("📊 صدور داده‌های پیشرفته", variant="primary")
1498
+
1499
+ advanced_export_status = gr.Textbox(
1500
+ label="📋 وضعیت صدور",
1501
+ interactive=False,
1502
+ elem_classes=["rtl"]
1503
+ )
1504
+
1505
+ advanced_export_file = gr.File(label="📁 فایل صادر شده")
1506
 
1507
+ # === اتصال Event Handlers ===
1508
+
1509
+ # مقداردهی سیستم
1510
+ initialize_btn.click(
1511
+ fn=self.initialize_models,
1512
+ outputs=[initialization_status],
1513
+ show_progress=True
1514
  )
1515
 
1516
+ # پردازش هوشمند
1517
+ process_intelligent_btn.click(
1518
+ fn=self.process_urls_intelligent,
1519
+ inputs=[urls_input],
1520
+ outputs=[intelligent_summary, intelligent_details],
1521
+ show_progress=True
1522
  )
 
 
 
 
1523
 
1524
+ # پاک کردن کش
1525
+ clear_cache_btn.click(
1526
+ fn=lambda: self.cache_system.cleanup_expired() or "🗑️ کش پاکسازی شد",
1527
+ outputs=[intelligent_summary]
1528
+ )
1529
 
1530
+ # ساخت ایندکس هوشمند
1531
+ build_advanced_index_btn.click(
1532
+ fn=self.build_intelligent_search_index,
1533
+ outputs=[advanced_index_status],
1534
+ show_progress=True
1535
+ )
1536
 
1537
+ # جستجوی هوشمند
1538
+ advanced_search_btn.click(
1539
+ fn=self.intelligent_semantic_search,
1540
+ inputs=[search_query, search_limit],
1541
+ outputs=[advanced_search_results_text, advanced_search_results_df]
1542
+ )
1543
 
1544
+ # مدیریت سیستم
1545
+ refresh_status_btn.click(
1546
+ fn=self.get_comprehensive_system_status,
1547
+ outputs=[comprehensive_status]
1548
+ )
1549
 
1550
+ optimize_system_btn.click(
1551
+ fn=lambda: (gc.collect(), self.cache_system.cleanup_expired(), "⚡ سیستم بهینه‌سازی شد")[2],
1552
+ outputs=[advanced_export_status]
1553
+ )
1554
 
1555
+ # صدور پیشرفته
1556
+ advanced_export_btn.click(
1557
+ fn=self.export_advanced_data,
1558
+ outputs=[advanced_export_status, advanced_export_file]
1559
+ )
1560
 
1561
+ # بارگذاری اولیه
1562
+ interface.load(
1563
+ fn=self.get_comprehensive_system_status,
1564
+ outputs=[comprehensive_status]
1565
+ )
1566
+
1567
+ return interface
1568
 
1569
+ # === تابع اصلی ===
1570
  def main():
1571
+ """تابع اصلی اجرای برنامه"""
1572
+ try:
1573
+ # ایجاد اپلیکیشن
1574
+ app = AdvancedLegalScrapingApp()
1575
+
1576
+ # ایجاد رابط کاربری
1577
+ interface = app.create_advanced_interface()
1578
+
1579
+ # اجرای برنامه
1580
+ interface.launch(
1581
+ server_name="0.0.0.0",
1582
+ server_port=7860,
1583
+ share=True,
1584
+ show_error=True,
1585
+ favicon_path=None,
1586
+ ssl_verify=False
1587
+ )
1588
+
1589
+ except Exception as e:
1590
+ logger.error(f"خطا در اجرای برنامه: {e}")
1591
+ print(f"❌ خطا در راه‌اندازی: {e}")
1592
 
1593
  if __name__ == "__main__":
1594
  main()