bigroll commited on
Commit
b769ac5
·
verified ·
1 Parent(s): 9231b1b

adding motherduch

Browse files
Files changed (1) hide show
  1. app3.py +166 -0
app3.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import plotly.express as px
4
+ import duckdb
5
+ import os
6
+
7
+ # --- Set page layout ---
8
+ st.set_page_config(layout="wide", page_title="Sentiment Analysis")
9
+
10
+ # --- CSS for Flag Toggle ---
11
+ st.markdown("""
12
+ <style>
13
+ .flag-container { display: flex; justify-content: center; gap: 10px; }
14
+ .flag { cursor: pointer; width: 40px; height: 30px; }
15
+ </style>
16
+ """, unsafe_allow_html=True)
17
+
18
+ # --- State & Language ---
19
+ flag_selection = st.radio("", ["🇬🇧", "🇧🇬"], horizontal=True, label_visibility="collapsed")
20
+ LANG = "English" if flag_selection == "🇬🇧" else "Български"
21
+
22
+ T = {
23
+ "English": {
24
+ "title": "📊 Sentiment Analysis Dashboard",
25
+ "select_entities": "Select Entities",
26
+ "select_domains": "Select Domains",
27
+ "score_type": "Select Score Type",
28
+ "group_by_domain": "Group by Domain",
29
+ "all_scores": "Show All Score Types",
30
+ "no_data": "No data matches the selected filters.",
31
+ "avg_over_time": "Average {} Over Time",
32
+ "scores_over_time": "Sentiment Scores Over Time",
33
+ "select_entities_prompt": "Please select at least one entity."
34
+ },
35
+ "Български": {
36
+ "title": "📊 Табло за анализ на настроенията",
37
+ "select_entities": "Изберете обекти",
38
+ "select_domains": "Изберете източници",
39
+ "score_type": "Изберете тип оценка",
40
+ "group_by_domain": "Групирай по сайт",
41
+ "all_scores": "Покажи всички типове оценки",
42
+ "no_data": "Няма данни за избраните филтри.",
43
+ "avg_over_time": "Средна стойност на {} във времето",
44
+ "scores_over_time": "Оценки на настроенията във времето",
45
+ "select_entities_prompt": "Моля, изберете поне един обект."
46
+ }
47
+ }
48
+
49
+ # --- MotherDuck Connection ---
50
+ # Set MOTHERDUCK_TOKEN in Hugging Face Secrets
51
+ @st.cache_resource
52
+ def get_connection():
53
+ token = st.secrets["MOTHERDUCK_TOKEN"]
54
+ return duckdb.connect(f"md:?motherduck_token={token}")
55
+
56
+ con = get_connection()
57
+
58
+ # --- Cached Metadata for Filters ---
59
+ @st.cache_data(ttl=600)
60
+ def get_filter_options():
61
+ # Use DuckDB to get counts extremely fast
62
+ entities = con.sql("SELECT entity, COUNT(*) as c FROM sentiment_analysis GROUP BY 1 ORDER BY c DESC").df()
63
+ domains = con.sql("SELECT domain, COUNT(*) as c FROM sentiment_analysis GROUP BY 1 ORDER BY c DESC").df()
64
+ return entities, domains
65
+
66
+ df_entities, df_domains = get_filter_options()
67
+
68
+ # --- Sidebar ---
69
+ st.title(T[LANG]["title"])
70
+
71
+ with st.sidebar:
72
+ selected_entities = st.multiselect(
73
+ T[LANG]["select_entities"],
74
+ options=df_entities['entity'].tolist(),
75
+ format_func=lambda x: f"{x} ({df_entities[df_entities['entity']==x]['c'].values[0]})"
76
+ )
77
+
78
+ selected_domains = st.multiselect(
79
+ T[LANG]["select_domains"],
80
+ options=df_domains['domain'].tolist(),
81
+ format_func=lambda x: f"{x} ({df_domains[df_domains['domain']==x]['c'].values[0]})"
82
+ )
83
+
84
+ score_type = st.selectbox(T[LANG]["score_type"], ["entity_score", "title_score", "overall_score"])
85
+ group_by_domain = st.checkbox(T[LANG]["group_by_domain"])
86
+ all_scores = st.checkbox(T[LANG]["all_scores"])
87
+
88
+ timeframes = {"All": 9999, "Last 7 Days": 7, "Last 30 Days": 30, "Last Year": 365}
89
+ time_choice = st.selectbox("Timeframe", list(timeframes.keys()))
90
+
91
+ # --- Query Building ---
92
+ if not selected_entities:
93
+ st.info(T[LANG]["select_entities_prompt"])
94
+ st.stop()
95
+
96
+ # Build dynamic SQL
97
+ where_clause = f"WHERE entity IN ({str(selected_entities)[1:-1]})"
98
+ if selected_domains:
99
+ where_clause += f" AND domain IN ({str(selected_domains)[1:-1]})"
100
+ if time_choice != "All":
101
+ where_clause += f" AND created_at >= (epoch(now()) - {timeframes[time_choice] * 86400})"
102
+
103
+ # Column selection logic
104
+ if all_scores:
105
+ # Use DuckDB UNPIVOT (equivalent to pandas melt but faster)
106
+ sql_query = f"""
107
+ SELECT
108
+ time_bucket(interval '1 day', to_timestamp(created_at)) as date,
109
+ entity,
110
+ domain,
111
+ score_name as score_type,
112
+ AVG(score_value) as score
113
+ FROM (
114
+ UNPIVOT sentiment_analysis
115
+ ON entity_score, title_score, overall_score
116
+ INTO NAME score_name VALUE score_value
117
+ )
118
+ {where_clause}
119
+ GROUP BY ALL
120
+ """
121
+ else:
122
+ sql_query = f"""
123
+ SELECT
124
+ time_bucket(interval '1 day', to_timestamp(created_at)) as date,
125
+ entity,
126
+ domain,
127
+ AVG({score_type}) as score
128
+ FROM sentiment_analysis
129
+ {where_clause}
130
+ GROUP BY ALL
131
+ """
132
+
133
+ # --- Execution & Plotting ---
134
+ filtered_df = con.sql(sql_query).df()
135
+
136
+ if filtered_df.empty:
137
+ st.warning(T[LANG]["no_data"])
138
+ else:
139
+ # Create the Legend Label
140
+ color_col = "entity"
141
+ if group_by_domain:
142
+ filtered_df["label"] = filtered_df["entity"] + " | " + filtered_df["domain"]
143
+ color_col = "label"
144
+ if all_scores:
145
+ filtered_df["label"] = filtered_df.get("label", filtered_df["entity"]) + " | " + filtered_df["score_type"]
146
+ color_col = "label"
147
+
148
+ fig = px.line(
149
+ filtered_df.sort_values("date"),
150
+ x="date", y="score", color=color_col,
151
+ title=T[LANG]["scores_over_time"] if all_scores else T[LANG]["avg_over_time"].format(score_type),
152
+ labels={"score": "Sentiment Score", "date": "Date"}
153
+ )
154
+
155
+ # Grid line styling
156
+ fig.update_layout(
157
+ yaxis=dict(range=[-10, 10], gridcolor="lightgrey"),
158
+ plot_bgcolor="white",
159
+ legend=dict(orientation="h", y=-0.2)
160
+ )
161
+
162
+ # Add emphasis lines at -5, 0, 5
163
+ for val in [-5, 0, 5]:
164
+ fig.add_hline(y=val, line_width=2 if val==0 else 1, line_dash="dash", line_color="black")
165
+
166
+ st.plotly_chart(fig, use_container_width=True)