bigroll commited on
Commit
83ccd09
·
verified ·
1 Parent(s): 448eb35

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -29
app.py CHANGED
@@ -58,12 +58,12 @@ def get_connection():
58
 
59
  con = get_connection()
60
 
61
- # --- Cached Metadata for Filters ---
62
  @st.cache_data(ttl=600)
63
  def get_filter_options():
64
- # Fast counts via DuckDB
65
- entities = con.sql("SELECT entity, COUNT(*) as c FROM sentiment_analysis GROUP BY 1 ORDER BY c DESC").df()
66
- domains = con.sql("SELECT domain, COUNT(*) as c FROM sentiment_analysis GROUP BY 1 ORDER BY c DESC").df()
67
  return entities, domains
68
 
69
  df_entities, df_domains = get_filter_options()
@@ -72,7 +72,7 @@ df_entities, df_domains = get_filter_options()
72
  st.title(T[LANG]["title"])
73
 
74
  with st.sidebar:
75
- # Create lookup dictionaries to avoid filtering dataframes in the lambda (prevents crashes)
76
  entity_lookup = dict(zip(df_entities['entity'], df_entities['c']))
77
  domain_lookup = dict(zip(df_domains['domain'], df_domains['c']))
78
 
@@ -95,25 +95,45 @@ with st.sidebar:
95
  timeframes = {"All": 9999, "Last 7 Days": 7, "Last 30 Days": 30, "Last Year": 365}
96
  time_choice = st.selectbox("Timeframe", list(timeframes.keys()))
97
 
98
- # --- Query Building ---
99
  if not selected_entities:
100
  st.info(T[LANG]["select_entities_prompt"])
101
  st.stop()
102
 
103
- # Dynamic SQL construction
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  where_clause = f"WHERE entity IN ({str(selected_entities)[1:-1]})"
105
  if selected_domains:
106
  where_clause += f" AND domain IN ({str(selected_domains)[1:-1]})"
107
  if time_choice != "All":
108
- where_clause += f" AND created_at >= (epoch(now()) - {timeframes[time_choice] * 86400})"
109
 
 
110
  if all_scores:
 
 
 
 
 
 
 
 
 
111
  sql_query = f"""
112
  SELECT
113
- time_bucket(interval '1 day', created_at) as date,
114
- entity,
115
- domain,
116
- score_name as score_type,
117
  AVG(score_value) as score
118
  FROM (
119
  UNPIVOT sentiment_analysis
@@ -121,18 +141,26 @@ if all_scores:
121
  INTO NAME score_name VALUE score_value
122
  )
123
  {where_clause}
124
- GROUP BY ALL
 
125
  """
126
  else:
 
 
 
 
 
 
 
 
127
  sql_query = f"""
128
  SELECT
129
- time_bucket(interval '1 day', created_at) as date,
130
- entity,
131
- domain,
132
  AVG({score_type}) as score
133
  FROM sentiment_analysis
134
  {where_clause}
135
- GROUP BY ALL
 
136
  """
137
 
138
  # --- Execution & Plotting ---
@@ -145,32 +173,34 @@ except Exception as e:
145
  if filtered_df.empty:
146
  st.warning(T[LANG]["no_data"])
147
  else:
148
- # Build dynamic labels for the legend
149
- color_col = "entity"
150
  if group_by_domain:
151
  filtered_df["label"] = filtered_df["entity"] + " | " + filtered_df["domain"]
152
- color_col = "label"
 
153
 
154
  if all_scores:
155
- current_label = filtered_df["label"] if group_by_domain else filtered_df["entity"]
156
- filtered_df["label"] = current_label + " | " + filtered_df["score_type"]
157
- color_col = "label"
158
 
 
159
  fig = px.line(
160
- filtered_df.sort_values("date"),
161
- x="date", y="score", color=color_col,
162
- title=T[LANG]["scores_over_time"] if all_scores else T[LANG]["avg_over_time"].format(score_type.replace('_', ' ').title()),
163
- labels={"score": "Sentiment Score", "date": "Date", "label": "Entity/Source"}
 
 
 
164
  )
165
 
166
- # Styling
167
  fig.update_layout(
168
  yaxis=dict(range=[-10, 10], gridcolor="lightgrey"),
169
  plot_bgcolor="white",
170
  legend=dict(orientation="h", y=-0.2, x=0.5, xanchor="center")
171
  )
172
 
173
- # Static horizontal reference lines
174
  for val in [-5, 0, 5]:
175
  fig.add_hline(y=val, line_width=2 if val==0 else 1, line_dash="dash", line_color="black")
176
 
 
58
 
59
  con = get_connection()
60
 
61
+ # --- Metadata for Filters ---
62
  @st.cache_data(ttl=600)
63
  def get_filter_options():
64
+ # Trim and count for cleaner lookups
65
+ entities = con.sql("SELECT trim(entity) as entity, COUNT(*) as c FROM sentiment_analysis GROUP BY 1 ORDER BY c DESC").df()
66
+ domains = con.sql("SELECT trim(domain) as domain, COUNT(*) as c FROM sentiment_analysis GROUP BY 1 ORDER BY c DESC").df()
67
  return entities, domains
68
 
69
  df_entities, df_domains = get_filter_options()
 
72
  st.title(T[LANG]["title"])
73
 
74
  with st.sidebar:
75
+ # Dictionary lookup prevents crashes on format_func
76
  entity_lookup = dict(zip(df_entities['entity'], df_entities['c']))
77
  domain_lookup = dict(zip(df_domains['domain'], df_domains['c']))
78
 
 
95
  timeframes = {"All": 9999, "Last 7 Days": 7, "Last 30 Days": 30, "Last Year": 365}
96
  time_choice = st.selectbox("Timeframe", list(timeframes.keys()))
97
 
98
+ # --- Logic: Stop if no selection ---
99
  if not selected_entities:
100
  st.info(T[LANG]["select_entities_prompt"])
101
  st.stop()
102
 
103
+ # --- Dynamic Granularity Logic (Targeting ~30 points) ---
104
+ days = timeframes[time_choice]
105
+
106
+ # Calculate optimal bucket size to get ~30 data points
107
+ if days <= 30:
108
+ bucket = "1 day"
109
+ elif days <= 210: # ~30 weeks
110
+ bucket = "1 week"
111
+ elif days <= 900: # ~30 months
112
+ bucket = "1 month"
113
+ else:
114
+ bucket = "1 year"
115
+
116
+ # --- Query Building ---
117
  where_clause = f"WHERE entity IN ({str(selected_entities)[1:-1]})"
118
  if selected_domains:
119
  where_clause += f" AND domain IN ({str(selected_domains)[1:-1]})"
120
  if time_choice != "All":
121
+ where_clause += f" AND created_at >= (epoch(now()) - {days * 86400})"
122
 
123
+ # --- CRITICAL FIX: Explicit Grouping for Smooth Lines ---
124
  if all_scores:
125
+ # Unpivot case: Group by Date, Entity, and Score Type
126
+ # If grouping by domain is active, add domain to the group key
127
+ group_keys = "1, 2, 3"
128
+ select_keys = "time_bucket(interval '{bucket}', created_at) as date, entity, score_name as score_type"
129
+
130
+ if group_by_domain:
131
+ select_keys += ", domain"
132
+ group_keys += ", 4"
133
+
134
  sql_query = f"""
135
  SELECT
136
+ {select_keys},
 
 
 
137
  AVG(score_value) as score
138
  FROM (
139
  UNPIVOT sentiment_analysis
 
141
  INTO NAME score_name VALUE score_value
142
  )
143
  {where_clause}
144
+ GROUP BY {group_keys}
145
+ ORDER BY 1 ASC
146
  """
147
  else:
148
+ # Standard case: Group by Date and Entity
149
+ group_keys = "1, 2"
150
+ select_keys = "time_bucket(interval '{bucket}', created_at) as date, entity"
151
+
152
+ if group_by_domain:
153
+ select_keys += ", domain"
154
+ group_keys += ", 3"
155
+
156
  sql_query = f"""
157
  SELECT
158
+ {select_keys},
 
 
159
  AVG({score_type}) as score
160
  FROM sentiment_analysis
161
  {where_clause}
162
+ GROUP BY {group_keys}
163
+ ORDER BY 1 ASC
164
  """
165
 
166
  # --- Execution & Plotting ---
 
173
  if filtered_df.empty:
174
  st.warning(T[LANG]["no_data"])
175
  else:
176
+ # Build legend labels
 
177
  if group_by_domain:
178
  filtered_df["label"] = filtered_df["entity"] + " | " + filtered_df["domain"]
179
+ else:
180
+ filtered_df["label"] = filtered_df["entity"]
181
 
182
  if all_scores:
183
+ filtered_df["label"] = filtered_df["label"] + " | " + filtered_df["score_type"]
 
 
184
 
185
+ # Plot
186
  fig = px.line(
187
+ filtered_df,
188
+ x="date",
189
+ y="score",
190
+ color="label",
191
+ title=f"{T[LANG]['scores_over_time']} (Avg by {bucket})",
192
+ labels={"score": "Score", "date": "Date", "label": "Legend"},
193
+ markers=True # Adds dots to points for clarity
194
  )
195
 
196
+ # Layout styling
197
  fig.update_layout(
198
  yaxis=dict(range=[-10, 10], gridcolor="lightgrey"),
199
  plot_bgcolor="white",
200
  legend=dict(orientation="h", y=-0.2, x=0.5, xanchor="center")
201
  )
202
 
203
+ # Reference lines
204
  for val in [-5, 0, 5]:
205
  fig.add_hline(y=val, line_width=2 if val==0 else 1, line_dash="dash", line_color="black")
206