Spaces:
Running
Running
Update app.py
Browse files
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 |
-
#
|
113 |
-
#
|
114 |
-
|
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,
|
169 |
-
if
|
170 |
-
final_plot_figs.append(
|
171 |
else:
|
172 |
-
|
173 |
-
|
|
|
174 |
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
180 |
|
181 |
-
except Exception as
|
182 |
-
error_msg = f"❌ Errore durante la generazione
|
183 |
logging.error(error_msg, exc_info=True)
|
184 |
-
|
185 |
-
return [error_msg] +
|
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
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
|
|
|
|
|
|
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 |
-
#
|
558 |
ui_obj["bomb_button"].click(
|
559 |
-
fn=
|
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=
|
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
|
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)
|