import numpy as np import pandas as pd from collections import Counter from typing import List, Dict, Optional, Tuple import logging from config import config from models import SentimentEngine, memory_cleanup, ThemeContext, handle_errors from analysis import AdvancedAnalysisEngine from visualization import PlotlyVisualizer from data_utils import HistoryManager, DataHandler, TextProcessor logger = logging.getLogger(__name__) class SentimentApp: """Optimized multilingual sentiment analysis application""" def __init__(self): self.engine = SentimentEngine() self.advanced_engine = AdvancedAnalysisEngine() self.history = HistoryManager() self.data_handler = DataHandler() # Multi-language examples self.examples = [ # Auto Detect ["The film had its moments, but overall it felt a bit too long and lacked emotional depth. Some scenes were visually impressive, yet they failed to connect emotionally. By the end, I found myself disengaged and unsatisfied."], # English ["I was completely blown away by the movie — the performances were raw and powerful, and the story stayed with me long after the credits rolled. Every scene felt purposeful, and the emotional arc was handled with incredible nuance. It's the kind of film that makes you reflect deeply on your own life."], # Chinese ["这部电影节奏拖沓,剧情老套,完全没有让我产生任何共鸣,是一次失望的观影体验。演员的表演也显得做作,缺乏真实感。看到最后甚至有点不耐烦,整体表现乏善可陈。"], # Spanish ["Una obra maestra del cine contemporáneo, con actuaciones sobresalientes, un guion bien escrito y una dirección impecable. Cada plano parecía cuidadosamente pensado, y la historia avanzaba con una intensidad emocional que mantenía al espectador cautivado. Definitivamente una película que vale la pena volver a ver."], # French ["Je m'attendais à beaucoup mieux. Le scénario était confus, les dialogues ennuyeux, et je me suis presque endormi au milieu du film. Même la mise en scène, habituellement un point fort, manquait cruellement d'inspiration cette fois-ci."], # German ["Der Film war ein emotionales Erlebnis mit großartigen Bildern, einem mitreißenden Soundtrack und einer Geschichte, die zum Nachdenken anregt. Besonders beeindruckend war die schauspielerische Leistung der Hauptdarsteller, die eine tiefe Menschlichkeit vermittelten. Es ist ein Film, der lange nachwirkt."], # Swedish ["Filmen var en besvikelse – tråkig handling, överdrivet skådespeleri och ett slut som inte gav något avslut alls. Den kändes forcerad och saknade en tydlig röd tråd. Jag gick från biografen med en känsla av tomhet och frustration."] ] @handle_errors(default_return=("Please enter text", None, None)) def analyze_single(self, text: str, language: str, theme: str, clean_text: bool, remove_punct: bool, remove_nums: bool): """Optimized single text analysis""" if not text.strip(): return "Please enter text", None, None # Map display names to language codes language_map = {v: k for k, v in config.SUPPORTED_LANGUAGES.items()} language_code = language_map.get(language, 'auto') preprocessing_options = { 'clean_text': clean_text, 'remove_punctuation': remove_punct, 'remove_numbers': remove_nums } with memory_cleanup(): result = self.engine.analyze_single(text, language_code, preprocessing_options) # Add to history history_entry = { 'text': text[:100] + '...' if len(text) > 100 else text, 'full_text': text, 'sentiment': result['sentiment'], 'confidence': result['confidence'], 'pos_prob': result.get('pos_prob', 0), 'neg_prob': result.get('neg_prob', 0), 'neu_prob': result.get('neu_prob', 0), 'language': result['language'], 'word_count': result['word_count'], 'analysis_type': 'single' } self.history.add(history_entry) # Create visualizations theme_ctx = ThemeContext(theme) gauge_fig = PlotlyVisualizer.create_sentiment_gauge(result, theme_ctx) bars_fig = PlotlyVisualizer.create_probability_bars(result, theme_ctx) # Create comprehensive result text info_text = f""" **Analysis Results:** - **Sentiment:** {result['sentiment']} ({result['confidence']:.3f} confidence) - **Language:** {result['language'].upper()} - **Statistics:** {result['word_count']} words, {result['char_count']} characters - **Probabilities:** Positive: {result.get('pos_prob', 0):.3f}, Negative: {result.get('neg_prob', 0):.3f}, Neutral: {result.get('neu_prob', 0):.3f} """ return info_text, gauge_fig, bars_fig @handle_errors(default_return=("Please enter texts", None, None, None)) def analyze_batch(self, batch_text: str, language: str, theme: str, clean_text: bool, remove_punct: bool, remove_nums: bool): """Enhanced batch analysis with parallel processing""" if not batch_text.strip(): return "Please enter texts (one per line)", None, None, None # Parse batch input texts = TextProcessor.parse_batch_input(batch_text) if len(texts) > config.BATCH_SIZE_LIMIT: return f"Too many texts. Maximum {config.BATCH_SIZE_LIMIT} allowed.", None, None, None if not texts: return "No valid texts found", None, None, None # Map display names to language codes language_map = {v: k for k, v in config.SUPPORTED_LANGUAGES.items()} language_code = language_map.get(language, 'auto') preprocessing_options = { 'clean_text': clean_text, 'remove_punctuation': remove_punct, 'remove_numbers': remove_nums } with memory_cleanup(): results = self.engine.analyze_batch(texts, language_code, preprocessing_options) # Add to history batch_entries = [] for result in results: if 'error' not in result: entry = { 'text': result['text'], 'full_text': result['full_text'], 'sentiment': result['sentiment'], 'confidence': result['confidence'], 'pos_prob': result.get('pos_prob', 0), 'neg_prob': result.get('neg_prob', 0), 'neu_prob': result.get('neu_prob', 0), 'language': result['language'], 'word_count': result['word_count'], 'analysis_type': 'batch', 'batch_index': result['batch_index'] } batch_entries.append(entry) self.history.add_batch(batch_entries) # Create visualizations theme_ctx = ThemeContext(theme) summary_fig = PlotlyVisualizer.create_batch_summary(results, theme_ctx) confidence_fig = PlotlyVisualizer.create_confidence_distribution(results) # Create results DataFrame df_data = [] for result in results: if 'error' in result: df_data.append({ 'Index': result['batch_index'] + 1, 'Text': result['text'], 'Sentiment': 'Error', 'Confidence': 0.0, 'Language': 'Unknown', 'Error': result['error'] }) else: df_data.append({ 'Index': result['batch_index'] + 1, 'Text': result['text'], 'Sentiment': result['sentiment'], 'Confidence': f"{result['confidence']:.3f}", 'Language': result['language'].upper(), 'Word_Count': result.get('word_count', 0) }) df = pd.DataFrame(df_data) # Create summary text successful_results = [r for r in results if 'error' not in r] error_count = len(results) - len(successful_results) if successful_results: sentiment_counts = Counter([r['sentiment'] for r in successful_results]) avg_confidence = np.mean([r['confidence'] for r in successful_results]) languages = Counter([r['language'] for r in successful_results]) summary_text = f""" **Batch Analysis Summary:** - **Total Texts:** {len(texts)} - **Successful:** {len(successful_results)} - **Errors:** {error_count} - **Average Confidence:** {avg_confidence:.3f} - **Sentiments:** {dict(sentiment_counts)} - **Languages Detected:** {dict(languages)} """ else: summary_text = f"All {len(texts)} texts failed to analyze." return summary_text, df, summary_fig, confidence_fig # FIXED advanced analysis methods with sample size control @handle_errors(default_return=("Please enter text", None)) def analyze_with_shap(self, text: str, language: str, num_samples: int = 100): """Perform FIXED SHAP analysis with configurable samples""" language_map = {v: k for k, v in config.SUPPORTED_LANGUAGES.items()} language_code = language_map.get(language, 'auto') return self.advanced_engine.analyze_with_shap(text, language_code, num_samples) @handle_errors(default_return=("Please enter text", None)) def analyze_with_lime(self, text: str, language: str, num_samples: int = 100): """Perform FIXED LIME analysis with configurable samples""" language_map = {v: k for k, v in config.SUPPORTED_LANGUAGES.items()} language_code = language_map.get(language, 'auto') return self.advanced_engine.analyze_with_lime(text, language_code, num_samples) @handle_errors(default_return=(None, "No history available")) def plot_history(self, theme: str = 'default'): """Plot comprehensive history analysis""" history = self.history.get_all() if len(history) < 2: return None, f"Need at least 2 analyses for trends. Current: {len(history)}" theme_ctx = ThemeContext(theme) with memory_cleanup(): fig = PlotlyVisualizer.create_history_dashboard(history, theme_ctx) stats = self.history.get_stats() stats_text = f""" **History Statistics:** - **Total Analyses:** {stats.get('total_analyses', 0)} - **Positive:** {stats.get('positive_count', 0)} - **Negative:** {stats.get('negative_count', 0)} - **Neutral:** {stats.get('neutral_count', 0)} - **Average Confidence:** {stats.get('avg_confidence', 0):.3f} - **Languages:** {stats.get('languages_detected', 0)} - **Most Common Language:** {stats.get('most_common_language', 'N/A').upper()} """ return fig, stats_text @handle_errors(default_return=("No data available",)) def get_history_status(self): """Get current history status""" stats = self.history.get_stats() if not stats: return "No analyses performed yet" return f""" **Current Status:** - **Total Analyses:** {stats['total_analyses']} - **Recent Sentiment Distribution:** * Positive: {stats['positive_count']} * Negative: {stats['negative_count']} * Neutral: {stats['neutral_count']} - **Average Confidence:** {stats['avg_confidence']:.3f} - **Languages Detected:** {stats['languages_detected']} """