""" Musora Sentiment Analysis Dashboard Main Streamlit Application Run with: streamlit run app.py """ import streamlit as st import sys from pathlib import Path import json # Add parent directory to path parent_dir = Path(__file__).resolve().parent sys.path.append(str(parent_dir)) from data.data_loader import SentimentDataLoader from components.dashboard import render_dashboard from components.sentiment_analysis import render_sentiment_analysis from components.reply_required import render_reply_required from utils.auth import check_authentication, render_login_page, logout, get_current_user # ── Load configuration ──────────────────────────────────────────────────────── config_path = parent_dir / "config" / "viz_config.json" with open(config_path, 'r') as f: config = json.load(f) # ── Page configuration ──────────────────────────────────────────────────────── st.set_page_config( page_title=config['page_config']['page_title'], page_icon=config['page_config']['page_icon'], layout=config['page_config']['layout'], initial_sidebar_state=config['page_config']['initial_sidebar_state'] ) # ── Authentication gate ─────────────────────────────────────────────────────── # render_login_page() calls st.stop() when the user is not authenticated, # so nothing below this point executes until login succeeds. if not check_authentication(): render_login_page() # ── Single data-loader instance (cheap: just reads config) ──────────────────── data_loader = SentimentDataLoader() def _ensure_dashboard_data(): """ Load dashboard data once and store in session_state. Subsequent calls within the same session (or until cache expires) are free. """ if 'dashboard_df' not in st.session_state or st.session_state['dashboard_df'] is None: with st.spinner("Loading dashboard data…"): df = data_loader.load_dashboard_data() st.session_state['dashboard_df'] = df return st.session_state['dashboard_df'] def main(): # ── Sidebar ─────────────────────────────────────────────────────────────── with st.sidebar: st.image("visualization/img/musora.png", use_container_width=True) # User info + logout current_user = get_current_user() if current_user: st.caption(f"Logged in as **{current_user}**") if st.button("🔓 Logout", use_container_width=True): logout() st.rerun() st.markdown("---") st.title("Navigation") page = st.radio( "Select Page", ["📊 Sentiment Dashboard", "🔍 Custom Sentiment Queries", "💬 Reply Required"], index=0 ) st.markdown("---") st.markdown("### 🔍 Global Filters") # Load / retrieve dashboard data for filter options dashboard_df = _ensure_dashboard_data() if dashboard_df.empty: st.error("No data available. Please check your Snowflake connection.") return filter_options = data_loader.get_filter_options(dashboard_df) # Restore previous filter values from session_state so widgets keep state prev = st.session_state.get('global_filters', {}) selected_platforms = st.multiselect( "Platforms", options=filter_options['platforms'], default=prev.get('platforms', []) ) selected_brands = st.multiselect( "Brands", options=filter_options['brands'], default=prev.get('brands', []) ) selected_sentiments = st.multiselect( "Sentiments", options=filter_options['sentiments'], default=prev.get('sentiments', []) ) # Date range filter if 'comment_timestamp' in dashboard_df.columns and not dashboard_df.empty: min_date = dashboard_df['comment_timestamp'].min().date() max_date = dashboard_df['comment_timestamp'].max().date() prev_range = prev.get('date_range') default_range = ( (prev_range[0], prev_range[1]) if prev_range and len(prev_range) == 2 else (min_date, max_date) ) date_range = st.date_input( "Date Range", value=default_range, min_value=min_date, max_value=max_date ) else: date_range = None # Apply / Reset if st.button("🔍 Apply Filters", use_container_width=True): st.session_state['global_filters'] = { 'platforms': selected_platforms, 'brands': selected_brands, 'sentiments': selected_sentiments, 'date_range': date_range if date_range and len(date_range) == 2 else None, } st.session_state['filters_applied'] = True if st.button("🔄 Reset Filters", use_container_width=True): st.session_state['global_filters'] = {} st.session_state['filters_applied'] = False st.rerun() st.markdown("---") # Data management st.markdown("### 🔄 Data Management") if st.button("♻️ Reload Data", use_container_width=True): st.cache_data.clear() st.session_state.pop('dashboard_df', None) st.rerun() # Data info st.markdown("---") st.markdown("### ℹ️ Data Info") st.info(f"**Total Records:** {len(dashboard_df):,}") if 'processed_at' in dashboard_df.columns and not dashboard_df.empty: last_update = dashboard_df['processed_at'].max() if hasattr(last_update, 'strftime'): st.info(f"**Last Updated:** {last_update.strftime('%Y-%m-%d %H:%M')}") # ── Build filtered dashboard_df for the Dashboard page ─────────────────── filters_applied = st.session_state.get('filters_applied', False) global_filters = st.session_state.get('global_filters', {}) if filters_applied and global_filters: filtered_df = data_loader.apply_filters( dashboard_df, platforms=global_filters.get('platforms') or None, brands=global_filters.get('brands') or None, sentiments=global_filters.get('sentiments') or None, date_range=global_filters.get('date_range') or None, ) if filtered_df.empty: st.warning("No data matches the selected filters. Please adjust your filters.") return st.info(f"Showing **{len(filtered_df):,}** records after applying filters") else: filtered_df = dashboard_df # ── Render selected page ────────────────────────────────────────────────── if page == "📊 Sentiment Dashboard": render_dashboard(filtered_df) elif page == "🔍 Custom Sentiment Queries": # SA page fetches its own data on demand; receives only data_loader render_sentiment_analysis(data_loader) elif page == "💬 Reply Required": # RR page fetches its own data on demand; receives only data_loader render_reply_required(data_loader) # ── Footer ──────────────────────────────────────────────────────────────── st.markdown("---") st.markdown( """
Musora Sentiment Analysis Dashboard v1.0
Powered by Streamlit | Data from Snowflake