Dmitry Beresnev
fix news feed
e9c5fb7
raw
history blame
6.22 kB
"""News display components for the financial dashboard."""
import streamlit as st
import pandas as pd
from datetime import datetime
import html as html_module
def display_news_card(news_item: dict):
"""Display a single news card with professional styling using Streamlit components."""
# Calculate time ago
time_diff = datetime.now() - news_item['timestamp']
if time_diff.seconds < 60:
time_ago = f"{time_diff.seconds}s ago"
elif time_diff.seconds < 3600:
time_ago = f"{time_diff.seconds // 60}m ago"
else:
time_ago = f"{time_diff.seconds // 3600}h ago"
# Create container with custom styling
with st.container():
# Add custom CSS for this card
st.markdown("""
<style>
.news-card {
background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
border: 1px solid #374151;
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
}
</style>
""", unsafe_allow_html=True)
# Header row with source and badges
col1, col2 = st.columns([3, 1])
with col1:
# Source and badges in one line
badge_cols = st.columns([2, 1, 1, 1])
with badge_cols[0]:
st.markdown(f"**:blue[{news_item['source']}]**")
with badge_cols[1]:
if news_item['impact'] == 'high':
st.markdown("πŸ”΄ **HIGH**")
elif news_item['impact'] == 'medium':
st.markdown("🟑 **MED**")
else:
st.markdown("🟒 **LOW**")
with badge_cols[2]:
sentiment_emoji = {'positive': 'πŸ“ˆ', 'negative': 'πŸ“‰', 'neutral': '➑️'}
st.markdown(f"{sentiment_emoji.get(news_item['sentiment'], '➑️')} {news_item['sentiment'].title()}")
with badge_cols[3]:
st.markdown(f"**#{news_item['category']}**")
with col2:
st.markdown(f"[**Read More β†’**]({news_item['url']})")
# Title/Summary
st.markdown(f"### {news_item.get('summary', '').strip()}")
# Meta information
st.caption(f"πŸ• {time_ago}")
st.markdown("---")
def display_news_feed(df: pd.DataFrame, max_items: int = 20):
"""Display a feed of news items."""
if df.empty:
st.info("πŸ“­ No news available. Adjust your filters or refresh the feed.")
return
# Add custom CSS for animations
st.markdown("""
<style>
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
</style>
""", unsafe_allow_html=True)
# Display news items
for idx, row in df.head(max_items).iterrows():
display_news_card(row.to_dict())
def display_news_statistics(stats: dict):
"""Display news feed statistics in metric cards."""
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric(
"Total Stories",
f"{stats['total']}",
help="Total news items in feed"
)
with col2:
st.metric(
"High Impact",
f"{stats['high_impact']}",
delta=f"{(stats['high_impact']/max(stats['total'], 1)*100):.0f}%",
help="High-impact market-moving news"
)
with col3:
st.metric(
"Breaking News",
f"{stats['breaking']}",
delta="LIVE" if stats['breaking'] > 0 else None,
help="Breaking news alerts"
)
with col4:
st.metric(
"Last Update",
stats['last_update'],
help="Time of last news fetch"
)
def display_category_breakdown(stats: dict):
"""Display news breakdown by category using Streamlit components."""
if 'by_category' not in stats:
return
st.markdown("### πŸ“Š News by Category")
categories = stats['by_category']
total = sum(categories.values())
if total == 0:
st.info("No categorized news available")
return
col1, col2, col3 = st.columns(3)
with col1:
macro_count = categories.get('macro', 0)
macro_pct = (macro_count / total) * 100
with st.container():
st.markdown("**:blue[πŸ“ˆ MACRO]**")
st.markdown(f"# {macro_count}")
st.caption(f"{macro_pct:.1f}% of total")
with col2:
geo_count = categories.get('geopolitical', 0)
geo_pct = (geo_count / total) * 100
with st.container():
st.markdown("**:orange[🌍 GEOPOLITICAL]**")
st.markdown(f"# {geo_count}")
st.caption(f"{geo_pct:.1f}% of total")
with col3:
markets_count = categories.get('markets', 0)
markets_pct = (markets_count / total) * 100
with st.container():
st.markdown("**:green[πŸ’Ή MARKETS]**")
st.markdown(f"# {markets_count}")
st.caption(f"{markets_pct:.1f}% of total")
def display_breaking_news_banner(df: pd.DataFrame):
"""Display breaking news banner at the top using Streamlit components."""
breaking = df[df['is_breaking'] == True] if not df.empty and 'is_breaking' in df.columns else pd.DataFrame()
if not breaking.empty:
latest = breaking.iloc[0]
# Create container with red background styling
with st.container():
st.markdown("""
<style>
.breaking-news-container {
background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);
border-radius: 12px;
padding: 20px;
margin-bottom: 24px;
border: 2px solid #fca5a5;
}
</style>
""", unsafe_allow_html=True)
# Layout with emoji and content
col1, col2, col3 = st.columns([1, 8, 2])
with col1:
st.markdown("# 🚨")
with col2:
st.markdown(f"**:red[BREAKING NEWS β€’ {latest['source'].upper()}]**")
st.markdown(f"## {latest.get('summary', '').strip()}")
with col3:
st.markdown(f"[**READ NOW β†’**]({latest['url']})")