Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,435 +1,413 @@
|
|
1 |
"""Streamlit frontend for the News Summarization application."""
|
2 |
|
3 |
import streamlit as st
|
4 |
-
|
5 |
import pandas as pd
|
6 |
import json
|
7 |
-
|
8 |
import os
|
9 |
import plotly.express as px
|
10 |
import altair as alt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
|
|
12 |
st.set_page_config(
|
13 |
page_title="News Summarization App",
|
14 |
page_icon="📰",
|
15 |
layout="wide"
|
16 |
)
|
17 |
|
18 |
-
|
19 |
-
|
|
|
20 |
try:
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
)
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
except Exception as e:
|
41 |
-
st.error(f"Error
|
42 |
-
return {"articles": [], "comparative_sentiment_score": {}, "final_sentiment_analysis": "", "
|
43 |
|
44 |
def main():
|
45 |
st.title("📰 News Summarization and Analysis")
|
46 |
-
|
47 |
-
# Sidebar
|
48 |
-
st.sidebar.header("Settings")
|
49 |
-
|
50 |
-
# Replace dropdown with text input
|
51 |
-
company = st.sidebar.text_input(
|
52 |
-
"Enter Company Name",
|
53 |
-
placeholder="e.g., Tesla, Apple, Microsoft, or any other company",
|
54 |
-
help="Enter the name of any company you want to analyze"
|
55 |
-
)
|
56 |
-
|
57 |
-
if st.sidebar.button("Analyze") and company:
|
58 |
-
if len(company.strip()) < 2:
|
59 |
st.sidebar.error("Please enter a valid company name (at least 2 characters)")
|
60 |
else:
|
61 |
with st.spinner("Analyzing news articles..."):
|
62 |
-
|
63 |
-
|
64 |
-
|
|
|
|
|
|
|
|
|
|
|
65 |
# Display Articles
|
66 |
st.header("📑 News Articles")
|
67 |
-
for idx, article in enumerate(
|
68 |
with st.expander(f"Article {idx}: {article['title']}"):
|
69 |
-
|
70 |
-
if "
|
71 |
-
st.
|
72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
|
74 |
# Enhanced sentiment display
|
75 |
if "sentiment" in article:
|
76 |
sentiment_col1, sentiment_col2 = st.columns(2)
|
77 |
with sentiment_col1:
|
78 |
-
st.
|
79 |
-
st.write("
|
|
|
|
|
80 |
|
81 |
with sentiment_col2:
|
82 |
# Display fine-grained sentiment if available
|
83 |
if "fine_grained_sentiment" in article and article["fine_grained_sentiment"]:
|
|
|
84 |
fine_grained = article["fine_grained_sentiment"]
|
85 |
if "category" in fine_grained:
|
86 |
-
st.write("
|
87 |
if "confidence" in fine_grained:
|
88 |
-
st.write("
|
89 |
|
90 |
# Display sentiment indices if available
|
91 |
if "sentiment_indices" in article and article["sentiment_indices"]:
|
92 |
-
st.markdown("**Sentiment Indices:**")
|
93 |
-
indices = article["sentiment_indices"]
|
94 |
-
|
95 |
-
# Create columns for displaying indices
|
96 |
-
idx_cols = st.columns(3)
|
97 |
-
|
98 |
-
# Display positivity and negativity in first column
|
99 |
-
with idx_cols[0]:
|
100 |
-
if "positivity_index" in indices:
|
101 |
-
st.markdown(f"**Positivity:** {indices['positivity_index']:.2f}")
|
102 |
-
if "negativity_index" in indices:
|
103 |
-
st.markdown(f"**Negativity:** {indices['negativity_index']:.2f}")
|
104 |
-
|
105 |
-
# Display emotional intensity and controversy in second column
|
106 |
-
with idx_cols[1]:
|
107 |
-
if "emotional_intensity" in indices:
|
108 |
-
st.markdown(f"**Emotional Intensity:** {indices['emotional_intensity']:.2f}")
|
109 |
-
if "controversy_score" in indices:
|
110 |
-
st.markdown(f"**Controversy:** {indices['controversy_score']:.2f}")
|
111 |
-
|
112 |
-
# Display confidence and ESG in third column
|
113 |
-
with idx_cols[2]:
|
114 |
-
if "confidence_score" in indices:
|
115 |
-
st.markdown(f"**Confidence:** {indices['confidence_score']:.2f}")
|
116 |
-
if "esg_relevance" in indices:
|
117 |
-
st.markdown(f"**ESG Relevance:** {indices['esg_relevance']:.2f}")
|
118 |
-
|
119 |
-
# Display entities if available
|
120 |
-
if "entities" in article and article["entities"]:
|
121 |
-
st.markdown("**Named Entities:**")
|
122 |
-
entities = article["entities"]
|
123 |
-
|
124 |
-
# Organizations
|
125 |
-
if "ORG" in entities and entities["ORG"]:
|
126 |
-
st.write("**Organizations:**", ", ".join(entities["ORG"]))
|
127 |
-
|
128 |
-
# People
|
129 |
-
if "PERSON" in entities and entities["PERSON"]:
|
130 |
-
st.write("**People:**", ", ".join(entities["PERSON"]))
|
131 |
-
|
132 |
-
# Locations
|
133 |
-
if "GPE" in entities and entities["GPE"]:
|
134 |
-
st.write("**Locations:**", ", ".join(entities["GPE"]))
|
135 |
-
|
136 |
-
# Money
|
137 |
-
if "MONEY" in entities and entities["MONEY"]:
|
138 |
-
st.write("**Financial Values:**", ", ".join(entities["MONEY"]))
|
139 |
-
|
140 |
-
# Display sentiment targets if available
|
141 |
-
if "sentiment_targets" in article and article["sentiment_targets"]:
|
142 |
-
st.markdown("**Sentiment Targets:**")
|
143 |
-
targets = article["sentiment_targets"]
|
144 |
-
for target in targets:
|
145 |
-
st.markdown(f"**{target['entity']}** ({target['type']}): {target['sentiment']} ({target['confidence']*100:.1f}%)")
|
146 |
st.markdown(f"> {target['context']}")
|
147 |
st.markdown("---")
|
148 |
|
|
|
149 |
if "url" in article:
|
150 |
-
st.
|
151 |
|
152 |
# Display Comparative Analysis
|
153 |
st.header("📊 Comparative Analysis")
|
154 |
-
analysis =
|
155 |
|
156 |
# Sentiment Distribution
|
157 |
if "sentiment_distribution" in analysis:
|
158 |
st.subheader("Sentiment Distribution")
|
159 |
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
sentiment_dist = analysis["sentiment_distribution"]
|
165 |
|
166 |
-
|
167 |
try:
|
168 |
# Extract basic sentiment data
|
169 |
if isinstance(sentiment_dist, dict):
|
170 |
-
if "basic" in sentiment_dist and isinstance(sentiment_dist["basic"], dict):
|
171 |
-
basic_dist = sentiment_dist["basic"]
|
172 |
-
elif any(k in sentiment_dist for k in ['positive', 'negative', 'neutral']):
|
173 |
-
basic_dist = {k: v for k, v in sentiment_dist.items()
|
174 |
-
if k in ['positive', 'negative', 'neutral']}
|
175 |
-
else:
|
176 |
-
basic_dist = {'positive': 0, 'negative': 0, 'neutral': 1}
|
177 |
-
else:
|
178 |
-
basic_dist = {'positive': 0, 'negative': 0, 'neutral': 1}
|
179 |
-
|
180 |
-
# Calculate percentages
|
181 |
-
total_articles = sum(basic_dist.values())
|
182 |
-
if total_articles > 0:
|
183 |
-
percentages = {
|
184 |
-
k: (v / total_articles) * 100
|
185 |
-
for k, v in basic_dist.items()
|
186 |
-
}
|
187 |
else:
|
188 |
percentages = {k: 0 for k in basic_dist}
|
189 |
|
190 |
-
# Display as
|
191 |
st.write("**Sentiment Distribution:**")
|
192 |
|
193 |
col1, col2, col3 = st.columns(3)
|
194 |
-
with col1:
|
195 |
-
st.metric(
|
196 |
-
"Positive",
|
197 |
-
basic_dist.get('positive', 0),
|
198 |
-
f"{percentages.get('positive', 0):.1f}%"
|
199 |
-
)
|
200 |
-
with col2:
|
201 |
-
st.metric(
|
202 |
-
"Negative",
|
203 |
-
basic_dist.get('negative', 0),
|
204 |
-
f"{percentages.get('negative', 0):.1f}%"
|
205 |
-
)
|
206 |
-
with col3:
|
207 |
-
st.metric(
|
208 |
-
"Neutral",
|
209 |
-
basic_dist.get('neutral', 0),
|
210 |
f"{percentages.get('neutral', 0):.1f}%"
|
211 |
)
|
212 |
|
213 |
-
# Create
|
214 |
-
|
215 |
-
|
216 |
chart_data = pd.DataFrame({
|
217 |
'Sentiment': ['Positive', 'Negative', 'Neutral'],
|
218 |
'Count': [
|
219 |
-
basic_dist.get('positive', 0),
|
220 |
basic_dist.get('negative', 0),
|
221 |
basic_dist.get('neutral', 0)
|
222 |
],
|
223 |
-
'Percentage': [
|
224 |
-
f"{percentages.get('positive', 0):.1f}%",
|
225 |
-
f"{percentages.get('negative', 0):.1f}%",
|
226 |
-
f"{percentages.get('neutral', 0):.1f}%"
|
227 |
]
|
228 |
})
|
229 |
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
chart = alt.Chart(chart_data).mark_bar().encode(
|
236 |
-
y='Sentiment',
|
237 |
-
x='Count',
|
238 |
color=alt.Color('Sentiment', scale=alt.Scale(
|
239 |
domain=['Positive', 'Negative', 'Neutral'],
|
240 |
range=['green', 'red', 'gray']
|
241 |
)),
|
242 |
-
tooltip=['Sentiment', 'Count', 'Percentage']
|
243 |
).properties(
|
244 |
width=600,
|
245 |
height=300
|
246 |
)
|
247 |
|
248 |
-
|
249 |
text = chart.mark_text(
|
250 |
align='left',
|
251 |
baseline='middle',
|
252 |
-
dx=3
|
253 |
).encode(
|
254 |
text='Percentage'
|
255 |
)
|
256 |
|
257 |
-
|
258 |
chart_with_text = (chart + text)
|
259 |
-
|
260 |
st.altair_chart(chart_with_text, use_container_width=True)
|
261 |
|
262 |
except Exception as e:
|
263 |
st.error(f"Error creating visualization: {str(e)}")
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
|
277 |
# Display sentiment indices if available
|
278 |
if "sentiment_indices" in analysis and analysis["sentiment_indices"]:
|
279 |
st.subheader("Sentiment Indices")
|
280 |
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
indices = analysis["sentiment_indices"]
|
287 |
|
288 |
-
|
289 |
try:
|
290 |
if isinstance(indices, dict):
|
291 |
-
# Display as
|
292 |
cols = st.columns(3)
|
293 |
|
294 |
-
|
295 |
display_names = {
|
296 |
"positivity_index": "Positivity",
|
297 |
"negativity_index": "Negativity",
|
298 |
-
"emotional_intensity": "Emotional Intensity",
|
299 |
-
"controversy_score": "Controversy",
|
300 |
-
"confidence_score": "Confidence",
|
301 |
"esg_relevance": "ESG Relevance"
|
302 |
}
|
303 |
|
304 |
-
|
305 |
for i, (key, value) in enumerate(indices.items()):
|
306 |
if isinstance(value, (int, float)):
|
307 |
with cols[i % 3]:
|
308 |
display_name = display_names.get(key, key.replace("_", " ").title())
|
309 |
st.metric(display_name, f"{value:.2f}")
|
310 |
|
311 |
-
# Create
|
312 |
-
|
313 |
-
|
314 |
chart_data = pd.DataFrame({
|
315 |
'Index': [display_names.get(k, k.replace("_", " ").title()) for k in indices.keys()],
|
316 |
'Value': [v if isinstance(v, (int, float)) else 0 for v in indices.values()]
|
317 |
})
|
318 |
|
319 |
-
|
320 |
chart = alt.Chart(chart_data).mark_bar().encode(
|
321 |
x='Value',
|
322 |
y='Index',
|
323 |
-
color=alt.Color('Index')
|
324 |
-
).properties(
|
325 |
-
width=600,
|
326 |
-
height=300
|
327 |
-
)
|
328 |
-
|
329 |
-
st.altair_chart(chart, use_container_width=True)
|
330 |
-
|
331 |
-
# Add descriptions
|
332 |
-
with st.expander("Sentiment Indices Explained"):
|
333 |
-
st.markdown("""
|
334 |
-
- **Positivity**: Measures the positive sentiment in the articles (0-1)
|
335 |
-
- **Negativity**: Measures the negative sentiment in the articles (0-1)
|
336 |
-
- **Emotional Intensity**: Measures the overall emotional content (0-1)
|
337 |
-
- **Controversy**: High when both positive and negative sentiments are strong (0-1)
|
338 |
- **Confidence**: Confidence in the sentiment analysis (0-1)
|
339 |
- **ESG Relevance**: Relevance to Environmental, Social, and Governance topics (0-1)
|
340 |
""")
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
except Exception as e:
|
345 |
st.error(f"Error creating indices visualization: {str(e)}")
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
# Source Distribution
|
355 |
-
if "source_distribution" in analysis:
|
356 |
-
st.subheader("Source Distribution")
|
357 |
-
source_df = pd.DataFrame.from_dict(
|
358 |
-
analysis["source_distribution"],
|
359 |
-
orient='index',
|
360 |
-
columns=['Count']
|
361 |
-
)
|
362 |
-
st.bar_chart(source_df)
|
363 |
|
364 |
-
#
|
365 |
-
|
366 |
-
|
367 |
-
|
|
|
|
|
|
|
|
|
|
|
368 |
|
369 |
-
#
|
370 |
-
if "
|
371 |
-
st.
|
372 |
-
|
373 |
-
if
|
374 |
-
|
375 |
-
|
|
|
|
|
|
|
|
|
|
|
376 |
else:
|
377 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
378 |
|
379 |
-
# Display
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
|
384 |
-
#
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
if "agreement" in ensemble:
|
402 |
-
st.metric("Model Agreement", f"{ensemble['agreement']*100:.1f}%")
|
403 |
-
|
404 |
-
# Individual model results
|
405 |
-
if "models" in ensemble:
|
406 |
-
st.subheader("Individual Model Results")
|
407 |
-
models_data = []
|
408 |
-
for model_name, model_info in ensemble["models"].items():
|
409 |
-
models_data.append({
|
410 |
-
"Model": model_name,
|
411 |
-
"Sentiment": model_info.get("sentiment", "N/A"),
|
412 |
-
"Confidence": f"{model_info.get('confidence', 0)*100:.1f}%"
|
413 |
-
})
|
414 |
-
|
415 |
-
if models_data:
|
416 |
-
st.table(pd.DataFrame(models_data))
|
417 |
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
424 |
|
425 |
# Total Articles
|
426 |
if "total_articles" in analysis:
|
427 |
st.sidebar.info(f"Found {analysis['total_articles']} articles")
|
|
|
|
|
|
|
|
|
428 |
|
429 |
# Add a disclaimer
|
430 |
-
st.sidebar.markdown("---")
|
431 |
-
st.sidebar.markdown("### About")
|
432 |
-
st.sidebar.write("This app analyzes news articles and provides sentiment analysis for any company.")
|
433 |
-
|
434 |
-
if __name__ == "__main__":
|
435 |
-
main()
|
|
|
1 |
"""Streamlit frontend for the News Summarization application."""
|
2 |
|
3 |
import streamlit as st
|
4 |
+
|
5 |
import pandas as pd
|
6 |
import json
|
7 |
+
|
8 |
import os
|
9 |
import plotly.express as px
|
10 |
import altair as alt
|
11 |
+
from utils import (
|
12 |
+
analyze_company_data,
|
13 |
+
TextToSpeechConverter,
|
14 |
+
get_translator,
|
15 |
+
NewsExtractor,
|
16 |
+
SentimentAnalyzer,
|
17 |
+
TextSummarizer
|
18 |
+
)
|
19 |
|
20 |
+
# Set page config
|
21 |
st.set_page_config(
|
22 |
page_title="News Summarization App",
|
23 |
page_icon="📰",
|
24 |
layout="wide"
|
25 |
)
|
26 |
|
27 |
+
# Show loading message
|
28 |
+
with st.spinner("Initializing the application... Please wait while we load the models."):
|
29 |
+
# Initialize components
|
30 |
try:
|
31 |
+
st.success("Application initialized successfully!")
|
32 |
+
except Exception as e:
|
33 |
+
st.error(f"Error initializing application: {str(e)}")
|
34 |
+
st.info("Please try refreshing the page.")
|
35 |
+
|
36 |
+
def process_company(company_name):
|
37 |
+
"""Process company data directly."""
|
38 |
+
try:
|
39 |
+
# Call the analysis function directly from utils
|
40 |
+
data = analyze_company_data(company_name)
|
41 |
+
|
42 |
+
# Generate Hindi audio from final analysis
|
43 |
+
if data.get("final_sentiment_analysis"):
|
44 |
+
# Get the translator
|
45 |
+
translator = get_translator()
|
46 |
+
if translator:
|
47 |
+
try:
|
48 |
+
# Create a more detailed Hindi explanation
|
49 |
+
sentiment_explanation = f"""
|
50 |
+
{company_name} के समाचारों का विश्लेषण:
|
51 |
+
|
52 |
+
समग्र भावना: {data['final_sentiment_analysis']}
|
53 |
+
|
54 |
+
भावनात्मक विश्लेषण:
|
55 |
+
- सकारात्मक भावना: {data.get('comparative_sentiment_score', {}).get('sentiment_indices', {}).get('positivity_index', 0):.2f}
|
56 |
+
- नकारात्मक भावना: {data.get('comparative_sentiment_score', {}).get('sentiment_indices', {}).get('negativity_index', 0):.2f}
|
57 |
+
- भावनात्मक तीव्रता: {data.get('comparative_sentiment_score', {}).get('sentiment_indices', {}).get('emotional_intensity', 0):.2f}
|
58 |
+
|
59 |
+
विश्वसनीयता स्कोर: {data.get('comparative_sentiment_score', {}).get('sentiment_indices', {}).get('confidence_score', 0):.2f}
|
60 |
+
"""
|
61 |
+
|
62 |
+
# Generate Hindi audio
|
63 |
+
tts_converter = TextToSpeechConverter()
|
64 |
+
audio_path = tts_converter.generate_audio(
|
65 |
+
sentiment_explanation,
|
66 |
+
f'{company_name}_summary'
|
67 |
+
)
|
68 |
+
data['audio_path'] = audio_path
|
69 |
+
except Exception as e:
|
70 |
+
print(f"Error generating Hindi audio: {str(e)}")
|
71 |
+
data['audio_path'] = None
|
72 |
+
else:
|
73 |
+
print("Translator not available")
|
74 |
+
data['audio_path'] = None
|
75 |
|
76 |
+
return data
|
77 |
+
|
78 |
+
|
79 |
+
|
80 |
+
|
81 |
+
|
82 |
+
|
83 |
+
|
84 |
+
|
85 |
except Exception as e:
|
86 |
+
st.error(f"Error processing company: {str(e)}")
|
87 |
+
return {"articles": [], "comparative_sentiment_score": {}, "final_sentiment_analysis": "", "audio_path": None}
|
88 |
|
89 |
def main():
|
90 |
st.title("📰 News Summarization and Analysis")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
st.sidebar.error("Please enter a valid company name (at least 2 characters)")
|
92 |
else:
|
93 |
with st.spinner("Analyzing news articles..."):
|
94 |
+
try:
|
95 |
+
# Process company data
|
96 |
+
data = process_company(company)
|
97 |
+
|
98 |
+
if not data["articles"]:
|
99 |
+
st.error("No articles found for analysis.")
|
100 |
+
return
|
101 |
+
|
102 |
# Display Articles
|
103 |
st.header("📑 News Articles")
|
104 |
+
for idx, article in enumerate(data["articles"], 1):
|
105 |
with st.expander(f"Article {idx}: {article['title']}"):
|
106 |
+
# Display content with proper formatting
|
107 |
+
if article.get("content"):
|
108 |
+
st.markdown("**Content:**")
|
109 |
+
st.write(article["content"])
|
110 |
+
else:
|
111 |
+
st.warning("No content available for this article")
|
112 |
+
|
113 |
+
# Display summary if available
|
114 |
+
if article.get("summary"):
|
115 |
+
st.markdown("**Summary:**")
|
116 |
+
st.write(article["summary"])
|
117 |
+
|
118 |
+
# Display source
|
119 |
+
if article.get("source"):
|
120 |
+
st.markdown("**Source:**")
|
121 |
+
st.write(article["source"])
|
122 |
|
123 |
# Enhanced sentiment display
|
124 |
if "sentiment" in article:
|
125 |
sentiment_col1, sentiment_col2 = st.columns(2)
|
126 |
with sentiment_col1:
|
127 |
+
st.markdown("**Basic Sentiment:**")
|
128 |
+
st.write(article["sentiment"])
|
129 |
+
if "sentiment_score" in article:
|
130 |
+
st.write(f"**Confidence Score:** {article['sentiment_score']*100:.1f}%")
|
131 |
|
132 |
with sentiment_col2:
|
133 |
# Display fine-grained sentiment if available
|
134 |
if "fine_grained_sentiment" in article and article["fine_grained_sentiment"]:
|
135 |
+
st.markdown("**Detailed Sentiment:**")
|
136 |
fine_grained = article["fine_grained_sentiment"]
|
137 |
if "category" in fine_grained:
|
138 |
+
st.write(f"Category: {fine_grained['category']}")
|
139 |
if "confidence" in fine_grained:
|
140 |
+
st.write(f"Confidence: {fine_grained['confidence']*100:.1f}%")
|
141 |
|
142 |
# Display sentiment indices if available
|
143 |
if "sentiment_indices" in article and article["sentiment_indices"]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
st.markdown(f"> {target['context']}")
|
145 |
st.markdown("---")
|
146 |
|
147 |
+
# Display URL if available
|
148 |
if "url" in article:
|
149 |
+
st.markdown(f"**[Read More]({article['url']})**")
|
150 |
|
151 |
# Display Comparative Analysis
|
152 |
st.header("📊 Comparative Analysis")
|
153 |
+
analysis = data.get("comparative_sentiment_score", {})
|
154 |
|
155 |
# Sentiment Distribution
|
156 |
if "sentiment_distribution" in analysis:
|
157 |
st.subheader("Sentiment Distribution")
|
158 |
|
159 |
+
|
160 |
+
|
161 |
+
|
162 |
+
|
163 |
sentiment_dist = analysis["sentiment_distribution"]
|
164 |
|
165 |
+
|
166 |
try:
|
167 |
# Extract basic sentiment data
|
168 |
if isinstance(sentiment_dist, dict):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
169 |
else:
|
170 |
percentages = {k: 0 for k in basic_dist}
|
171 |
|
172 |
+
# Display as metrics
|
173 |
st.write("**Sentiment Distribution:**")
|
174 |
|
175 |
col1, col2, col3 = st.columns(3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
f"{percentages.get('neutral', 0):.1f}%"
|
177 |
)
|
178 |
|
179 |
+
# Create visualization
|
180 |
+
|
181 |
+
|
182 |
chart_data = pd.DataFrame({
|
183 |
'Sentiment': ['Positive', 'Negative', 'Neutral'],
|
184 |
'Count': [
|
185 |
+
basic_dist.get('positive', 0),
|
186 |
basic_dist.get('negative', 0),
|
187 |
basic_dist.get('neutral', 0)
|
188 |
],
|
|
|
|
|
|
|
|
|
189 |
]
|
190 |
})
|
191 |
|
192 |
+
|
193 |
+
|
194 |
+
|
195 |
+
|
196 |
+
|
197 |
chart = alt.Chart(chart_data).mark_bar().encode(
|
198 |
+
y='Sentiment',
|
199 |
+
x='Count',
|
200 |
color=alt.Color('Sentiment', scale=alt.Scale(
|
201 |
domain=['Positive', 'Negative', 'Neutral'],
|
202 |
range=['green', 'red', 'gray']
|
203 |
)),
|
204 |
+
tooltip=['Sentiment', 'Count', 'Percentage']
|
205 |
).properties(
|
206 |
width=600,
|
207 |
height=300
|
208 |
)
|
209 |
|
210 |
+
|
211 |
text = chart.mark_text(
|
212 |
align='left',
|
213 |
baseline='middle',
|
214 |
+
dx=3
|
215 |
).encode(
|
216 |
text='Percentage'
|
217 |
)
|
218 |
|
219 |
+
|
220 |
chart_with_text = (chart + text)
|
221 |
+
|
222 |
st.altair_chart(chart_with_text, use_container_width=True)
|
223 |
|
224 |
except Exception as e:
|
225 |
st.error(f"Error creating visualization: {str(e)}")
|
226 |
+
|
227 |
+
|
228 |
+
|
229 |
+
|
230 |
+
|
231 |
+
|
232 |
+
|
233 |
+
|
234 |
+
|
235 |
+
|
236 |
+
|
237 |
+
|
238 |
|
239 |
# Display sentiment indices if available
|
240 |
if "sentiment_indices" in analysis and analysis["sentiment_indices"]:
|
241 |
st.subheader("Sentiment Indices")
|
242 |
|
243 |
+
|
244 |
+
|
245 |
+
|
246 |
+
|
247 |
+
|
248 |
indices = analysis["sentiment_indices"]
|
249 |
|
250 |
+
|
251 |
try:
|
252 |
if isinstance(indices, dict):
|
253 |
+
# Display as metrics in columns
|
254 |
cols = st.columns(3)
|
255 |
|
256 |
+
|
257 |
display_names = {
|
258 |
"positivity_index": "Positivity",
|
259 |
"negativity_index": "Negativity",
|
|
|
|
|
|
|
260 |
"esg_relevance": "ESG Relevance"
|
261 |
}
|
262 |
|
263 |
+
|
264 |
for i, (key, value) in enumerate(indices.items()):
|
265 |
if isinstance(value, (int, float)):
|
266 |
with cols[i % 3]:
|
267 |
display_name = display_names.get(key, key.replace("_", " ").title())
|
268 |
st.metric(display_name, f"{value:.2f}")
|
269 |
|
270 |
+
# Create visualization
|
271 |
+
|
272 |
+
|
273 |
chart_data = pd.DataFrame({
|
274 |
'Index': [display_names.get(k, k.replace("_", " ").title()) for k in indices.keys()],
|
275 |
'Value': [v if isinstance(v, (int, float)) else 0 for v in indices.values()]
|
276 |
})
|
277 |
|
278 |
+
|
279 |
chart = alt.Chart(chart_data).mark_bar().encode(
|
280 |
x='Value',
|
281 |
y='Index',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
282 |
- **Confidence**: Confidence in the sentiment analysis (0-1)
|
283 |
- **ESG Relevance**: Relevance to Environmental, Social, and Governance topics (0-1)
|
284 |
""")
|
285 |
+
|
286 |
+
|
287 |
+
|
288 |
except Exception as e:
|
289 |
st.error(f"Error creating indices visualization: {str(e)}")
|
290 |
+
|
291 |
+
|
292 |
+
|
293 |
+
|
294 |
+
|
295 |
+
|
296 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
297 |
|
298 |
+
# Display Final Analysis
|
299 |
+
st.header("📊 Final Analysis")
|
300 |
+
|
301 |
+
|
302 |
+
|
303 |
+
|
304 |
+
|
305 |
+
|
306 |
+
|
307 |
|
308 |
+
# Display overall sentiment analysis with enhanced formatting
|
309 |
+
if data.get("final_sentiment_analysis"):
|
310 |
+
st.markdown("### Overall Sentiment Analysis")
|
311 |
+
analysis_parts = data["final_sentiment_analysis"].split(". ")
|
312 |
+
if len(analysis_parts) >= 2:
|
313 |
+
# First sentence - Overall sentiment
|
314 |
+
st.markdown(f"**{analysis_parts[0]}.**")
|
315 |
+
# Second sentence - Key findings
|
316 |
+
st.markdown(f"**{analysis_parts[1]}.**")
|
317 |
+
# Third sentence - Additional insights (if available)
|
318 |
+
if len(analysis_parts) > 2:
|
319 |
+
st.markdown(f"**{analysis_parts[2]}.**")
|
320 |
else:
|
321 |
+
st.write(data["final_sentiment_analysis"])
|
322 |
+
|
323 |
+
# Add sentiment strength indicator
|
324 |
+
if data.get("ensemble_info"):
|
325 |
+
ensemble_info = data["ensemble_info"]
|
326 |
+
if "model_agreement" in ensemble_info:
|
327 |
+
agreement = ensemble_info["model_agreement"]
|
328 |
+
strength = "Strong" if agreement > 0.8 else "Moderate" if agreement > 0.6 else "Weak"
|
329 |
+
st.markdown(f"**Sentiment Strength:** {strength} (Agreement: {agreement:.2f})")
|
330 |
|
331 |
+
# Display ensemble model details
|
332 |
+
if data.get("ensemble_info"):
|
333 |
+
st.subheader("Ensemble Model Details")
|
334 |
+
ensemble_info = data["ensemble_info"]
|
335 |
|
336 |
+
# Create columns for model details
|
337 |
+
model_cols = st.columns(3)
|
338 |
+
|
339 |
+
|
340 |
+
|
341 |
+
|
342 |
+
|
343 |
+
|
344 |
+
|
345 |
+
|
346 |
|
347 |
+
with model_cols[0]:
|
348 |
+
st.markdown("**Primary Model:**")
|
349 |
+
if "models" in ensemble_info and "transformer" in ensemble_info["models"]:
|
350 |
+
model = ensemble_info["models"]["transformer"]
|
351 |
+
st.write(f"Sentiment: {model['sentiment']}")
|
352 |
+
st.write(f"Score: {model['score']:.3f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
353 |
|
354 |
+
with model_cols[1]:
|
355 |
+
st.markdown("**TextBlob Analysis:**")
|
356 |
+
if "models" in ensemble_info and "textblob" in ensemble_info["models"]:
|
357 |
+
model = ensemble_info["models"]["textblob"]
|
358 |
+
st.write(f"Sentiment: {model['sentiment']}")
|
359 |
+
st.write(f"Score: {model['score']:.3f}")
|
360 |
+
|
361 |
+
with model_cols[2]:
|
362 |
+
st.markdown("**VADER Analysis:**")
|
363 |
+
if "models" in ensemble_info and "vader" in ensemble_info["models"]:
|
364 |
+
model = ensemble_info["models"]["vader"]
|
365 |
+
st.write(f"Sentiment: {model['sentiment']}")
|
366 |
+
st.write(f"Score: {model['score']:.3f}")
|
367 |
+
|
368 |
+
# Display ensemble agreement if available
|
369 |
+
if "model_agreement" in ensemble_info:
|
370 |
+
st.markdown(f"**Model Agreement:** {ensemble_info['model_agreement']:.3f}")
|
371 |
+
|
372 |
+
# Display Hindi audio player
|
373 |
+
st.subheader("🔊 Listen to Analysis (Hindi)")
|
374 |
+
if data.get("audio_path") and os.path.exists(data["audio_path"]):
|
375 |
+
st.audio(data["audio_path"])
|
376 |
+
else:
|
377 |
+
st.info("Generating Hindi audio summary...")
|
378 |
+
with st.spinner("Please wait while we generate the Hindi audio summary..."):
|
379 |
+
# Try to generate audio again
|
380 |
+
translator = get_translator()
|
381 |
+
if translator and data.get("final_sentiment_analysis"):
|
382 |
+
try:
|
383 |
+
# Translate final analysis to Hindi
|
384 |
+
translated_analysis = translator.translate(
|
385 |
+
data["final_sentiment_analysis"],
|
386 |
+
dest='hi'
|
387 |
+
).text
|
388 |
+
|
389 |
+
# Generate Hindi audio
|
390 |
+
tts_converter = TextToSpeechConverter()
|
391 |
+
audio_path = tts_converter.generate_audio(
|
392 |
+
translated_analysis,
|
393 |
+
f'{company}_summary'
|
394 |
+
)
|
395 |
+
if audio_path and os.path.exists(audio_path):
|
396 |
+
st.audio(audio_path)
|
397 |
+
else:
|
398 |
+
st.error("Hindi audio summary not available")
|
399 |
+
except Exception as e:
|
400 |
+
st.error(f"Error generating Hindi audio: {str(e)}")
|
401 |
+
else:
|
402 |
+
st.error("Hindi audio summary not available")
|
403 |
|
404 |
# Total Articles
|
405 |
if "total_articles" in analysis:
|
406 |
st.sidebar.info(f"Found {analysis['total_articles']} articles")
|
407 |
+
|
408 |
+
except Exception as e:
|
409 |
+
st.error(f"Error analyzing company data: {str(e)}")
|
410 |
+
print(f"Error: {str(e)}")
|
411 |
|
412 |
# Add a disclaimer
|
413 |
+
st.sidebar.markdown("---")
|
|
|
|
|
|
|
|
|
|