File size: 8,504 Bytes
9858829 24b804f 9858829 24b804f 9858829 24b804f 9858829 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | """
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(
"""
<div style='text-align: center; color: gray; padding: 20px;'>
<p>Musora Sentiment Analysis Dashboard v1.0</p>
<p>Powered by Streamlit | Data from Snowflake</p>
</div>
""",
unsafe_allow_html=True
)
if __name__ == "__main__":
try:
main()
except Exception as e:
st.error(f"An error occurred: {str(e)}")
st.exception(e) |