""" Sentiment Analysis Page Analyze content performance across all sentiment types with advanced filtering. Data is fetched on-demand: user sets filters then clicks "Fetch Data". Global filters (platform/brand/date) from the sidebar are pre-populated. """ import streamlit as st import pandas as pd import sys from pathlib import Path parent_dir = Path(__file__).resolve().parent.parent sys.path.append(str(parent_dir)) from visualizations.sentiment_charts import SentimentCharts from visualizations.distribution_charts import DistributionCharts from visualizations.content_cards import ContentCards from agents.content_summary_agent import ContentSummaryAgent def render_sentiment_analysis(data_loader): """ Render the Sentiment Analysis page. Args: data_loader: SentimentDataLoader instance """ st.title("π Custom Sentiment Queries") st.markdown("Analyze content performance based on sentiment patterns and user feedback") st.markdown("---") sentiment_charts = SentimentCharts() distribution_charts = DistributionCharts() summary_agent = ContentSummaryAgent(model="gpt-5-nano", temperature=1) if 'content_summaries' not in st.session_state: st.session_state.content_summaries = {} # ββ Get filter options from the already-loaded (lightweight) dashboard df β dashboard_df = st.session_state.get('dashboard_df') if dashboard_df is None or dashboard_df.empty: st.warning("Dashboard data not loaded yet. Please wait for the app to initialise.") return available_platforms = sorted(dashboard_df['platform'].dropna().unique().tolist()) available_brands = sorted(dashboard_df['brand'].dropna().unique().tolist()) # ββ Pre-populate from global sidebar filters βββββββββββββββββββββββββββββββ global_filters = st.session_state.get('global_filters', {}) global_platforms = global_filters.get('platforms', []) global_brands = global_filters.get('brands', []) global_date_range = global_filters.get('date_range') # ββ Platform & Brand selection βββββββββββββββββββββββββββββββββββββββββββββ st.markdown("### π― Select Platform and Brand") st.info( "β‘ **Performance**: Choose a platform and brand, set optional filters, " "then click **Fetch Data** to run a targeted Snowflake query." ) filter_col1, filter_col2 = st.columns(2) with filter_col1: default_platform_idx = 0 if global_platforms and global_platforms[0] in available_platforms: default_platform_idx = available_platforms.index(global_platforms[0]) + 1 # +1 for blank selected_platform = st.selectbox( "Platform *", options=[''] + available_platforms, index=default_platform_idx, help="Select the platform to analyse" ) with filter_col2: default_brand_idx = 0 if global_brands and global_brands[0] in available_brands: default_brand_idx = available_brands.index(global_brands[0]) + 1 selected_brand = st.selectbox( "Brand *", options=[''] + available_brands, index=default_brand_idx, help="Select the brand to analyse" ) if not selected_platform or not selected_brand: st.warning("β οΈ Please select both **Platform** and **Brand** to continue.") st.markdown("---") # Quick summary from dashboard data st.markdown("### π Available Data Summary") col1, col2, col3 = st.columns(3) with col1: st.metric("Total Comments", f"{len(dashboard_df):,}") with col2: st.metric("Platforms", len(available_platforms)) with st.expander("View Platforms"): for p in available_platforms: cnt = (dashboard_df['platform'] == p).sum() st.write(f"- **{p}**: {cnt:,} comments") with col3: st.metric("Brands", len(available_brands)) with st.expander("View Brands"): for b in available_brands: cnt = (dashboard_df['brand'] == b).sum() st.write(f"- **{b}**: {cnt:,} comments") return st.markdown("---") # ββ Content filters ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ st.markdown("### π Content Filters") # Build available sentiment / intent options from dashboard_df filtered to # selected platform+brand (fast β no text columns involved) mask = (dashboard_df['platform'] == selected_platform) & (dashboard_df['brand'] == selected_brand) preview_df = dashboard_df[mask] filter_col1, filter_col2, filter_col3, filter_col4 = st.columns(4) with filter_col1: sentiment_options = sorted(preview_df['sentiment_polarity'].unique().tolist()) selected_sentiments = st.multiselect( "Sentiment", options=sentiment_options, default=[], help="Filter by dominant sentiment. Leave empty for all." ) with filter_col2: intent_list = ( preview_df['intent'] .str.split(',').explode().str.strip() .dropna().unique().tolist() ) selected_intents = st.multiselect( "Intent", options=sorted(i for i in intent_list if i), default=[], help="Filter contents that have comments with these intents" ) with filter_col3: top_n = st.selectbox( "Top N Contents", options=[5, 10, 15, 20, 25], index=1, help="Number of contents to display" ) with filter_col4: filter_active = bool(selected_sentiments or selected_intents) st.metric( "Filters Active", "β Yes" if filter_active else "β No", help="Sentiment or intent filters applied" if filter_active else "Showing all sentiments" ) st.markdown("---") # ββ Advanced ranking controls ββββββββββββββββββββββββββββββββββββββββββββββ with st.expander("βοΈ Advanced Ranking Controls", expanded=False): adv_col1, adv_col2 = st.columns(2) with adv_col1: min_comments = st.slider( "Minimum Comments Required", min_value=1, max_value=50, value=10, step=1, help="Exclude contents with fewer comments than this threshold." ) with adv_col2: sort_by = st.selectbox( "Sort By", options=[ ('severity_score', 'π― Severity Score (Balanced) β Recommended'), ('sentiment_percentage', 'π Sentiment Percentage'), ('sentiment_count', 'π’ Sentiment Count (Absolute)'), ('total_comments', 'π¬ Total Comments (Volume)'), ], format_func=lambda x: x[1], index=0 ) sort_by_value = sort_by[0] sentiment_label = "selected sentiments" if selected_sentiments else "negative sentiments" info_map = { 'severity_score': f"π **Severity Score** = Sentiment % Γ β(Total Comments). Balances {sentiment_label} % with volume.", 'sentiment_percentage': f"π Ranks by highest % of {sentiment_label}. May include low-volume contents.", 'sentiment_count': f"π Ranks by absolute number of {sentiment_label} comments.", 'total_comments': "π Ranks by total comment volume, regardless of sentiment.", } st.info(info_map.get(sort_by_value, "")) # Date range for the query (inherit from global filters if set) if global_date_range and len(global_date_range) == 2: query_date_range = global_date_range else: query_date_range = None # ββ Fetch button βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ fetch_key = ( selected_platform, selected_brand, top_n, min_comments, sort_by_value, tuple(sorted(selected_sentiments)), tuple(sorted(selected_intents)), str(query_date_range) ) fetch_col, info_col = st.columns([1, 3]) with fetch_col: fetch_clicked = st.button("π Fetch Data", use_container_width=True, type="primary") # Auto-fetch if the key hasn't changed and we already have data has_data = ( 'sa_contents' in st.session_state and st.session_state.get('sa_fetch_key') == fetch_key and not st.session_state['sa_contents'].empty ) with info_col: if has_data: n_contents = len(st.session_state['sa_contents']) n_comments = len(st.session_state.get('sa_comments', [])) st.success(f"β Showing **{n_contents}** contents with **{n_comments:,}** sampled comments") elif fetch_clicked: pass # spinner shown below else: st.info("π Click **Fetch Data** to run a targeted Snowflake query with the settings above.") if fetch_clicked: with st.spinner("Fetching data from Snowflakeβ¦"): contents_df, comments_df = data_loader.load_sa_data( platform=selected_platform, brand=selected_brand, top_n=top_n, min_comments=min_comments, sort_by=sort_by_value, sentiments=selected_sentiments or None, intents=selected_intents or None, date_range=query_date_range, ) st.session_state['sa_contents'] = contents_df st.session_state['sa_comments'] = comments_df st.session_state['sa_fetch_key'] = fetch_key st.session_state['sa_platform'] = selected_platform st.session_state['sa_brand'] = selected_brand # Reset pagination on new fetch st.session_state['sentiment_page'] = 1 st.rerun() # ββ Nothing fetched yet ββββββββββββββββββββββββββββββββββββββββββββββββββββ if not has_data and not fetch_clicked: return filtered_contents = st.session_state.get('sa_contents', pd.DataFrame()) comments_df = st.session_state.get('sa_comments', pd.DataFrame()) if filtered_contents.empty: st.warning("No content data found with the selected filters. Try adjusting and re-fetching.") return # ββ Summary stats ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ st.markdown("### π Summary") col1, col2, col3, col4 = st.columns(4) with col1: st.metric("Contents Analysed", len(filtered_contents)) with col2: if 'selected_sentiment_percentage' in filtered_contents.columns: avg_pct = filtered_contents['selected_sentiment_percentage'].mean() label = "Selected Sentiment %" if selected_sentiments else "Avg Negative %" st.metric(label, f"{avg_pct:.1f}%") else: st.metric("Avg Negative %", f"{filtered_contents['negative_percentage'].mean():.1f}%") with col3: st.metric("Total Comments", int(filtered_contents['total_comments'].sum())) with col4: st.metric("Total Replies Needed", int(filtered_contents['reply_required_count'].sum())) st.markdown("---") # ββ Engagement scatter βββββββββββββββββββββββββββββββββββββββββββββββββββββ st.markdown("### π Content Engagement Analysis") scatter = distribution_charts.create_engagement_scatter( filtered_contents, title="Content Engagement vs. Sentiment" ) st.plotly_chart(scatter, use_container_width=True, key="engagement_scatter_chart") st.markdown("---") # ββ Paginated content cards ββββββββββββββββββββββββββββββββββββββββββββββββ st.markdown("### π Detailed Content Analysis") if 'sentiment_page' not in st.session_state: st.session_state.sentiment_page = 1 items_per_page = 5 total_contents = len(filtered_contents) total_pages = (total_contents + items_per_page - 1) // items_per_page if total_contents > items_per_page: st.info(f"π Page {st.session_state.sentiment_page} of {total_pages} ({total_contents} total contents)") col_prev, col_info, col_next = st.columns([1, 2, 1]) with col_prev: if st.button("β¬ οΈ Previous", key="prev_top", disabled=st.session_state.sentiment_page == 1): st.session_state.sentiment_page -= 1 st.rerun() with col_info: st.markdown( f"