mgbam commited on
Commit
fcc261b
·
verified ·
1 Parent(s): 12fa967

Update ui/callbacks.py

Browse files
Files changed (1) hide show
  1. 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: The "Controller" of the application. This module contains all
8
- # the Gradio event handlers (callbacks) that connect the UI (view)
9
- # to the core analysis engine (model).
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
- from modules.text import generate_word_cloud
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
- def register_callbacks(components):
 
28
  """
29
- Binds all event handlers (callbacks) to the UI components.
30
-
31
- Args:
32
- components (dict): A dictionary mapping component names to their
33
- Gradio component objects.
34
  """
35
-
36
- # --- Main Analysis Trigger ---
37
- def run_full_analysis(file_obj, progress=gr.Progress(track_tqdm=True)):
38
- """
39
- The primary orchestration function triggered by the user. It loads data,
40
- runs the standard analysis, and spawns a thread for the AI report.
41
- """
42
- # 1. Input Validation (File)
43
- if file_obj is None:
44
- raise gr.Error("No file uploaded. Please upload a CSV or Excel file.")
45
-
46
- # 2. Runtime Configuration Validation (API Key)
47
- # This is the critical fix. We check for the key here, at the point of use,
48
- # rather than letting it crash the app at startup.
49
- progress(0, desc="Validating configuration...")
50
- if not settings.GOOGLE_API_KEY:
51
- logging.error("Analysis attempted without GOOGLE_API_KEY set.")
52
- raise gr.Error(
53
- "CRITICAL: GOOGLE_API_KEY is not configured. "
54
- "The AI Strategy Report cannot be generated. Please add the key to your "
55
- ".env file (for local development) or as a platform secret (for deployed apps) and restart."
56
- )
57
-
58
- try:
59
- # 3. Data Loading & Core Analysis
60
- progress(0.1, desc="Loading and parsing data...")
61
- df = pd.read_csv(file_obj.name) if file_obj.name.endswith('.csv') else pd.read_excel(file_obj.name)
62
- if len(df) > settings.MAX_UI_ROWS:
63
- df = df.sample(n=settings.MAX_UI_ROWS, random_state=42)
64
-
65
- progress(0.3, desc="Instantiating analysis engine...")
66
- analyzer = DataAnalyzer(df)
67
- meta = analyzer.metadata
68
-
69
- # 4. Asynchronous AI Narrative Generation
70
- ai_report_queue = [""] # Use a mutable list to pass string by reference
71
- def generate_ai_report_threaded(analyzer_instance):
72
- narrative_generator = GeminiNarrativeGenerator(api_key=settings.GOOGLE_API_KEY)
73
- ai_report_queue[0] = narrative_generator.generate_narrative(analyzer_instance)
74
-
75
- thread = Thread(target=generate_ai_report_threaded, args=(analyzer,))
76
- thread.start()
77
-
78
- # 5. Generate Standard Reports and Visuals (runs immediately)
79
- progress(0.5, desc="Generating data profiles...")
80
- missing_df, num_df, cat_df = analyzer.get_profiling_reports()
81
-
82
- progress(0.7, desc="Creating overview visualizations...")
83
- fig_types, fig_missing, fig_corr = analyzer.get_overview_visuals()
84
-
85
- # 6. Prepare and yield initial UI updates
86
- progress(0.9, desc="Building initial dashboard...")
87
- initial_updates = {
88
- components["state_analyzer"]: analyzer,
89
- components["ai_report_output"]: gr.update(value="⏳ Generating AI-powered report in the background... The main dashboard is ready now."),
90
- components["profile_missing_df"]: gr.update(value=missing_df),
91
- components["profile_numeric_df"]: gr.update(value=num_df),
92
- components["profile_categorical_df"]: gr.update(value=cat_df),
93
- components["plot_types"]: gr.update(value=fig_types),
94
- components["plot_missing"]: gr.update(value=fig_missing),
95
- components["plot_correlation"]: gr.update(value=fig_corr),
96
- components["dd_hist_col"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][0] if meta['numeric_cols'] else None),
97
- components["dd_scatter_x"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][0] if meta['numeric_cols'] else None),
98
- components["dd_scatter_y"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][1] if len(meta['numeric_cols']) > 1 else None),
99
- components["dd_scatter_color"]: gr.update(choices=meta['columns']),
100
- components["tab_timeseries"]: gr.update(visible=bool(meta['datetime_cols'])),
101
- components["tab_text"]: gr.update(visible=bool(meta['text_cols'])),
102
- components["tab_cluster"]: gr.update(visible=len(meta['numeric_cols']) > 1),
103
- }
104
- yield initial_updates
105
-
106
- # 7. Wait for AI thread and yield final update
107
- thread.join()
108
- progress(1.0, desc="AI Report complete!")
109
- final_updates = initial_updates.copy()
110
- final_updates[components["ai_report_output"]] = ai_report_queue[0]
111
- yield final_updates
112
-
113
- except DataProcessingError as e:
114
- logging.error(f"User-facing data processing error: {e}", exc_info=True)
115
- raise gr.Error(str(e))
116
- except Exception as e:
117
- logging.error(f"A critical unhandled error occurred: {e}", exc_info=True)
118
- raise gr.Error(f"Analysis Failed! An unexpected error occurred: {str(e)}")
119
-
120
- # Bind the main analysis function
121
- # Note: `outputs` must be a list of all components being updated.
122
- output_component_list = list(components.values())
123
- components["analyze_button"].click(
124
- fn=run_full_analysis,
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