| |
| """ |
| 🔌 Fallback Integrator - اتصال سیستم fallback نهایی به پروژه موجود |
| Integration of Ultimate Fallback System with existing project |
| """ |
|
|
| import logging |
| from typing import Optional, Dict, Any, List |
| from datetime import datetime |
|
|
| try: |
| import httpx |
| HTTPX_AVAILABLE = True |
| except ImportError: |
| HTTPX_AVAILABLE = False |
| |
| try: |
| import aiohttp |
| AIOHTTP_AVAILABLE = True |
| except ImportError: |
| AIOHTTP_AVAILABLE = False |
|
|
| from backend.services.ultimate_fallback_system import ( |
| ultimate_fallback, |
| fetch_with_fallback, |
| Resource |
| ) |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| class FallbackIntegrator: |
| """ |
| کلاس ادغامکننده سیستم fallback با collectors موجود |
| Integrator class for fallback system with existing collectors |
| """ |
| |
| def __init__(self): |
| self.http_client = None |
| if HTTPX_AVAILABLE: |
| import httpx |
| self.http_client = httpx.AsyncClient(timeout=30.0) |
| elif AIOHTTP_AVAILABLE: |
| import aiohttp |
| self.session = None |
| |
| self.stats = { |
| 'total_requests': 0, |
| 'successful_requests': 0, |
| 'failed_requests': 0, |
| 'sources_used': {} |
| } |
| |
| logger.info(f"🔌 FallbackIntegrator initialized (httpx={HTTPX_AVAILABLE}, aiohttp={AIOHTTP_AVAILABLE})") |
| |
| async def fetch_market_data( |
| self, |
| symbol: str, |
| vs_currency: str = 'usd', |
| max_attempts: int = 10 |
| ) -> Optional[Dict]: |
| """ |
| دریافت دادههای بازار با fallback خودکار |
| |
| Args: |
| symbol: نماد ارز (bitcoin, ethereum, etc.) |
| vs_currency: ارز مبنا |
| max_attempts: حداکثر تلاش |
| |
| Returns: |
| دادههای بازار یا None |
| """ |
| self.stats['total_requests'] += 1 |
| |
| |
| resources = ultimate_fallback.get_fallback_chain('market_data', count=max_attempts) |
| |
| for resource in resources: |
| if not resource.is_available(): |
| continue |
| |
| try: |
| logger.info(f"🔄 Trying {resource.name} for {symbol}") |
| |
| |
| if 'coingecko' in resource.base_url: |
| url = f"{resource.base_url}/simple/price" |
| params = {'ids': symbol, 'vs_currencies': vs_currency} |
| elif 'binance' in resource.base_url: |
| |
| symbol_upper = symbol.upper() |
| if symbol_upper == 'BITCOIN': |
| symbol_upper = 'BTC' |
| elif symbol_upper == 'ETHEREUM': |
| symbol_upper = 'ETH' |
| |
| url = f"{resource.base_url}/ticker/price" |
| params = {'symbol': f"{symbol_upper}USDT"} |
| elif 'coinpaprika' in resource.base_url: |
| url = f"{resource.base_url}/tickers/{symbol}-{symbol}" |
| params = {} |
| elif 'coincap' in resource.base_url: |
| url = f"{resource.base_url}/assets/{symbol}" |
| params = {} |
| else: |
| |
| url = f"{resource.base_url}/price" |
| params = {'symbol': symbol, 'currency': vs_currency} |
| |
| |
| headers = {} |
| if resource.auth_type == "apiKeyHeader": |
| api_key = resource.get_api_key() |
| if api_key and resource.header_name: |
| headers[resource.header_name] = api_key |
| elif resource.auth_type == "apiKeyQuery": |
| api_key = resource.get_api_key() |
| if api_key and resource.param_name: |
| params[resource.param_name] = api_key |
| |
| |
| response = await self.http_client.get(url, params=params, headers=headers) |
| response.raise_for_status() |
| |
| data = response.json() |
| |
| |
| normalized = self._normalize_market_data(data, symbol, resource) |
| |
| |
| ultimate_fallback.mark_result(resource.id, 'market_data', True) |
| self.stats['successful_requests'] += 1 |
| self.stats['sources_used'][resource.name] = \ |
| self.stats['sources_used'].get(resource.name, 0) + 1 |
| |
| logger.info(f"✅ Success from {resource.name}: ${normalized.get('price', 'N/A')}") |
| return normalized |
| |
| except httpx.HTTPStatusError as e: |
| if e.response.status_code == 429: |
| logger.warning(f"⏳ {resource.name} rate limited") |
| ultimate_fallback.mark_result(resource.id, 'market_data', False, 'rate_limit') |
| else: |
| logger.warning(f"❌ {resource.name} HTTP error: {e.response.status_code}") |
| ultimate_fallback.mark_result(resource.id, 'market_data', False) |
| |
| except Exception as e: |
| logger.warning(f"❌ {resource.name} failed: {e}") |
| ultimate_fallback.mark_result(resource.id, 'market_data', False) |
| continue |
| |
| |
| self.stats['failed_requests'] += 1 |
| logger.error(f"❌ All {max_attempts} sources failed for {symbol}") |
| return None |
| |
| async def fetch_news( |
| self, |
| query: str = 'cryptocurrency', |
| limit: int = 10, |
| max_attempts: int = 10 |
| ) -> List[Dict]: |
| """ |
| دریافت اخبار با fallback خودکار |
| |
| Args: |
| query: کلمه کلیدی جستجو |
| limit: تعداد اخبار |
| max_attempts: حداکثر تلاش |
| |
| Returns: |
| لیست اخبار |
| """ |
| self.stats['total_requests'] += 1 |
| |
| resources = ultimate_fallback.get_fallback_chain('news', count=max_attempts) |
| |
| for resource in resources: |
| if not resource.is_available(): |
| continue |
| |
| try: |
| logger.info(f"🔄 Trying {resource.name} for news") |
| |
| |
| if 'cryptopanic' in resource.base_url: |
| url = f"{resource.base_url}/posts" |
| params = {'filter': 'hot'} |
| elif 'newsapi' in resource.base_url: |
| url = f"{resource.base_url}/everything" |
| params = {'q': query, 'pageSize': limit} |
| elif 'rss' in resource.name.lower(): |
| |
| url = resource.base_url |
| params = {} |
| else: |
| url = f"{resource.base_url}/news" |
| params = {'limit': limit} |
| |
| |
| headers = {} |
| if resource.auth_type in ["apiKeyHeader", "apiKeyHeaderOptional"]: |
| api_key = resource.get_api_key() |
| if api_key and resource.header_name: |
| headers[resource.header_name] = api_key |
| elif resource.auth_type in ["apiKeyQuery", "apiKeyQueryOptional"]: |
| api_key = resource.get_api_key() |
| if api_key and resource.param_name: |
| params[resource.param_name] = api_key |
| |
| response = await self.http_client.get(url, params=params, headers=headers) |
| response.raise_for_status() |
| |
| |
| if 'rss' in resource.name.lower() or 'xml' in response.headers.get('content-type', ''): |
| news_items = self._parse_rss_feed(response.text) |
| else: |
| data = response.json() |
| news_items = self._normalize_news_data(data, resource) |
| |
| |
| ultimate_fallback.mark_result(resource.id, 'news', True) |
| self.stats['successful_requests'] += 1 |
| self.stats['sources_used'][resource.name] = \ |
| self.stats['sources_used'].get(resource.name, 0) + 1 |
| |
| logger.info(f"✅ Got {len(news_items)} news from {resource.name}") |
| return news_items[:limit] |
| |
| except Exception as e: |
| logger.warning(f"❌ {resource.name} failed: {e}") |
| ultimate_fallback.mark_result(resource.id, 'news', False) |
| continue |
| |
| self.stats['failed_requests'] += 1 |
| logger.error(f"❌ All news sources failed") |
| return [] |
| |
| async def fetch_sentiment( |
| self, |
| max_attempts: int = 10 |
| ) -> Optional[Dict]: |
| """ |
| دریافت شاخص احساسات با fallback خودکار |
| |
| Args: |
| max_attempts: حداکثر تلاش |
| |
| Returns: |
| دادههای احساسات یا None |
| """ |
| self.stats['total_requests'] += 1 |
| |
| resources = ultimate_fallback.get_fallback_chain('sentiment', count=max_attempts) |
| |
| for resource in resources: |
| if not resource.is_available(): |
| continue |
| |
| try: |
| logger.info(f"🔄 Trying {resource.name} for sentiment") |
| |
| |
| if 'alternative.me' in resource.base_url: |
| url = f"{resource.base_url}/fng/" |
| params = {'limit': 1, 'format': 'json'} |
| elif 'cfgi' in resource.base_url: |
| url = f"{resource.base_url}/v1/fear-greed" |
| params = {} |
| else: |
| url = resource.base_url |
| params = {} |
| |
| response = await self.http_client.get(url, params=params) |
| response.raise_for_status() |
| |
| data = response.json() |
| normalized = self._normalize_sentiment_data(data, resource) |
| |
| ultimate_fallback.mark_result(resource.id, 'sentiment', True) |
| self.stats['successful_requests'] += 1 |
| |
| logger.info(f"✅ Sentiment from {resource.name}: {normalized.get('value', 'N/A')}") |
| return normalized |
| |
| except Exception as e: |
| logger.warning(f"❌ {resource.name} failed: {e}") |
| ultimate_fallback.mark_result(resource.id, 'sentiment', False) |
| continue |
| |
| self.stats['failed_requests'] += 1 |
| return None |
| |
| async def analyze_with_hf_models( |
| self, |
| text: str, |
| task: str = 'sentiment', |
| max_models: int = 5 |
| ) -> Dict: |
| """ |
| آنالیز متن با چند مدل HuggingFace |
| |
| Args: |
| text: متن برای آنالیز |
| task: نوع task (sentiment, generation, summarization) |
| max_models: حداکثر تعداد مدل |
| |
| Returns: |
| نتیجه آنالیز |
| """ |
| models = ultimate_fallback.get_fallback_chain('hf_models', count=max_models) |
| results = [] |
| |
| for model in models: |
| if not model.is_available(): |
| continue |
| |
| |
| if task == 'sentiment' and 'sentiment' not in model.name.lower(): |
| continue |
| if task == 'generation' and 'gpt' not in model.name.lower(): |
| continue |
| if task == 'summarization' and 'summar' not in model.name.lower(): |
| continue |
| |
| try: |
| logger.info(f"🔄 Analyzing with {model.name}") |
| |
| headers = {} |
| api_key = model.get_api_key() |
| if api_key: |
| headers['Authorization'] = f'Bearer {api_key}' |
| |
| payload = {'inputs': text} |
| |
| response = await self.http_client.post( |
| model.base_url, |
| json=payload, |
| headers=headers, |
| timeout=60.0 |
| ) |
| response.raise_for_status() |
| |
| result = response.json() |
| results.append({ |
| 'model': model.name, |
| 'result': result |
| }) |
| |
| ultimate_fallback.mark_result(model.id, 'hf_models', True) |
| |
| |
| if len(results) >= 3: |
| break |
| |
| except Exception as e: |
| logger.warning(f"❌ {model.name} failed: {e}") |
| ultimate_fallback.mark_result(model.id, 'hf_models', False) |
| continue |
| |
| |
| if results: |
| return self._ensemble_results(results, task) |
| else: |
| return {'status': 'error', 'message': 'All models failed'} |
| |
| def _normalize_market_data(self, data: Dict, symbol: str, resource: Resource) -> Dict: |
| """Normalize market data format""" |
| try: |
| |
| if symbol in data: |
| return { |
| 'symbol': symbol, |
| 'price': data[symbol].get('usd', 0), |
| 'source': resource.name, |
| 'timestamp': datetime.now().isoformat() |
| } |
| |
| |
| if 'price' in data: |
| return { |
| 'symbol': symbol, |
| 'price': float(data['price']), |
| 'source': resource.name, |
| 'timestamp': datetime.now().isoformat() |
| } |
| |
| |
| if 'quotes' in data: |
| return { |
| 'symbol': symbol, |
| 'price': data['quotes'].get('USD', {}).get('price', 0), |
| 'source': resource.name, |
| 'timestamp': datetime.now().isoformat() |
| } |
| |
| |
| return { |
| 'symbol': symbol, |
| 'price': data.get('price', data.get('last', 0)), |
| 'source': resource.name, |
| 'timestamp': datetime.now().isoformat(), |
| 'raw_data': data |
| } |
| except Exception as e: |
| logger.error(f"Error normalizing market data: {e}") |
| return {'symbol': symbol, 'price': 0, 'error': str(e)} |
| |
| def _normalize_news_data(self, data: Dict, resource: Resource) -> List[Dict]: |
| """Normalize news data format""" |
| try: |
| news_items = [] |
| |
| |
| if 'results' in data: |
| for item in data['results'][:10]: |
| news_items.append({ |
| 'title': item.get('title'), |
| 'url': item.get('url'), |
| 'source': item.get('source', {}).get('title', resource.name), |
| 'published': item.get('published_at') |
| }) |
| |
| |
| elif 'articles' in data: |
| for item in data['articles'][:10]: |
| news_items.append({ |
| 'title': item.get('title'), |
| 'url': item.get('url'), |
| 'source': item.get('source', {}).get('name', resource.name), |
| 'published': item.get('publishedAt') |
| }) |
| |
| |
| elif isinstance(data, list): |
| for item in data[:10]: |
| news_items.append({ |
| 'title': item.get('title', item.get('headline')), |
| 'url': item.get('url', item.get('link')), |
| 'source': resource.name, |
| 'published': item.get('published', item.get('date')) |
| }) |
| |
| return news_items |
| except Exception as e: |
| logger.error(f"Error normalizing news data: {e}") |
| return [] |
| |
| def _normalize_sentiment_data(self, data: Dict, resource: Resource) -> Dict: |
| """Normalize sentiment data format""" |
| try: |
| |
| if 'data' in data and isinstance(data['data'], list): |
| item = data['data'][0] |
| return { |
| 'value': int(item.get('value', 50)), |
| 'classification': item.get('value_classification', 'neutral'), |
| 'source': resource.name, |
| 'timestamp': item.get('timestamp') |
| } |
| |
| |
| return { |
| 'value': data.get('value', data.get('score', 50)), |
| 'classification': data.get('classification', 'neutral'), |
| 'source': resource.name, |
| 'timestamp': datetime.now().isoformat() |
| } |
| except Exception as e: |
| logger.error(f"Error normalizing sentiment data: {e}") |
| return {'value': 50, 'classification': 'neutral', 'error': str(e)} |
| |
| def _parse_rss_feed(self, xml_content: str) -> List[Dict]: |
| """Parse RSS feed (basic implementation)""" |
| |
| return [] |
| |
| def _ensemble_results(self, results: List[Dict], task: str) -> Dict: |
| """Combine results from multiple models""" |
| if not results: |
| return {'status': 'error', 'message': 'No results'} |
| |
| if task == 'sentiment': |
| |
| sentiments = [] |
| for r in results: |
| model_result = r['result'] |
| if isinstance(model_result, list) and len(model_result) > 0: |
| |
| label = model_result[0].get('label', 'neutral') |
| sentiments.append(label) |
| |
| |
| if sentiments: |
| most_common = max(set(sentiments), key=sentiments.count) |
| return { |
| 'sentiment': most_common, |
| 'models_used': len(results), |
| 'confidence': sentiments.count(most_common) / len(sentiments), |
| 'details': results |
| } |
| |
| return { |
| 'status': 'success', |
| 'models_used': len(results), |
| 'results': results |
| } |
| |
| def get_stats(self) -> Dict: |
| """دریافت آمار استفاده""" |
| success_rate = 0 |
| if self.stats['total_requests'] > 0: |
| success_rate = (self.stats['successful_requests'] / self.stats['total_requests']) * 100 |
| |
| return { |
| 'total_requests': self.stats['total_requests'], |
| 'successful_requests': self.stats['successful_requests'], |
| 'failed_requests': self.stats['failed_requests'], |
| 'success_rate': round(success_rate, 2), |
| 'sources_used': self.stats['sources_used'] |
| } |
| |
| async def close(self): |
| """بستن http client""" |
| if self.http_client and HTTPX_AVAILABLE: |
| await self.http_client.aclose() |
| elif AIOHTTP_AVAILABLE and hasattr(self, 'session') and self.session: |
| await self.session.close() |
|
|
|
|
| |
| |
| |
|
|
| fallback_integrator = FallbackIntegrator() |
|
|
|
|
| |
| |
| |
|
|
| async def test_integrator(): |
| """تست integrator""" |
| print("=" * 80) |
| print("🧪 Testing Fallback Integrator") |
| print("=" * 80) |
| print() |
| |
| |
| print("📊 Test 1: Market Data") |
| data = await fallback_integrator.fetch_market_data('bitcoin') |
| if data: |
| print(f"✅ Price: ${data.get('price', 'N/A')} from {data.get('source')}") |
| else: |
| print("❌ Failed to fetch market data") |
| print() |
| |
| |
| print("📰 Test 2: News") |
| news = await fallback_integrator.fetch_news('bitcoin', limit=5) |
| print(f"✅ Got {len(news)} news articles") |
| if news: |
| print(f" First: {news[0].get('title', 'N/A')}") |
| print() |
| |
| |
| print("💭 Test 3: Sentiment") |
| sentiment = await fallback_integrator.fetch_sentiment() |
| if sentiment: |
| print(f"✅ Sentiment: {sentiment.get('classification', 'N/A')} ({sentiment.get('value', 'N/A')})") |
| else: |
| print("❌ Failed to fetch sentiment") |
| print() |
| |
| |
| print("=" * 80) |
| print("📊 Statistics") |
| print("=" * 80) |
| stats = fallback_integrator.get_stats() |
| print(f"Total Requests: {stats['total_requests']}") |
| print(f"Successful: {stats['successful_requests']}") |
| print(f"Failed: {stats['failed_requests']}") |
| print(f"Success Rate: {stats['success_rate']}%") |
| print() |
| print("Sources Used:") |
| for source, count in stats['sources_used'].items(): |
| print(f" - {source}: {count}") |
| |
| await fallback_integrator.close() |
|
|
|
|
| if __name__ == "__main__": |
| import asyncio |
| asyncio.run(test_integrator()) |
|
|