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

Update ui/callbacks.py

Browse files
Files changed (1) hide show
  1. ui/callbacks.py +74 -33
ui/callbacks.py CHANGED
@@ -1,4 +1,13 @@
1
  # ui/callbacks.py
 
 
 
 
 
 
 
 
 
2
  import gradio as gr
3
  import pandas as pd
4
  import logging
@@ -7,54 +16,83 @@ from threading import Thread
7
  from core.analyzer import DataAnalyzer
8
  from core.llm import GeminiNarrativeGenerator
9
  from core.config import settings
10
- from core.exceptions import APIKeyMissingError, DataProcessingError
11
  from modules.clustering import perform_clustering
12
- # ... other module imports
 
 
 
 
13
 
14
  def register_callbacks(components):
15
- """Binds event handlers to the UI components."""
 
 
 
 
 
 
16
 
17
  # --- Main Analysis Trigger ---
18
- def run_full_analysis(file_obj):
19
- # 1. Input Validation
 
 
 
 
20
  if file_obj is None:
21
  raise gr.Error("No file uploaded. Please upload a CSV or Excel file.")
 
 
 
 
 
22
  if not settings.GOOGLE_API_KEY:
23
- raise APIKeyMissingError("CRITICAL: GOOGLE_API_KEY not found in .env file.")
 
 
 
 
 
24
 
25
  try:
26
- # 2. Data Loading & Pre-processing
27
- logging.info(f"Processing uploaded file: {file_obj.name}")
28
  df = pd.read_csv(file_obj.name) if file_obj.name.endswith('.csv') else pd.read_excel(file_obj.name)
29
  if len(df) > settings.MAX_UI_ROWS:
30
  df = df.sample(n=settings.MAX_UI_ROWS, random_state=42)
31
-
32
- # 3. Core Analysis
33
  analyzer = DataAnalyzer(df)
34
  meta = analyzer.metadata
35
- missing_df, num_df, cat_df = analyzer.get_profiling_reports()
36
- fig_types, fig_missing, fig_corr = analyzer.get_overview_visuals()
37
 
38
  # 4. Asynchronous AI Narrative Generation
39
- ai_report_queue = [""] # Use a mutable list to pass string by reference
40
  def generate_ai_report_threaded(analyzer_instance):
41
  narrative_generator = GeminiNarrativeGenerator(api_key=settings.GOOGLE_API_KEY)
42
  ai_report_queue[0] = narrative_generator.generate_narrative(analyzer_instance)
43
 
44
  thread = Thread(target=generate_ai_report_threaded, args=(analyzer,))
45
  thread.start()
46
-
47
- # 5. Prepare Initial UI Updates (Instantaneous)
48
- updates = {
 
 
 
 
 
 
 
 
49
  components["state_analyzer"]: analyzer,
50
- components["ai_report_output"]: "⏳ Generating AI-powered report... This may take a moment.",
51
  components["profile_missing_df"]: gr.update(value=missing_df),
52
  components["profile_numeric_df"]: gr.update(value=num_df),
53
  components["profile_categorical_df"]: gr.update(value=cat_df),
54
  components["plot_types"]: gr.update(value=fig_types),
55
  components["plot_missing"]: gr.update(value=fig_missing),
56
  components["plot_correlation"]: gr.update(value=fig_corr),
57
- # ... update dropdowns and visibility ...
58
  components["dd_hist_col"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][0] if meta['numeric_cols'] else None),
59
  components["dd_scatter_x"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][0] if meta['numeric_cols'] else None),
60
  components["dd_scatter_y"]: gr.update(choices=meta['numeric_cols'], value=meta['numeric_cols'][1] if len(meta['numeric_cols']) > 1 else None),
@@ -63,29 +101,32 @@ def register_callbacks(components):
63
  components["tab_text"]: gr.update(visible=bool(meta['text_cols'])),
64
  components["tab_cluster"]: gr.update(visible=len(meta['numeric_cols']) > 1),
65
  }
66
- yield updates
67
 
68
- # 6. Final UI Update (When AI report is ready)
69
- thread.join() # Wait for the AI thread to finish
70
- updates[components["ai_report_output"]] = ai_report_queue[0]
71
- yield updates
 
 
72
 
73
- except (DataProcessingError, APIKeyMissingError) as e:
74
- logging.error(f"User-facing error: {e}", exc_info=True)
75
  raise gr.Error(str(e))
76
  except Exception as e:
77
  logging.error(f"A critical unhandled error occurred: {e}", exc_info=True)
78
  raise gr.Error(f"Analysis Failed! An unexpected error occurred: {str(e)}")
79
 
80
-
81
- # Bind the main function
 
82
  components["analyze_button"].click(
83
- fn=run_full_analysis,
84
- inputs=[components["upload_button"]],
85
- outputs=list(components.values())
86
  )
87
 
88
- # --- Clustering Tab Callback ---
89
  def update_clustering(analyzer, k):
90
  if not analyzer: return gr.update(), gr.update(), gr.update()
91
  fig_cluster, fig_elbow, summary = perform_clustering(analyzer.df, analyzer.metadata['numeric_cols'], k)
@@ -96,5 +137,5 @@ def register_callbacks(components):
96
  inputs=[components["state_analyzer"], components["num_clusters"]],
97
  outputs=[components["plot_cluster"], components["plot_elbow"], components["md_cluster_summary"]]
98
  )
99
-
100
- # ... Register other callbacks for histogram, scatter, etc. in a similar fashion ...
 
1
  # ui/callbacks.py
2
+
3
+ # -*- coding: utf-8 -*-
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
13
  import logging
 
16
  from core.analyzer import DataAnalyzer
17
  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),
 
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)
 
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)