GuglielmoTor commited on
Commit
3b4dccb
Β·
verified Β·
1 Parent(s): 8019346

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +119 -34
app.py CHANGED
@@ -21,11 +21,23 @@ from ui_generators import (
21
  run_mentions_tab_display,
22
  run_follower_stats_tab_display
23
  )
24
- import analytics_plot_generators
25
- import analytics_data_processing
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  # Configure logging
28
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
29
 
30
  # --- Analytics Tab: Plot Update Function ---
31
  def update_analytics_plots(token_state_value, date_filter_option, custom_start_date, custom_end_date):
@@ -37,34 +49,73 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
37
  if not token_state_value or not token_state_value.get("token"):
38
  message = "❌ Access denied. No token. Cannot generate analytics."
39
  logging.warning(message)
40
- return message, None, None, None, None, None
 
 
 
41
 
42
- # --- Prepare Data (Moved to analytics_data_processing) ---
43
  try:
44
- filtered_posts_df, filtered_mentions_df, follower_stats_df, start_dt_for_msg, end_dt_for_msg = \
45
- analytics_data_processing.prepare_filtered_analytics_data(
 
 
 
 
 
46
  token_state_value, date_filter_option, custom_start_date, custom_end_date
47
  )
48
  except Exception as e:
49
  error_msg = f"❌ Error preparing analytics data: {e}"
50
  logging.error(error_msg, exc_info=True)
51
- return error_msg, None, None, None, None, None
 
 
52
 
53
- # Date column names (still needed for plot generators)
54
  date_column_posts = token_state_value.get("config_date_col_posts", "published_at")
55
  date_column_mentions = token_state_value.get("config_date_col_mentions", "date")
56
  date_column_followers = token_state_value.get("config_date_col_followers", "date")
57
 
58
- logging.info(f"Data for plotting - Filtered posts: {len(filtered_posts_df)} rows, Filtered Mentions: {len(filtered_mentions_df)} rows.")
59
- logging.info(f"Follower stats (unfiltered by global range): {len(follower_stats_df)} rows.")
60
 
61
  # --- Generate Plots ---
62
  try:
63
- plot_posts_activity = analytics_plot_generators.generate_posts_activity_plot(filtered_posts_df, date_column_posts)
64
- plot_engagement_type = analytics_plot_generators.generate_engagement_type_plot(filtered_posts_df)
65
- plot_mentions_activity = analytics_plot_generators.generate_mentions_activity_plot(filtered_mentions_df, date_column_mentions)
66
- plot_mention_sentiment = analytics_plot_generators.generate_mention_sentiment_plot(filtered_mentions_df)
67
- plot_follower_growth = analytics_plot_generators.generate_follower_growth_plot(follower_stats_df, date_column_followers)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
  message = f"πŸ“Š Analytics updated for period: {date_filter_option}"
70
  if date_filter_option == "Custom Range":
@@ -72,14 +123,22 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
72
  e_display = end_dt_for_msg.strftime('%Y-%m-%d') if end_dt_for_msg else "Any"
73
  message += f" (From: {s_display} To: {e_display})"
74
 
75
- num_plots_generated = sum(1 for p in [plot_posts_activity, plot_engagement_type, plot_mentions_activity, plot_mention_sentiment, plot_follower_growth] if p is not None)
 
 
 
 
 
 
76
  logging.info(f"Successfully generated {num_plots_generated} plots.")
77
 
78
- return message, plot_posts_activity, plot_engagement_type, plot_mentions_activity, plot_mention_sentiment, plot_follower_growth
79
  except Exception as e:
80
  error_msg = f"❌ Error generating analytics plots: {e}"
81
  logging.error(error_msg, exc_info=True)
82
- return error_msg, None, None, None, None, None
 
 
83
 
84
 
85
  # --- Gradio UI Blocks ---
@@ -88,12 +147,14 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
88
 
89
  token_state = gr.State(value={
90
  "token": None, "client_id": None, "org_urn": None,
91
- "bubble_posts_df": pd.DataFrame(), "fetch_count_for_api": 0,
 
92
  "bubble_mentions_df": pd.DataFrame(),
93
  "bubble_follower_stats_df": pd.DataFrame(),
 
94
  "url_user_token_temp_storage": None,
95
  "config_date_col_posts": "published_at",
96
- "config_date_col_mentions": "date",
97
  "config_date_col_followers": "date"
98
  })
99
 
@@ -107,7 +168,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
107
  def initial_load_sequence(url_token, org_urn_val, current_state):
108
  logging.info(f"Initial load sequence triggered. Org URN: {org_urn_val}, URL Token: {'Present' if url_token else 'Absent'}")
109
  status_msg, new_state, btn_update = process_and_store_bubble_token(url_token, org_urn_val, current_state)
110
- dashboard_content = display_main_dashboard(new_state)
111
  return status_msg, new_state, btn_update, dashboard_content
112
 
113
  with gr.Tabs() as tabs:
@@ -144,17 +205,16 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
144
 
145
  with gr.TabItem("2️⃣ Analytics", id="tab_analytics"):
146
  gr.Markdown("## πŸ“ˆ LinkedIn Performance Analytics")
147
- gr.Markdown("Select a date range to filter Posts and Mentions analytics. Follower analytics show overall trends and are not affected by this date filter.")
148
 
149
  analytics_status_md = gr.Markdown("Analytics status will appear here...")
150
 
151
  with gr.Row():
152
  date_filter_selector = gr.Radio(
153
  ["All Time", "Last 7 Days", "Last 30 Days", "Custom Range"],
154
- label="Select Date Range (for Posts & Mentions)",
155
  value="Last 30 Days"
156
  )
157
- # Corrected to gr.DateTime
158
  custom_start_date_picker = gr.DateTime(label="Start Date (Custom)", visible=False, include_time=False, type="string")
159
  custom_end_date_picker = gr.DateTime(label="End Date (Custom)", visible=False, include_time=False, type="string")
160
 
@@ -164,7 +224,6 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
164
  is_custom = selection == "Custom Range"
165
  return gr.update(visible=is_custom), gr.update(visible=is_custom)
166
 
167
-
168
  date_filter_selector.change(
169
  fn=toggle_custom_date_pickers,
170
  inputs=[date_filter_selector],
@@ -181,32 +240,58 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
181
  mentions_activity_plot = gr.Plot(label="Mentions Activity Over Time")
182
  mention_sentiment_plot = gr.Plot(label="Mention Sentiment Distribution")
183
 
184
- gr.Markdown("### Follower Overview (Not Filtered by Date Range Selector)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  with gr.Row():
186
- follower_growth_plot = gr.Plot(label="Follower Growth Over Time")
 
 
 
 
 
 
 
 
 
 
 
187
 
188
  apply_filter_btn.click(
189
  fn=update_analytics_plots,
190
  inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
191
- outputs=[analytics_status_md, posts_activity_plot, engagement_type_plot, mentions_activity_plot, mention_sentiment_plot, follower_growth_plot],
192
  show_progress="full"
193
  )
194
 
195
- sync_click_event.then(
 
196
  fn=update_analytics_plots,
197
  inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
198
- outputs=[analytics_status_md, posts_activity_plot, engagement_type_plot, mentions_activity_plot, mention_sentiment_plot, follower_growth_plot],
199
  show_progress="full"
200
  )
201
 
202
-
203
  with gr.TabItem("3️⃣ Mentions", id="tab_mentions"):
204
  refresh_mentions_display_btn = gr.Button("πŸ”„ Refresh Mentions Display (from local data)", variant="secondary")
205
  mentions_html = gr.HTML("Mentions data loads from Bubble after sync. Click refresh to view current local data.")
206
  mentions_sentiment_dist_plot = gr.Plot(label="Mention Sentiment Distribution")
207
  refresh_mentions_display_btn.click(
208
  fn=run_mentions_tab_display, inputs=[token_state],
209
- outputs=[mentions_html, mentions_sentiment_dist_plot],
210
  show_progress="full"
211
  )
212
 
@@ -221,7 +306,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
221
 
222
  refresh_follower_stats_btn.click(
223
  fn=run_follower_stats_tab_display, inputs=[token_state],
224
- outputs=[follower_stats_html, fs_plot_monthly_gains, fs_plot_seniority, fs_plot_industry],
225
  show_progress="full"
226
  )
227
 
 
21
  run_mentions_tab_display,
22
  run_follower_stats_tab_display
23
  )
24
+ # Corrected import for analytics_data_processing
25
+ from analytics_data_processing import prepare_filtered_analytics_data
26
+ from analytics_plot_generator import (
27
+ generate_posts_activity_plot, generate_engagement_type_plot,
28
+ generate_mentions_activity_plot, generate_mention_sentiment_plot,
29
+ # generate_total_follower_growth_plot, # Kept for reference, decide if needed
30
+ generate_followers_count_over_time_plot,
31
+ generate_followers_growth_rate_plot,
32
+ generate_followers_by_demographics_plot,
33
+ generate_engagement_rate_over_time_plot,
34
+ generate_reach_over_time_plot,
35
+ generate_impressions_over_time_plot,
36
+ create_placeholder_plot # For initializing plots
37
+ )
38
 
39
  # Configure logging
40
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
41
 
42
  # --- Analytics Tab: Plot Update Function ---
43
  def update_analytics_plots(token_state_value, date_filter_option, custom_start_date, custom_end_date):
 
49
  if not token_state_value or not token_state_value.get("token"):
50
  message = "❌ Access denied. No token. Cannot generate analytics."
51
  logging.warning(message)
52
+ # Return placeholders for all plots
53
+ num_expected_plots = 13 # Number of plots we expect to generate
54
+ placeholder_figs = [create_placeholder_plot(title="Access Denied", message="No token.") for _ in range(num_expected_plots)]
55
+ return [message] + placeholder_figs
56
 
57
+ # --- Prepare Data ---
58
  try:
59
+ # Updated unpacking to match prepare_filtered_analytics_data return signature
60
+ (filtered_merged_posts_df,
61
+ filtered_mentions_df,
62
+ date_filtered_follower_stats_df,
63
+ raw_follower_stats_df,
64
+ start_dt_for_msg, end_dt_for_msg) = \
65
+ prepare_filtered_analytics_data( # Direct call to imported function
66
  token_state_value, date_filter_option, custom_start_date, custom_end_date
67
  )
68
  except Exception as e:
69
  error_msg = f"❌ Error preparing analytics data: {e}"
70
  logging.error(error_msg, exc_info=True)
71
+ num_expected_plots = 13
72
+ placeholder_figs = [create_placeholder_plot(title="Data Preparation Error", message=str(e)) for _ in range(num_expected_plots)]
73
+ return [error_msg] + placeholder_figs
74
 
75
+ # Date column names (still needed for plot generators if not passed as args or if defaults are not suitable)
76
  date_column_posts = token_state_value.get("config_date_col_posts", "published_at")
77
  date_column_mentions = token_state_value.get("config_date_col_mentions", "date")
78
  date_column_followers = token_state_value.get("config_date_col_followers", "date")
79
 
80
+ logging.info(f"Data for plotting - Filtered Merged Posts: {len(filtered_merged_posts_df)} rows, Filtered Mentions: {len(filtered_mentions_df)} rows.")
81
+ logging.info(f"Date-Filtered Follower Stats: {len(date_filtered_follower_stats_df)} rows, Raw Follower Stats: {len(raw_follower_stats_df)} rows.")
82
 
83
  # --- Generate Plots ---
84
  try:
85
+ # Existing plots (using filtered_merged_posts_df for post-related plots)
86
+ plot_posts_activity = generate_posts_activity_plot(filtered_merged_posts_df, date_column=date_column_posts)
87
+ # Ensure likeCount, commentCount, shareCount are in filtered_merged_posts_df
88
+ plot_engagement_type = generate_engagement_type_plot(filtered_merged_posts_df)
89
+ plot_mentions_activity = generate_mentions_activity_plot(filtered_mentions_df, date_column=date_column_mentions)
90
+ plot_mention_sentiment = generate_mention_sentiment_plot(filtered_mentions_df)
91
+
92
+ # New Follower Dynamics Plots
93
+ plot_followers_count = generate_followers_count_over_time_plot(
94
+ date_filtered_follower_stats_df,
95
+ date_column=date_column_followers, # Make sure 'date' is the correct column name
96
+ count_column='follower_count_o', # Or relevant count column
97
+ type_filter_column='follower_count_type',
98
+ type_value='follower_gains_monthly' # As per your requirement
99
+ )
100
+ plot_followers_growth_rate = generate_followers_growth_rate_plot(
101
+ date_filtered_follower_stats_df,
102
+ date_column=date_column_followers,
103
+ count_column='follower_count_o',
104
+ type_filter_column='follower_count_type',
105
+ type_value='follower_gains_monthly'
106
+ )
107
+
108
+ # New Follower Demographics Plots (using raw_follower_stats_df)
109
+ plot_followers_by_location = generate_followers_by_demographics_plot(raw_follower_stats_df, category_col='category_name', count_column='follower_count_o', type_filter_column='follower_count_type', type_value='follower_geo', plot_title="Followers by Location")
110
+ plot_followers_by_role = generate_followers_by_demographics_plot(raw_follower_stats_df, category_col='category_name', count_column='follower_count_o', type_filter_column='follower_count_type', type_value='follower_function', plot_title="Followers by Role")
111
+ plot_followers_by_industry = generate_followers_by_demographics_plot(raw_follower_stats_df, category_col='category_name', count_column='follower_count_o', type_filter_column='follower_count_type', type_value='follower_industry', plot_title="Followers by Industry")
112
+ plot_followers_by_seniority = generate_followers_by_demographics_plot(raw_follower_stats_df, category_col='category_name', count_column='follower_count_o', type_filter_column='follower_count_type', type_value='follower_seniority', plot_title="Followers by Seniority")
113
+
114
+ # New Post Engagement/Performance Plots (using filtered_merged_posts_df)
115
+ # Ensure 'engagement', 'clickCount', 'impressionCount' are in filtered_merged_posts_df
116
+ plot_engagement_rate = generate_engagement_rate_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, engagement_rate_col='engagement')
117
+ plot_reach_over_time = generate_reach_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, reach_col='clickCount')
118
+ plot_impressions_over_time = generate_impressions_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, impressions_col='impressionCount')
119
 
120
  message = f"πŸ“Š Analytics updated for period: {date_filter_option}"
121
  if date_filter_option == "Custom Range":
 
123
  e_display = end_dt_for_msg.strftime('%Y-%m-%d') if end_dt_for_msg else "Any"
124
  message += f" (From: {s_display} To: {e_display})"
125
 
126
+ all_generated_plots = [
127
+ plot_posts_activity, plot_engagement_type, plot_mentions_activity, plot_mention_sentiment,
128
+ plot_followers_count, plot_followers_growth_rate,
129
+ plot_followers_by_location, plot_followers_by_role, plot_followers_by_industry, plot_followers_by_seniority,
130
+ plot_engagement_rate, plot_reach_over_time, plot_impressions_over_time
131
+ ]
132
+ num_plots_generated = sum(1 for p in all_generated_plots if p is not None and not isinstance(p, str)) # Check it's a figure
133
  logging.info(f"Successfully generated {num_plots_generated} plots.")
134
 
135
+ return [message] + all_generated_plots
136
  except Exception as e:
137
  error_msg = f"❌ Error generating analytics plots: {e}"
138
  logging.error(error_msg, exc_info=True)
139
+ num_expected_plots = 13
140
+ placeholder_figs = [create_placeholder_plot(title="Plot Generation Error", message=str(e)) for _ in range(num_expected_plots)]
141
+ return [error_msg] + placeholder_figs
142
 
143
 
144
  # --- Gradio UI Blocks ---
 
147
 
148
  token_state = gr.State(value={
149
  "token": None, "client_id": None, "org_urn": None,
150
+ "bubble_posts_df": pd.DataFrame(),
151
+ "bubble_post_stats_df": pd.DataFrame(), # Added for merged post data
152
  "bubble_mentions_df": pd.DataFrame(),
153
  "bubble_follower_stats_df": pd.DataFrame(),
154
+ "fetch_count_for_api": 0, # Retained from original if used elsewhere
155
  "url_user_token_temp_storage": None,
156
  "config_date_col_posts": "published_at",
157
+ "config_date_col_mentions": "date",
158
  "config_date_col_followers": "date"
159
  })
160
 
 
168
  def initial_load_sequence(url_token, org_urn_val, current_state):
169
  logging.info(f"Initial load sequence triggered. Org URN: {org_urn_val}, URL Token: {'Present' if url_token else 'Absent'}")
170
  status_msg, new_state, btn_update = process_and_store_bubble_token(url_token, org_urn_val, current_state)
171
+ dashboard_content = display_main_dashboard(new_state) # Assumes this function exists and works
172
  return status_msg, new_state, btn_update, dashboard_content
173
 
174
  with gr.Tabs() as tabs:
 
205
 
206
  with gr.TabItem("2️⃣ Analytics", id="tab_analytics"):
207
  gr.Markdown("## πŸ“ˆ LinkedIn Performance Analytics")
208
+ gr.Markdown("Select a date range to filter Posts and Mentions analytics. Follower demographic plots show overall latest data. Follower time-series plots respect the selected date range if applicable to their data source (e.g. monthly gains).")
209
 
210
  analytics_status_md = gr.Markdown("Analytics status will appear here...")
211
 
212
  with gr.Row():
213
  date_filter_selector = gr.Radio(
214
  ["All Time", "Last 7 Days", "Last 30 Days", "Custom Range"],
215
+ label="Select Date Range (for Posts, Mentions, and some Follower time-series)",
216
  value="Last 30 Days"
217
  )
 
218
  custom_start_date_picker = gr.DateTime(label="Start Date (Custom)", visible=False, include_time=False, type="string")
219
  custom_end_date_picker = gr.DateTime(label="End Date (Custom)", visible=False, include_time=False, type="string")
220
 
 
224
  is_custom = selection == "Custom Range"
225
  return gr.update(visible=is_custom), gr.update(visible=is_custom)
226
 
 
227
  date_filter_selector.change(
228
  fn=toggle_custom_date_pickers,
229
  inputs=[date_filter_selector],
 
240
  mentions_activity_plot = gr.Plot(label="Mentions Activity Over Time")
241
  mention_sentiment_plot = gr.Plot(label="Mention Sentiment Distribution")
242
 
243
+ gr.Markdown("### Follower Dynamics")
244
+ with gr.Row():
245
+ followers_count_plot = gr.Plot(label="Followers Count Over Time (e.g., Monthly Gains)")
246
+ followers_growth_rate_plot = gr.Plot(label="Followers Growth Rate (e.g., Monthly Gains)")
247
+
248
+ gr.Markdown("### Follower Demographics (Overall Latest Data)")
249
+ with gr.Row():
250
+ followers_by_location_plot = gr.Plot(label="Followers by Location")
251
+ followers_by_role_plot = gr.Plot(label="Followers by Role (Function)")
252
+ with gr.Row():
253
+ followers_by_industry_plot = gr.Plot(label="Followers by Industry")
254
+ followers_by_seniority_plot = gr.Plot(label="Followers by Seniority")
255
+
256
+ gr.Markdown("### Post Performance Insights (Filtered by Date)")
257
+ with gr.Row(): # Single plot, can take full width or be in a row
258
+ engagement_rate_plot = gr.Plot(label="Engagement Rate Over Time")
259
  with gr.Row():
260
+ reach_over_time_plot = gr.Plot(label="Reach Over Time (Clicks)")
261
+ impressions_over_time_plot = gr.Plot(label="Impressions Over Time")
262
+
263
+ # Define all plot outputs for the analytics tab
264
+ analytics_plot_outputs = [
265
+ analytics_status_md, posts_activity_plot, engagement_type_plot,
266
+ mentions_activity_plot, mention_sentiment_plot,
267
+ followers_count_plot, followers_growth_rate_plot,
268
+ followers_by_location_plot, followers_by_role_plot,
269
+ followers_by_industry_plot, followers_by_seniority_plot,
270
+ engagement_rate_plot, reach_over_time_plot, impressions_over_time_plot
271
+ ]
272
 
273
  apply_filter_btn.click(
274
  fn=update_analytics_plots,
275
  inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
276
+ outputs=analytics_plot_outputs,
277
  show_progress="full"
278
  )
279
 
280
+ # Also update analytics plots after a data sync
281
+ sync_click_event.then( # Chaining after the sync operations
282
  fn=update_analytics_plots,
283
  inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
284
+ outputs=analytics_plot_outputs,
285
  show_progress="full"
286
  )
287
 
 
288
  with gr.TabItem("3️⃣ Mentions", id="tab_mentions"):
289
  refresh_mentions_display_btn = gr.Button("πŸ”„ Refresh Mentions Display (from local data)", variant="secondary")
290
  mentions_html = gr.HTML("Mentions data loads from Bubble after sync. Click refresh to view current local data.")
291
  mentions_sentiment_dist_plot = gr.Plot(label="Mention Sentiment Distribution")
292
  refresh_mentions_display_btn.click(
293
  fn=run_mentions_tab_display, inputs=[token_state],
294
+ outputs=[mentions_html, mentions_sentiment_dist_plot], # Assuming run_mentions_tab_display returns these
295
  show_progress="full"
296
  )
297
 
 
306
 
307
  refresh_follower_stats_btn.click(
308
  fn=run_follower_stats_tab_display, inputs=[token_state],
309
+ outputs=[follower_stats_html, fs_plot_monthly_gains, fs_plot_seniority, fs_plot_industry], # Assuming this matches
310
  show_progress="full"
311
  )
312