GuglielmoTor commited on
Commit
98de4a1
·
verified ·
1 Parent(s): 7f592b2

Create ui_generators.py

Browse files
Files changed (1) hide show
  1. ui_generators.py +280 -0
ui_generators.py ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ui_generators.py
2
+ """
3
+ Generates HTML content and Matplotlib plots for the Gradio UI tabs.
4
+ """
5
+ import pandas as pd
6
+ import logging
7
+ import matplotlib.pyplot as plt
8
+ import matplotlib # To ensure backend is switched before any plt import from other modules if app structure changes
9
+
10
+ # Switch backend for Matplotlib to Agg for Gradio compatibility
11
+ matplotlib.use('Agg')
12
+
13
+
14
+ # Assuming config.py contains all necessary constants
15
+ from config import (
16
+ BUBBLE_POST_DATE_COLUMN_NAME, BUBBLE_MENTIONS_DATE_COLUMN_NAME, BUBBLE_MENTIONS_ID_COLUMN_NAME,
17
+ FOLLOWER_STATS_TYPE_COLUMN, FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN,
18
+ FOLLOWER_STATS_PAID_COLUMN, FOLLOWER_STATS_CATEGORY_COLUMN_DT, UI_DATE_FORMAT, UI_MONTH_FORMAT
19
+ )
20
+
21
+ def display_main_dashboard(token_state):
22
+ """Generates HTML for the main dashboard display using data from token_state."""
23
+ if not token_state or not token_state.get("token"):
24
+ logging.warning("Dashboard display: Access denied. No token available.")
25
+ return "❌ Access denied. No token available for dashboard."
26
+
27
+ html_parts = ["<div style='padding:10px;'><h3>Dashboard Overview</h3>"]
28
+
29
+ # Display Recent Posts
30
+ posts_df = token_state.get("bubble_posts_df", pd.DataFrame())
31
+ html_parts.append(f"<h4>Recent Posts ({len(posts_df)} in Bubble):</h4>")
32
+ if not posts_df.empty:
33
+ cols_to_show_posts = [col for col in [BUBBLE_POST_DATE_COLUMN_NAME, 'text', 'sentiment', 'summary_text', 'li_eb_label'] if col in posts_df.columns]
34
+ if not cols_to_show_posts:
35
+ html_parts.append("<p>No relevant post columns found to display.</p>")
36
+ else:
37
+ display_df_posts = posts_df.copy()
38
+ if BUBBLE_POST_DATE_COLUMN_NAME in display_df_posts.columns:
39
+ try:
40
+ display_df_posts[BUBBLE_POST_DATE_COLUMN_NAME] = pd.to_datetime(display_df_posts[BUBBLE_POST_DATE_COLUMN_NAME], errors='coerce').dt.strftime(UI_DATE_FORMAT)
41
+ display_df_posts = display_df_posts.sort_values(by=BUBBLE_POST_DATE_COLUMN_NAME, ascending=False)
42
+ except Exception as e:
43
+ logging.error(f"Error formatting post dates for display: {e}")
44
+ html_parts.append("<p>Error formatting post dates.</p>")
45
+ html_parts.append(display_df_posts[cols_to_show_posts].head().to_html(escape=False, index=False, classes="table table-striped table-sm"))
46
+ else:
47
+ html_parts.append("<p>No posts loaded from Bubble.</p>")
48
+ html_parts.append("<hr/>")
49
+
50
+ # Display Recent Mentions
51
+ mentions_df = token_state.get("bubble_mentions_df", pd.DataFrame())
52
+ html_parts.append(f"<h4>Recent Mentions ({len(mentions_df)} in Bubble):</h4>")
53
+ if not mentions_df.empty:
54
+ cols_to_show_mentions = [col for col in [BUBBLE_MENTIONS_DATE_COLUMN_NAME, "mention_text", "sentiment_label"] if col in mentions_df.columns]
55
+ if not cols_to_show_mentions:
56
+ html_parts.append("<p>No relevant mention columns found to display.</p>")
57
+ else:
58
+ display_df_mentions = mentions_df.copy()
59
+ if BUBBLE_MENTIONS_DATE_COLUMN_NAME in display_df_mentions.columns:
60
+ try:
61
+ display_df_mentions[BUBBLE_MENTIONS_DATE_COLUMN_NAME] = pd.to_datetime(display_df_mentions[BUBBLE_MENTIONS_DATE_COLUMN_NAME], errors='coerce').dt.strftime(UI_DATE_FORMAT)
62
+ display_df_mentions = display_df_mentions.sort_values(by=BUBBLE_MENTIONS_DATE_COLUMN_NAME, ascending=False)
63
+ except Exception as e:
64
+ logging.error(f"Error formatting mention dates for display: {e}")
65
+ html_parts.append("<p>Error formatting mention dates.</p>")
66
+ html_parts.append(display_df_mentions[cols_to_show_mentions].head().to_html(escape=False, index=False, classes="table table-striped table-sm"))
67
+ else:
68
+ html_parts.append("<p>No mentions loaded from Bubble.</p>")
69
+ html_parts.append("<hr/>")
70
+
71
+ # Display Follower Statistics Summary
72
+ follower_stats_df = token_state.get("bubble_follower_stats_df", pd.DataFrame())
73
+ html_parts.append(f"<h4>Follower Statistics ({len(follower_stats_df)} entries in Bubble):</h4>")
74
+ if not follower_stats_df.empty:
75
+ monthly_gains = follower_stats_df[follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_gains_monthly'].copy()
76
+ if not monthly_gains.empty and FOLLOWER_STATS_CATEGORY_COLUMN in monthly_gains.columns and \
77
+ FOLLOWER_STATS_ORGANIC_COLUMN in monthly_gains.columns and FOLLOWER_STATS_PAID_COLUMN in monthly_gains.columns:
78
+ try:
79
+ # FOLLOWER_STATS_CATEGORY_COLUMN for monthly gains is 'YYYY-MM-DD'
80
+ monthly_gains.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN_DT] = pd.to_datetime(monthly_gains[FOLLOWER_STATS_CATEGORY_COLUMN], errors='coerce')
81
+ # Format original date column for display after sorting by datetime
82
+ monthly_gains_display = monthly_gains.sort_values(by=FOLLOWER_STATS_CATEGORY_COLUMN_DT, ascending=False)
83
+ latest_gain = monthly_gains_display.head(1).copy() # Work with a copy for modification
84
+ if not latest_gain.empty:
85
+ latest_gain.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN] = latest_gain[FOLLOWER_STATS_CATEGORY_COLUMN_DT].dt.strftime(UI_DATE_FORMAT) # or UI_MONTH_FORMAT
86
+ html_parts.append("<h5>Latest Monthly Follower Gain:</h5>")
87
+ html_parts.append(latest_gain[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].to_html(escape=True, index=False, classes="table table-sm"))
88
+ else:
89
+ html_parts.append("<p>No valid monthly follower gain data to display after processing.</p>")
90
+ except Exception as e:
91
+ logging.error(f"Error formatting follower gain dates for display: {e}", exc_info=True)
92
+ html_parts.append("<p>Error displaying monthly follower gain data.</p>")
93
+ else:
94
+ html_parts.append("<p>No monthly follower gain data or required columns are missing.</p>")
95
+
96
+ demographics_count = len(follower_stats_df[follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] != 'follower_gains_monthly'])
97
+ html_parts.append(f"<p>Total demographic entries (seniority, industry, etc.): {demographics_count}</p>")
98
+ else:
99
+ html_parts.append("<p>No follower statistics loaded from Bubble.</p>")
100
+
101
+ html_parts.append("</div>")
102
+ return "".join(html_parts)
103
+
104
+
105
+ def run_mentions_tab_display(token_state):
106
+ """Generates HTML and a plot for the Mentions tab."""
107
+ logging.info("Updating Mentions Tab display.")
108
+ if not token_state or not token_state.get("token"):
109
+ logging.warning("Mentions tab: Access denied. No token.")
110
+ return "❌ Access denied. No token available for mentions.", None
111
+
112
+ mentions_df = token_state.get("bubble_mentions_df", pd.DataFrame())
113
+ if mentions_df.empty:
114
+ logging.info("Mentions tab: No mentions data in Bubble.")
115
+ return "<p style='text-align:center;'>No mentions data in Bubble. Try syncing.</p>", None
116
+
117
+ html_parts = ["<h3 style='text-align:center;'>Recent Mentions</h3>"]
118
+ display_columns = [col for col in [BUBBLE_MENTIONS_DATE_COLUMN_NAME, "mention_text", "sentiment_label", BUBBLE_MENTIONS_ID_COLUMN_NAME] if col in mentions_df.columns]
119
+
120
+ mentions_df_display = mentions_df.copy()
121
+ if BUBBLE_MENTIONS_DATE_COLUMN_NAME in mentions_df_display.columns:
122
+ try:
123
+ mentions_df_display[BUBBLE_MENTIONS_DATE_COLUMN_NAME] = pd.to_datetime(mentions_df_display[BUBBLE_MENTIONS_DATE_COLUMN_NAME], errors='coerce').dt.strftime(UI_DATE_FORMAT)
124
+ mentions_df_display = mentions_df_display.sort_values(by=BUBBLE_MENTIONS_DATE_COLUMN_NAME, ascending=False)
125
+ except Exception as e:
126
+ logging.error(f"Error formatting mention dates for tab display: {e}")
127
+ html_parts.append("<p>Error formatting mention dates.</p>")
128
+
129
+ if not display_columns or mentions_df_display[display_columns].empty:
130
+ html_parts.append("<p>Required columns for mentions display are missing or no data after processing.</p>")
131
+ else:
132
+ html_parts.append(mentions_df_display[display_columns].head(20).to_html(escape=False, index=False, classes="table table-sm"))
133
+
134
+ mentions_html_output = "\n".join(html_parts)
135
+ fig = None
136
+ if not mentions_df.empty and "sentiment_label" in mentions_df.columns:
137
+ try:
138
+ fig_plot, ax = plt.subplots(figsize=(6,4))
139
+ sentiment_counts = mentions_df["sentiment_label"].value_counts()
140
+ sentiment_counts.plot(kind='bar', ax=ax, color=['#4CAF50', '#FFC107', '#F44336', '#9E9E9E', '#2196F3'])
141
+ ax.set_title("Mention Sentiment Distribution")
142
+ ax.set_ylabel("Count")
143
+ plt.xticks(rotation=45, ha='right')
144
+ plt.tight_layout()
145
+ fig = fig_plot
146
+ logging.info("Mentions tab: Sentiment distribution plot generated.")
147
+ except Exception as e:
148
+ logging.error(f"Error generating mentions plot: {e}", exc_info=True)
149
+ fig = None
150
+ else:
151
+ logging.info("Mentions tab: Not enough data or 'sentiment_label' column missing for plot.")
152
+
153
+ return mentions_html_output, fig
154
+
155
+
156
+ def run_follower_stats_tab_display(token_state):
157
+ """Generates HTML and plots for the Follower Stats tab."""
158
+ logging.info("Updating Follower Stats Tab display.")
159
+ if not token_state or not token_state.get("token"):
160
+ logging.warning("Follower stats tab: Access denied. No token.")
161
+ return "❌ Access denied. No token available for follower stats.", None, None, None
162
+
163
+ follower_stats_df_orig = token_state.get("bubble_follower_stats_df", pd.DataFrame())
164
+ if follower_stats_df_orig.empty:
165
+ logging.info("Follower stats tab: No follower stats data in Bubble.")
166
+ return "<p style='text-align:center;'>No follower stats data in Bubble. Try syncing.</p>", None, None, None
167
+
168
+ follower_stats_df = follower_stats_df_orig.copy()
169
+ html_parts = ["<div style='padding:10px;'><h3 style='text-align:center;'>Follower Statistics Overview</h3>"]
170
+
171
+ plot_monthly_gains = None
172
+ plot_seniority_dist = None
173
+ plot_industry_dist = None
174
+
175
+ # --- Monthly Gains Table & Plot ---
176
+ monthly_gains_df = follower_stats_df[
177
+ (follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_gains_monthly') &
178
+ (follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) &
179
+ (follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna()) &
180
+ (follower_stats_df[FOLLOWER_STATS_PAID_COLUMN].notna())
181
+ ].copy()
182
+
183
+ if not monthly_gains_df.empty:
184
+ try:
185
+ monthly_gains_df.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN_DT] = pd.to_datetime(monthly_gains_df[FOLLOWER_STATS_CATEGORY_COLUMN], errors='coerce')
186
+ monthly_gains_df_sorted_table = monthly_gains_df.sort_values(by=FOLLOWER_STATS_CATEGORY_COLUMN_DT, ascending=False)
187
+
188
+ html_parts.append("<h4>Monthly Follower Gains (Last 13 Months):</h4>")
189
+ table_display_df = monthly_gains_df_sorted_table.copy()
190
+ table_display_df.loc[:,FOLLOWER_STATS_CATEGORY_COLUMN] = table_display_df[FOLLOWER_STATS_CATEGORY_COLUMN_DT].dt.strftime(UI_MONTH_FORMAT) # Use YYYY-MM for table
191
+ html_parts.append(table_display_df[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].head(13).to_html(escape=True, index=False, classes="table table-sm"))
192
+
193
+ monthly_gains_df_sorted_plot = monthly_gains_df.sort_values(by=FOLLOWER_STATS_CATEGORY_COLUMN_DT, ascending=True).copy()
194
+ # For plotting, group by month string to ensure unique x-ticks if multiple entries exist for a month (though unlikely for this data type)
195
+ monthly_gains_df_sorted_plot.loc[:, '_plot_month'] = monthly_gains_df_sorted_plot[FOLLOWER_STATS_CATEGORY_COLUMN_DT].dt.strftime(UI_MONTH_FORMAT)
196
+ plot_data = monthly_gains_df_sorted_plot.groupby('_plot_month').agg(
197
+ organic=(FOLLOWER_STATS_ORGANIC_COLUMN, 'sum'),
198
+ paid=(FOLLOWER_STATS_PAID_COLUMN, 'sum')
199
+ ).reset_index().sort_values(by='_plot_month')
200
+
201
+
202
+ fig_gains, ax_gains = plt.subplots(figsize=(10,5))
203
+ ax_gains.plot(plot_data['_plot_month'], plot_data['organic'], marker='o', linestyle='-', label='Organic Gain')
204
+ ax_gains.plot(plot_data['_plot_month'], plot_data['paid'], marker='x', linestyle='--', label='Paid Gain')
205
+ ax_gains.set_title("Monthly Follower Gains Over Time")
206
+ ax_gains.set_ylabel("Follower Count")
207
+ ax_gains.set_xlabel("Month (YYYY-MM)")
208
+ plt.xticks(rotation=45, ha='right')
209
+ ax_gains.legend()
210
+ plt.grid(True, linestyle='--', alpha=0.7)
211
+ plt.tight_layout()
212
+ plot_monthly_gains = fig_gains
213
+ logging.info("Follower stats tab: Monthly gains plot generated.")
214
+ except Exception as e:
215
+ logging.error(f"Error processing or plotting monthly gains: {e}", exc_info=True)
216
+ html_parts.append("<p>Error displaying monthly follower gain data.</p>")
217
+ else:
218
+ html_parts.append("<p>No monthly follower gain data available or required columns missing.</p>")
219
+ html_parts.append("<hr/>")
220
+
221
+ # --- Seniority Table & Plot ---
222
+ seniority_df = follower_stats_df[
223
+ (follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_seniority') &
224
+ (follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) &
225
+ (follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna())
226
+ ].copy()
227
+ if not seniority_df.empty:
228
+ try:
229
+ seniority_df_sorted = seniority_df.sort_values(by=FOLLOWER_STATS_ORGANIC_COLUMN, ascending=False)
230
+ html_parts.append("<h4>Followers by Seniority (Top 10 Organic):</h4>")
231
+ html_parts.append(seniority_df_sorted[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].head(10).to_html(escape=True, index=False, classes="table table-sm"))
232
+
233
+ fig_seniority, ax_seniority = plt.subplots(figsize=(8,5))
234
+ top_n_seniority = seniority_df_sorted.nlargest(10, FOLLOWER_STATS_ORGANIC_COLUMN)
235
+ ax_seniority.bar(top_n_seniority[FOLLOWER_STATS_CATEGORY_COLUMN], top_n_seniority[FOLLOWER_STATS_ORGANIC_COLUMN], color='skyblue')
236
+ ax_seniority.set_title("Follower Distribution by Seniority (Top 10 Organic)")
237
+ ax_seniority.set_ylabel("Organic Follower Count")
238
+ plt.xticks(rotation=45, ha='right')
239
+ plt.grid(axis='y', linestyle='--', alpha=0.7)
240
+ plt.tight_layout()
241
+ plot_seniority_dist = fig_seniority
242
+ logging.info("Follower stats tab: Seniority distribution plot generated.")
243
+ except Exception as e:
244
+ logging.error(f"Error processing or plotting seniority data: {e}", exc_info=True)
245
+ html_parts.append("<p>Error displaying follower seniority data.</p>")
246
+ else:
247
+ html_parts.append("<p>No follower seniority data available or required columns missing.</p>")
248
+ html_parts.append("<hr/>")
249
+
250
+ # --- Industry Table & Plot ---
251
+ industry_df = follower_stats_df[
252
+ (follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_industry') &
253
+ (follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) &
254
+ (follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna())
255
+ ].copy()
256
+ if not industry_df.empty:
257
+ try:
258
+ industry_df_sorted = industry_df.sort_values(by=FOLLOWER_STATS_ORGANIC_COLUMN, ascending=False)
259
+ html_parts.append("<h4>Followers by Industry (Top 10 Organic):</h4>")
260
+ html_parts.append(industry_df_sorted[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].head(10).to_html(escape=True, index=False, classes="table table-sm"))
261
+
262
+ fig_industry, ax_industry = plt.subplots(figsize=(8,5))
263
+ top_n_industry = industry_df_sorted.nlargest(10, FOLLOWER_STATS_ORGANIC_COLUMN)
264
+ ax_industry.bar(top_n_industry[FOLLOWER_STATS_CATEGORY_COLUMN], top_n_industry[FOLLOWER_STATS_ORGANIC_COLUMN], color='lightcoral')
265
+ ax_industry.set_title("Follower Distribution by Industry (Top 10 Organic)")
266
+ ax_industry.set_ylabel("Organic Follower Count")
267
+ plt.xticks(rotation=45, ha='right')
268
+ plt.grid(axis='y', linestyle='--', alpha=0.7)
269
+ plt.tight_layout()
270
+ plot_industry_dist = fig_industry
271
+ logging.info("Follower stats tab: Industry distribution plot generated.")
272
+ except Exception as e:
273
+ logging.error(f"Error processing or plotting industry data: {e}", exc_info=True)
274
+ html_parts.append("<p>Error displaying follower industry data.</p>")
275
+ else:
276
+ html_parts.append("<p>No follower industry data available or required columns missing.</p>")
277
+
278
+ html_parts.append("</div>")
279
+ follower_html_output = "\n".join(html_parts)
280
+ return follower_html_output, plot_monthly_gains, plot_seniority_dist, plot_industry_dist