GuglielmoTor commited on
Commit
dc88746
·
verified ·
1 Parent(s): 998bc4b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -80
app.py CHANGED
@@ -22,7 +22,7 @@ from ui_generators import (
22
  build_analytics_tab_plot_area,
23
  BOMB_ICON, EXPLORE_ICON, FORMULA_ICON, ACTIVE_ICON
24
  )
25
- from analytics_data_processing import prepare_filtered_analytics_data
26
  from analytics_plot_generator import (
27
  generate_posts_activity_plot,
28
  generate_mentions_activity_plot, generate_mention_sentiment_plot,
@@ -81,17 +81,19 @@ PLOT_ID_TO_FORMULA_KEY_MAP = {
81
  # --- Analytics Tab: Plot Figure Generation Function ---
82
  def update_analytics_plots_figures(token_state_value, date_filter_option, custom_start_date, custom_end_date):
83
  logging.info(f"Updating analytics plot figures. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")
84
- num_expected_plots = 19
 
85
  if not token_state_value or not token_state_value.get("token"):
86
  message = "❌ Accesso negato. Nessun token. Impossibile generare le analisi."
87
  logging.warning(message)
88
  placeholder_figs = [create_placeholder_plot(title="Accesso Negato", message="Nessun token.") for _ in range(num_expected_plots)]
89
  return [message] + placeholder_figs
90
  try:
 
91
  (filtered_merged_posts_df,
92
  filtered_mentions_df,
93
- date_filtered_follower_stats_df,
94
- raw_follower_stats_df,
95
  start_dt_for_msg, end_dt_for_msg) = \
96
  prepare_filtered_analytics_data(
97
  token_state_value, date_filter_option, custom_start_date, custom_end_date
@@ -107,82 +109,94 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
107
  media_type_col_name = token_state_value.get("config_media_type_col", "media_type")
108
  eb_labels_col_name = token_state_value.get("config_eb_labels_col", "li_eb_label")
109
 
110
- plot_figs = []
 
 
 
 
111
  try:
112
- # Define plot functions and their arguments
113
- # Order matters and must match plot_configs
114
- plot_definitions = [
115
- {"func": generate_followers_count_over_time_plot, "args": [date_filtered_follower_stats_df, 'follower_gains_monthly'], "is_demographic": False},
116
- {"func": generate_followers_growth_rate_plot, "args": [date_filtered_follower_stats_df, 'follower_gains_monthly'], "is_demographic": False},
117
- {"func": generate_followers_by_demographics_plot, "args": [raw_follower_stats_df, 'follower_geo', "Follower per Località"], "type_value_key": "follower_geo", "is_demographic": True},
118
- {"func": generate_followers_by_demographics_plot, "args": [raw_follower_stats_df, 'follower_function', "Follower per Ruolo"], "type_value_key": "follower_function", "is_demographic": True},
119
- {"func": generate_followers_by_demographics_plot, "args": [raw_follower_stats_df, 'follower_industry', "Follower per Settore"], "type_value_key": "follower_industry", "is_demographic": True},
120
- {"func": generate_followers_by_demographics_plot, "args": [raw_follower_stats_df, 'follower_seniority', "Follower per Anzianità"], "type_value_key": "follower_seniority", "is_demographic": True},
121
- {"func": generate_engagement_rate_over_time_plot, "args": [filtered_merged_posts_df, date_column_posts], "is_demographic": False},
122
- {"func": generate_reach_over_time_plot, "args": [filtered_merged_posts_df, date_column_posts], "is_demographic": False},
123
- {"func": generate_impressions_over_time_plot, "args": [filtered_merged_posts_df, date_column_posts], "is_demographic": False},
124
- {"func": generate_likes_over_time_plot, "args": [filtered_merged_posts_df, date_column_posts], "is_demographic": False},
125
- {"func": generate_clicks_over_time_plot, "args": [filtered_merged_posts_df, date_column_posts], "is_demographic": False},
126
- {"func": generate_shares_over_time_plot, "args": [filtered_merged_posts_df, date_column_posts], "is_demographic": False},
127
- {"func": generate_comments_over_time_plot, "args": [filtered_merged_posts_df, date_column_posts], "is_demographic": False},
128
- {"func": generate_comments_sentiment_breakdown_plot, "args": [filtered_merged_posts_df, 'comment_sentiment'], "is_demographic": False},
129
- {"func": generate_post_frequency_plot, "args": [filtered_merged_posts_df, date_column_posts], "is_demographic": False},
130
- {"func": generate_content_format_breakdown_plot, "args": [filtered_merged_posts_df, media_type_col_name], "is_demographic": False},
131
- {"func": generate_content_topic_breakdown_plot, "args": [filtered_merged_posts_df, eb_labels_col_name], "is_demographic": False},
132
- {"func": generate_mentions_activity_plot, "args": [filtered_mentions_df, date_column_mentions], "is_demographic": False},
133
- {"func": generate_mention_sentiment_plot, "args": [filtered_mentions_df], "is_demographic": False}
134
- ]
135
-
136
- for i, plot_def in enumerate(plot_definitions):
137
- plot_fn = plot_def["func"]
138
- args = plot_def["args"]
139
- plot_title_for_error = args[2] if plot_def["is_demographic"] else plot_fn.__name__
140
- try:
141
- # Specific check for demographic plots if raw_follower_stats_df is empty or missing key columns
142
- if plot_def["is_demographic"]:
143
- df_arg = args[0] # raw_follower_stats_df
144
- type_val_col = plot_def["type_value_key"]
145
- if df_arg is None or df_arg.empty:
146
- logging.warning(f"raw_follower_stats_df is empty. Cannot generate demographic plot: {plot_title_for_error}")
147
- raise ValueError(f"Dati demografici mancanti (raw_follower_stats_df vuoto).")
148
- if type_val_col not in df_arg.columns:
149
- logging.warning(f"Colonna '{type_val_col}' mancante in raw_follower_stats_df per il grafico '{plot_title_for_error}'. Colonne disponibili: {df_arg.columns.tolist()}")
150
- raise KeyError(f"Colonna dati '{type_val_col}' non trovata.")
151
-
152
- fig = plot_fn(*args)
153
- plot_figs.append(fig)
154
- except (KeyError, ValueError) as plot_e: # Catch KeyError for missing columns, ValueError for other data issues
155
- logging.error(f"Errore generazione grafico '{plot_title_for_error}' (slot {i}): {plot_e}", exc_info=False) # Set exc_info to False for cleaner logs for known data issues
156
- plot_figs.append(create_placeholder_plot(title=f"Errore Dati: {plot_title_for_error}", message=f"Impossibile generare: {str(plot_e)}"))
157
- except Exception as plot_e: # Catch other unexpected errors
158
- logging.error(f"Errore imprevisto generazione grafico '{plot_title_for_error}' (slot {i}): {plot_e}", exc_info=True)
159
- plot_figs.append(create_placeholder_plot(title=f"Errore Grafico: {plot_title_for_error}", message=f"Dettaglio: {str(plot_e)[:100]}"))
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  message = f"📊 Analisi aggiornate per il periodo: {date_filter_option}"
162
  if date_filter_option == "Intervallo Personalizzato":
163
  s_display = start_dt_for_msg.strftime('%Y-%m-%d') if start_dt_for_msg else "Qualsiasi"
164
  e_display = end_dt_for_msg.strftime('%Y-%m-%d') if end_dt_for_msg else "Qualsiasi"
165
  message += f" (Da: {s_display} A: {e_display})"
166
 
 
167
  final_plot_figs = []
168
- for i, p_fig in enumerate(plot_figs):
169
- if p_fig is not None and not isinstance(p_fig, str):
170
- final_plot_figs.append(p_fig)
171
  else:
172
- logging.warning(f"Plot generation failed or unexpected type for slot {i}, using placeholder. Figure: {p_fig}")
173
- final_plot_figs.append(create_placeholder_plot(title="Errore Grafico", message="Impossibile generare questa figura."))
 
174
 
175
- while len(final_plot_figs) < num_expected_plots:
176
- logging.warning(f"Adding missing plot placeholder. Expected {num_expected_plots}, got {len(final_plot_figs)}.")
177
- final_plot_figs.append(create_placeholder_plot(title="Grafico Mancante", message="Figura non generata."))
178
-
179
- return [message] + final_plot_figs[:num_expected_plots]
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
- except Exception as e:
182
- error_msg = f"❌ Errore durante la generazione delle figure dei grafici analitici: {e}"
183
  logging.error(error_msg, exc_info=True)
184
- placeholder_figs = [create_placeholder_plot(title="Errore Generazione Grafici", message=str(e)) for _ in range(num_expected_plots)]
185
- return [error_msg] + placeholder_figs
186
 
187
 
188
  # --- Gradio UI Blocks ---
@@ -540,29 +554,31 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
540
  ]
541
  explore_click_inputs = [explored_plot_id_state]
542
 
543
- # --- Define async wrapper functions for click handlers ---
544
- async def insights_click_wrapper(current_active_val, current_chats_val, current_chat_pid, p_id):
545
- return await handle_panel_action(p_id, "insights", current_active_val, current_chats_val, current_chat_pid)
546
-
547
- async def formula_click_wrapper(current_active_val, current_chats_val, current_chat_pid, p_id):
548
- return await handle_panel_action(p_id, "formula", current_active_val, current_chats_val, current_chat_pid)
549
- # --- End async wrapper functions ---
 
 
 
550
 
551
  for config_item in plot_configs:
552
  plot_id = config_item["id"]
553
- # plot_label = config_item["label"] # Not needed here anymore
554
  if plot_id in plot_ui_objects:
555
  ui_obj = plot_ui_objects[plot_id]
556
 
557
- # Use a standard lambda to call the async wrapper, capturing p_id
558
  ui_obj["bomb_button"].click(
559
- fn=lambda cav, ccv, ccpid, p=plot_id: insights_click_wrapper(cav, ccv, ccpid, p),
560
  inputs=action_click_inputs,
561
  outputs=action_panel_outputs_list,
562
  api_name=f"action_insights_{plot_id}"
563
  )
564
  ui_obj["formula_button"].click(
565
- fn=lambda cav, ccv, ccpid, p=plot_id: formula_click_wrapper(cav, ccv, ccpid, p),
566
  inputs=action_click_inputs,
567
  outputs=action_panel_outputs_list,
568
  api_name=f"action_formula_{plot_id}"
@@ -645,7 +661,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
645
 
646
  all_updates.append(None)
647
 
648
- logging.info(f"Preparati {len(all_updates)} aggiornamenti per il refresh completo delle analisi.")
649
  return all_updates
650
 
651
  apply_filter_and_sync_outputs_list = [analytics_status_md]
@@ -747,6 +763,6 @@ if __name__ == "__main__":
747
  try:
748
  logging.info(f"Versione Matplotlib: {matplotlib.__version__}, Backend: {matplotlib.get_backend()}")
749
  except ImportError:
750
- logging.warning("Matplotlib non trovato direttamente, ma potrebbe essere usato dai generatori di grafici.")
751
 
752
  app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
 
22
  build_analytics_tab_plot_area,
23
  BOMB_ICON, EXPLORE_ICON, FORMULA_ICON, ACTIVE_ICON
24
  )
25
+ from analytics_data_processing import prepare_filtered_analytics_data # This is key for data structure
26
  from analytics_plot_generator import (
27
  generate_posts_activity_plot,
28
  generate_mentions_activity_plot, generate_mention_sentiment_plot,
 
81
  # --- Analytics Tab: Plot Figure Generation Function ---
82
  def update_analytics_plots_figures(token_state_value, date_filter_option, custom_start_date, custom_end_date):
83
  logging.info(f"Updating analytics plot figures. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")
84
+ num_expected_plots = 19 # Ensure this matches the number of plots generated
85
+
86
  if not token_state_value or not token_state_value.get("token"):
87
  message = "❌ Accesso negato. Nessun token. Impossibile generare le analisi."
88
  logging.warning(message)
89
  placeholder_figs = [create_placeholder_plot(title="Accesso Negato", message="Nessun token.") for _ in range(num_expected_plots)]
90
  return [message] + placeholder_figs
91
  try:
92
+ # This call is crucial. The structure of these DataFrames determines if plots work.
93
  (filtered_merged_posts_df,
94
  filtered_mentions_df,
95
+ date_filtered_follower_stats_df, # For time-based follower plots
96
+ raw_follower_stats_df, # For demographic follower plots
97
  start_dt_for_msg, end_dt_for_msg) = \
98
  prepare_filtered_analytics_data(
99
  token_state_value, date_filter_option, custom_start_date, custom_end_date
 
109
  media_type_col_name = token_state_value.get("config_media_type_col", "media_type")
110
  eb_labels_col_name = token_state_value.get("config_eb_labels_col", "li_eb_label")
111
 
112
+ plot_figs = [] # Initialize list to hold plot figures
113
+
114
+ # Titles for error reporting, matching the order in plot_configs
115
+ plot_titles_for_errors = [p_cfg["label"] for p_cfg in plot_configs]
116
+
117
  try:
118
+ # Attempt to generate plots. If DataFrames are not as expected by generator functions,
119
+ # KeyErrors for missing columns or other ValueErrors might occur.
120
+ # The order of appends MUST match the order in plot_configs for UI updates.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ # Dinamiche dei Follower (2 plots)
123
+ plot_figs.append(generate_followers_count_over_time_plot(date_filtered_follower_stats_df, type_value='follower_gains_monthly'))
124
+ plot_figs.append(generate_followers_growth_rate_plot(date_filtered_follower_stats_df, type_value='follower_gains_monthly'))
125
+
126
+ # Demografia Follower (4 plots)
127
+ plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_geo', plot_title="Follower per Località"))
128
+ plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_function', plot_title="Follower per Ruolo"))
129
+ plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_industry', plot_title="Follower per Settore"))
130
+ plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_seniority', plot_title="Follower per Anzianità"))
131
+
132
+ # Approfondimenti Performance Post (4 plots)
133
+ plot_figs.append(generate_engagement_rate_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
134
+ plot_figs.append(generate_reach_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
135
+ plot_figs.append(generate_impressions_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
136
+ plot_figs.append(generate_likes_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
137
+
138
+ # Engagement Dettagliato Post nel Tempo (4 plots)
139
+ plot_figs.append(generate_clicks_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
140
+ plot_figs.append(generate_shares_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
141
+ plot_figs.append(generate_comments_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
142
+ plot_figs.append(generate_comments_sentiment_breakdown_plot(filtered_merged_posts_df, sentiment_column='comment_sentiment'))
143
+
144
+ # Analisi Strategia Contenuti (3 plots)
145
+ plot_figs.append(generate_post_frequency_plot(filtered_merged_posts_df, date_column=date_column_posts))
146
+ plot_figs.append(generate_content_format_breakdown_plot(filtered_merged_posts_df, format_col=media_type_col_name))
147
+ plot_figs.append(generate_content_topic_breakdown_plot(filtered_merged_posts_df, topics_col=eb_labels_col_name))
148
+
149
+ # Analisi Menzioni (Dettaglio) (2 plots) - These might use pre-generated figs if logic was complex
150
+ # For simplicity here, assuming direct calls. If they were shared, that logic would be here.
151
+ plot_figs.append(generate_mentions_activity_plot(filtered_mentions_df, date_column=date_column_mentions))
152
+ plot_figs.append(generate_mention_sentiment_plot(filtered_mentions_df))
153
+
154
+ if len(plot_figs) != num_expected_plots:
155
+ logging.warning(f"Mismatch in generated plots. Expected {num_expected_plots}, got {len(plot_figs)}. This will cause UI update issues.")
156
+ # Fill with placeholders if not enough plots were generated due to an early exit or logic error
157
+ while len(plot_figs) < num_expected_plots:
158
+ plot_figs.append(create_placeholder_plot(title="Grafico Non Generato", message="Logica di generazione incompleta."))
159
+
160
  message = f"📊 Analisi aggiornate per il periodo: {date_filter_option}"
161
  if date_filter_option == "Intervallo Personalizzato":
162
  s_display = start_dt_for_msg.strftime('%Y-%m-%d') if start_dt_for_msg else "Qualsiasi"
163
  e_display = end_dt_for_msg.strftime('%Y-%m-%d') if end_dt_for_msg else "Qualsiasi"
164
  message += f" (Da: {s_display} A: {e_display})"
165
 
166
+ # Ensure all plot_figs are actual plot objects, not None or error strings
167
  final_plot_figs = []
168
+ for i, p_fig_candidate in enumerate(plot_figs):
169
+ if p_fig_candidate is not None and not isinstance(p_fig_candidate, str): # Basic check for a plot object
170
+ final_plot_figs.append(p_fig_candidate)
171
  else:
172
+ err_title = plot_titles_for_errors[i] if i < len(plot_titles_for_errors) else f"Grafico {i+1}"
173
+ logging.warning(f"Plot {err_title} (index {i}) non è una figura valida: {p_fig_candidate}. Uso placeholder.")
174
+ final_plot_figs.append(create_placeholder_plot(title=f"Errore: {err_title}", message="Impossibile generare figura."))
175
 
176
+ return [message] + final_plot_figs[:num_expected_plots] # Slice to ensure correct number
177
+
178
+ except (KeyError, ValueError) as e_plot_data:
179
+ # This catches errors if a specific plot function fails due to missing columns or bad data
180
+ logging.error(f"Errore dati durante la generazione di un grafico specifico: {e_plot_data}", exc_info=True)
181
+ # We don't know which plot failed, so fill all with placeholders after this point
182
+ # Or, more robustly, identify which plot failed and insert placeholder there.
183
+ # For now, this will likely result in fewer than num_expected_plots if it happens mid-way.
184
+ # The loop below will fill remaining with placeholders.
185
+ error_msg_display = f"Errore dati in un grafico: {str(e_plot_data)[:100]}"
186
+
187
+ # Fill remaining plot_figs with placeholders
188
+ num_already_generated = len(plot_figs)
189
+ for i in range(num_already_generated, num_expected_plots):
190
+ err_title_fill = plot_titles_for_errors[i] if i < len(plot_titles_for_errors) else f"Grafico {i+1}"
191
+ plot_figs.append(create_placeholder_plot(title=f"Errore Dati: {err_title_fill}", message=f"Precedente errore: {str(e_plot_data)[:50]}"))
192
+
193
+ return [error_msg_display] + plot_figs[:num_expected_plots]
194
 
195
+ except Exception as e_general:
196
+ error_msg = f"❌ Errore generale durante la generazione dei grafici: {e_general}"
197
  logging.error(error_msg, exc_info=True)
198
+ placeholder_figs_general = [create_placeholder_plot(title=plot_titles_for_errors[i] if i < len(plot_titles_for_errors) else f"Grafico {i+1}", message=str(e_general)) for i in range(num_expected_plots)]
199
+ return [error_msg] + placeholder_figs_general
200
 
201
 
202
  # --- Gradio UI Blocks ---
 
554
  ]
555
  explore_click_inputs = [explored_plot_id_state]
556
 
557
+ # --- Define "creator" functions for async click handlers ---
558
+ def create_panel_action_handler(p_id, action_type_str):
559
+ # This inner function is what Gradio will call. It's async.
560
+ async def _handler(current_active_val, current_chats_val, current_chat_pid):
561
+ logging.debug(f"Entering _handler for plot_id: {p_id}, action: {action_type_str}")
562
+ result = await handle_panel_action(p_id, action_type_str, current_active_val, current_chats_val, current_chat_pid)
563
+ logging.debug(f"_handler for plot_id: {p_id}, action: {action_type_str} completed.")
564
+ return result
565
+ return _handler # Return the async handler function itself
566
+ # --- End "creator" functions ---
567
 
568
  for config_item in plot_configs:
569
  plot_id = config_item["id"]
 
570
  if plot_id in plot_ui_objects:
571
  ui_obj = plot_ui_objects[plot_id]
572
 
573
+ # The 'fn' passed to .click() is the async function returned by the creator
574
  ui_obj["bomb_button"].click(
575
+ fn=create_panel_action_handler(plot_id, "insights"),
576
  inputs=action_click_inputs,
577
  outputs=action_panel_outputs_list,
578
  api_name=f"action_insights_{plot_id}"
579
  )
580
  ui_obj["formula_button"].click(
581
+ fn=create_panel_action_handler(plot_id, "formula"),
582
  inputs=action_click_inputs,
583
  outputs=action_panel_outputs_list,
584
  api_name=f"action_formula_{plot_id}"
 
661
 
662
  all_updates.append(None)
663
 
664
+ logging.info(f"Preparati {len(all_updates)} aggiornamenti per il refresh delle analisi.")
665
  return all_updates
666
 
667
  apply_filter_and_sync_outputs_list = [analytics_status_md]
 
763
  try:
764
  logging.info(f"Versione Matplotlib: {matplotlib.__version__}, Backend: {matplotlib.get_backend()}")
765
  except ImportError:
766
+ logging.warning("Matplotlib non trovato direttamente, ma potrebbe essere usato dai generatori di grafici.") # Changed from error to warning
767
 
768
  app.launch(server_name="0.0.0.0", server_port=7860, debug=True)