entropy25 commited on
Commit
c1f4a3d
·
verified ·
1 Parent(s): f823c9f

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +255 -0
app.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+ from collections import Counter
4
+ from typing import List, Dict, Optional, Tuple
5
+ import logging
6
+
7
+ from config import config
8
+ from models import SentimentEngine, memory_cleanup, ThemeContext, handle_errors
9
+ from analysis import AdvancedAnalysisEngine
10
+ from visualization import PlotlyVisualizer
11
+ from data_utils import HistoryManager, DataHandler, TextProcessor
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class SentimentApp:
16
+ """Optimized multilingual sentiment analysis application"""
17
+
18
+ def __init__(self):
19
+ self.engine = SentimentEngine()
20
+ self.advanced_engine = AdvancedAnalysisEngine()
21
+ self.history = HistoryManager()
22
+ self.data_handler = DataHandler()
23
+
24
+ # Multi-language examples
25
+ self.examples = [
26
+ # Auto Detect
27
+ ["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."],
28
+
29
+ # English
30
+ ["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."],
31
+
32
+ # Chinese
33
+ ["这部电影节奏拖沓,剧情老套,完全没有让我产生任何共鸣,是一次失望的观影体验。演员的表演也显得做作,缺乏真实感。看到最后甚至有点不耐烦,整体表现乏善可陈。"],
34
+ # Spanish
35
+ ["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."],
36
+ # French
37
+ ["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."],
38
+ # German
39
+ ["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."],
40
+ # Swedish
41
+ ["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."]
42
+ ]
43
+
44
+ @handle_errors(default_return=("Please enter text", None, None))
45
+ def analyze_single(self, text: str, language: str, theme: str, clean_text: bool,
46
+ remove_punct: bool, remove_nums: bool):
47
+ """Optimized single text analysis"""
48
+ if not text.strip():
49
+ return "Please enter text", None, None
50
+
51
+ # Map display names to language codes
52
+ language_map = {v: k for k, v in config.SUPPORTED_LANGUAGES.items()}
53
+ language_code = language_map.get(language, 'auto')
54
+
55
+ preprocessing_options = {
56
+ 'clean_text': clean_text,
57
+ 'remove_punctuation': remove_punct,
58
+ 'remove_numbers': remove_nums
59
+ }
60
+
61
+ with memory_cleanup():
62
+ result = self.engine.analyze_single(text, language_code, preprocessing_options)
63
+
64
+ # Add to history
65
+ history_entry = {
66
+ 'text': text[:100] + '...' if len(text) > 100 else text,
67
+ 'full_text': text,
68
+ 'sentiment': result['sentiment'],
69
+ 'confidence': result['confidence'],
70
+ 'pos_prob': result.get('pos_prob', 0),
71
+ 'neg_prob': result.get('neg_prob', 0),
72
+ 'neu_prob': result.get('neu_prob', 0),
73
+ 'language': result['language'],
74
+ 'word_count': result['word_count'],
75
+ 'analysis_type': 'single'
76
+ }
77
+ self.history.add(history_entry)
78
+
79
+ # Create visualizations
80
+ theme_ctx = ThemeContext(theme)
81
+ gauge_fig = PlotlyVisualizer.create_sentiment_gauge(result, theme_ctx)
82
+ bars_fig = PlotlyVisualizer.create_probability_bars(result, theme_ctx)
83
+
84
+ # Create comprehensive result text
85
+ info_text = f"""
86
+ **Analysis Results:**
87
+ - **Sentiment:** {result['sentiment']} ({result['confidence']:.3f} confidence)
88
+ - **Language:** {result['language'].upper()}
89
+ - **Statistics:** {result['word_count']} words, {result['char_count']} characters
90
+ - **Probabilities:** Positive: {result.get('pos_prob', 0):.3f}, Negative: {result.get('neg_prob', 0):.3f}, Neutral: {result.get('neu_prob', 0):.3f}
91
+ """
92
+
93
+ return info_text, gauge_fig, bars_fig
94
+
95
+ @handle_errors(default_return=("Please enter texts", None, None, None))
96
+ def analyze_batch(self, batch_text: str, language: str, theme: str,
97
+ clean_text: bool, remove_punct: bool, remove_nums: bool):
98
+ """Enhanced batch analysis with parallel processing"""
99
+ if not batch_text.strip():
100
+ return "Please enter texts (one per line)", None, None, None
101
+
102
+ # Parse batch input
103
+ texts = TextProcessor.parse_batch_input(batch_text)
104
+ if len(texts) > config.BATCH_SIZE_LIMIT:
105
+ return f"Too many texts. Maximum {config.BATCH_SIZE_LIMIT} allowed.", None, None, None
106
+
107
+ if not texts:
108
+ return "No valid texts found", None, None, None
109
+
110
+ # Map display names to language codes
111
+ language_map = {v: k for k, v in config.SUPPORTED_LANGUAGES.items()}
112
+ language_code = language_map.get(language, 'auto')
113
+
114
+ preprocessing_options = {
115
+ 'clean_text': clean_text,
116
+ 'remove_punctuation': remove_punct,
117
+ 'remove_numbers': remove_nums
118
+ }
119
+
120
+ with memory_cleanup():
121
+ results = self.engine.analyze_batch(texts, language_code, preprocessing_options)
122
+
123
+ # Add to history
124
+ batch_entries = []
125
+ for result in results:
126
+ if 'error' not in result:
127
+ entry = {
128
+ 'text': result['text'],
129
+ 'full_text': result['full_text'],
130
+ 'sentiment': result['sentiment'],
131
+ 'confidence': result['confidence'],
132
+ 'pos_prob': result.get('pos_prob', 0),
133
+ 'neg_prob': result.get('neg_prob', 0),
134
+ 'neu_prob': result.get('neu_prob', 0),
135
+ 'language': result['language'],
136
+ 'word_count': result['word_count'],
137
+ 'analysis_type': 'batch',
138
+ 'batch_index': result['batch_index']
139
+ }
140
+ batch_entries.append(entry)
141
+
142
+ self.history.add_batch(batch_entries)
143
+
144
+ # Create visualizations
145
+ theme_ctx = ThemeContext(theme)
146
+ summary_fig = PlotlyVisualizer.create_batch_summary(results, theme_ctx)
147
+ confidence_fig = PlotlyVisualizer.create_confidence_distribution(results)
148
+
149
+ # Create results DataFrame
150
+ df_data = []
151
+ for result in results:
152
+ if 'error' in result:
153
+ df_data.append({
154
+ 'Index': result['batch_index'] + 1,
155
+ 'Text': result['text'],
156
+ 'Sentiment': 'Error',
157
+ 'Confidence': 0.0,
158
+ 'Language': 'Unknown',
159
+ 'Error': result['error']
160
+ })
161
+ else:
162
+ df_data.append({
163
+ 'Index': result['batch_index'] + 1,
164
+ 'Text': result['text'],
165
+ 'Sentiment': result['sentiment'],
166
+ 'Confidence': f"{result['confidence']:.3f}",
167
+ 'Language': result['language'].upper(),
168
+ 'Word_Count': result.get('word_count', 0)
169
+ })
170
+
171
+ df = pd.DataFrame(df_data)
172
+
173
+ # Create summary text
174
+ successful_results = [r for r in results if 'error' not in r]
175
+ error_count = len(results) - len(successful_results)
176
+
177
+ if successful_results:
178
+ sentiment_counts = Counter([r['sentiment'] for r in successful_results])
179
+ avg_confidence = np.mean([r['confidence'] for r in successful_results])
180
+ languages = Counter([r['language'] for r in successful_results])
181
+
182
+ summary_text = f"""
183
+ **Batch Analysis Summary:**
184
+ - **Total Texts:** {len(texts)}
185
+ - **Successful:** {len(successful_results)}
186
+ - **Errors:** {error_count}
187
+ - **Average Confidence:** {avg_confidence:.3f}
188
+ - **Sentiments:** {dict(sentiment_counts)}
189
+ - **Languages Detected:** {dict(languages)}
190
+ """
191
+ else:
192
+ summary_text = f"All {len(texts)} texts failed to analyze."
193
+
194
+ return summary_text, df, summary_fig, confidence_fig
195
+
196
+ # FIXED advanced analysis methods with sample size control
197
+ @handle_errors(default_return=("Please enter text", None))
198
+ def analyze_with_shap(self, text: str, language: str, num_samples: int = 100):
199
+ """Perform FIXED SHAP analysis with configurable samples"""
200
+ language_map = {v: k for k, v in config.SUPPORTED_LANGUAGES.items()}
201
+ language_code = language_map.get(language, 'auto')
202
+
203
+ return self.advanced_engine.analyze_with_shap(text, language_code, num_samples)
204
+
205
+ @handle_errors(default_return=("Please enter text", None))
206
+ def analyze_with_lime(self, text: str, language: str, num_samples: int = 100):
207
+ """Perform FIXED LIME analysis with configurable samples"""
208
+ language_map = {v: k for k, v in config.SUPPORTED_LANGUAGES.items()}
209
+ language_code = language_map.get(language, 'auto')
210
+
211
+ return self.advanced_engine.analyze_with_lime(text, language_code, num_samples)
212
+
213
+ @handle_errors(default_return=(None, "No history available"))
214
+ def plot_history(self, theme: str = 'default'):
215
+ """Plot comprehensive history analysis"""
216
+ history = self.history.get_all()
217
+ if len(history) < 2:
218
+ return None, f"Need at least 2 analyses for trends. Current: {len(history)}"
219
+
220
+ theme_ctx = ThemeContext(theme)
221
+
222
+ with memory_cleanup():
223
+ fig = PlotlyVisualizer.create_history_dashboard(history, theme_ctx)
224
+ stats = self.history.get_stats()
225
+
226
+ stats_text = f"""
227
+ **History Statistics:**
228
+ - **Total Analyses:** {stats.get('total_analyses', 0)}
229
+ - **Positive:** {stats.get('positive_count', 0)}
230
+ - **Negative:** {stats.get('negative_count', 0)}
231
+ - **Neutral:** {stats.get('neutral_count', 0)}
232
+ - **Average Confidence:** {stats.get('avg_confidence', 0):.3f}
233
+ - **Languages:** {stats.get('languages_detected', 0)}
234
+ - **Most Common Language:** {stats.get('most_common_language', 'N/A').upper()}
235
+ """
236
+
237
+ return fig, stats_text
238
+
239
+ @handle_errors(default_return=("No data available",))
240
+ def get_history_status(self):
241
+ """Get current history status"""
242
+ stats = self.history.get_stats()
243
+ if not stats:
244
+ return "No analyses performed yet"
245
+
246
+ return f"""
247
+ **Current Status:**
248
+ - **Total Analyses:** {stats['total_analyses']}
249
+ - **Recent Sentiment Distribution:**
250
+ * Positive: {stats['positive_count']}
251
+ * Negative: {stats['negative_count']}
252
+ * Neutral: {stats['neutral_count']}
253
+ - **Average Confidence:** {stats['avg_confidence']:.3f}
254
+ - **Languages Detected:** {stats['languages_detected']}
255
+ """