GuglielmoTor commited on
Commit
deb2291
Β·
verified Β·
1 Parent(s): adab1ec

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -23
app.py CHANGED
@@ -32,7 +32,13 @@ from analytics_plot_generator import (
32
  generate_engagement_rate_over_time_plot,
33
  generate_reach_over_time_plot,
34
  generate_impressions_over_time_plot,
35
- create_placeholder_plot # For initializing plots
 
 
 
 
 
 
36
  )
37
 
38
  # Configure logging
@@ -45,14 +51,18 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
45
  """
46
  logging.info(f"Updating analytics plots. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")
47
 
 
 
 
48
  if not token_state_value or not token_state_value.get("token"):
49
  message = "❌ Access denied. No token. Cannot generate analytics."
50
  logging.warning(message)
51
- num_expected_plots = 13
52
  placeholder_figs = [create_placeholder_plot(title="Access Denied", message="No token.") for _ in range(num_expected_plots)]
53
  return [message] + placeholder_figs
54
 
55
  try:
 
 
56
  (filtered_merged_posts_df,
57
  filtered_mentions_df,
58
  date_filtered_follower_stats_df,
@@ -61,18 +71,22 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
61
  prepare_filtered_analytics_data(
62
  token_state_value, date_filter_option, custom_start_date, custom_end_date
63
  )
 
 
 
 
 
 
 
 
64
  except Exception as e:
65
  error_msg = f"❌ Error preparing analytics data: {e}"
66
  logging.error(error_msg, exc_info=True)
67
- num_expected_plots = 13
68
  placeholder_figs = [create_placeholder_plot(title="Data Preparation Error", message=str(e)) for _ in range(num_expected_plots)]
69
  return [error_msg] + placeholder_figs
70
 
71
  date_column_posts = token_state_value.get("config_date_col_posts", "published_at")
72
  date_column_mentions = token_state_value.get("config_date_col_mentions", "date")
73
- # This 'date_column_followers' from token_state is for the *source* DataFrame's date column,
74
- # but the plot generator now uses 'date_info_column' for the 'category_name' that holds date strings.
75
- # We'll use the default 'category_name' in the plot functions directly.
76
  # config_date_col_followers_source = token_state_value.get("config_date_col_followers", "date")
77
 
78
 
@@ -80,22 +94,19 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
80
  logging.info(f"Date-Filtered Follower Stats: {len(date_filtered_follower_stats_df)} rows, Raw Follower Stats: {len(raw_follower_stats_df)} rows.")
81
 
82
  try:
 
83
  plot_posts_activity = generate_posts_activity_plot(filtered_merged_posts_df, date_column=date_column_posts)
84
  plot_engagement_type = generate_engagement_type_plot(filtered_merged_posts_df)
85
  plot_mentions_activity = generate_mentions_activity_plot(filtered_mentions_df, date_column=date_column_mentions)
86
  plot_mention_sentiment = generate_mention_sentiment_plot(filtered_mentions_df)
87
 
88
- # Corrected calls for follower plots: use date_info_column (defaults to 'category_name' in plot generator)
89
  plot_followers_count = generate_followers_count_over_time_plot(
90
  date_filtered_follower_stats_df,
91
- # date_info_column is defaulted in the function to 'category_name'
92
- # organic_count_col, paid_count_col are defaulted
93
- type_filter_column='follower_count_type', # Ensure this column exists
94
  type_value='follower_gains_monthly'
95
  )
96
  plot_followers_growth_rate = generate_followers_growth_rate_plot(
97
  date_filtered_follower_stats_df,
98
- # date_info_column is defaulted
99
  type_filter_column='follower_count_type',
100
  type_value='follower_gains_monthly'
101
  )
@@ -109,6 +120,24 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
109
  plot_reach_over_time = generate_reach_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, reach_col='clickCount')
110
  plot_impressions_over_time = generate_impressions_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, impressions_col='impressionCount')
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  message = f"πŸ“Š Analytics updated for period: {date_filter_option}"
113
  if date_filter_option == "Custom Range":
114
  s_display = start_dt_for_msg.strftime('%Y-%m-%d') if start_dt_for_msg else "Any"
@@ -119,23 +148,46 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
119
  plot_posts_activity, plot_engagement_type, plot_mentions_activity, plot_mention_sentiment,
120
  plot_followers_count, plot_followers_growth_rate,
121
  plot_followers_by_location, plot_followers_by_role, plot_followers_by_industry, plot_followers_by_seniority,
122
- plot_engagement_rate, plot_reach_over_time, plot_impressions_over_time
 
 
 
 
123
  ]
124
  num_plots_generated = sum(1 for p in all_generated_plots if p is not None and not isinstance(p, str))
125
- logging.info(f"Successfully generated {num_plots_generated} plots.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
- return [message] + all_generated_plots
128
  except Exception as e:
129
  error_msg = f"❌ Error generating analytics plots: {e}"
130
  logging.error(error_msg, exc_info=True)
131
- num_expected_plots = 13
132
  placeholder_figs = [create_placeholder_plot(title="Plot Generation Error", message=str(e)) for _ in range(num_expected_plots)]
133
  return [error_msg] + placeholder_figs
134
 
135
 
136
  # --- Gradio UI Blocks ---
137
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
138
- title="LinkedIn Organization Dashboard") as app:
139
 
140
  token_state = gr.State(value={
141
  "token": None, "client_id": None, "org_urn": None,
@@ -143,11 +195,12 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
143
  "bubble_post_stats_df": pd.DataFrame(),
144
  "bubble_mentions_df": pd.DataFrame(),
145
  "bubble_follower_stats_df": pd.DataFrame(),
 
146
  "fetch_count_for_api": 0,
147
  "url_user_token_temp_storage": None,
148
  "config_date_col_posts": "published_at",
149
- "config_date_col_mentions": "date",
150
- "config_date_col_followers": "date" # This is for the original follower data's date column, if different from category_name for time series
151
  })
152
 
153
  gr.Markdown("# πŸš€ LinkedIn Organization Dashboard")
@@ -207,8 +260,8 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
207
  label="Select Date Range (for Posts, Mentions, and some Follower time-series)",
208
  value="Last 30 Days"
209
  )
210
- custom_start_date_picker = gr.DateTime(label="Start Date (Custom)", visible=False, include_time=False, type="string")
211
- custom_end_date_picker = gr.DateTime(label="End Date (Custom)", visible=False, include_time=False, type="string")
212
 
213
  apply_filter_btn = gr.Button("πŸ” Apply Filter & Refresh Analytics", variant="primary")
214
 
@@ -248,9 +301,22 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
248
  gr.Markdown("### Post Performance Insights (Filtered by Date)")
249
  with gr.Row():
250
  engagement_rate_plot = gr.Plot(label="Engagement Rate Over Time")
251
- with gr.Row():
252
- reach_over_time_plot = gr.Plot(label="Reach Over Time (Clicks)")
253
  impressions_over_time_plot = gr.Plot(label="Impressions Over Time")
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
  analytics_plot_outputs = [
256
  analytics_status_md, posts_activity_plot, engagement_type_plot,
@@ -258,7 +324,11 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
258
  followers_count_plot, followers_growth_rate_plot,
259
  followers_by_location_plot, followers_by_role_plot,
260
  followers_by_industry_plot, followers_by_seniority_plot,
261
- engagement_rate_plot, reach_over_time_plot, impressions_over_time_plot
 
 
 
 
262
  ]
263
 
264
  apply_filter_btn.click(
@@ -268,6 +338,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
268
  show_progress="full"
269
  )
270
 
 
271
  sync_click_event.then(
272
  fn=update_analytics_plots,
273
  inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
 
32
  generate_engagement_rate_over_time_plot,
33
  generate_reach_over_time_plot,
34
  generate_impressions_over_time_plot,
35
+ create_placeholder_plot, # For initializing plots
36
+ # --- Import new plot functions ---
37
+ generate_likes_over_time_plot,
38
+ generate_clicks_over_time_plot,
39
+ generate_shares_over_time_plot,
40
+ generate_comments_over_time_plot,
41
+ generate_comments_sentiment_breakdown_plot
42
  )
43
 
44
  # Configure logging
 
51
  """
52
  logging.info(f"Updating analytics plots. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")
53
 
54
+ # --- Increased number of expected plots ---
55
+ num_expected_plots = 18 # Was 13, added 5 new plots
56
+
57
  if not token_state_value or not token_state_value.get("token"):
58
  message = "❌ Access denied. No token. Cannot generate analytics."
59
  logging.warning(message)
 
60
  placeholder_figs = [create_placeholder_plot(title="Access Denied", message="No token.") for _ in range(num_expected_plots)]
61
  return [message] + placeholder_figs
62
 
63
  try:
64
+ # prepare_filtered_analytics_data might need to be updated if new DFs are required for new plots (e.g. comment sentiment)
65
+ # For now, we assume it returns the same set of DFs and new plots will try to use them or handle missing data.
66
  (filtered_merged_posts_df,
67
  filtered_mentions_df,
68
  date_filtered_follower_stats_df,
 
71
  prepare_filtered_analytics_data(
72
  token_state_value, date_filter_option, custom_start_date, custom_end_date
73
  )
74
+
75
+ # Hypothetical: If prepare_filtered_analytics_data was updated to return comment sentiment data:
76
+ # filtered_comments_with_sentiment_df = ... # (This would be the 7th item in the tuple)
77
+ # For now, we will pass filtered_merged_posts_df to generate_comments_sentiment_breakdown_plot,
78
+ # and that function will handle missing sentiment columns by showing a placeholder.
79
+ # Or, if you have comment sentiment data in another DataFrame in token_state, retrieve it here.
80
+ # e.g., comments_df_with_sentiment = token_state_value.get("bubble_comments_sentiment_df", pd.DataFrame())
81
+
82
  except Exception as e:
83
  error_msg = f"❌ Error preparing analytics data: {e}"
84
  logging.error(error_msg, exc_info=True)
 
85
  placeholder_figs = [create_placeholder_plot(title="Data Preparation Error", message=str(e)) for _ in range(num_expected_plots)]
86
  return [error_msg] + placeholder_figs
87
 
88
  date_column_posts = token_state_value.get("config_date_col_posts", "published_at")
89
  date_column_mentions = token_state_value.get("config_date_col_mentions", "date")
 
 
 
90
  # config_date_col_followers_source = token_state_value.get("config_date_col_followers", "date")
91
 
92
 
 
94
  logging.info(f"Date-Filtered Follower Stats: {len(date_filtered_follower_stats_df)} rows, Raw Follower Stats: {len(raw_follower_stats_df)} rows.")
95
 
96
  try:
97
+ # Existing plots
98
  plot_posts_activity = generate_posts_activity_plot(filtered_merged_posts_df, date_column=date_column_posts)
99
  plot_engagement_type = generate_engagement_type_plot(filtered_merged_posts_df)
100
  plot_mentions_activity = generate_mentions_activity_plot(filtered_mentions_df, date_column=date_column_mentions)
101
  plot_mention_sentiment = generate_mention_sentiment_plot(filtered_mentions_df)
102
 
 
103
  plot_followers_count = generate_followers_count_over_time_plot(
104
  date_filtered_follower_stats_df,
105
+ type_filter_column='follower_count_type',
 
 
106
  type_value='follower_gains_monthly'
107
  )
108
  plot_followers_growth_rate = generate_followers_growth_rate_plot(
109
  date_filtered_follower_stats_df,
 
110
  type_filter_column='follower_count_type',
111
  type_value='follower_gains_monthly'
112
  )
 
120
  plot_reach_over_time = generate_reach_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, reach_col='clickCount')
121
  plot_impressions_over_time = generate_impressions_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, impressions_col='impressionCount')
122
 
123
+ # --- Generate new plots ---
124
+ plot_likes_over_time = generate_likes_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, likes_col='likeCount')
125
+ plot_clicks_over_time = generate_clicks_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, clicks_col='clickCount')
126
+ plot_shares_over_time = generate_shares_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, shares_col='shareCount')
127
+ plot_comments_over_time = generate_comments_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, comments_col='commentCount')
128
+
129
+ # For comment sentiment, pass a DataFrame that is expected to have comment-level sentiment.
130
+ # If `filtered_merged_posts_df` is passed and lacks 'comment_sentiment' column, the plot function will show a placeholder.
131
+ # If you have a specific df for this, e.g., `filtered_comments_with_sentiment_df` from `prepare_filtered_analytics_data` (if modified)
132
+ # or from `token_state_value.get("bubble_comments_sentiment_df")`, use that one.
133
+ # For this example, we assume `filtered_merged_posts_df` is passed and the plot function handles it.
134
+ plot_comments_sentiment_breakdown = generate_comments_sentiment_breakdown_plot(
135
+ filtered_merged_posts_df, # Or your specific df with comment sentiments
136
+ sentiment_column='sentiment' # Assuming 'sentiment' column in post_df might be a proxy, or change to 'comment_sentiment' if that column exists
137
+ # The plot function will show a placeholder if this column isn't suitable or found.
138
+ )
139
+
140
+
141
  message = f"πŸ“Š Analytics updated for period: {date_filter_option}"
142
  if date_filter_option == "Custom Range":
143
  s_display = start_dt_for_msg.strftime('%Y-%m-%d') if start_dt_for_msg else "Any"
 
148
  plot_posts_activity, plot_engagement_type, plot_mentions_activity, plot_mention_sentiment,
149
  plot_followers_count, plot_followers_growth_rate,
150
  plot_followers_by_location, plot_followers_by_role, plot_followers_by_industry, plot_followers_by_seniority,
151
+ plot_engagement_rate, plot_reach_over_time, plot_impressions_over_time,
152
+ # --- Add new plot objects to the list ---
153
+ plot_likes_over_time, plot_clicks_over_time,
154
+ plot_shares_over_time, plot_comments_over_time,
155
+ plot_comments_sentiment_breakdown
156
  ]
157
  num_plots_generated = sum(1 for p in all_generated_plots if p is not None and not isinstance(p, str))
158
+ logging.info(f"Successfully generated {num_plots_generated} plots out of {num_expected_plots} expected.")
159
+
160
+ # Ensure the number of returned plots matches num_expected_plots, padding with placeholders if necessary
161
+ # This is crucial if some plot functions might return None on error and we need to match the Gradio outputs list length
162
+ final_plots_list = []
163
+ for p in all_generated_plots:
164
+ if p is not None and not isinstance(p, str): # isinstance check for safety, though plots should be figs
165
+ final_plots_list.append(p)
166
+ else: # If a plot failed and returned None or an error string (which it shouldn't, should be placeholder fig)
167
+ logging.warning(f"A plot generation failed or returned unexpected type, using placeholder. Plot: {p}")
168
+ final_plots_list.append(create_placeholder_plot(title="Plot Error", message="Failed to generate this plot."))
169
+
170
+ # If fewer plots were generated than expected (e.g. due to early exit or major error in a plot function)
171
+ while len(final_plots_list) < num_expected_plots:
172
+ logging.warning(f"Padding missing plot with placeholder. Expected {num_expected_plots}, got {len(final_plots_list)} so far.")
173
+ final_plots_list.append(create_placeholder_plot(title="Missing Plot", message="Plot could not be generated."))
174
+ if len(final_plots_list) > num_expected_plots + 5: # Safety break
175
+ logging.error("Too many placeholders added, breaking loop.")
176
+ break
177
+
178
+
179
+ return [message] + final_plots_list[:num_expected_plots] # Ensure correct number of outputs
180
 
 
181
  except Exception as e:
182
  error_msg = f"❌ Error generating analytics plots: {e}"
183
  logging.error(error_msg, exc_info=True)
 
184
  placeholder_figs = [create_placeholder_plot(title="Plot Generation Error", message=str(e)) for _ in range(num_expected_plots)]
185
  return [error_msg] + placeholder_figs
186
 
187
 
188
  # --- Gradio UI Blocks ---
189
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
190
+ title="LinkedIn Organization Dashboard") as app:
191
 
192
  token_state = gr.State(value={
193
  "token": None, "client_id": None, "org_urn": None,
 
195
  "bubble_post_stats_df": pd.DataFrame(),
196
  "bubble_mentions_df": pd.DataFrame(),
197
  "bubble_follower_stats_df": pd.DataFrame(),
198
+ # Consider adding "bubble_comments_sentiment_df": pd.DataFrame() if you plan to fetch this data
199
  "fetch_count_for_api": 0,
200
  "url_user_token_temp_storage": None,
201
  "config_date_col_posts": "published_at",
202
+ "config_date_col_mentions": "date",
203
+ "config_date_col_followers": "date"
204
  })
205
 
206
  gr.Markdown("# πŸš€ LinkedIn Organization Dashboard")
 
260
  label="Select Date Range (for Posts, Mentions, and some Follower time-series)",
261
  value="Last 30 Days"
262
  )
263
+ custom_start_date_picker = gr.DateTime(label="Start Date (Custom)", visible=False, include_time=False, type="datetime") # Changed to datetime
264
+ custom_end_date_picker = gr.DateTime(label="End Date (Custom)", visible=False, include_time=False, type="datetime") # Changed to datetime
265
 
266
  apply_filter_btn = gr.Button("πŸ” Apply Filter & Refresh Analytics", variant="primary")
267
 
 
301
  gr.Markdown("### Post Performance Insights (Filtered by Date)")
302
  with gr.Row():
303
  engagement_rate_plot = gr.Plot(label="Engagement Rate Over Time")
304
+ reach_over_time_plot = gr.Plot(label="Reach Over Time (Clicks)") # This was originally in its own row
305
+ with gr.Row(): # Moved impressions to be paired with reach if desired, or keep separate
306
  impressions_over_time_plot = gr.Plot(label="Impressions Over Time")
307
+ # New plots will start here, keeping 2 per row
308
+ likes_over_time_plot = gr.Plot(label="Reactions (Likes) Over Time")
309
+
310
+ gr.Markdown("### Detailed Post Engagement Over Time (Filtered by Date)")
311
+ with gr.Row():
312
+ clicks_over_time_plot = gr.Plot(label="Clicks Over Time")
313
+ shares_over_time_plot = gr.Plot(label="Shares Over Time")
314
+ with gr.Row():
315
+ comments_over_time_plot = gr.Plot(label="Comments Over Time")
316
+ # For the 5th new plot, "Breakdown of Comments by Sentiment"
317
+ # It will be alone in this row, or you can add another plot next to it later.
318
+ comments_sentiment_plot = gr.Plot(label="Breakdown of Comments by Sentiment")
319
+
320
 
321
  analytics_plot_outputs = [
322
  analytics_status_md, posts_activity_plot, engagement_type_plot,
 
324
  followers_count_plot, followers_growth_rate_plot,
325
  followers_by_location_plot, followers_by_role_plot,
326
  followers_by_industry_plot, followers_by_seniority_plot,
327
+ engagement_rate_plot, reach_over_time_plot, impressions_over_time_plot,
328
+ # --- Add new plot components to the output list in the correct order ---
329
+ likes_over_time_plot, clicks_over_time_plot,
330
+ shares_over_time_plot, comments_over_time_plot,
331
+ comments_sentiment_plot
332
  ]
333
 
334
  apply_filter_btn.click(
 
338
  show_progress="full"
339
  )
340
 
341
+ # Also update analytics after sync
342
  sync_click_event.then(
343
  fn=update_analytics_plots,
344
  inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],