GuglielmoTor commited on
Commit
c6716b6
Β·
verified Β·
1 Parent(s): 617c2c1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -52
app.py CHANGED
@@ -33,12 +33,16 @@ from analytics_plot_generator import (
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
@@ -52,7 +56,11 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
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."
@@ -61,8 +69,9 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
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,
@@ -72,12 +81,14 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
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}"
@@ -89,16 +100,17 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
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
-
93
  logging.info(f"Data for plotting - Filtered Merged Posts: {len(filtered_merged_posts_df)} rows, Filtered Mentions: {len(filtered_mentions_df)} rows.")
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,
@@ -120,23 +132,28 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
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":
@@ -145,36 +162,45 @@ def update_analytics_plots(token_state_value, date_filter_option, custom_start_d
145
  message += f" (From: {s_display} To: {e_display})"
146
 
147
  all_generated_plots = [
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
 
@@ -196,11 +222,15 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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")
@@ -280,10 +310,11 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
280
  posts_activity_plot = gr.Plot(label="Posts Activity Over Time")
281
  engagement_type_plot = gr.Plot(label="Post Engagement Types")
282
 
 
283
  gr.Markdown("### Mentions Overview (Filtered by Date)")
284
  with gr.Row():
285
- mentions_activity_plot = gr.Plot(label="Mentions Activity Over Time")
286
- mention_sentiment_plot = gr.Plot(label="Mention Sentiment Distribution")
287
 
288
  gr.Markdown("### Follower Dynamics")
289
  with gr.Row():
@@ -301,35 +332,57 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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,
323
- mentions_activity_plot, mention_sentiment_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(
335
  fn=update_analytics_plots,
 
33
  generate_reach_over_time_plot,
34
  generate_impressions_over_time_plot,
35
  create_placeholder_plot, # For initializing plots
36
+ # --- Import existing new plot functions ---
37
  generate_likes_over_time_plot,
38
+ generate_clicks_over_time_plot, # Note: can be same as reach
39
  generate_shares_over_time_plot,
40
  generate_comments_over_time_plot,
41
+ generate_comments_sentiment_breakdown_plot,
42
+ # --- Import NEW plot functions for Content Strategy ---
43
+ generate_post_frequency_plot,
44
+ generate_content_format_breakdown_plot,
45
+ generate_content_topic_breakdown_plot
46
  )
47
 
48
  # Configure logging
 
56
  logging.info(f"Updating analytics plots. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")
57
 
58
  # --- Increased number of expected plots ---
59
+ # Original 13 + 5 engagement = 18
60
+ # New Content Strategy (3: freq, format, topics)
61
+ # New Mention Analysis (2: volume, sentiment - these reuse existing plot objects but are new UI slots)
62
+ # Total = 18 + 3 + 2 = 23
63
+ num_expected_plots = 23
64
 
65
  if not token_state_value or not token_state_value.get("token"):
66
  message = "❌ Access denied. No token. Cannot generate analytics."
 
69
  return [message] + placeholder_figs
70
 
71
  try:
72
+ # prepare_filtered_analytics_data might need to be updated if new DFs are required for new plots
73
+ # (e.g. if 'media_type' or 'eb_labels' are not in 'bubble_posts_df' and need special handling)
74
+ # For now, we assume 'filtered_merged_posts_df' contains these columns.
75
  (filtered_merged_posts_df,
76
  filtered_mentions_df,
77
  date_filtered_follower_stats_df,
 
81
  token_state_value, date_filter_option, custom_start_date, custom_end_date
82
  )
83
 
84
+ # Ensure 'media_type' and 'eb_labels' exist in filtered_merged_posts_df for new plots,
85
+ # or handle their absence gracefully in the plot functions themselves (which they do).
86
+ # Example: Add dummy columns if they might be missing, for robust testing:
87
+ # if 'media_type' not in filtered_merged_posts_df.columns:
88
+ # filtered_merged_posts_df['media_type'] = 'Unknown'
89
+ # if 'eb_labels' not in filtered_merged_posts_df.columns:
90
+ # filtered_merged_posts_df['eb_labels'] = None
91
+
92
 
93
  except Exception as e:
94
  error_msg = f"❌ Error preparing analytics data: {e}"
 
100
  date_column_mentions = token_state_value.get("config_date_col_mentions", "date")
101
  # config_date_col_followers_source = token_state_value.get("config_date_col_followers", "date")
102
 
 
103
  logging.info(f"Data for plotting - Filtered Merged Posts: {len(filtered_merged_posts_df)} rows, Filtered Mentions: {len(filtered_mentions_df)} rows.")
104
  logging.info(f"Date-Filtered Follower Stats: {len(date_filtered_follower_stats_df)} rows, Raw Follower Stats: {len(raw_follower_stats_df)} rows.")
105
 
106
  try:
107
+ # Existing plots (13)
108
  plot_posts_activity = generate_posts_activity_plot(filtered_merged_posts_df, date_column=date_column_posts)
109
  plot_engagement_type = generate_engagement_type_plot(filtered_merged_posts_df)
110
+
111
+ # These two will be used for the new "Mention Analysis" section as well
112
+ fig_mentions_activity_shared = generate_mentions_activity_plot(filtered_mentions_df, date_column=date_column_mentions)
113
+ fig_mention_sentiment_shared = generate_mention_sentiment_plot(filtered_mentions_df)
114
 
115
  plot_followers_count = generate_followers_count_over_time_plot(
116
  date_filtered_follower_stats_df,
 
132
  plot_reach_over_time = generate_reach_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, reach_col='clickCount')
133
  plot_impressions_over_time = generate_impressions_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, impressions_col='impressionCount')
134
 
135
+ # Additional Engagement plots (5)
136
  plot_likes_over_time = generate_likes_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, likes_col='likeCount')
137
  plot_clicks_over_time = generate_clicks_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, clicks_col='clickCount')
138
  plot_shares_over_time = generate_shares_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, shares_col='shareCount')
139
  plot_comments_over_time = generate_comments_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts, comments_col='commentCount')
140
 
141
+ # Assuming 'comment_sentiment' column might exist, or 'sentiment' as fallback (handled in plot function)
 
 
 
 
142
  plot_comments_sentiment_breakdown = generate_comments_sentiment_breakdown_plot(
143
+ filtered_merged_posts_df,
144
+ sentiment_column='comment_sentiment'
 
145
  )
146
 
147
+ # --- Generate NEW plots for Content Strategy (3) ---
148
+ # Assuming 'media_type' and 'eb_labels' are in filtered_merged_posts_df
149
+ # The plot functions themselves have fallbacks/placeholders if columns are missing.
150
+ media_type_col_name = token_state_value.get("config_media_type_col", "media_type") # Example if configurable
151
+ eb_labels_col_name = token_state_value.get("config_eb_labels_col", "eb_labels") # Example if configurable
152
+
153
+ plot_post_frequency = generate_post_frequency_plot(filtered_merged_posts_df, date_column=date_column_posts)
154
+ plot_content_format_breakdown = generate_content_format_breakdown_plot(filtered_merged_posts_df, format_col=media_type_col_name)
155
+ plot_content_topic_breakdown = generate_content_topic_breakdown_plot(filtered_merged_posts_df, topics_col=eb_labels_col_name)
156
+
157
 
158
  message = f"πŸ“Š Analytics updated for period: {date_filter_option}"
159
  if date_filter_option == "Custom Range":
 
162
  message += f" (From: {s_display} To: {e_display})"
163
 
164
  all_generated_plots = [
165
+ plot_posts_activity, plot_engagement_type,
166
+ fig_mentions_activity_shared, fig_mention_sentiment_shared, # Original mention plots
167
  plot_followers_count, plot_followers_growth_rate,
168
  plot_followers_by_location, plot_followers_by_role, plot_followers_by_industry, plot_followers_by_seniority,
169
  plot_engagement_rate, plot_reach_over_time, plot_impressions_over_time,
170
+ # Add new engagement plot objects to the list
171
  plot_likes_over_time, plot_clicks_over_time,
172
  plot_shares_over_time, plot_comments_over_time,
173
+ plot_comments_sentiment_breakdown,
174
+ # --- Add NEW Content Strategy plot objects ---
175
+ plot_post_frequency, plot_content_format_breakdown, plot_content_topic_breakdown,
176
+ # --- Add plots for the NEW "Mention Analysis" section (reusing figures) ---
177
+ fig_mentions_activity_shared, # Reused figure for new UI slot
178
+ fig_mention_sentiment_shared # Reused figure for new UI slot
179
  ]
180
  num_plots_generated = sum(1 for p in all_generated_plots if p is not None and not isinstance(p, str))
181
+ logging.info(f"Successfully generated {num_plots_generated} plot figures for {num_expected_plots} UI slots.")
182
 
183
  # Ensure the number of returned plots matches num_expected_plots, padding with placeholders if necessary
 
184
  final_plots_list = []
185
+ for i, p in enumerate(all_generated_plots): # Iterate up to the expected number of plots
186
+ if i < num_expected_plots: # Ensure we don't exceed the expected number of outputs
187
+ if p is not None and not isinstance(p, str): # isinstance check for safety
188
+ final_plots_list.append(p)
189
+ else:
190
+ logging.warning(f"A plot generation failed or returned unexpected type for slot {i}, using placeholder. Plot: {p}")
191
+ final_plots_list.append(create_placeholder_plot(title="Plot Error", message="Failed to generate this plot."))
192
+ else:
193
+ logging.warning(f"Generated more plot figures ({len(all_generated_plots)}) than expected UI slots ({num_expected_plots}). Truncating.")
194
+ break
195
+
196
 
197
  # If fewer plots were generated than expected (e.g. due to early exit or major error in a plot function)
198
  while len(final_plots_list) < num_expected_plots:
199
  logging.warning(f"Padding missing plot with placeholder. Expected {num_expected_plots}, got {len(final_plots_list)} so far.")
200
  final_plots_list.append(create_placeholder_plot(title="Missing Plot", message="Plot could not be generated."))
201
  if len(final_plots_list) > num_expected_plots + 5: # Safety break
202
+ logging.error("Too many placeholders added, breaking loop.")
203
+ break
 
204
 
205
  return [message] + final_plots_list[:num_expected_plots] # Ensure correct number of outputs
206
 
 
222
  "bubble_mentions_df": pd.DataFrame(),
223
  "bubble_follower_stats_df": pd.DataFrame(),
224
  # Consider adding "bubble_comments_sentiment_df": pd.DataFrame() if you plan to fetch this data
225
+ # Add keys for new data if needed by prepare_filtered_analytics_data, e.g.
226
+ # "bubble_posts_with_content_details_df": pd.DataFrame(),
227
  "fetch_count_for_api": 0,
228
  "url_user_token_temp_storage": None,
229
  "config_date_col_posts": "published_at",
230
  "config_date_col_mentions": "date",
231
+ "config_date_col_followers": "date",
232
+ "config_media_type_col": "media_type", # For new plot
233
+ "config_eb_labels_col": "eb_labels" # For new plot
234
  })
235
 
236
  gr.Markdown("# πŸš€ LinkedIn Organization Dashboard")
 
310
  posts_activity_plot = gr.Plot(label="Posts Activity Over Time")
311
  engagement_type_plot = gr.Plot(label="Post Engagement Types")
312
 
313
+ # Original Mentions Overview - these plots will also be used for the "Mention Analysis" section below
314
  gr.Markdown("### Mentions Overview (Filtered by Date)")
315
  with gr.Row():
316
+ mentions_activity_plot = gr.Plot(label="Mentions Activity Over Time") # Will be updated by fig_mentions_activity_shared
317
+ mention_sentiment_plot = gr.Plot(label="Mention Sentiment Distribution") # Will be updated by fig_mention_sentiment_shared
318
 
319
  gr.Markdown("### Follower Dynamics")
320
  with gr.Row():
 
332
  gr.Markdown("### Post Performance Insights (Filtered by Date)")
333
  with gr.Row():
334
  engagement_rate_plot = gr.Plot(label="Engagement Rate Over Time")
335
+ reach_over_time_plot = gr.Plot(label="Reach Over Time (Clicks)")
336
+ with gr.Row():
337
  impressions_over_time_plot = gr.Plot(label="Impressions Over Time")
 
338
  likes_over_time_plot = gr.Plot(label="Reactions (Likes) Over Time")
339
 
340
  gr.Markdown("### Detailed Post Engagement Over Time (Filtered by Date)")
341
  with gr.Row():
342
+ clicks_over_time_plot = gr.Plot(label="Clicks Over Time") # Can be same as reach
343
  shares_over_time_plot = gr.Plot(label="Shares Over Time")
344
  with gr.Row():
345
  comments_over_time_plot = gr.Plot(label="Comments Over Time")
 
 
346
  comments_sentiment_plot = gr.Plot(label="Breakdown of Comments by Sentiment")
347
 
348
+ # --- NEW: Content Strategy Analysis ---
349
+ gr.Markdown("### πŸ“Š Content Strategy Analysis (Filtered by Date)")
350
+ with gr.Row():
351
+ post_frequency_cs_plot = gr.Plot(label="Post Frequency") # New plot component
352
+ content_format_breakdown_cs_plot = gr.Plot(label="Breakdown of Content by Format") # New
353
+ with gr.Row():
354
+ content_topic_breakdown_cs_plot = gr.Plot(label="Breakdown of Content by Topics") # New (might need more width)
355
+ # You can add another plot here or make the topic plot wider if needed, e.g. by itself in a row.
356
+ # For now, placing it here. If it's too cramped:
357
+ # content_topic_breakdown_cs_plot = gr.Plot(label="Breakdown of Content by Topics", elem_id="topic_plot_wide") # and use CSS for width if needed
358
+
359
+ # --- NEW: Mention Analysis (reusing plots from above) ---
360
+ gr.Markdown("### πŸ’¬ Mention Analysis (Filtered by Date)")
361
+ with gr.Row():
362
+ mention_analysis_volume_plot = gr.Plot(label="Mentions Volume Over Time") # New UI slot, uses fig_mentions_activity_shared
363
+ mention_analysis_sentiment_plot = gr.Plot(label="Breakdown of Mentions by Sentiment") # New UI slot, uses fig_mention_sentiment_shared
364
+
365
 
366
  analytics_plot_outputs = [
367
+ analytics_status_md,
368
+ posts_activity_plot, engagement_type_plot,
369
+ mentions_activity_plot, mention_sentiment_plot, # Original mention plots
370
  followers_count_plot, followers_growth_rate_plot,
371
  followers_by_location_plot, followers_by_role_plot,
372
  followers_by_industry_plot, followers_by_seniority_plot,
373
  engagement_rate_plot, reach_over_time_plot, impressions_over_time_plot,
374
+ # Add new engagement plot components to the output list
375
  likes_over_time_plot, clicks_over_time_plot,
376
  shares_over_time_plot, comments_over_time_plot,
377
+ comments_sentiment_plot,
378
+ # --- Add NEW Content Strategy plot components ---
379
+ post_frequency_cs_plot, content_format_breakdown_cs_plot, content_topic_breakdown_cs_plot,
380
+ # --- Add NEW Mention Analysis plot components (these will receive the reused figures) ---
381
+ mention_analysis_volume_plot, mention_analysis_sentiment_plot
382
  ]
383
+ # Expected length: 1 (status) + 13 (original plots) + 5 (new engagement) + 3 (content strategy) + 2 (mention analysis) = 24
384
+ # The update_analytics_plots function returns message + 23 plots. So len(analytics_plot_outputs) should be 24.
385
+ # Current count: 1 + 2 + 2 + 2 + 4 + 3 + 5 + 3 + 2 = 24. Correct.
386
 
387
  apply_filter_btn.click(
388
  fn=update_analytics_plots,