Spaces:
Paused
Paused
""" | |
پلتفرم پیشرفته هوشمند اسناد حقوقی ایران - نسخه ارتقاء یافته | |
مجهز به مدلهای 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" | |
} | |
} | |
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 | |
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() |