Spaces:
Sleeping
Sleeping
Update ui/callbacks.py
Browse files- ui/callbacks.py +98 -121
ui/callbacks.py
CHANGED
@@ -4,9 +4,9 @@
|
|
4 |
#
|
5 |
# PROJECT: CognitiveEDA v5.0 - The QuantumLeap Intelligence Platform
|
6 |
#
|
7 |
-
# DESCRIPTION:
|
8 |
-
#
|
9 |
-
#
|
10 |
|
11 |
import gradio as gr
|
12 |
import pandas as pd
|
@@ -18,124 +18,101 @@ from core.llm import GeminiNarrativeGenerator
|
|
18 |
from core.config import settings
|
19 |
from core.exceptions import DataProcessingError
|
20 |
from modules.clustering import perform_clustering
|
21 |
-
|
22 |
-
from modules.timeseries import analyze_time_series
|
23 |
-
# NOTE: The UI layout file would need to be updated to pass all the individual component
|
24 |
-
# references, which is done in the create_main_layout function. This callback assumes
|
25 |
-
# a dictionary of components is passed to it.
|
26 |
|
27 |
-
|
|
|
28 |
"""
|
29 |
-
|
30 |
-
|
31 |
-
Args:
|
32 |
-
components (dict): A dictionary mapping component names to their
|
33 |
-
Gradio component objects.
|
34 |
"""
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
inputs=[components["upload_button"]],
|
126 |
-
outputs=output_component_list
|
127 |
-
)
|
128 |
-
|
129 |
-
# --- Other Interactive Callbacks ---
|
130 |
-
def update_clustering(analyzer, k):
|
131 |
-
if not analyzer: return gr.update(), gr.update(), gr.update()
|
132 |
-
fig_cluster, fig_elbow, summary = perform_clustering(analyzer.df, analyzer.metadata['numeric_cols'], k)
|
133 |
-
return fig_cluster, fig_elbow, summary
|
134 |
-
|
135 |
-
components["num_clusters"].change(
|
136 |
-
fn=update_clustering,
|
137 |
-
inputs=[components["state_analyzer"], components["num_clusters"]],
|
138 |
-
outputs=[components["plot_cluster"], components["plot_elbow"], components["md_cluster_summary"]]
|
139 |
-
)
|
140 |
-
|
141 |
-
# (Imagine other callbacks for scatter, histogram, etc., are registered here)
|
|
|
4 |
#
|
5 |
# PROJECT: CognitiveEDA v5.0 - The QuantumLeap Intelligence Platform
|
6 |
#
|
7 |
+
# DESCRIPTION: This module now contains only the CORE LOGIC for the Gradio
|
8 |
+
# event handlers. It exports these functions to be attached to
|
9 |
+
# listeners within the main application context.
|
10 |
|
11 |
import gradio as gr
|
12 |
import pandas as pd
|
|
|
18 |
from core.config import settings
|
19 |
from core.exceptions import DataProcessingError
|
20 |
from modules.clustering import perform_clustering
|
21 |
+
# ... other module imports
|
|
|
|
|
|
|
|
|
22 |
|
23 |
+
# --- Main Analysis Logic ---
|
24 |
+
def run_full_analysis(file_obj, progress=gr.Progress(track_tqdm=True)):
|
25 |
"""
|
26 |
+
The primary orchestration function. This is the logic that will be called
|
27 |
+
by the 'analyze_button.click' event.
|
|
|
|
|
|
|
28 |
"""
|
29 |
+
# 1. Input Validation (File)
|
30 |
+
if file_obj is None:
|
31 |
+
raise gr.Error("No file uploaded. Please upload a CSV or Excel file.")
|
32 |
+
|
33 |
+
# 2. Runtime Configuration Validation (API Key)
|
34 |
+
progress(0, desc="Validating configuration...")
|
35 |
+
if not settings.GOOGLE_API_KEY:
|
36 |
+
logging.error("Analysis attempted without GOOGLE_API_KEY set.")
|
37 |
+
raise gr.Error(
|
38 |
+
"CRITICAL: GOOGLE_API_KEY is not configured. "
|
39 |
+
"Please add it to your .env file or as a platform secret and restart."
|
40 |
+
)
|
41 |
+
|
42 |
+
try:
|
43 |
+
# 3. Data Loading & Core Analysis
|
44 |
+
progress(0.1, desc="Loading and parsing data...")
|
45 |
+
df = pd.read_csv(file_obj.name) if file_obj.name.endswith('.csv') else pd.read_excel(file_obj.name)
|
46 |
+
if len(df) > settings.MAX_UI_ROWS:
|
47 |
+
df = df.sample(n=settings.MAX_UI_ROWS, random_state=42)
|
48 |
+
|
49 |
+
progress(0.3, desc="Instantiating analysis engine...")
|
50 |
+
analyzer = DataAnalyzer(df)
|
51 |
+
return analyzer # We will return the analyzer and handle the rest in a subsequent step
|
52 |
+
|
53 |
+
except DataProcessingError as e:
|
54 |
+
logging.error(f"User-facing data processing error: {e}", exc_info=True)
|
55 |
+
raise gr.Error(str(e))
|
56 |
+
except Exception as e:
|
57 |
+
logging.error(f"A critical unhandled error occurred: {e}", exc_info=True)
|
58 |
+
raise gr.Error(f"Analysis Failed! An unexpected error occurred: {str(e)}")
|
59 |
+
|
60 |
+
|
61 |
+
def generate_reports_and_visuals(analyzer, progress=gr.Progress(track_tqdm=True)):
|
62 |
+
"""
|
63 |
+
A generator function that yields UI updates. Triggered after the analyzer is created.
|
64 |
+
"""
|
65 |
+
if not analyzer:
|
66 |
+
# This prevents errors if the initial analysis failed.
|
67 |
+
# Create an empty dictionary that matches the structure of `updates`
|
68 |
+
# so Gradio has something to unpack.
|
69 |
+
return { "state_analyzer": None }
|
70 |
+
|
71 |
+
# 1. Start AI thread
|
72 |
+
progress(0.1, desc="Spawning AI report thread...")
|
73 |
+
ai_report_queue = [""]
|
74 |
+
def generate_ai_report_threaded(analyzer_instance):
|
75 |
+
narrative_generator = GeminiNarrativeGenerator(api_key=settings.GOOGLE_API_KEY)
|
76 |
+
ai_report_queue[0] = narrative_generator.generate_narrative(analyzer_instance)
|
77 |
+
|
78 |
+
thread = Thread(target=generate_ai_report_threaded, args=(analyzer,))
|
79 |
+
thread.start()
|
80 |
+
|
81 |
+
# 2. Generate standard reports
|
82 |
+
progress(0.4, desc="Generating data profiles...")
|
83 |
+
meta = analyzer.metadata
|
84 |
+
missing_df, num_df, cat_df = analyzer.get_profiling_reports()
|
85 |
+
fig_types, fig_missing, fig_corr = analyzer.get_overview_visuals()
|
86 |
+
|
87 |
+
# 3. Yield initial updates
|
88 |
+
progress(0.8, desc="Building initial dashboard...")
|
89 |
+
initial_updates = {
|
90 |
+
"ai_report_output": gr.update(value="⏳ Generating AI report... Main dashboard is ready."),
|
91 |
+
"profile_missing_df": gr.update(value=missing_df),
|
92 |
+
"profile_numeric_df": gr.update(value=num_df),
|
93 |
+
"profile_categorical_df": gr.update(value=cat_df),
|
94 |
+
"plot_types": gr.update(value=fig_types),
|
95 |
+
"plot_missing": gr.update(value=fig_missing),
|
96 |
+
"plot_correlation": gr.update(value=fig_corr),
|
97 |
+
"dd_hist_col": gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][0] if meta['numeric_cols'] else None),
|
98 |
+
"dd_scatter_x": gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][0] if meta['numeric_cols'] else None),
|
99 |
+
"dd_scatter_y": gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][1] if len(meta['numeric_cols']) > 1 else None),
|
100 |
+
"dd_scatter_color": gr.update(choices=meta['columns']),
|
101 |
+
"tab_timeseries": gr.update(visible=bool(meta['datetime_cols'])),
|
102 |
+
"tab_text": gr.update(visible=bool(meta['text_cols'])),
|
103 |
+
"tab_cluster": gr.update(visible=len(meta['numeric_cols']) > 1),
|
104 |
+
}
|
105 |
+
yield initial_updates
|
106 |
+
|
107 |
+
# 4. Wait for thread and yield final AI report
|
108 |
+
thread.join()
|
109 |
+
progress(1.0, desc="AI Report complete!")
|
110 |
+
final_updates = initial_updates.copy()
|
111 |
+
final_updates["ai_report_output"] = ai_report_queue[0]
|
112 |
+
yield final_updates
|
113 |
+
|
114 |
+
# --- Other Interactive Callback Logic ---
|
115 |
+
def update_clustering(analyzer, k):
|
116 |
+
if not analyzer: return gr.update(), gr.update(), gr.update()
|
117 |
+
fig_cluster, fig_elbow, summary = perform_clustering(analyzer.df, analyzer.metadata['numeric_cols'], k)
|
118 |
+
return fig_cluster, fig_elbow, summary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|