Hoghoghi / app.py
Really-amin's picture
Update app.py
0fb08de verified
"""
پلتفرم پیشرفته هوشمند اسناد حقوقی ایران - نسخه ارتقاء یافته
مجهز به مدل‌های SOTA فارسی، سیستم کش هوشمند و امتیازدهی پیشرفته
"""
import os
import gc
import sys
import time
import json
import logging
import hashlib
import resource
import requests
import threading
import re
import random
import sqlite3
import pickle
from pathlib import Path
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional, Tuple, Union
from dataclasses import dataclass, field
from concurrent.futures import ThreadPoolExecutor, as_completed, ProcessPoolExecutor
from urllib.parse import urljoin, urlparse
import warnings
import asyncio
from functools import lru_cache
import numpy as np
import gradio as gr
import pandas as pd
import torch
from bs4 import BeautifulSoup
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification,
pipeline,
logging as transformers_logging,
AutoModel
)
from sentence_transformers import SentenceTransformer, util
from hazm import Normalizer, word_tokenize, Lemmatizer
import faiss
# === تنظیمات اولیه پیشرفته ===
warnings.filterwarnings('ignore')
transformers_logging.set_verbosity_error()
try:
resource.setrlimit(resource.RLIMIT_AS, (4*1024*1024*1024, 4*1024*1024*1024))
except:
pass
os.environ.update({
'TRANSFORMERS_CACHE': '/tmp/hf_cache',
'HF_HOME': '/tmp/hf_cache',
'TORCH_HOME': '/tmp/torch_cache',
'TOKENIZERS_PARALLELISM': 'false',
'CUDA_VISIBLE_DEVICES': '0' if torch.cuda.is_available() else ''
})
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# === ثابت‌های سیستم ===
DB_PATH = "data/iranian_legal_archive_advanced.sqlite"
CACHE_DB_PATH = "data/cache_system.sqlite"
EMBEDDINGS_CACHE_PATH = "data/embeddings_cache.pkl"
VECTOR_INDEX_PATH = "data/faiss_index.bin"
SVG_ICONS = {
'search': '🔍', 'document': '📄', 'analyze': '🤖', 'export': '📊',
'settings': '⚙️', 'link': '🔗', 'success': '✅', 'error': '❌',
'warning': '⚠️', 'database': '🗄️', 'crawler': '🔄', 'brain': '🧠',
'cache': '⚡', 'score': '📈', 'classify': '🏷️', 'similarity': '🎯'
}
# منابع حقوقی با پیکربندی پیشرفته
LEGAL_SOURCES = {
"مجلس شورای اسلامی": {
"base_url": "https://rc.majlis.ir",
"patterns": ["/fa/law/show/", "/fa/report/show/"],
"selectors": [".main-content", ".article-body", "article"],
"delay_range": (2, 5),
"priority": 1,
"reliability_score": 0.95
},
"پورتال ملی قوانین": {
"base_url": "https://www.dotic.ir",
"patterns": ["/portal/law/", "/regulation/"],
"selectors": [".content-area", ".law-content"],
"delay_range": (1, 4),
"priority": 1,
"reliability_score": 0.90
},
"قوه قضاییه": {
"base_url": "https://www.judiciary.ir",
"patterns": ["/fa/news/", "/fa/verdict/"],
"selectors": [".news-content", ".main-content"],
"delay_range": (3, 6),
"priority": 2,
"reliability_score": 0.85
},
"وزارت دادگستری": {
"base_url": "https://www.moj.ir",
"patterns": ["/fa/news/", "/fa/regulation/"],
"selectors": [".content-area", ".news-content"],
"delay_range": (2, 4),
"priority": 2,
"reliability_score": 0.80
}
}
# واژگان تخصصی حقوقی گسترده
PERSIAN_LEGAL_TERMS = {
"قوانین_اساسی": ["قانون اساسی", "مجلس شورای اسلامی", "شورای نگهبان", "ولایت فقیه", "اصول قانون اساسی"],
"قوانین_عادی": ["ماده", "تبصره", "فصل", "باب", "قانون مدنی", "قانون جزا", "قانون تجارت", "قانون کار"],
"اصطلاحات_حقوقی": ["شخص حقیقی", "شخص حقوقی", "دعوا", "خواهان", "خوانده", "مجازات", "قرارداد", "تعهد"],
"نهادهای_قضایی": ["دادگاه", "قاضی", "وکیل", "مدعی‌العموم", "رای", "حکم", "دادنامه", "قرار"],
"اصطلاحات_مالی": ["مالیات", "عوارض", "جریمه", "خسارت", "تأمین", "ضمانت", "وثیقه", "دیه"],
"جرائم": ["جنایت", "جنحه", "تخلف", "قصاص", "دیه", "تعزیر", "حدود", "قذف"]
}
# مدل‌های SOTA پیشنهادی
AVAILABLE_MODELS = {
"classification": {
"fabert": "sbunlp/fabert",
"parsbert": "HooshvareLab/bert-base-parsbert-uncased",
"legal_roberta": "lexlms/legal-roberta-base"
},
"embedding": {
"maux_gte": "xmanii/maux-gte-persian",
"sentence_transformer": "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
},
"ner": {
"parsbert_ner": "HooshvareLab/bert-fa-base-uncased-ner"
}
}
@dataclass
class ProcessingResult:
"""نتیجه پردازش سند"""
url: str
title: str
content: str
source: str
quality_score: float
classification: Dict[str, float]
sentiment_score: float
legal_entities: List[str]
embeddings: Optional[np.ndarray]
processing_time: float
cache_hit: bool = False
@dataclass
class SystemMetrics:
"""متریک‌های عملکرد سیستم"""
total_processed: int = 0
cache_hits: int = 0
classification_accuracy: float = 0.0
avg_processing_time: float = 0.0
memory_usage: float = 0.0
active_crawlers: int = 0
# === سیستم کش هوشمند ===
class IntelligentCacheSystem:
"""سیستم کش هوشمند با TTL و اولویت‌بندی"""
def __init__(self, cache_db_path: str = CACHE_DB_PATH):
self.cache_db_path = cache_db_path
self.memory_cache = {}
self.access_count = {}
self.max_memory_items = 1000
self._init_database()
def _init_database(self):
"""ایجاد پایگاه داده کش"""
os.makedirs(os.path.dirname(self.cache_db_path), exist_ok=True)
with sqlite3.connect(self.cache_db_path) as conn:
conn.execute('''
CREATE TABLE IF NOT EXISTS cache_entries (
key TEXT PRIMARY KEY,
value BLOB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
access_count INTEGER DEFAULT 1,
ttl_seconds INTEGER DEFAULT 3600,
priority INTEGER DEFAULT 1
)
''')
conn.execute('''
CREATE INDEX IF NOT EXISTS idx_created_ttl ON cache_entries(created_at, ttl_seconds)
''')
def _generate_key(self, url: str, model_type: str = "general") -> str:
"""تولید کلید یکتا برای کش"""
content = f"{url}:{model_type}"
return hashlib.md5(content.encode()).hexdigest()
def get(self, url: str, model_type: str = "general") -> Optional[Dict]:
"""دریافت از کش با بررسی TTL"""
key = self._generate_key(url, model_type)
# بررسی memory cache
if key in self.memory_cache:
self.access_count[key] = self.access_count.get(key, 0) + 1
return self.memory_cache[key]
# بررسی database cache
with sqlite3.connect(self.cache_db_path) as conn:
cursor = conn.execute('''
SELECT value, created_at, ttl_seconds, access_count
FROM cache_entries
WHERE key = ?
''', (key,))
row = cursor.fetchone()
if row:
value_blob, created_at, ttl_seconds, access_count = row
created_time = datetime.fromisoformat(created_at)
# بررسی انقضاء
if datetime.now() - created_time < timedelta(seconds=ttl_seconds):
# بروزرسانی شمارنده دسترسی
conn.execute(
'UPDATE cache_entries SET access_count = access_count + 1 WHERE key = ?',
(key,)
)
# اضافه به memory cache
value = pickle.loads(value_blob)
self._add_to_memory_cache(key, value)
return value
else:
# حذف entry منقضی
conn.execute('DELETE FROM cache_entries WHERE key = ?', (key,))
return None
def set(self, url: str, value: Dict, model_type: str = "general",
ttl_seconds: int = 3600, priority: int = 1):
"""ذخیره در کش"""
key = self._generate_key(url, model_type)
# ذخیره در memory cache
self._add_to_memory_cache(key, value)
# ذخیره در database
with sqlite3.connect(self.cache_db_path) as conn:
value_blob = pickle.dumps(value)
conn.execute('''
INSERT OR REPLACE INTO cache_entries
(key, value, ttl_seconds, priority)
VALUES (?, ?, ?, ?)
''', (key, value_blob, ttl_seconds, priority))
def _add_to_memory_cache(self, key: str, value: Dict):
"""اضافه کردن به memory cache با مدیریت حد"""
if len(self.memory_cache) >= self.max_memory_items:
# حذف کم‌استفاده‌ترین item
least_used_key = min(self.access_count.keys(), key=self.access_count.get)
del self.memory_cache[least_used_key]
del self.access_count[least_used_key]
self.memory_cache[key] = value
self.access_count[key] = self.access_count.get(key, 0) + 1
def cleanup_expired(self):
"""پاکسازی entries منقضی"""
with sqlite3.connect(self.cache_db_path) as conn:
conn.execute('''
DELETE FROM cache_entries
WHERE datetime(created_at, '+' || ttl_seconds || ' seconds') < datetime('now')
''')
def get_stats(self) -> Dict:
"""آمار کش"""
with sqlite3.connect(self.cache_db_path) as conn:
cursor = conn.execute('''
SELECT
COUNT(*) as total_entries,
SUM(access_count) as total_accesses,
AVG(access_count) as avg_accesses
FROM cache_entries
''')
stats = cursor.fetchone()
return {
'memory_cache_size': len(self.memory_cache),
'database_entries': stats[0] if stats[0] else 0,
'total_accesses': stats[1] if stats[1] else 0,
'average_accesses': round(stats[2], 2) if stats[2] else 0
}
# === سیستم امتیازدهی پیشرفته ===
class AdvancedScoringSystem:
"""سیستم امتیازدهی پیشرفته با وزن‌دهی چندگانه"""
def __init__(self):
self.weights = {
'content_length': 0.15,
'legal_terms_density': 0.25,
'source_reliability': 0.20,
'structure_quality': 0.15,
'linguistic_quality': 0.15,
'citation_count': 0.10
}
self.normalizer = Normalizer()
self.lemmatizer = Lemmatizer()
def calculate_comprehensive_score(self, content: str, source_info: Dict,
legal_entities: List[str]) -> Dict[str, float]:
"""محاسبه امتیاز جامع"""
scores = {}
# امتیاز طول محتوا
scores['content_length'] = self._score_content_length(content)
# تراکم اصطلاحات حقوقی
scores['legal_terms_density'] = self._score_legal_terms_density(content)
# قابلیت اعتماد منبع
scores['source_reliability'] = source_info.get('reliability_score', 0.5)
# کیفیت ساختار
scores['structure_quality'] = self._score_structure_quality(content)
# کیفیت زبانی
scores['linguistic_quality'] = self._score_linguistic_quality(content)
# تعداد ارجاعات
scores['citation_count'] = self._score_citations(content, legal_entities)
# محاسبه امتیاز نهایی
final_score = sum(
scores[factor] * self.weights[factor]
for factor in scores
)
scores['final_score'] = min(100, max(0, final_score * 100))
return scores
def _score_content_length(self, content: str) -> float:
"""امتیازدهی بر اساس طول محتوا"""
word_count = len(content.split())
if word_count < 50:
return word_count / 50 * 0.5
elif word_count > 2000:
return 1.0
else:
return 0.5 + (word_count - 50) / 1950 * 0.5
def _score_legal_terms_density(self, content: str) -> float:
"""امتیازدهی تراکم اصطلاحات حقوقی"""
total_terms = 0
content_lower = content.lower()
for category, terms in PERSIAN_LEGAL_TERMS.items():
for term in terms:
total_terms += content_lower.count(term.lower())
words = len(content.split())
if words == 0:
return 0.0
density = total_terms / words
return min(1.0, density * 20) # نرمال‌سازی
def _score_structure_quality(self, content: str) -> float:
"""امتیازدهی کیفیت ساختار"""
score = 0.0
# بررسی وجود ساختار مواد و تبصره‌ها
if 'ماده' in content:
score += 0.3
if 'تبصره' in content:
score += 0.2
if 'فصل' in content or 'باب' in content:
score += 0.2
# بررسی پاراگراف‌بندی
paragraphs = content.split('\n')
if len(paragraphs) > 2:
score += 0.3
return min(1.0, score)
def _score_linguistic_quality(self, content: str) -> float:
"""امتیازدهی کیفیت زبانی"""
score = 0.0
# نسبت کاراکترهای فارسی
persian_chars = sum(1 for c in content if '\u0600' <= c <= '\u06FF')
persian_ratio = persian_chars / len(content) if content else 0
score += persian_ratio * 0.5
# بررسی وجود علائم نگارشی
punctuation_count = sum(1 for c in content if c in '.,;:!؟')
words_count = len(content.split())
if words_count > 0:
punctuation_ratio = punctuation_count / words_count
score += min(0.3, punctuation_ratio * 3)
# بررسی طول متوسط جملات
sentences = re.split(r'[.؟!]', content)
if sentences:
avg_sentence_length = sum(len(s.split()) for s in sentences) / len(sentences)
if 10 <= avg_sentence_length <= 25:
score += 0.2
return min(1.0, score)
def _score_citations(self, content: str, legal_entities: List[str]) -> float:
"""امتیازدهی ارجاعات قانونی"""
citation_patterns = [
r'ماده\s*\d+', r'تبصره\s*\d+', r'بند\s*\d+',
r'فصل\s*\d+', r'قانون\s+[آ-ی\s]+', r'مصوبه\s+[آ-ی\s]+'
]
total_citations = 0
for pattern in citation_patterns:
total_citations += len(re.findall(pattern, content))
# اضافه کردن موجودیت‌های قانونی
total_citations += len(legal_entities)
# نرمال‌سازی (هر 5 ارجاع = امتیاز کامل)
return min(1.0, total_citations / 5)
# === سیستم طبقه‌بندی هوشمند ===
class IntelligentClassificationSystem:
"""سیستم طبقه‌بندی هوشمند چندمرحله‌ای"""
def __init__(self, cache_system: IntelligentCacheSystem):
self.cache_system = cache_system
self.models = {}
self.is_ready = False
# دسته‌های حقوقی
self.legal_categories = {
'قانون': ['قانون', 'مقررات', 'آیین‌نامه'],
'دادنامه': ['دادنامه', 'رای', 'حکم'],
'قرارداد': ['قرارداد', 'توافق‌نامه', 'پروتکل'],
'لایحه': ['لایحه', 'طرح', 'پیشنهاد'],
'بخشنامه': ['بخشنامه', 'دستورالعمل', 'رهنمود'],
'نظریه': ['نظریه', 'استعلام', 'پاسخ']
}
def load_models(self):
"""بارگذاری مدل‌های طبقه‌بندی"""
try:
# مدل اصلی طبقه‌بندی
self.models['classifier'] = pipeline(
"text-classification",
model=AVAILABLE_MODELS['classification']['parsbert'],
device=0 if torch.cuda.is_available() else -1,
batch_size=8 if torch.cuda.is_available() else 2
)
# مدل تشخیص موجودیت
self.models['ner'] = pipeline(
"ner",
model=AVAILABLE_MODELS['ner']['parsbert_ner'],
device=0 if torch.cuda.is_available() else -1,
aggregation_strategy="simple"
)
# مدل embedding
self.models['embedder'] = SentenceTransformer(
AVAILABLE_MODELS['embedding']['maux_gte']
)
self.is_ready = True
logger.info("تمام مدل‌های طبقه‌بندی بارگذاری شدند")
except Exception as e:
logger.error(f"خطا در بارگذاری مدل‌ها: {e}")
self.is_ready = False
def classify_document(self, content: str, use_cache: bool = True) -> Dict:
"""طبقه‌بندی هوشمند سند"""
if not self.is_ready:
return {'error': 'مدل‌ها آماده نیستند'}
# بررسی کش
cache_key = hashlib.md5(content.encode()).hexdigest()
if use_cache:
cached = self.cache_system.get(cache_key, 'classification')
if cached:
cached['cache_hit'] = True
return cached
start_time = time.time()
result = {}
try:
# متن نمونه برای پردازش
text_sample = ' '.join(content.split()[:400])
# طبقه‌بندی با مدل ترنسفورمر
if 'classifier' in self.models:
classification = self.models['classifier'](text_sample)
result['transformer_classification'] = classification[:3]
# طبقه‌بندی بر اساس قوانین
result['rule_based_classification'] = self._rule_based_classify(content)
# تشخیص موجودیت‌ها
if 'ner' in self.models:
entities = self.models['ner'](text_sample)
result['named_entities'] = [
e for e in entities if e.get('score', 0) > 0.8
][:10]
# استخراج موجودیت‌های حقوقی
result['legal_entities'] = self._extract_legal_entities(content)
# تولید embedding
if 'embedder' in self.models:
result['embedding'] = self.models['embedder'].encode(text_sample)
# محاسبه اعتماد ترکیبی
result['confidence_score'] = self._calculate_combined_confidence(result)
result['processing_time'] = time.time() - start_time
result['cache_hit'] = False
# ذخیره در کش
if use_cache:
self.cache_system.set(cache_key, result, 'classification', ttl_seconds=7200)
except Exception as e:
logger.error(f"خطا در طبقه‌بندی: {e}")
result['error'] = str(e)
return result
def _rule_based_classify(self, content: str) -> Dict[str, float]:
"""طبقه‌بندی بر اساس قوانین"""
scores = {}
content_lower = content.lower()
for category, keywords in self.legal_categories.items():
score = 0.0
for keyword in keywords:
count = content_lower.count(keyword.lower())
score += count * 0.1
scores[category] = min(1.0, score)
# نرمال‌سازی امتیازها
total_score = sum(scores.values())
if total_score > 0:
scores = {k: v/total_score for k, v in scores.items()}
return scores
def _extract_legal_entities(self, content: str) -> List[str]:
"""استخراج موجودیت‌های حقوقی"""
entities = []
# الگوهای ارجاعات قانونی
patterns = [
r'ماده\s*(\d+)', r'تبصره\s*(\d+)', r'بند\s*([الف-ی]|\d+)',
r'فصل\s*(\d+)', r'قانون\s+([آ-ی\s]{5,50})',
r'مصوبه\s+([آ-ی\s]{5,50})'
]
for pattern in patterns:
matches = re.findall(pattern, content)
entities.extend(matches)
return list(set(entities))[:20]
def _calculate_combined_confidence(self, result: Dict) -> float:
"""محاسبه اعتماد ترکیبی"""
confidence = 0.0
weights = {'transformer': 0.6, 'rule_based': 0.3, 'entities': 0.1}
# اعتماد مدل ترنسفورمر
if 'transformer_classification' in result:
transformer_conf = result['transformer_classification'][0].get('score', 0)
confidence += transformer_conf * weights['transformer']
# اعتماد طبقه‌بندی قانونی
if 'rule_based_classification' in result:
rule_conf = max(result['rule_based_classification'].values())
confidence += rule_conf * weights['rule_based']
# تعداد موجودیت‌های قانونی
entity_count = len(result.get('legal_entities', []))
entity_conf = min(1.0, entity_count / 5)
confidence += entity_conf * weights['entities']
return round(confidence, 3)
# === سیستم جستجوی معنایی پیشرفته ===
class SemanticSearchEngine:
"""موتور جستجوی معنایی با ایندکس FAISS"""
def __init__(self, cache_system: IntelligentCacheSystem):
self.cache_system = cache_system
self.embedder = None
self.faiss_index = None
self.documents = []
self.document_embeddings = None
self.is_ready = False
def initialize(self):
"""مقداردهی اولیه موتور جستجو"""
try:
self.embedder = SentenceTransformer(
AVAILABLE_MODELS['embedding']['maux_gte']
)
logger.info("مدل embedding بارگذاری شد")
# بارگذاری ایندکس موجود در صورت وجود
if os.path.exists(VECTOR_INDEX_PATH) and os.path.exists(EMBEDDINGS_CACHE_PATH):
self._load_existing_index()
self.is_ready = True
except Exception as e:
logger.error(f"خطا در مقداردهی موتور جستجو: {e}")
def build_index(self, documents: List[Tuple], progress_callback=None):
"""ساخت ایندکس FAISS"""
try:
if not documents:
return False
if progress_callback:
progress_callback("استخراج متون...", 0.1)
self.documents = documents
texts = [doc[3] for doc in documents if doc[3]] # content field
if progress_callback:
progress_callback(f"تولید embedding برای {len(texts)} سند...", 0.3)
# تولید embeddings با batch processing
batch_size = 16
all_embeddings = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i+batch_size]
batch_embeddings = self.embedder.encode(
batch,
convert_to_tensor=True,
show_progress_bar=False
)
all_embeddings.append(batch_embeddings)
if progress_callback:
progress = 0.3 + (i / len(texts)) * 0.6
progress_callback(f"پردازش batch {i//batch_size + 1}...", progress)
# ترکیب embeddings
self.document_embeddings = torch.cat(all_embeddings, dim=0).cpu().numpy()
if progress_callback:
progress_callback("ساخت ایندکس FAISS...", 0.9)
# ساخت ایندکس FAISS
dimension = self.document_embeddings.shape[1]
self.faiss_index = faiss.IndexFlatIP(dimension) # Inner Product for cosine similarity
# نرمال‌سازی برای cosine similarity
faiss.normalize_L2(self.document_embeddings)
self.faiss_index.add(self.document_embeddings)
# ذخیره ایندکس
self._save_index()
if progress_callback:
progress_callback("تکمیل ایندکس‌سازی", 1.0)
logger.info(f"ایندکس با {len(documents)} سند آماده شد")
return True
except Exception as e:
logger.error(f"خطا در ساخت ایندکس: {e}")
return False
def search(self, query: str, top_k: int = 10, threshold: float = 0.3) -> List[Dict]:
"""جستجوی معنایی پیشرفته"""
if not self.is_ready or self.faiss_index is None:
return []
try:
# بررسی کش
cache_key = f"search:{hashlib.md5(query.encode()).hexdigest()}:{top_k}"
cached = self.cache_system.get(cache_key, 'search')
if cached:
return cached
# تولید embedding برای query
query_embedding = self.embedder.encode([query], convert_to_tensor=True)
query_embedding = query_embedding.cpu().numpy()
faiss.normalize_L2(query_embedding)
# جستجو در ایندکس
similarities, indices = self.faiss_index.search(query_embedding, top_k * 2)
results = []
for i, (similarity, idx) in enumerate(zip(similarities[0], indices[0])):
if similarity < threshold:
continue
if idx < len(self.documents):
doc = self.documents[idx]
results.append({
'rank': i + 1,
'document_id': doc[0],
'url': doc[1],
'title': doc[2],
'content_preview': doc[3][:300] + '...' if len(doc[3]) > 300 else doc[3],
'similarity_score': float(similarity),
'relevance_category': self._categorize_relevance(similarity)
})
# مرتب‌سازی نهایی
results = results[:top_k]
# ذخیره در کش
self.cache_system.set(cache_key, results, 'search', ttl_seconds=1800)
return results
except Exception as e:
logger.error(f"خطا در جستجو: {e}")
return []
def _categorize_relevance(self, similarity: float) -> str:
"""دسته‌بندی میزان ارتباط"""
if similarity >= 0.8:
return "بسیار مرتبط"
elif similarity >= 0.6:
return "مرتبط"
elif similarity >= 0.4:
return "نسبتاً مرتبط"
else:
return "کم‌ارتباط"
def _save_index(self):
"""ذخیره ایندکس و embeddings"""
try:
if self.faiss_index:
faiss.write_index(self.faiss_index, VECTOR_INDEX_PATH)
if self.document_embeddings is not None:
with open(EMBEDDINGS_CACHE_PATH, 'wb') as f:
pickle.dump({
'embeddings': self.document_embeddings,
'documents_info': [(doc[0], doc[1], doc[2]) for doc in self.documents]
}, f)
logger.info("ایندکس ذخیره شد")
except Exception as e:
logger.error(f"خطا در ذخیره ایندکس: {e}")
def _load_existing_index(self):
"""بارگذاری ایندکس موجود"""
try:
self.faiss_index = faiss.read_index(VECTOR_INDEX_PATH)
with open(EMBEDDINGS_CACHE_PATH, 'rb') as f:
cache_data = pickle.load(f)
self.document_embeddings = cache_data['embeddings']
# بازسازی documents از اطلاعات ذخیره شده
# نیاز به query از دیتابیس برای محتوای کامل
logger.info("ایندکس موجود بارگذاری شد")
except Exception as e:
logger.error(f"خطا در بارگذاری ایندکس: {e}")
# === مدیریت پایگاه داده پیشرفته ===
class AdvancedDatabaseManager:
"""مدیریت پیشرفته پایگاه داده با بهینه‌سازی‌های جدید"""
def __init__(self, db_path: str = DB_PATH):
self.db_path = db_path
os.makedirs(os.path.dirname(db_path), exist_ok=True)
self._init_database()
def _init_database(self):
"""ایجاد پایگاه داده با جداول پیشرفته"""
with sqlite3.connect(self.db_path) as conn:
# جدول اسناد اصلی
conn.execute('''
CREATE TABLE IF NOT EXISTS documents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT UNIQUE NOT NULL,
title TEXT,
source TEXT,
content TEXT,
word_count INTEGER,
quality_scores TEXT,
classification_result TEXT,
legal_entities TEXT,
embedding_vector BLOB,
scraped_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
processing_status TEXT DEFAULT 'pending'
)
''')
# ایجاد ایندکس‌ها برای جدول documents
conn.execute('CREATE INDEX IF NOT EXISTS idx_documents_source ON documents(source)')
conn.execute('CREATE INDEX IF NOT EXISTS idx_documents_scraped_at ON documents(scraped_at)')
conn.execute('CREATE INDEX IF NOT EXISTS idx_documents_status ON documents(processing_status)')
conn.execute('CREATE INDEX IF NOT EXISTS idx_documents_updated ON documents(last_updated)')
# جدول متریک‌های عملکرد
conn.execute('''
CREATE TABLE IF NOT EXISTS performance_metrics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
metric_name TEXT,
metric_value REAL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# ایندکس‌ها برای جدول metrics
conn.execute('CREATE INDEX IF NOT EXISTS idx_metrics_name ON performance_metrics(metric_name)')
conn.execute('CREATE INDEX IF NOT EXISTS idx_metrics_timestamp ON performance_metrics(timestamp)')
# جدول لاگ‌های پردازش
conn.execute('''
CREATE TABLE IF NOT EXISTS processing_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
document_id INTEGER,
operation TEXT,
status TEXT,
details TEXT,
processing_time REAL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (document_id) REFERENCES documents (id)
)
''')
# ایندکس‌ها برای جدول logs
conn.execute('CREATE INDEX IF NOT EXISTS idx_logs_operation ON processing_logs(operation)')
conn.execute('CREATE INDEX IF NOT EXISTS idx_logs_status ON processing_logs(status)')
conn.execute('CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON processing_logs(timestamp)')
conn.execute('CREATE INDEX IF NOT EXISTS idx_logs_document_id ON processing_logs(document_id)')
def save_document_advanced(self, result: ProcessingResult) -> bool:
"""ذخیره پیشرفته سند"""
try:
with sqlite3.connect(self.db_path) as conn:
# محاسبه embedding bytes
embedding_blob = None
if result.embeddings is not None:
embedding_blob = result.embeddings.tobytes()
conn.execute('''
INSERT OR REPLACE INTO documents
(url, title, source, content, word_count, quality_scores,
classification_result, legal_entities, embedding_vector,
processing_status, last_updated)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'completed', datetime('now'))
''', (
result.url,
result.title,
result.source,
result.content,
len(result.content.split()),
json.dumps({'quality_score': result.quality_score}, ensure_ascii=False),
json.dumps(result.classification, ensure_ascii=False),
json.dumps(result.legal_entities, ensure_ascii=False),
embedding_blob
))
# ثبت لاگ
document_id = conn.lastrowid
conn.execute('''
INSERT INTO processing_logs
(document_id, operation, status, processing_time)
VALUES (?, 'save', 'success', ?)
''', (document_id, result.processing_time))
return True
except Exception as e:
logger.error(f"خطا در ذخیره پیشرفته: {e}")
return False
def get_documents_with_embeddings(self) -> List[Tuple]:
"""دریافت اسناد همراه با embeddings"""
with sqlite3.connect(self.db_path) as conn:
return conn.execute('''
SELECT id, url, title, content, embedding_vector
FROM documents
WHERE processing_status = 'completed'
AND content IS NOT NULL
ORDER BY last_updated DESC
''').fetchall()
def get_advanced_stats(self) -> Dict:
"""آمار پیشرفته سیستم"""
with sqlite3.connect(self.db_path) as conn:
# آمار کلی
total_docs = conn.execute("SELECT COUNT(*) FROM documents").fetchone()[0]
# آمار بر اساس منبع
source_stats = conn.execute('''
SELECT source, COUNT(*), AVG(word_count)
FROM documents
GROUP BY source
ORDER BY COUNT(*) DESC
''').fetchall()
# آمار عملکرد
avg_processing_time = conn.execute('''
SELECT AVG(processing_time)
FROM processing_logs
WHERE operation = 'save' AND status = 'success'
''').fetchone()[0] or 0
# آمار وضعیت پردازش
status_stats = conn.execute('''
SELECT processing_status, COUNT(*)
FROM documents
GROUP BY processing_status
''').fetchall()
return {
'total_documents': total_docs,
'source_statistics': source_stats,
'average_processing_time': round(avg_processing_time, 2),
'status_statistics': status_stats,
'last_updated': datetime.now().isoformat()
}
# === کرالر خودکار پیشرفته ===
class AdvancedCrawlerSystem:
"""سیستم کرالر خودکار با قابلیت‌های هوشمند"""
def __init__(self, scraper, db_manager: AdvancedDatabaseManager,
classification_system: IntelligentClassificationSystem):
self.scraper = scraper
self.db_manager = db_manager
self.classification_system = classification_system
self.is_active = False
self.crawler_stats = SystemMetrics()
self.discovered_urls = set()
self.processed_urls = set()
def start_intelligent_crawling(self, duration_hours: int = 24,
max_docs_per_cycle: int = 10) -> None:
"""شروع کرالر هوشمند"""
self.is_active = True
self.crawler_stats.active_crawlers = 1
def crawler_worker():
end_time = time.time() + (duration_hours * 3600)
cycle_count = 0
logger.info(f"کرالر هوشمند برای {duration_hours} ساعت شروع شد")
while time.time() < end_time and self.is_active:
cycle_count += 1
cycle_start = time.time()
try:
# فاز 1: کشف URLs جدید
new_urls = self._discover_quality_urls()
logger.info(f"چرخه {cycle_count}: {len(new_urls)} URL جدید کشف شد")
# فاز 2: اولویت‌بندی URLs
prioritized_urls = self._prioritize_urls(new_urls)[:max_docs_per_cycle]
# فاز 3: پردازش هوشمند
for i, url in enumerate(prioritized_urls):
if not self.is_active:
break
try:
# استخراج محتوا
scraped_result = self.scraper.scrape_document(url)
if scraped_result.get('status') == 'موفق':
# طبقه‌بندی هوشمند
classification = self.classification_system.classify_document(
scraped_result['content']
)
# ساخت ProcessingResult
processing_result = ProcessingResult(
url=url,
title=scraped_result.get('title', ''),
content=scraped_result.get('content', ''),
source=scraped_result.get('source_info', {}).get('name', ''),
quality_score=scraped_result.get('quality_assessment', {}).get('overall_score', 0),
classification=classification.get('rule_based_classification', {}),
sentiment_score=0.5, # پیش‌فرض
legal_entities=classification.get('legal_entities', []),
embeddings=classification.get('embedding'),
processing_time=classification.get('processing_time', 0)
)
# ذخیره در دیتابیس
if self.db_manager.save_document_advanced(processing_result):
self.crawler_stats.total_processed += 1
logger.info(f"سند ذخیره شد: {processing_result.title[:50]}...")
self.processed_urls.add(url)
# تأخیر بین درخواست‌ها
time.sleep(random.uniform(2, 5))
except Exception as e:
logger.error(f"خطا در پردازش {url}: {e}")
# فاز 4: بهینه‌سازی و پاکسازی
self._optimize_system()
cycle_duration = time.time() - cycle_start
logger.info(f"چرخه {cycle_count} در {cycle_duration:.1f} ثانیه تکمیل شد")
# استراحت بین چرخه‌ها
if self.is_active:
time.sleep(600) # 10 دقیقه
except Exception as e:
logger.error(f"خطا در چرخه کرالر: {e}")
time.sleep(300) # 5 دقیقه در صورت خطا
self.is_active = False
self.crawler_stats.active_crawlers = 0
logger.info("کرالر متوقف شد")
# اجرای کرالر در thread جداگانه
crawler_thread = threading.Thread(target=crawler_worker, daemon=True)
crawler_thread.start()
def _discover_quality_urls(self) -> List[str]:
"""کشف URLs با کیفیت"""
new_urls = []
for source_name, config in LEGAL_SOURCES.items():
if not self.is_active:
break
try:
# درخواست صفحه اصلی
response = self.scraper.session.get(
config['base_url'],
timeout=15,
headers=self.scraper.anti_ddos.get_random_headers()
)
soup = BeautifulSoup(response.content, 'html.parser')
# استخراج لینک‌ها
for link in soup.find_all('a', href=True):
full_url = urljoin(config['base_url'], link['href'])
# فیلتر بر اساس patterns
if any(pattern in full_url for pattern in config['patterns']):
if (full_url not in self.processed_urls and
full_url not in self.discovered_urls):
new_urls.append(full_url)
self.discovered_urls.add(full_url)
# محدودیت تعداد URLs از هر منبع
if len(new_urls) >= 50:
break
except Exception as e:
logger.error(f"خطا در کشف URLs از {source_name}: {e}")
return new_urls[:100] # محدودیت کلی
def _prioritize_urls(self, urls: List[str]) -> List[str]:
"""اولویت‌بندی URLs بر اساس کیفیت احتمالی"""
url_priorities = []
for url in urls:
priority_score = 0.0
# امتیاز بر اساس منبع
for source_name, config in LEGAL_SOURCES.items():
if config['base_url'] in url:
priority_score += config.get('reliability_score', 0.5) * 0.4
priority_score += (3 - config.get('priority', 3)) * 0.2
break
# امتیاز بر اساس URL pattern
if '/law/' in url or '/legal/' in url:
priority_score += 0.3
if '/verdict/' in url or '/judgment/' in url:
priority_score += 0.2
if any(term in url.lower() for term in ['قانون', 'مقرره', 'آیین‌نامه']):
priority_score += 0.1
url_priorities.append((url, priority_score))
# مرتب‌سازی بر اساس اولویت
url_priorities.sort(key=lambda x: x[1], reverse=True)
return [url for url, score in url_priorities]
def _optimize_system(self):
"""بهینه‌سازی دوره‌ای سیستم"""
try:
# پاکسازی حافظه
gc.collect()
# پاکسازی کش منقضی
if hasattr(self.scraper, 'cache_system'):
self.scraper.cache_system.cleanup_expired()
# محدود کردن discovered_urls
if len(self.discovered_urls) > 5000:
# حذف قدیمی‌ترین‌ها (شبیه‌سازی FIFO)
self.discovered_urls = set(list(self.discovered_urls)[-3000:])
# آپدیت آمار حافظه
self.crawler_stats.memory_usage = self._get_memory_usage()
except Exception as e:
logger.error(f"خطا در بهینه‌سازی: {e}")
def _get_memory_usage(self) -> float:
"""دریافت میزان مصرف حافظه"""
try:
with open('/proc/self/status') as f:
for line in f:
if line.startswith('VmRSS:'):
return float(line.split()[1]) / 1024
except:
pass
return 0.0
def stop_crawler(self):
"""توقف کرالر"""
self.is_active = False
self.crawler_stats.active_crawlers = 0
logger.info("درخواست توقف کرالر ارسال شد")
def get_crawler_stats(self) -> Dict:
"""آمار کرالر"""
return {
'is_active': self.is_active,
'total_processed': self.crawler_stats.total_processed,
'discovered_urls': len(self.discovered_urls),
'processed_urls': len(self.processed_urls),
'memory_usage': self.crawler_stats.memory_usage,
'cache_hits': self.crawler_stats.cache_hits
}
# === اپلیکیشن اصلی پیشرفته ===
class AdvancedIranianLegalArchive:
"""اپلیکیشن پیشرفته با تمام قابلیت‌های ارتقاء یافته"""
def __init__(self):
# مقداردهی اجزاء سیستم
self.cache_system = IntelligentCacheSystem()
self.db_manager = AdvancedDatabaseManager()
self.scoring_system = AdvancedScoringSystem()
self.classification_system = IntelligentClassificationSystem(self.cache_system)
self.search_engine = SemanticSearchEngine(self.cache_system)
# مدیریت حالت سیستم
self.system_ready = False
self.system_metrics = SystemMetrics()
# کرالر (بعد از مقداردهی scraper)
self.crawler_system = None
# مقداردهی سیستم
self._initialize_advanced_system()
def _initialize_advanced_system(self):
"""مقداردهی پیشرفته سیستم"""
logger.info("🚀 شروع مقداردهی سیستم پیشرفته...")
try:
# بارگذاری مدل‌های طبقه‌بندی
self.classification_system.load_models()
# مقداردهی موتور جستجو
self.search_engine.initialize()
# ایجاد scraper ساده
from types import SimpleNamespace
self.scraper = SimpleNamespace()
self.scraper.cache_system = self.cache_system
self.scraper.session = requests.Session()
self.scraper.scrape_document = self._create_scraper_method()
# مقداردهی کرالر
self.crawler_system = AdvancedCrawlerSystem(
self.scraper, self.db_manager, self.classification_system
)
self.system_ready = True
logger.info("✅ سیستم پیشرفته آماده است")
# مقداردهی اولیه در صورت خالی بودن دیتابیس
if self.db_manager.get_advanced_stats()['total_documents'] == 0:
self._seed_with_sample_documents()
except Exception as e:
logger.error(f"❌ خطا در مقداردهی: {e}")
self.system_ready = False
def _create_scraper_method(self):
"""ایجاد متد scraper"""
def scrape_document(url: str) -> Dict:
try:
# شبیه‌سازی scraping ساده
response = self.scraper.session.get(url, timeout=30)
soup = BeautifulSoup(response.content, 'html.parser')
title = soup.find('title')
title = title.text if title else "بدون عنوان"
content = soup.get_text(strip=True)[:2000]
# امتیازدهی
scores = self.scoring_system.calculate_comprehensive_score(
content, {'reliability_score': 0.8}, []
)
return {
'status': 'موفق',
'title': title,
'content': content,
'quality_assessment': scores,
'source_info': {'name': 'منبع نمونه'}
}
except Exception as e:
return {'status': 'ناموفق', 'error': str(e)}
return scrape_document
def _seed_with_sample_documents(self):
"""مقداردهی نمونه"""
sample_docs = [
{
'url': 'https://example.com/law/1',
'title': 'قانون نمونه شماره 1',
'content': 'ماده 1- این قانون به منظور تنظیم روابط حقوقی وضع شده است. تبصره: موارد استثنا در آیین‌نامه اجرایی تعیین می‌شود.',
'source': 'منبع نمونه'
},
{
'url': 'https://example.com/law/2',
'title': 'آیین‌نامه اجرایی نمونه',
'content': 'بند الف) کلیه اشخاص حقیقی و حقوقی مشمول این مقررات هستند. بند ب) تخلفات مربوطه در دادگاه رسیدگی می‌شود.',
'source': 'منبع نمونه'
}
]
for doc in sample_docs:
# طبقه‌بندی
classification = self.classification_system.classify_document(doc['content'])
# ساخت ProcessingResult
result = ProcessingResult(
url=doc['url'],
title=doc['title'],
content=doc['content'],
source=doc['source'],
quality_score=85.0,
classification=classification.get('rule_based_classification', {}),
sentiment_score=0.6,
legal_entities=classification.get('legal_entities', []),
embeddings=classification.get('embedding'),
processing_time=0.5
)
self.db_manager.save_document_advanced(result)
logger.info("مقداردهی نمونه تکمیل شد")
def process_urls_intelligent(self, urls_text: str, progress=gr.Progress()) -> Tuple[str, str]:
"""پردازش هوشمند URLs با تمام قابلیت‌ها"""
if not urls_text or not urls_text.strip():
return "❌ لطفاً فهرست آدرس‌ها را وارد کنید", ""
urls = [url.strip() for url in urls_text.strip().split('\n')
if url.strip().startswith(('http://', 'https://'))]
if not urls:
return "❌ آدرس معتبری یافت نشد", ""
# محدودیت برای HF Spaces
if len(urls) > 15:
urls = urls[:15]
warning = "⚠️ به دلیل محدودیت‌ها، تنها 15 آدرس اول پردازش می‌شود.\n\n"
else:
warning = ""
results = []
cache_hits = 0
total_processing_time = 0
for i, url in enumerate(urls):
progress_value = (i + 1) / len(urls)
progress(progress_value, desc=f"پردازش هوشمند {i+1} از {len(urls)}")
start_time = time.time()
try:
# بررسی کش
cached = self.cache_system.get(url, 'comprehensive')
if cached:
cache_hits += 1
results.append(cached)
continue
# استخراج محتوا
scraped_result = self.scraper.scrape_document(url)
if scraped_result.get('status') == 'موفق':
# طبقه‌بندی هوشمند
classification = self.classification_system.classify_document(
scraped_result['content']
)
# امتیازدهی پیشرفته
scores = self.scoring_system.calculate_comprehensive_score(
scraped_result['content'],
scraped_result.get('source_info', {}),
classification.get('legal_entities', [])
)
# ساخت نتیجه جامع
processing_result = ProcessingResult(
url=url,
title=scraped_result.get('title', ''),
content=scraped_result.get('content', ''),
source=scraped_result.get('source_info', {}).get('name', ''),
quality_score=scores['final_score'],
classification=classification.get('rule_based_classification', {}),
sentiment_score=0.5,
legal_entities=classification.get('legal_entities', []),
embeddings=classification.get('embedding'),
processing_time=time.time() - start_time,
cache_hit=False
)
# ذخیره در دیتابیس
self.db_manager.save_document_advanced(processing_result)
# ذخیره در کش
cache_data = {
'title': processing_result.title,
'quality_score': processing_result.quality_score,
'classification': processing_result.classification,
'legal_entities': processing_result.legal_entities,
'status': 'موفق'
}
self.cache_system.set(url, cache_data, 'comprehensive', ttl_seconds=7200)
results.append(cache_data)
else:
results.append({'status': 'ناموفق', 'error': scraped_result.get('error', '')})
total_processing_time += time.time() - start_time
except Exception as e:
logger.error(f"خطا در پردازش {url}: {e}")
results.append({'status': 'ناموفق', 'error': str(e)})
# تأخیر بین درخواست‌ها
time.sleep(1)
# تولید گزارش جامع
successful = sum(1 for r in results if r.get('status') == 'موفق')
failed = len(results) - successful
avg_quality = sum(r.get('quality_score', 0) for r in results if r.get('quality_score')) / max(successful, 1)
summary = f"""{warning}📊 **گزارش پردازش هوشمند:**
{SVG_ICONS['success']} موفق: {successful}
{SVG_ICONS['error']} ناموفق: {failed}
{SVG_ICONS['cache']} استفاده از کش: {cache_hits}
📈 میانگین کیفیت: {avg_quality:.1f}/100
⏱️ زمان کل: {total_processing_time:.1f} ثانیه
📚 کل اسناد در آرشیو: {self.db_manager.get_advanced_stats()['total_documents']}"""
# جزئیات نتایج
details_lines = []
for i, result in enumerate(results, 1):
if result.get('status') == 'موفق':
title = result.get('title', 'بدون عنوان')[:50]
quality = result.get('quality_score', 0)
entities_count = len(result.get('legal_entities', []))
cache_icon = SVG_ICONS['cache'] if result.get('cache_hit') else ''
details_lines.append(
f"{i}. {cache_icon} {title}... (کیفیت: {quality:.1f}, موجودیت‌ها: {entities_count})"
)
else:
error = result.get('error', 'نامشخص')[:50]
details_lines.append(f"{i}. {SVG_ICONS['error']} خطا: {error}...")
details = '\n'.join(details_lines)
return summary, details
def build_intelligent_search_index(self, progress=gr.Progress()) -> str:
"""ساخت ایندکس جستجوی هوشمند"""
def progress_callback(message: str, value: float):
progress(value, desc=message)
try:
documents = self.db_manager.get_documents_with_embeddings()
if not documents:
return "❌ سندی برای ایندکس‌سازی یافت نشد"
success = self.search_engine.build_index(documents, progress_callback)
if success:
return f"✅ ایندکس هوشمند با {len(documents)} سند آماده شد"
else:
return "❌ خطا در ساخت ایندکس"
except Exception as e:
logger.error(f"خطا در ساخت ایندکس: {e}")
return f"❌ خطا: {str(e)}"
def intelligent_semantic_search(self, query: str, top_k: int = 10) -> Tuple[str, pd.DataFrame]:
"""جستجوی معنایی هوشمند"""
if not query or not query.strip():
return "❌ لطفاً عبارت جستجو را وارد کنید", pd.DataFrame()
if not self.search_engine.is_ready or self.search_engine.faiss_index is None:
return "❌ ایندکس جستجو آماده نیست", pd.DataFrame()
try:
start_time = time.time()
results = self.search_engine.search(query.strip(), top_k, threshold=0.2)
search_time = time.time() - start_time
if not results:
return f"🔍 نتیجه‌ای برای '{query}' یافت نشد", pd.DataFrame()
# تبدیل به DataFrame
df_data = []
for result in results:
df_data.append({
'رتبه': result['rank'],
'امتیاز شباهت': f"{result['similarity_score']:.3f}",
'درجه ارتباط': result['relevance_category'],
'عنوان': result['title'],
'خلاصه محتوا': result['content_preview'],
'آدرس': result['url']
})
df = pd.DataFrame(df_data)
message = f"""🔍 **نتایج جستجوی هوشمند:** {query}
📊 {len(results)} نتیجه در {search_time:.2f} ثانیه
🎯 حد آستانه شباهت: 0.2
{SVG_ICONS['cache']} استفاده از کش سیستم"""
return message, df
except Exception as e:
logger.error(f"خطا در جستجو: {e}")
return f"❌ خطا در جستجو: {str(e)}", pd.DataFrame()
def start_intelligent_crawler(self, duration_hours: int) -> str:
"""شروع کرالر هوشمند"""
if not self.crawler_system:
return "❌ سیستم کرالر آماده نیست"
if self.crawler_system.is_active:
return "❌ کرالر در حال حاضر فعال است"
try:
self.crawler_system.start_intelligent_crawling(duration_hours, max_docs_per_cycle=5)
return f"🚀 کرالر هوشمند برای {duration_hours} ساعت شروع شد"
except Exception as e:
return f"❌ خطا در شروع کرالر: {str(e)}"
def stop_intelligent_crawler(self) -> str:
"""توقف کرالر هوشمند"""
if self.crawler_system:
self.crawler_system.stop_crawler()
return "🛑 کرالر متوقف شد"
return "❌ کرالر یافت نشد"
def get_comprehensive_system_status(self) -> str:
"""وضعیت جامع سیستم"""
try:
# آمار پایگاه داده
db_stats = self.db_manager.get_advanced_stats()
# آمار کش
cache_stats = self.cache_system.get_stats()
# آمار کرالر
crawler_stats = self.crawler_system.get_crawler_stats() if self.crawler_system else {}
# آمار مدل‌ها
models_ready = self.classification_system.is_ready
search_ready = self.search_engine.is_ready
status_parts = [
f"🏛️ **آرشیو هوشمند اسناد حقوقی ایران**",
f"📊 **آمار پایگاه داده:**",
f"📚 اسناد: {db_stats['total_documents']:,}",
f"⏱️ میانگین زمان پردازش: {db_stats['average_processing_time']} ثانیه",
"",
f"{SVG_ICONS['cache']} **سیستم کش:**",
f"💾 حافظه کش: {cache_stats['memory_cache_size']} آیتم",
f"🗄️ کش دیتابیس: {cache_stats['database_entries']} ورودی",
f"📈 کل دسترسی‌ها: {cache_stats['total_accesses']:,}",
"",
f"🤖 **سیستم‌های هوشمند:**",
f"🏷️ طبقه‌بندی: {'آماده' if models_ready else 'غیرآماده'}",
f"🔍 جستجوی معنایی: {'آماده' if search_ready else 'غیرآماده'}",
"",
f"{SVG_ICONS['crawler']} **کرالر خودکار:**",
f"وضعیت: {'فعال' if crawler_stats.get('is_active') else 'غیرفعال'}",
f"پردازش شده: {crawler_stats.get('total_processed', 0)}",
f"کشف شده: {crawler_stats.get('discovered_urls', 0)} URL",
f"مصرف حافظه: {crawler_stats.get('memory_usage', 0):.1f} MB",
"",
f"📈 **منابع:**"
]
# اضافه کردن آمار منابع
for source_data in db_stats.get('source_statistics', []):
source, count, avg_words = source_data
status_parts.append(f" • {source}: {count:,} سند (میانگین: {avg_words:.0f} کلمه)")
return '\n'.join(status_parts)
except Exception as e:
logger.error(f"خطا در دریافت وضعیت: {e}")
return f"❌ خطا در دریافت وضعیت: {str(e)}"
def export_advanced_data(self) -> Tuple[str, str]:
"""صدور پیشرفته داده‌ها"""
try:
# دریافت اسناد با جزئیات کامل
with sqlite3.connect(self.db_manager.db_path) as conn:
docs = conn.execute('''
SELECT url, title, source, word_count, quality_scores,
classification_result, legal_entities, scraped_at
FROM documents
WHERE processing_status = 'completed'
ORDER BY scraped_at DESC
''').fetchall()
if not docs:
return "❌ سندی برای صدور یافت نشد", ""
# تبدیل به DataFrame
df_data = []
for doc in docs:
quality_data = json.loads(doc[4]) if doc[4] else {}
classification_data = json.loads(doc[5]) if doc[5] else {}
entities_data = json.loads(doc[6]) if doc[6] else []
df_data.append({
'آدرس': doc[0],
'عنوان': doc[1],
'منبع': doc[2],
'تعداد کلمات': doc[3],
'امتیاز کیفیت': quality_data.get('quality_score', 0),
'دسته‌بندی اصلی': max(classification_data.keys(), key=classification_data.get) if classification_data else 'نامشخص',
'تعداد موجودیت‌های حقوقی': len(entities_data),
'تاریخ استخراج': doc[7]
})
df = pd.DataFrame(df_data)
# ذخیره فایل
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f"iranian_legal_archive_advanced_{timestamp}.csv"
filepath = f"/tmp/{filename}"
df.to_csv(filepath, index=False, encoding='utf-8-sig')
return f"✅ {len(docs)} سند با جزئیات کامل صادر شد", filepath
except Exception as e:
logger.error(f"خطا در صدور: {e}")
return f"❌ خطا در صدور: {str(e)}", ""
def create_advanced_interface(self):
"""ایجاد رابط کاربری پیشرفته"""
custom_css = """
.rtl { direction: rtl; text-align: right; font-family: 'Vazirmatn', 'Tahoma', sans-serif; }
.main-title {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; padding: 25px; border-radius: 15px; text-align: center;
margin-bottom: 25px; box-shadow: 0 8px 32px rgba(0,0,0,0.1);
}
.metric-card {
background: linear-gradient(45deg, #f093fb 0%, #f5576c 100%);
color: white; padding: 15px; border-radius: 10px; margin: 5px;
}
.advanced-tab {
border: 2px solid #667eea; border-radius: 10px; padding: 15px; margin: 10px;
}
"""
with gr.Blocks(
title="آرشیو هوشمند پیشرفته اسناد حقوقی ایران",
theme=gr.themes.Soft(primary_hue=gr.themes.colors.purple, secondary_hue=gr.themes.colors.indigo),
css=custom_css
) as interface:
# عنوان اصلی پیشرفته
gr.HTML(f"""
<div class="main-title">
<h1>🏛️ آرشیو هوشمند پیشرفته اسناد حقوقی ایران</h1>
<p>سیستم جامع مجهز به هوش مصنوعی پیشرفته، کش هوشمند و کرالر خودکار</p>
<p>🧠 مدل‌های SOTA فارسی | ⚡ کش هوشمند | 🎯 امتیازدهی پیشرفته | 🔄 کرالر خودکار</p>
</div>
""")
with gr.Tabs():
# تب پردازش هوشمند
with gr.Tab("🧠 پردازش هوشمند"):
gr.Markdown("### پردازش هوشمند با تمام قابلیت‌های پیشرفته")
urls_input = gr.Textbox(
label="فهرست آدرس‌های اسناد حقوقی",
placeholder="""https://rc.majlis.ir/fa/law/show/139030
https://www.judiciary.ir/fa/news/12345
https://www.dotic.ir/portal/law/67890""",
lines=10,
elem_classes=["rtl"]
)
with gr.Row():
process_intelligent_btn = gr.Button(
"🚀 پردازش هوشمند (کش + امتیازدهی + طبقه‌بندی)",
variant="primary",
size="lg"
)
clear_cache_btn = gr.Button("🗑️ پاک کردن کش", variant="secondary")
with gr.Row():
intelligent_summary = gr.Textbox(
label="گزارش جامع پردازش",
interactive=False,
lines=8,
elem_classes=["rtl"]
)
intelligent_details = gr.Textbox(
label="جزئیات و امتیازها",
interactive=False,
lines=8,
elem_classes=["rtl"]
)
# تب جستجوی هوشمند
with gr.Tab("🔍 جستجوی معنایی پیشرفته"):
gr.Markdown("### جستجوی معنایی با الگوریتم‌های پیشرفته")
with gr.Row():
build_advanced_index_btn = gr.Button("🔧 ساخت ایندکس هوشمند", variant="secondary")
advanced_index_status = gr.Textbox(
label="وضعیت ایندکس",
interactive=False,
elem_classes=["rtl"]
)
with gr.Row():
search_query = gr.Textbox(
label="عبارت جستجو",
placeholder="مسئولیت کیفری اشخاص حقوقی",
elem_classes=["rtl"],
scale=3
)
search_limit = gr.Slider(
minimum=5, maximum=20, value=10, step=1,
label="تعداد نتایج", scale=1
)
advanced_search_btn = gr.Button("🔍 جستجوی هوشمند", variant="primary")
advanced_search_results_text = gr.Markdown(elem_classes=["rtl"])
advanced_search_results_df = gr.DataFrame(
label="نتایج تفصیلی",
interactive=False
)
# تب کرالر خودکار
with gr.Tab("🔄 کرالر هوشمند خودکار"):
gr.Markdown("### سیستم کرالر خودکار با هوش مصنوعی")
with gr.Row():
crawler_duration = gr.Slider(
minimum=1, maximum=48, value=12, step=1,
label="مدت فعالیت (ساعت)"
)
max_docs_per_cycle = gr.Slider(
minimum=5, maximum=20, value=10, step=1,
label="حداکثر اسناد در هر چرخه"
)
with gr.Row():
start_advanced_crawler_btn = gr.Button("🚀 شروع کرالر هوشمند", variant="primary")
stop_advanced_crawler_btn = gr.Button("🛑 توقف کرالر", variant="stop")
crawler_status_btn = gr.Button("📊 وضعیت کرالر", variant="secondary")
advanced_crawler_status = gr.Textbox(
label="وضعیت و آمار کرالر",
interactive=False,
lines=10,
elem_classes=["rtl"]
)
# تب سیستم و آمار
with gr.Tab("📊 مدیریت سیستم پیشرفته"):
with gr.Row():
with gr.Column():
gr.Markdown("### وضعیت جامع سیستم")
comprehensive_status = gr.Markdown(elem_classes=["rtl"])
with gr.Row():
refresh_status_btn = gr.Button("🔄 بروزرسانی")
optimize_system_btn = gr.Button("⚡ بهینه‌سازی سیستم")
with gr.Column():
gr.Markdown("### صدور و پشتیبان‌گیری")
advanced_export_btn = gr.Button("📊 صدور داده‌های پیشرفته", variant="primary")
advanced_export_status = gr.Textbox(
label="وضعیت صدور",
interactive=False,
elem_classes=["rtl"]
)
advanced_export_file = gr.File(label="فایل صادر شده")
# === اتصال Event Handlers ===
# پردازش هوشمند
process_intelligent_btn.click(
fn=self.process_urls_intelligent,
inputs=[urls_input],
outputs=[intelligent_summary, intelligent_details],
show_progress=True
)
# پاک کردن کش
clear_cache_btn.click(
fn=lambda: self.cache_system.cleanup_expired() or "🗑️ کش پاکسازی شد",
outputs=[intelligent_summary]
)
# ساخت ایندکس هوشمند
build_advanced_index_btn.click(
fn=self.build_intelligent_search_index,
outputs=[advanced_index_status],
show_progress=True
)
# جستجوی هوشمند
advanced_search_btn.click(
fn=self.intelligent_semantic_search,
inputs=[search_query, search_limit],
outputs=[advanced_search_results_text, advanced_search_results_df]
)
# کرالر هوشمند
start_advanced_crawler_btn.click(
fn=self.start_intelligent_crawler,
inputs=[crawler_duration],
outputs=[advanced_crawler_status]
)
stop_advanced_crawler_btn.click(
fn=self.stop_intelligent_crawler,
outputs=[advanced_crawler_status]
)
crawler_status_btn.click(
fn=lambda: json.dumps(self.crawler_system.get_crawler_stats(), indent=2, ensure_ascii=False) if self.crawler_system else "کرالر یافت نشد",
outputs=[advanced_crawler_status]
)
# مدیریت سیستم
refresh_status_btn.click(
fn=self.get_comprehensive_system_status,
outputs=[comprehensive_status]
)
optimize_system_btn.click(
fn=lambda: (gc.collect(), self.cache_system.cleanup_expired(), "⚡ سیستم بهینه‌سازی شد")[2],
outputs=[advanced_crawler_status]
)
# صدور پیشرفته
advanced_export_btn.click(
fn=self.export_advanced_data,
outputs=[advanced_export_status, advanced_export_file]
)
# بارگذاری اولیه
interface.load(
fn=self.get_comprehensive_system_status,
outputs=[comprehensive_status]
)
return interface
# === اجرای اپلیکیشن ===
def main():
"""اجرای اپلیکیشن پیشرفته"""
logger.info("🚀 راه‌اندازی آرشیو هوشمند پیشرفته اسناد حقوقی ایران...")
try:
# ایجاد اپلیکیشن پیشرفته
app = AdvancedIranianLegalArchive()
if not app.system_ready:
logger.error("❌ سیستم آماده نشد")
# ایجاد رابط ساده در صورت خطا
simple_interface = gr.Interface(
fn=lambda: "سیستم در حال راه‌اندازی است...",
inputs=[],
outputs="text",
title="سیستم در حال بارگذاری"
)
simple_interface.launch()
return
# ایجاد رابط کاربری پیشرفته
interface = app.create_advanced_interface()
# راه‌اندازی با تنظیمات بهینه
interface.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
show_error=True,
enable_queue=True,
max_threads=6, # افزایش threads
show_api=False,
quiet=False
)
except Exception as e:
logger.error(f"❌ خطا در راه‌اندازی: {e}")
# fallback interface
try:
fallback = gr.Interface(
fn=lambda x: f"خطا در سیستم: {str(e)}",
inputs="text",
outputs="text",
title="خطا در سیستم"
)
fallback.launch()
except:
pass
if __name__ == "__main__":
main()