GuglielmoTor commited on
Commit
63031db
·
verified ·
1 Parent(s): fd8976c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +321 -64
app.py CHANGED
@@ -8,7 +8,8 @@ import matplotlib
8
  matplotlib.use('Agg') # Set backend for Matplotlib to avoid GUI conflicts with Gradio
9
  import matplotlib.pyplot as plt
10
  import time # For profiling if needed
11
- from datetime import datetime
 
12
 
13
  # --- Module Imports ---
14
  from gradio_utils import get_url_user_token
@@ -86,89 +87,345 @@ def generate_chatbot_data_summaries(
86
  plot_configs_list,
87
  filtered_merged_posts_df,
88
  filtered_mentions_df,
89
- date_filtered_follower_stats_df,
90
- raw_follower_stats_df,
91
- token_state_value # To get column names if needed
92
  ):
93
  """
94
- Generates textual summaries for each plot ID to be used by the chatbot.
 
95
  """
96
  data_summaries = {}
97
- date_col_posts = token_state_value.get("config_date_col_posts", "published_at")
98
 
99
- # Ensure date columns are datetime objects for proper formatting and comparison
100
- # Make copies to avoid SettingWithCopyWarning if dataframes are slices
101
- if not filtered_merged_posts_df.empty and date_col_posts in filtered_merged_posts_df.columns:
102
- # Use .loc to ensure modification happens on the correct DataFrame
103
- filtered_merged_posts_df = filtered_merged_posts_df.copy()
104
- filtered_merged_posts_df[date_col_posts] = pd.to_datetime(filtered_merged_posts_df[date_col_posts], errors='coerce')
 
 
105
 
106
- if not date_filtered_follower_stats_df.empty and 'date' in date_filtered_follower_stats_df.columns:
107
- date_filtered_follower_stats_df = date_filtered_follower_stats_df.copy()
108
- date_filtered_follower_stats_df['date'] = pd.to_datetime(date_filtered_follower_stats_df['date'], errors='coerce')
109
- # Add more date conversions as needed for other dataframes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
  for plot_cfg in plot_configs_list:
112
  plot_id = plot_cfg["id"]
113
- summary_text = f"Nessun sommario dati specifico disponibile per '{plot_cfg['label']}' per il periodo selezionato." # Ensure this matches prompt logic
 
114
 
115
  try:
116
- if plot_id == "followers_count" and not date_filtered_follower_stats_df.empty and 'follower_gains_monthly' in date_filtered_follower_stats_df.columns and 'date' in date_filtered_follower_stats_df.columns:
117
- df_summary = date_filtered_follower_stats_df[['date', 'follower_gains_monthly']].copy()
118
- df_summary['date'] = df_summary['date'].dt.strftime('%Y-%m-%d')
119
- summary_text = f"Follower Count (Monthly Gains):\n{df_summary.sort_values(by='date').tail(5).to_string(index=False)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
- elif plot_id == "followers_growth_rate" and not date_filtered_follower_stats_df.empty and 'growth_rate_monthly' in date_filtered_follower_stats_df.columns and 'date' in date_filtered_follower_stats_df.columns:
122
- df_summary = date_filtered_follower_stats_df[['date', 'growth_rate_monthly']].copy()
123
- df_summary['date'] = df_summary['date'].dt.strftime('%Y-%m-%d')
124
- summary_text = f"Follower Growth Rate (Monthly %):\n{df_summary.sort_values(by='date').tail(5).to_string(index=False)}"
125
-
126
- elif plot_id == "followers_by_location" and not raw_follower_stats_df.empty:
127
- # This assumes 'follower_geo' is a column that needs processing similar to the plot generator
128
- # For simplicity, if your raw_follower_stats_df might have pre-aggregated 'geo_name' and 'geo_count'
129
- if 'follower_geo_processed_for_summary' in raw_follower_stats_df.columns: # Placeholder for actual processed data
130
- # Example: df_summary = raw_follower_stats_df[['geo_name', 'geo_count']].nlargest(5, 'geo_count')
131
- # summary_text = f"Followers by Location (Top 5):\n{df_summary.to_string(index=False)}"
132
- pass # Implement actual summary logic based on your data structure
133
- else: # Fallback if specific processed columns aren't ready for summary
134
- summary_text = f"Follower location demographic data for '{plot_cfg['label']}' is complex; specific summary requires pre-processing aligned with the chart."
135
-
136
-
137
- elif plot_id == "engagement_rate" and not filtered_merged_posts_df.empty and 'engagement_rate' in filtered_merged_posts_df.columns and date_col_posts in filtered_merged_posts_df.columns:
138
- df_summary = filtered_merged_posts_df[[date_col_posts, 'engagement_rate']].copy()
139
- df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
140
- summary_text = f"Engagement Rate Over Time (%):\n{df_summary.sort_values(by=date_col_posts).tail(5).to_string(index=False)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
- elif plot_id == "reach_over_time" and not filtered_merged_posts_df.empty and 'reach' in filtered_merged_posts_df.columns and date_col_posts in filtered_merged_posts_df.columns:
143
- df_summary = filtered_merged_posts_df[[date_col_posts, 'reach']].copy()
144
- df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
145
- summary_text = f"Reach Over Time:\n{df_summary.sort_values(by=date_col_posts).tail(5).to_string(index=False)}"
146
-
147
- elif plot_id == "impressions_over_time" and not filtered_merged_posts_df.empty and 'impressions_sum' in filtered_merged_posts_df.columns and date_col_posts in filtered_merged_posts_df.columns:
148
- df_summary = filtered_merged_posts_df[[date_col_posts, 'impressions_sum']].copy()
149
- df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
150
- summary_text = f"Impressions Over Time:\n{df_summary.sort_values(by=date_col_posts).tail(5).to_string(index=False)}"
151
-
152
- elif plot_id == "comments_sentiment" and not filtered_merged_posts_df.empty and 'comment_sentiment' in filtered_merged_posts_df.columns:
153
- sentiment_counts = filtered_merged_posts_df['comment_sentiment'].value_counts().reset_index()
154
- sentiment_counts.columns = ['Sentiment', 'Count']
155
- summary_text = f"Comments Sentiment Breakdown:\n{sentiment_counts.to_string(index=False)}"
 
 
 
 
 
156
 
157
- # Add more elif blocks for other plot_ids, extracting relevant data:
158
- # e.g., likes_over_time, clicks_over_time, shares_over_time, comments_over_time
159
- # post_frequency_cs, content_format_breakdown_cs, content_topic_breakdown_cs
160
- # mentions_activity, mention_sentiment
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
  data_summaries[plot_id] = summary_text
163
  except KeyError as e:
164
- logging.warning(f"KeyError generating summary for {plot_id}: {e}. Using default summary.")
165
- data_summaries[plot_id] = f"Data summary generation error for {plot_cfg['label']} (missing column: {e})."
166
  except Exception as e:
167
- logging.error(f"Error generating summary for {plot_id}: {e}", exc_info=True)
168
- data_summaries[plot_id] = f"Error generating data summary for {plot_cfg['label']}."
169
 
170
  return data_summaries
171
-
172
  # --- Analytics Tab: Plot Figure Generation Function ---
173
  def update_analytics_plots_figures(token_state_value, date_filter_option, custom_start_date, custom_end_date, current_plot_configs):
174
  logging.info(f"Updating analytics plot figures. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")
 
8
  matplotlib.use('Agg') # Set backend for Matplotlib to avoid GUI conflicts with Gradio
9
  import matplotlib.pyplot as plt
10
  import time # For profiling if needed
11
+ from datetime import datetime, timedelta # Added timedelta
12
+ import numpy as np
13
 
14
  # --- Module Imports ---
15
  from gradio_utils import get_url_user_token
 
87
  plot_configs_list,
88
  filtered_merged_posts_df,
89
  filtered_mentions_df,
90
+ date_filtered_follower_stats_df, # Expected to contain 'follower_gains_monthly'
91
+ raw_follower_stats_df, # Expected to contain other demographics like 'follower_geo', 'follower_industry'
92
+ token_state_value
93
  ):
94
  """
95
+ Generates textual summaries for each plot ID to be used by the chatbot,
96
+ based on the corrected understanding of DataFrame structures and follower count columns.
97
  """
98
  data_summaries = {}
 
99
 
100
+ # --- Date and Config Columns from token_state ---
101
+ # For Posts
102
+ date_col_posts = token_state_value.get("config_date_col_posts", "published_at")
103
+ media_type_col_name = token_state_value.get("config_media_type_col", "media_type")
104
+ eb_labels_col_name = token_state_value.get("config_eb_labels_col", "li_eb_label")
105
+ # For Mentions
106
+ date_col_mentions = token_state_value.get("config_date_col_mentions", "date")
107
+ mentions_sentiment_col = "sentiment_label" # As per user's mention df structure
108
 
109
+ # For Follower Stats - Actual column names provided by user
110
+ follower_count_organic_col = "follower_count_organic"
111
+ follower_count_paid_col = "follower_count_paid"
112
+
113
+ # For Follower Stats (Demographics from raw_follower_stats_df)
114
+ follower_demographics_type_col = "follower_count_type" # Column indicating 'follower_geo', 'follower_industry'
115
+ follower_demographics_category_col = "category_name" # Column indicating 'USA', 'Technology'
116
+
117
+ # For Follower Gains/Growth (from date_filtered_follower_stats_df)
118
+ follower_gains_type_col = "follower_count_type" # Should be 'follower_gains_monthly'
119
+ follower_gains_date_col = "category_name" # This is 'YYYY-MM-DD'
120
+
121
+ # --- Helper: Safely convert to datetime ---
122
+ def safe_to_datetime(series, errors='coerce'):
123
+ return pd.to_datetime(series, errors=errors)
124
+
125
+ # --- Prepare DataFrames (copy and convert dates) ---
126
+ if filtered_merged_posts_df is not None and not filtered_merged_posts_df.empty:
127
+ posts_df = filtered_merged_posts_df.copy()
128
+ if date_col_posts in posts_df.columns:
129
+ posts_df[date_col_posts] = safe_to_datetime(posts_df[date_col_posts])
130
+ else:
131
+ logging.warning(f"Date column '{date_col_posts}' not found in posts_df for chatbot summary.")
132
+ else:
133
+ posts_df = pd.DataFrame()
134
+
135
+ if filtered_mentions_df is not None and not filtered_mentions_df.empty:
136
+ mentions_df = filtered_mentions_df.copy()
137
+ if date_col_mentions in mentions_df.columns:
138
+ mentions_df[date_col_mentions] = safe_to_datetime(mentions_df[date_col_mentions])
139
+ else:
140
+ logging.warning(f"Date column '{date_col_mentions}' not found in mentions_df for chatbot summary.")
141
+ else:
142
+ mentions_df = pd.DataFrame()
143
+
144
+ # For date_filtered_follower_stats_df (monthly gains)
145
+ if date_filtered_follower_stats_df is not None and not date_filtered_follower_stats_df.empty:
146
+ follower_monthly_df = date_filtered_follower_stats_df.copy()
147
+ if follower_gains_type_col in follower_monthly_df.columns:
148
+ follower_monthly_df = follower_monthly_df[follower_monthly_df[follower_gains_type_col] == 'follower_gains_monthly'].copy()
149
+
150
+ if follower_gains_date_col in follower_monthly_df.columns:
151
+ follower_monthly_df['datetime_obj'] = safe_to_datetime(follower_monthly_df[follower_gains_date_col])
152
+ follower_monthly_df = follower_monthly_df.dropna(subset=['datetime_obj'])
153
+
154
+ # Calculate total gains
155
+ if follower_count_organic_col in follower_monthly_df.columns and follower_count_paid_col in follower_monthly_df.columns:
156
+ follower_monthly_df[follower_count_organic_col] = pd.to_numeric(follower_monthly_df[follower_count_organic_col], errors='coerce').fillna(0)
157
+ follower_monthly_df[follower_count_paid_col] = pd.to_numeric(follower_monthly_df[follower_count_paid_col], errors='coerce').fillna(0)
158
+ follower_monthly_df['total_monthly_gains'] = follower_monthly_df[follower_count_organic_col] + follower_monthly_df[follower_count_paid_col]
159
+ elif follower_count_organic_col in follower_monthly_df.columns: # Only organic exists
160
+ follower_monthly_df[follower_count_organic_col] = pd.to_numeric(follower_monthly_df[follower_count_organic_col], errors='coerce').fillna(0)
161
+ follower_monthly_df['total_monthly_gains'] = follower_monthly_df[follower_count_organic_col]
162
+ elif follower_count_paid_col in follower_monthly_df.columns: # Only paid exists
163
+ follower_monthly_df[follower_count_paid_col] = pd.to_numeric(follower_monthly_df[follower_count_paid_col], errors='coerce').fillna(0)
164
+ follower_monthly_df['total_monthly_gains'] = follower_monthly_df[follower_count_paid_col]
165
+ else:
166
+ logging.warning(f"Neither '{follower_count_organic_col}' nor '{follower_count_paid_col}' found in follower_monthly_df for total gains calculation.")
167
+ follower_monthly_df['total_monthly_gains'] = 0 # Avoid KeyError later
168
+ else:
169
+ logging.warning(f"Date column '{follower_gains_date_col}' (from category_name) not found in follower_monthly_df for chatbot summary.")
170
+ if 'datetime_obj' not in follower_monthly_df.columns:
171
+ follower_monthly_df['datetime_obj'] = pd.NaT
172
+ if 'total_monthly_gains' not in follower_monthly_df.columns:
173
+ follower_monthly_df['total_monthly_gains'] = 0
174
+ else:
175
+ follower_monthly_df = pd.DataFrame(columns=[follower_gains_date_col, 'total_monthly_gains', 'datetime_obj'])
176
+
177
+
178
+ if raw_follower_stats_df is not None and not raw_follower_stats_df.empty:
179
+ follower_demographics_df = raw_follower_stats_df.copy()
180
+ # Calculate total followers for demographics
181
+ if follower_count_organic_col in follower_demographics_df.columns and follower_count_paid_col in follower_demographics_df.columns:
182
+ follower_demographics_df[follower_count_organic_col] = pd.to_numeric(follower_demographics_df[follower_count_organic_col], errors='coerce').fillna(0)
183
+ follower_demographics_df[follower_count_paid_col] = pd.to_numeric(follower_demographics_df[follower_count_paid_col], errors='coerce').fillna(0)
184
+ follower_demographics_df['total_follower_count'] = follower_demographics_df[follower_count_organic_col] + follower_demographics_df[follower_count_paid_col]
185
+ elif follower_count_organic_col in follower_demographics_df.columns:
186
+ follower_demographics_df[follower_count_organic_col] = pd.to_numeric(follower_demographics_df[follower_count_organic_col], errors='coerce').fillna(0)
187
+ follower_demographics_df['total_follower_count'] = follower_demographics_df[follower_count_organic_col]
188
+ elif follower_count_paid_col in follower_demographics_df.columns:
189
+ follower_demographics_df[follower_count_paid_col] = pd.to_numeric(follower_demographics_df[follower_count_paid_col], errors='coerce').fillna(0)
190
+ follower_demographics_df['total_follower_count'] = follower_demographics_df[follower_count_paid_col]
191
+ else:
192
+ logging.warning(f"Neither '{follower_count_organic_col}' nor '{follower_count_paid_col}' found in follower_demographics_df for total count calculation.")
193
+ if 'total_follower_count' not in follower_demographics_df.columns:
194
+ follower_demographics_df['total_follower_count'] = 0
195
+ else:
196
+ follower_demographics_df = pd.DataFrame()
197
+
198
 
199
  for plot_cfg in plot_configs_list:
200
  plot_id = plot_cfg["id"]
201
+ plot_label = plot_cfg["label"]
202
+ summary_text = f"No specific data summary available for '{plot_label}' for the selected period."
203
 
204
  try:
205
+ # --- FOLLOWER STATS ---
206
+ if plot_id == "followers_count": # Uses follower_monthly_df
207
+ if not follower_monthly_df.empty and 'total_monthly_gains' in follower_monthly_df.columns and 'datetime_obj' in follower_monthly_df.columns and not follower_monthly_df['datetime_obj'].isnull().all():
208
+ df_summary = follower_monthly_df[['datetime_obj', 'total_monthly_gains']].copy()
209
+ df_summary['datetime_obj'] = df_summary['datetime_obj'].dt.strftime('%Y-%m-%d')
210
+ df_summary.rename(columns={'datetime_obj': 'Date', 'total_monthly_gains': 'Total Monthly Gains'}, inplace=True)
211
+ summary_text = f"Follower Count (Total Monthly Gains):\n{df_summary.sort_values(by='Date').tail(5).to_string(index=False)}"
212
+ else:
213
+ summary_text = f"Follower count data (total monthly gains) is unavailable or incomplete for '{plot_label}'."
214
+
215
+ elif plot_id == "followers_growth_rate": # Uses follower_monthly_df
216
+ if not follower_monthly_df.empty and 'total_monthly_gains' in follower_monthly_df.columns and 'datetime_obj' in follower_monthly_df.columns and not follower_monthly_df['datetime_obj'].isnull().all():
217
+ df_calc = follower_monthly_df.sort_values(by='datetime_obj').copy()
218
+ # Growth rate is calculated on the total monthly gains (which are changes, not cumulative counts)
219
+ # To calculate growth rate of followers, we'd need cumulative follower count.
220
+ # The plot logic also uses pct_change on the gains themselves.
221
+ # If 'total_monthly_gains' represents the *change* in followers, then pct_change on this is rate of change of gains.
222
+ # If it represents the *cumulative* followers at that point, then pct_change is follower growth rate.
223
+ # Assuming 'total_monthly_gains' is the *change* for the month, like the plot logic.
224
+ df_calc['total_monthly_gains'] = pd.to_numeric(df_calc['total_monthly_gains'], errors='coerce')
225
+ if len(df_calc) >= 2:
226
+ # Calculate cumulative sum to get follower count if 'total_monthly_gains' are indeed just gains
227
+ # If your 'total_monthly_gains' already IS the total follower count at end of month, remove next line
228
+ # For now, assuming it's GAINS, so we need cumulative for growth rate of total followers.
229
+ # However, the original plot logic applies pct_change directly to 'follower_gains_monthly'.
230
+ # Let's stick to pct_change on the gains/count column for consistency with plot.
231
+
232
+ # If 'total_monthly_gains' is the actual follower count for that month:
233
+ df_calc['growth_rate_monthly'] = df_calc['total_monthly_gains'].pct_change() * 100
234
+ df_calc['growth_rate_monthly'] = df_calc['growth_rate_monthly'].round(2)
235
+ df_calc.replace([np.inf, -np.inf], np.nan, inplace=True) # Handle division by zero if a gain was 0
236
+
237
+ df_summary = df_calc[['datetime_obj', 'growth_rate_monthly']].dropna().copy()
238
+ df_summary['datetime_obj'] = df_summary['datetime_obj'].dt.strftime('%Y-%m-%d')
239
+ df_summary.rename(columns={'datetime_obj': 'Date', 'growth_rate_monthly': 'Growth Rate (%)'}, inplace=True)
240
+ if not df_summary.empty:
241
+ summary_text = f"Follower Growth Rate (Monthly % based on Total Follower Count/Gains):\n{df_summary.sort_values(by='Date').tail(5).to_string(index=False)}"
242
+ else:
243
+ summary_text = f"Not enough data points or valid transitions to calculate follower growth rate for '{plot_label}'."
244
+ else:
245
+ summary_text = f"Not enough data points (need at least 2) to calculate follower growth rate for '{plot_label}'."
246
+ else:
247
+ summary_text = f"Follower growth rate data (total monthly gains) is unavailable or incomplete for '{plot_label}'."
248
+
249
+ elif plot_id in ["followers_by_location", "followers_by_role", "followers_by_industry", "followers_by_seniority"]:
250
+ demographic_type_map = {
251
+ "followers_by_location": "follower_geo",
252
+ "followers_by_role": "follower_function",
253
+ "followers_by_industry": "follower_industry",
254
+ "followers_by_seniority": "follower_seniority"
255
+ }
256
+ current_demographic_type = demographic_type_map.get(plot_id)
257
+ if not follower_demographics_df.empty and \
258
+ follower_demographics_type_col in follower_demographics_df.columns and \
259
+ follower_demographics_category_col in follower_demographics_df.columns and \
260
+ 'total_follower_count' in follower_demographics_df.columns: # Check for the calculated total
261
+
262
+ df_filtered_demographics = follower_demographics_df[
263
+ follower_demographics_df[follower_demographics_type_col] == current_demographic_type
264
+ ].copy()
265
+
266
+ if not df_filtered_demographics.empty:
267
+ df_summary = df_filtered_demographics.groupby(follower_demographics_category_col)['total_follower_count'].sum().reset_index()
268
+ df_summary.rename(columns={follower_demographics_category_col: 'Category', 'total_follower_count': 'Total Follower Count'}, inplace=True)
269
+ top_5 = df_summary.nlargest(5, 'Total Follower Count')
270
+ summary_text = f"Top 5 {plot_label} (Total Followers):\n{top_5.to_string(index=False)}"
271
+ else:
272
+ summary_text = f"No data available for demographic type '{current_demographic_type}' in '{plot_label}'."
273
+ else:
274
+ summary_text = f"Follower demographic data columns (including total_follower_count) are missing or incomplete for '{plot_label}'."
275
+
276
+ # --- POSTS STATS ---
277
+ elif plot_id == "engagement_rate":
278
+ if not posts_df.empty and 'engagement' in posts_df.columns and date_col_posts in posts_df.columns and not posts_df[date_col_posts].isnull().all():
279
+ df_resampled = posts_df.set_index(date_col_posts)['engagement'].resample('W').mean().reset_index()
280
+ df_resampled['engagement'] = pd.to_numeric(df_resampled['engagement'], errors='coerce').round(2)
281
+ df_summary = df_resampled[[date_col_posts, 'engagement']].dropna().copy()
282
+ df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
283
+ summary_text = f"Engagement Rate Over Time (Weekly Avg %):\n{df_summary.sort_values(by=date_col_posts).tail(5).to_string(index=False)}"
284
+ else:
285
+ summary_text = f"Engagement rate data is unavailable for '{plot_label}'."
286
 
287
+ elif plot_id == "reach_over_time":
288
+ if not posts_df.empty and 'reach' in posts_df.columns and date_col_posts in posts_df.columns and not posts_df[date_col_posts].isnull().all():
289
+ df_resampled = posts_df.set_index(date_col_posts)['reach'].resample('W').sum().reset_index()
290
+ df_resampled['reach'] = pd.to_numeric(df_resampled['reach'], errors='coerce')
291
+ df_summary = df_resampled[[date_col_posts, 'reach']].dropna().copy()
292
+ df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
293
+ summary_text = f"Reach Over Time (Weekly Sum):\n{df_summary.sort_values(by=date_col_posts).tail(5).to_string(index=False)}"
294
+ else:
295
+ summary_text = f"Reach data is unavailable for '{plot_label}'."
296
+
297
+ elif plot_id == "impressions_over_time":
298
+ if not posts_df.empty and 'impressionCount' in posts_df.columns and date_col_posts in posts_df.columns and not posts_df[date_col_posts].isnull().all():
299
+ df_resampled = posts_df.set_index(date_col_posts)['impressionCount'].resample('W').sum().reset_index()
300
+ df_resampled['impressionCount'] = pd.to_numeric(df_resampled['impressionCount'], errors='coerce')
301
+ df_summary = df_resampled[[date_col_posts, 'impressionCount']].dropna().copy()
302
+ df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
303
+ df_summary.rename(columns={'impressionCount': 'Impressions'}, inplace=True)
304
+ summary_text = f"Impressions Over Time (Weekly Sum):\n{df_summary.sort_values(by=date_col_posts).tail(5).to_string(index=False)}"
305
+ else:
306
+ summary_text = f"Impressions data is unavailable for '{plot_label}'."
307
+
308
+ elif plot_id == "likes_over_time":
309
+ if not posts_df.empty and 'likeCount' in posts_df.columns and date_col_posts in posts_df.columns and not posts_df[date_col_posts].isnull().all():
310
+ df_resampled = posts_df.set_index(date_col_posts)['likeCount'].resample('W').sum().reset_index()
311
+ df_resampled['likeCount'] = pd.to_numeric(df_resampled['likeCount'], errors='coerce')
312
+ df_summary = df_resampled[[date_col_posts, 'likeCount']].dropna().copy()
313
+ df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
314
+ df_summary.rename(columns={'likeCount': 'Likes'}, inplace=True)
315
+ summary_text = f"Likes Over Time (Weekly Sum):\n{df_summary.sort_values(by=date_col_posts).tail(5).to_string(index=False)}"
316
+ else:
317
+ summary_text = f"Likes data is unavailable for '{plot_label}'."
318
+
319
+ elif plot_id == "clicks_over_time":
320
+ if not posts_df.empty and 'clickCount' in posts_df.columns and date_col_posts in posts_df.columns and not posts_df[date_col_posts].isnull().all():
321
+ df_resampled = posts_df.set_index(date_col_posts)['clickCount'].resample('W').sum().reset_index()
322
+ df_resampled['clickCount'] = pd.to_numeric(df_resampled['clickCount'], errors='coerce')
323
+ df_summary = df_resampled[[date_col_posts, 'clickCount']].dropna().copy()
324
+ df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
325
+ df_summary.rename(columns={'clickCount': 'Clicks'}, inplace=True)
326
+ summary_text = f"Clicks Over Time (Weekly Sum):\n{df_summary.sort_values(by=date_col_posts).tail(5).to_string(index=False)}"
327
+ else:
328
+ summary_text = f"Clicks data is unavailable for '{plot_label}'."
329
+
330
+ elif plot_id == "shares_over_time":
331
+ if not posts_df.empty and 'shareCount' in posts_df.columns and date_col_posts in posts_df.columns and not posts_df[date_col_posts].isnull().all():
332
+ df_resampled = posts_df.set_index(date_col_posts)['shareCount'].resample('W').sum().reset_index()
333
+ df_resampled['shareCount'] = pd.to_numeric(df_resampled['shareCount'], errors='coerce')
334
+ df_summary = df_resampled[[date_col_posts, 'shareCount']].dropna().copy()
335
+ df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
336
+ df_summary.rename(columns={'shareCount': 'Shares'}, inplace=True)
337
+ summary_text = f"Shares Over Time (Weekly Sum):\n{df_summary.sort_values(by=date_col_posts).tail(5).to_string(index=False)}"
338
+ elif 'shareCount' not in posts_df.columns and not posts_df.empty : # Check if posts_df is not empty before assuming column is the only issue
339
+ summary_text = f"Shares data column ('shareCount') not found for '{plot_label}'."
340
+ else:
341
+ summary_text = f"Shares data is unavailable for '{plot_label}'."
342
 
343
+ elif plot_id == "comments_over_time":
344
+ if not posts_df.empty and 'commentCount' in posts_df.columns and date_col_posts in posts_df.columns and not posts_df[date_col_posts].isnull().all():
345
+ df_resampled = posts_df.set_index(date_col_posts)['commentCount'].resample('W').sum().reset_index()
346
+ df_resampled['commentCount'] = pd.to_numeric(df_resampled['commentCount'], errors='coerce')
347
+ df_summary = df_resampled[[date_col_posts, 'commentCount']].dropna().copy()
348
+ df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
349
+ df_summary.rename(columns={'commentCount': 'Comments'}, inplace=True)
350
+ summary_text = f"Comments Over Time (Weekly Sum):\n{df_summary.sort_values(by=date_col_posts).tail(5).to_string(index=False)}"
351
+ else:
352
+ summary_text = f"Comments data is unavailable for '{plot_label}'."
353
+
354
+ elif plot_id == "comments_sentiment":
355
+ comment_sentiment_col_posts = "sentiment"
356
+ if not posts_df.empty and comment_sentiment_col_posts in posts_df.columns:
357
+ sentiment_counts = posts_df[comment_sentiment_col_posts].value_counts().reset_index()
358
+ sentiment_counts.columns = ['Sentiment', 'Count']
359
+ summary_text = f"Comments Sentiment Breakdown (Posts Data):\n{sentiment_counts.to_string(index=False)}"
360
+ else:
361
+ summary_text = f"Comment sentiment data ('{comment_sentiment_col_posts}') is unavailable for '{plot_label}'."
362
 
363
+ elif plot_id == "post_frequency_cs":
364
+ if not posts_df.empty and date_col_posts in posts_df.columns and not posts_df[date_col_posts].isnull().all():
365
+ post_counts_weekly = posts_df.set_index(date_col_posts).resample('W').size().reset_index(name='post_count')
366
+ post_counts_weekly.rename(columns={date_col_posts: 'Week', 'post_count': 'Posts'}, inplace=True)
367
+ post_counts_weekly['Week'] = post_counts_weekly['Week'].dt.strftime('%Y-%m-%d (Week of)')
368
+ summary_text = f"Post Frequency (Weekly):\n{post_counts_weekly.sort_values(by='Week').tail(5).to_string(index=False)}"
369
+ else:
370
+ summary_text = f"Post frequency data is unavailable for '{plot_label}'."
371
+
372
+ elif plot_id == "content_format_breakdown_cs":
373
+ if not posts_df.empty and media_type_col_name in posts_df.columns:
374
+ format_counts = posts_df[media_type_col_name].value_counts().reset_index()
375
+ format_counts.columns = ['Format', 'Count']
376
+ summary_text = f"Content Format Breakdown:\n{format_counts.nlargest(5, 'Count').to_string(index=False)}"
377
+ else:
378
+ summary_text = f"Content format data ('{media_type_col_name}') is unavailable for '{plot_label}'."
379
+
380
+ elif plot_id == "content_topic_breakdown_cs":
381
+ if not posts_df.empty and eb_labels_col_name in posts_df.columns:
382
+ try:
383
+ # Ensure the column is not all NaN before trying to check for lists or explode
384
+ if posts_df[eb_labels_col_name].notna().any():
385
+ if posts_df[eb_labels_col_name].apply(lambda x: isinstance(x, list)).any():
386
+ topic_counts = posts_df.explode(eb_labels_col_name)[eb_labels_col_name].value_counts().reset_index()
387
+ else:
388
+ topic_counts = posts_df[eb_labels_col_name].value_counts().reset_index()
389
+ topic_counts.columns = ['Topic', 'Count']
390
+ summary_text = f"Content Topic Breakdown (Top 5):\n{topic_counts.nlargest(5, 'Count').to_string(index=False)}"
391
+ else:
392
+ summary_text = f"Content topic data ('{eb_labels_col_name}') contains no valid topics for '{plot_label}'."
393
+ except Exception as e_topic:
394
+ logging.warning(f"Could not process topic breakdown for '{eb_labels_col_name}': {e_topic}")
395
+ summary_text = f"Content topic data ('{eb_labels_col_name}') could not be processed for '{plot_label}'."
396
+ else:
397
+ summary_text = f"Content topic data ('{eb_labels_col_name}') is unavailable for '{plot_label}'."
398
+
399
+ # --- MENTIONS STATS ---
400
+ elif plot_id == "mention_analysis_volume":
401
+ if not mentions_df.empty and date_col_mentions in mentions_df.columns and not mentions_df[date_col_mentions].isnull().all():
402
+ mentions_over_time = mentions_df.set_index(date_col_mentions).resample('W').size().reset_index(name='mention_count')
403
+ mentions_over_time.rename(columns={date_col_mentions: 'Week', 'mention_count': 'Mentions'}, inplace=True)
404
+ mentions_over_time['Week'] = mentions_over_time['Week'].dt.strftime('%Y-%m-%d (Week of)')
405
+ if not mentions_over_time.empty:
406
+ summary_text = f"Mentions Volume (Weekly):\n{mentions_over_time.sort_values(by='Week').tail(5).to_string(index=False)}"
407
+ else:
408
+ summary_text = f"No mention activity found for '{plot_label}' in the selected period."
409
+ else:
410
+ summary_text = f"Mentions volume data is unavailable for '{plot_label}'."
411
+
412
+ elif plot_id == "mention_analysis_sentiment":
413
+ if not mentions_df.empty and mentions_sentiment_col in mentions_df.columns:
414
+ sentiment_counts = mentions_df[mentions_sentiment_col].value_counts().reset_index()
415
+ sentiment_counts.columns = ['Sentiment', 'Count']
416
+ summary_text = f"Mentions Sentiment Breakdown:\n{sentiment_counts.to_string(index=False)}"
417
+ else:
418
+ summary_text = f"Mention sentiment data ('{mentions_sentiment_col}') is unavailable for '{plot_label}'."
419
 
420
  data_summaries[plot_id] = summary_text
421
  except KeyError as e:
422
+ logging.warning(f"KeyError generating summary for {plot_id} ('{plot_label}'): {e}. Using default summary.")
423
+ data_summaries[plot_id] = f"Data summary generation error for '{plot_label}' (missing column: {e})."
424
  except Exception as e:
425
+ logging.error(f"Error generating summary for {plot_id} ('{plot_label}'): {e}", exc_info=True)
426
+ data_summaries[plot_id] = f"Error generating data summary for '{plot_label}'."
427
 
428
  return data_summaries
 
429
  # --- Analytics Tab: Plot Figure Generation Function ---
430
  def update_analytics_plots_figures(token_state_value, date_filter_option, custom_start_date, custom_end_date, current_plot_configs):
431
  logging.info(f"Updating analytics plot figures. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")