Tamannathakur commited on
Commit
7cd03fd
·
verified ·
1 Parent(s): 2ad2aa7

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +803 -0
  2. prompts.py +129 -0
  3. sparknova.py +413 -0
app.py ADDED
@@ -0,0 +1,803 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import numpy as np
4
+ import os
5
+ import json
6
+ import plotly.express as px
7
+ import plotly.graph_objects as go
8
+ from langchain_groq import ChatGroq
9
+ from prompts import ENHANCED_SYSTEM_PROMPT, SAMPLE_QUESTIONS, get_chart_prompt, validate_plot_spec, INSIGHTS_SYSTEM_PROMPT, get_insights_prompt
10
+
11
+ GROQ_API_KEY = "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"
12
+ llm = None
13
+ uploaded_df = None
14
+ dataset_name = None
15
+
16
+ def initialize_llm():
17
+ global llm
18
+ try:
19
+ os.environ["GROQ_API_KEY"] = GROQ_API_KEY
20
+ llm = ChatGroq(model="llama-3.3-70b-versatile", api_key=GROQ_API_KEY, temperature=0.0)
21
+ return True
22
+ except:
23
+ return False
24
+
25
+ def upload_dataset(file):
26
+ global uploaded_df, dataset_name
27
+ if file is None:
28
+ return "No file uploaded", gr.update(visible=False), gr.update(choices=[]), gr.update(visible=False)
29
+ try:
30
+ dataset_name = os.path.basename(file.name)
31
+ if file.name.endswith('.csv'):
32
+ uploaded_df = pd.read_csv(file.name)
33
+ elif file.name.endswith(('.xlsx', '.xls')):
34
+ uploaded_df = pd.read_excel(file.name)
35
+ else:
36
+ return "Unsupported file format. Please upload CSV or Excel files.", gr.update(visible=False), gr.update(choices=[]), gr.update(visible=False)
37
+ uploaded_df = clean_numeric(uploaded_df)
38
+ info_text = f"**Dataset Loaded:** {dataset_name} ({uploaded_df.shape[0]} rows × {uploaded_df.shape[1]} columns)"
39
+ return info_text, gr.update(visible=False), gr.update(choices=list(uploaded_df.columns), value=[]), gr.update(visible=True)
40
+ except Exception as e:
41
+ return f"Error loading file: {str(e)}", gr.update(visible=False), gr.update(choices=[]), gr.update(visible=False)
42
+
43
+ def clean_numeric(df):
44
+ df = df.copy()
45
+ for col in df.columns:
46
+ if pd.api.types.is_string_dtype(df[col]) or df[col].dtype == object:
47
+ s = df[col].astype(str).str.strip()
48
+ if s.str.contains("%", na=False).any():
49
+ numeric_vals = pd.to_numeric(s.str.replace("%", "", regex=False), errors="coerce")
50
+ if numeric_vals.notna().sum() / len(df) > 0.5:
51
+ df[col] = numeric_vals / 100.0
52
+ continue
53
+ cleaned = s.str.replace(",", "", regex=False).str.replace("₹", "", regex=False).str.replace("$", "", regex=False)
54
+ numeric_vals = pd.to_numeric(cleaned, errors="coerce")
55
+ if numeric_vals.notna().sum() / len(df) > 0.5:
56
+ df[col] = numeric_vals
57
+ return df
58
+
59
+ def display_data_format(format_type, selected_columns):
60
+ global uploaded_df
61
+ if uploaded_df is None or format_type == "None":
62
+ return None
63
+ df_to_show = uploaded_df[selected_columns] if selected_columns else uploaded_df
64
+ return df_to_show.head(100) if format_type == "DataFrame" else None
65
+
66
+ def display_text_format(format_type, selected_columns):
67
+ global uploaded_df
68
+ if uploaded_df is None or format_type == "None":
69
+ return ""
70
+ df_to_show = uploaded_df[selected_columns] if selected_columns else uploaded_df
71
+ if format_type == "JSON":
72
+ return df_to_show.head(20).to_json(orient='records', indent=2)
73
+ elif format_type == "Dictionary":
74
+ return str(df_to_show.head(20).to_dict(orient='records'))
75
+ return ""
76
+
77
+ def run_analysis(analysis_type, selected_columns):
78
+ global uploaded_df
79
+ if uploaded_df is None:
80
+ return "Please upload a dataset first.", gr.update(visible=True), None
81
+ if analysis_type == "None" or analysis_type is None:
82
+ return "", gr.update(visible=False), None
83
+
84
+ whole_dataset_analyses = ["Summary", "Top 5 Rows", "Bottom 5 Rows", "Missing Values"]
85
+ if analysis_type in whole_dataset_analyses:
86
+ df_to_analyze = uploaded_df
87
+ else:
88
+ if not selected_columns:
89
+ return f"Please select columns for {analysis_type} analysis.", gr.update(visible=True), None
90
+ df_to_analyze = uploaded_df[selected_columns]
91
+
92
+ try:
93
+ if analysis_type == "Summary":
94
+ numeric_cols = uploaded_df.select_dtypes(include=[np.number]).columns
95
+ categorical_cols = uploaded_df.select_dtypes(include=['object', 'category']).columns
96
+ result = f"Dataset Summary:\nRows: {len(uploaded_df):,}\nColumns: {len(uploaded_df.columns)}\nNumeric Columns: {len(numeric_cols)}\nText Columns: {len(categorical_cols)}\n\n"
97
+ if len(numeric_cols) > 0:
98
+ result += "Numeric Columns: " + ", ".join(numeric_cols.tolist()) + "\n"
99
+ if len(categorical_cols) > 0:
100
+ result += "Text Columns: " + ", ".join(categorical_cols.tolist())
101
+ return result, gr.update(visible=True), None
102
+
103
+ elif analysis_type == "Describe":
104
+ result = "Column Description:\n" + "=" * 30 + "\n\n"
105
+ for col in selected_columns:
106
+ if col in df_to_analyze.columns:
107
+ result += f"Column: {col}\n"
108
+ if pd.api.types.is_numeric_dtype(df_to_analyze[col]):
109
+ stats = df_to_analyze[col].describe()
110
+ result += f" Type: Numeric\n Count: {stats['count']:.0f}\n Mean: {stats['mean']:.3f}\n Std: {stats['std']:.3f}\n Min: {stats['min']:.3f}\n 25%: {stats['25%']:.3f}\n 50%: {stats['50%']:.3f}\n 75%: {stats['75%']:.3f}\n Max: {stats['max']:.3f}\n\n"
111
+ else:
112
+ unique_count = df_to_analyze[col].nunique()
113
+ null_count = df_to_analyze[col].isnull().sum()
114
+ most_common = df_to_analyze[col].mode().iloc[0] if len(df_to_analyze[col].mode()) > 0 else "N/A"
115
+ result += f" Type: Categorical/Text\n Unique Values: {unique_count}\n Missing Values: {null_count}\n Most Common: {most_common}\n"
116
+ top_values = df_to_analyze[col].value_counts().head(5)
117
+ result += " Top Values:\n"
118
+ for val, count in top_values.items():
119
+ result += f" {val}: {count} times\n"
120
+ result += "\n"
121
+ return result, gr.update(visible=True), None
122
+
123
+ elif analysis_type == "Top 5 Rows":
124
+ return "Top 5 Rows - See data table below", gr.update(visible=True), df_to_analyze.head(5)
125
+
126
+ elif analysis_type == "Bottom 5 Rows":
127
+ return "Bottom 5 Rows - See data table below", gr.update(visible=True), df_to_analyze.tail(5)
128
+
129
+ elif analysis_type == "Missing Values":
130
+ missing = df_to_analyze.isnull().sum()
131
+ result = "Missing Values Analysis:\n" + "=" * 30 + "\n\n"
132
+
133
+ for col in df_to_analyze.columns:
134
+ missing_count = missing[col]
135
+ missing_percent = (missing_count / len(df_to_analyze)) * 100
136
+ result += f"{col}: {missing_count} missing ({missing_percent:.2f}%)\n"
137
+
138
+ return result, gr.update(visible=True), None
139
+
140
+ elif analysis_type == "Highest Correlation":
141
+ numeric_cols = df_to_analyze.select_dtypes(include=[np.number]).columns
142
+ if len(numeric_cols) < 2:
143
+ return "Need at least 2 numeric columns for correlation analysis.", gr.update(visible=True), None
144
+ corr_matrix = df_to_analyze[numeric_cols].corr()
145
+ result = "Highest Correlations:\n" + "=" * 25 + "\n\n"
146
+ correlations = []
147
+ for i in range(len(corr_matrix.columns)):
148
+ for j in range(i+1, len(corr_matrix.columns)):
149
+ col1, col2 = corr_matrix.columns[i], corr_matrix.columns[j]
150
+ corr_val = corr_matrix.iloc[i, j]
151
+ correlations.append((abs(corr_val), col1, col2, corr_val))
152
+ correlations.sort(reverse=True)
153
+ for _, col1, col2, corr_val in correlations[:10]:
154
+ result += f"{col1} ↔ {col2}: {corr_val:.3f}\n"
155
+ return result, gr.update(visible=True), None
156
+
157
+ elif analysis_type == "Group & Aggregate":
158
+ if not selected_columns:
159
+ result = "Please select columns for grouping and aggregation."
160
+ else:
161
+ categorical_cols = [col for col in selected_columns if not pd.api.types.is_numeric_dtype(df_to_analyze[col])]
162
+ numeric_cols = [col for col in selected_columns if pd.api.types.is_numeric_dtype(df_to_analyze[col])]
163
+
164
+ if categorical_cols and numeric_cols:
165
+ group_col = categorical_cols[0]
166
+ agg_col = numeric_cols[0]
167
+ grouped = df_to_analyze.groupby(group_col)[agg_col].agg(['count', 'mean', 'sum']).round(2)
168
+ result = f"Group & Aggregate Analysis:\n" + "=" * 35 + "\n\n"
169
+ result += f"Grouped by: {group_col}\nAggregated: {agg_col}\n\n"
170
+ result += grouped.to_string()
171
+ elif categorical_cols:
172
+ group_col = categorical_cols[0]
173
+ grouped = df_to_analyze[group_col].value_counts()
174
+ result = f"Group Count Analysis:\n" + "=" * 25 + "\n\n"
175
+ result += grouped.to_string()
176
+ else:
177
+ result = "Please select at least one categorical column for grouping."
178
+ return result, gr.update(visible=True), None
179
+
180
+ elif analysis_type == "Calculate Expressions":
181
+ numeric_cols = df_to_analyze.select_dtypes(include=[np.number]).columns
182
+
183
+ if len(numeric_cols) >= 2:
184
+ col1, col2 = numeric_cols[0], numeric_cols[1]
185
+ df_calc = df_to_analyze.copy()
186
+ df_calc['Sum'] = df_calc[col1] + df_calc[col2]
187
+ df_calc['Difference'] = df_calc[col1] - df_calc[col2]
188
+
189
+ result = f"Calculated Expressions:\n" + "=" * 30 + "\n\n"
190
+ result += f"Using columns: {col1} and {col2}\n\n"
191
+ result += f"New calculated columns:\nSum = {col1} + {col2}\nDifference = {col1} - {col2}\n\n"
192
+ result += "Sample results:\n"
193
+ result += df_calc[['Sum', 'Difference']].head().to_string()
194
+ else:
195
+ result = "Need at least 2 numeric columns for calculations."
196
+ return result, gr.update(visible=True), None
197
+
198
+ else:
199
+ return f"Analysis type '{analysis_type}' is under development.", gr.update(visible=True), None
200
+
201
+ except Exception as e:
202
+ return f"Error in analysis: {str(e)}", gr.update(visible=True), None
203
+
204
+ def create_chart_explanation(viz_type, df_to_plot, selected_columns, fig_data=None):
205
+ if viz_type == "Bar Chart" and len(selected_columns) >= 2:
206
+ x_col, y_col = selected_columns[0], selected_columns[1]
207
+ if pd.api.types.is_numeric_dtype(df_to_plot[y_col]):
208
+ max_val_idx = df_to_plot[y_col].idxmax()
209
+ max_category = df_to_plot.loc[max_val_idx, x_col]
210
+ max_value = df_to_plot[y_col].max()
211
+ y_mean = df_to_plot[y_col].mean()
212
+ else:
213
+ grouped = df_to_plot.groupby(x_col)[y_col].count()
214
+ max_category = grouped.idxmax()
215
+ max_value = grouped.max()
216
+ y_mean = grouped.mean()
217
+ return f"BAR CHART ANALYSIS: {y_col} by {x_col}\n\nKEY INSIGHTS:\n• Highest bar: {max_category} with value {max_value:.2f}\n• Average value: {y_mean:.2f}\n• Categories analyzed: {df_to_plot[x_col].nunique()}\n\nINTERPRETATION:\n• Each bar represents a {x_col} category\n• Bar height shows {y_col} value for that category\n• Compare bars to identify highest/lowest performing categories"
218
+ elif viz_type == "Line Chart" and fig_data is not None:
219
+ max_combo = fig_data.loc[fig_data['Count'].idxmax()]
220
+ min_combo = fig_data.loc[fig_data['Count'].idxmin()]
221
+ return f"LINE CHART ANALYSIS: Distribution\n\nKEY INSIGHTS:\n• Highest point: {max_combo[selected_columns[1]]} in {max_combo[selected_columns[0]]} ({max_combo['Count']} records)\n• Lowest point: {min_combo[selected_columns[1]]} in {min_combo[selected_columns[0]]} ({min_combo['Count']} records)\n• Total records analyzed: {len(df_to_plot)}\n\nINTERPRETATION:\n• Each line represents a category\n• Line height shows count of records\n• Compare lines to see patterns between variables"
222
+ return f"• {viz_type} showing data visualization\n• Use to understand data patterns and relationships"
223
+
224
+ def create_visualization(viz_type, selected_columns):
225
+ global uploaded_df
226
+ if uploaded_df is None or viz_type == "None":
227
+ return None, "", gr.update(visible=False)
228
+ if not selected_columns:
229
+ return None, "Please select columns for visualization.", gr.update(visible=False)
230
+ df_to_plot = uploaded_df[selected_columns]
231
+
232
+ try:
233
+ if viz_type == "Bar Chart":
234
+ if len(selected_columns) >= 2:
235
+ x_col, y_col = selected_columns[0], selected_columns[1]
236
+ color_col = selected_columns[2] if len(selected_columns) > 2 else None
237
+ fig = px.bar(df_to_plot.head(50), x=x_col, y=y_col, color=color_col, title=f"{y_col} by {x_col}")
238
+ explanation = create_chart_explanation(viz_type, df_to_plot, selected_columns)
239
+ else:
240
+ col = selected_columns[0]
241
+ if pd.api.types.is_numeric_dtype(df_to_plot[col]):
242
+ fig = px.histogram(df_to_plot, x=col, title=f"Distribution of {col}")
243
+ else:
244
+ value_counts = df_to_plot[col].value_counts().head(10)
245
+ fig = px.bar(x=value_counts.index, y=value_counts.values, title=f"Top Values in {col}")
246
+ explanation = f"• Chart showing distribution of {col}"
247
+ fig.update_layout(width=800, height=500)
248
+ return fig, explanation, gr.update(visible=True)
249
+
250
+ elif viz_type == "Pie Chart":
251
+ col = selected_columns[0]
252
+ if len(selected_columns) >= 2 and pd.api.types.is_numeric_dtype(df_to_plot[selected_columns[1]]):
253
+ grouped_data = df_to_plot.groupby(col)[selected_columns[1]].sum().reset_index()
254
+ fig = px.pie(grouped_data, values=selected_columns[1], names=col, title=f"Total {selected_columns[1]} by {col}")
255
+ else:
256
+ value_counts = df_to_plot[col].value_counts().head(10)
257
+ fig = px.pie(values=value_counts.values, names=value_counts.index, title=f"Distribution of {col}")
258
+ explanation = f"PIE CHART ANALYSIS: {col} Distribution\n\nKEY INSIGHTS:\n• Shows proportion of each category\n• Use to understand category distribution patterns"
259
+ fig.update_layout(width=800, height=500)
260
+ return fig, explanation, gr.update(visible=True)
261
+
262
+ elif viz_type == "Scatter Plot":
263
+ if len(selected_columns) >= 2:
264
+ x_col, y_col = selected_columns[0], selected_columns[1]
265
+ color_col = selected_columns[2] if len(selected_columns) > 2 else None
266
+ fig = px.scatter(df_to_plot, x=x_col, y=y_col, color=color_col, title=f"{y_col} vs {x_col}")
267
+ explanation = f"• Scatter plot showing relationship between {x_col} and {y_col}"
268
+ else:
269
+ return None, "Scatter plot requires at least 2 columns.", gr.update(visible=False)
270
+ fig.update_layout(width=800, height=500)
271
+ return fig, explanation, gr.update(visible=True)
272
+
273
+ elif viz_type == "Line Chart":
274
+ if len(selected_columns) >= 2:
275
+ x_col, y_col = selected_columns[0], selected_columns[1]
276
+ if not pd.api.types.is_numeric_dtype(df_to_plot[y_col]):
277
+ crosstab = pd.crosstab(df_to_plot[x_col], df_to_plot[y_col])
278
+ melted = pd.melt(crosstab.reset_index(), id_vars=[x_col], var_name=y_col, value_name='Count')
279
+ fig = px.line(melted, x=x_col, y='Count', color=y_col, title=f"Distribution of {y_col} across {x_col}", markers=True)
280
+ explanation = create_chart_explanation(viz_type, df_to_plot, selected_columns, melted)
281
+ else:
282
+ fig = px.line(df_to_plot.sort_values(x_col), x=x_col, y=y_col, title=f"Trend of {y_col} over {x_col}", markers=True)
283
+ explanation = f"• Line chart showing trend of {y_col} over {x_col}"
284
+ else:
285
+ return None, "Line chart requires at least 2 columns.", gr.update(visible=False)
286
+ fig.update_layout(width=800, height=500)
287
+ return fig, explanation, gr.update(visible=True)
288
+
289
+ elif viz_type == "Histogram":
290
+ col = selected_columns[0]
291
+ if pd.api.types.is_numeric_dtype(df_to_plot[col]):
292
+ fig = px.histogram(df_to_plot, x=col, title=f"Distribution of {col}", nbins=30)
293
+ explanation = f"• Histogram showing distribution of {col}"
294
+ else:
295
+ return None, f"Histogram requires numeric data. Try Bar Chart instead.", gr.update(visible=False)
296
+ fig.update_layout(width=800, height=500)
297
+ return fig, explanation, gr.update(visible=True)
298
+
299
+ elif viz_type == "Heat Map":
300
+ if len(selected_columns) >= 2:
301
+ numeric_cols = [col for col in selected_columns if pd.api.types.is_numeric_dtype(df_to_plot[col])]
302
+ if len(numeric_cols) >= 2:
303
+ corr_matrix = df_to_plot[numeric_cols].corr()
304
+ fig = px.imshow(corr_matrix, text_auto=True, aspect="auto", title="Correlation Heatmap", color_continuous_scale='RdBu')
305
+ explanation = f"• Heatmap showing correlations between numeric columns"
306
+ else:
307
+ x_col, y_col = selected_columns[0], selected_columns[1]
308
+ crosstab = pd.crosstab(df_to_plot[x_col], df_to_plot[y_col])
309
+ fig = px.imshow(crosstab.values, x=crosstab.columns, y=crosstab.index, text_auto=True, aspect="auto", title=f"Cross-tabulation: {y_col} vs {x_col}")
310
+ explanation = f"• Heatmap showing cross-tabulation between {x_col} and {y_col}"
311
+ else:
312
+ return None, "Heat map requires at least 2 columns.", gr.update(visible=False)
313
+ fig.update_layout(width=800, height=500)
314
+ return fig, explanation, gr.update(visible=True)
315
+
316
+ elif viz_type == "Box Plot":
317
+ y_col = selected_columns[0]
318
+ if pd.api.types.is_numeric_dtype(df_to_plot[y_col]):
319
+ x_col = selected_columns[1] if len(selected_columns) > 1 else None
320
+ fig = px.box(df_to_plot, x=x_col, y=y_col, title=f"Box Plot of {y_col}")
321
+ explanation = f"• Box plot showing distribution of {y_col}"
322
+ else:
323
+ return None, f"Box plot requires numeric data.", gr.update(visible=False)
324
+ fig.update_layout(width=800, height=500)
325
+ return fig, explanation, gr.update(visible=True)
326
+
327
+ else:
328
+ return None, f"Visualization type '{viz_type}' is under development.", gr.update(visible=False)
329
+
330
+ except Exception as e:
331
+ return None, f"Error creating visualization: {str(e)}", gr.update(visible=False)
332
+
333
+ def parse_plan(raw_text):
334
+ txt = raw_text.strip().replace("```json", "").replace("```", "").strip()
335
+ try:
336
+ start = txt.index("{")
337
+ end = txt.rindex("}") + 1
338
+ plan = json.loads(txt[start:end])
339
+ plan.setdefault("type", "analysis")
340
+ plan.setdefault("operations", [])
341
+ plan.setdefault("plot", None)
342
+ plan.setdefault("narrative", "")
343
+ plan.setdefault("insights_needed", False)
344
+ return plan
345
+ except Exception as e:
346
+ return {
347
+ "type": "error",
348
+ "operations": [],
349
+ "plot": None,
350
+ "narrative": f"Error parsing response: {str(e)}",
351
+ "insights_needed": False
352
+ }
353
+
354
+ def execute_plan(df, plan):
355
+ dfw = df.copy()
356
+ describe_stats = {}
357
+
358
+ try:
359
+ for op in plan.get("operations", []):
360
+ optype = op.get("op", "").lower()
361
+
362
+ if optype == "describe":
363
+ cols = op.get("columns", [])
364
+ for col in cols:
365
+ if col in dfw.columns:
366
+ stats = dfw[col].describe()
367
+ describe_stats[col] = stats
368
+
369
+ elif optype == "groupby":
370
+ cols = op.get("columns", [])
371
+ agg_col = op.get("agg_col")
372
+ agg_func = op.get("agg_func", "count")
373
+
374
+ if cols and all(c in dfw.columns for c in cols):
375
+ if agg_func == "count" or not agg_col:
376
+ dfw = dfw.groupby(cols).size().reset_index(name="count")
377
+ else:
378
+ if agg_col in dfw.columns:
379
+ result_col = f"{agg_func}_{agg_col}"
380
+ dfw = dfw.groupby(cols)[agg_col].agg(agg_func).reset_index(name=result_col)
381
+
382
+ elif optype == "filter":
383
+ expr = op.get("expr", "")
384
+ if expr:
385
+ dfw = dfw.query(expr)
386
+
387
+ elif optype == "calculate":
388
+ expr = op.get("expr", "")
389
+ new_col = op.get("new_col", "Calculated")
390
+ if expr:
391
+ dfw[new_col] = dfw.eval(expr)
392
+
393
+ return dfw, describe_stats
394
+ except Exception as e:
395
+ raise Exception(f"Execution error: {str(e)}")
396
+
397
+ def create_simple_chart(df, selected_columns=None):
398
+ try:
399
+ if selected_columns and len(selected_columns) >= 2:
400
+ x_col, y_col = selected_columns[0], selected_columns[1]
401
+ if x_col in df.columns and y_col in df.columns:
402
+ fig = px.bar(df.head(50), x=x_col, y=y_col, title=f"{y_col} by {x_col}")
403
+ fig.update_layout(width=900, height=500)
404
+ return fig
405
+ if selected_columns and len(selected_columns) == 1:
406
+ col = selected_columns[0]
407
+ if col in df.columns:
408
+ if pd.api.types.is_numeric_dtype(df[col]):
409
+ fig = px.histogram(df, x=col, title=f"Distribution of {col}")
410
+ else:
411
+ value_counts = df[col].value_counts().head(10)
412
+ fig = px.bar(x=value_counts.index, y=value_counts.values, title=f"Top Values in {col}")
413
+ fig.update_layout(width=900, height=500)
414
+ return fig
415
+ categorical_cols = df.select_dtypes(include=['object', 'category']).columns
416
+ numeric_cols = df.select_dtypes(include=[np.number]).columns
417
+ if len(numeric_cols) > 1:
418
+ fig = px.scatter(df.head(100), x=numeric_cols[0], y=numeric_cols[1], title=f"{numeric_cols[1]} vs {numeric_cols[0]}")
419
+ elif len(categorical_cols) > 0 and len(numeric_cols) > 0:
420
+ fig = px.bar(df.head(50), x=categorical_cols[0], y=numeric_cols[0], title=f"{numeric_cols[0]} by {categorical_cols[0]}")
421
+ elif len(categorical_cols) > 0:
422
+ value_counts = df[categorical_cols[0]].value_counts().head(10)
423
+ fig = px.pie(values=value_counts.values, names=value_counts.index, title=f"Distribution of {categorical_cols[0]}")
424
+ elif len(numeric_cols) > 0:
425
+ fig = px.histogram(df, x=numeric_cols[0], title=f"Distribution of {numeric_cols[0]}")
426
+ else:
427
+ return None
428
+ fig.update_layout(width=900, height=500)
429
+ return fig
430
+ except:
431
+ return None
432
+
433
+ def create_plot(df, dfw, plan, describe_stats, selected_columns=None):
434
+ plot_spec = plan.get("plot")
435
+ if not plot_spec:
436
+ return None
437
+ ptype = plot_spec.get("type", "bar")
438
+ title = plot_spec.get("title", "Chart")
439
+ plot_df = df if describe_stats else dfw
440
+ x = plot_spec.get("x")
441
+ y = plot_spec.get("y")
442
+
443
+ if not x and len(plot_df.columns) > 0:
444
+ categorical_cols = plot_df.select_dtypes(include=['object', 'category']).columns
445
+ x = categorical_cols[0] if len(categorical_cols) > 0 else plot_df.columns[0]
446
+ if not y:
447
+ numeric_cols = plot_df.select_dtypes(include=[np.number]).columns
448
+ y = numeric_cols[0] if len(numeric_cols) > 0 else None
449
+
450
+ try:
451
+ if ptype == "pie" and x and x in plot_df.columns:
452
+ value_counts = plot_df[x].value_counts()
453
+ fig = go.Figure(data=[go.Pie(labels=value_counts.index, values=value_counts.values, hole=0.3)])
454
+ fig.update_layout(title=title, width=900, height=500)
455
+ return fig
456
+ elif ptype == "bar" and x and x in plot_df.columns and y and y in plot_df.columns:
457
+ fig = px.bar(plot_df, x=x, y=y, title=title)
458
+ fig.update_layout(width=900, height=500)
459
+ return fig
460
+ elif ptype == "line" and x and x in plot_df.columns and y and y in plot_df.columns:
461
+ fig = px.line(plot_df, x=x, y=y, title=title, markers=True)
462
+ fig.update_layout(width=900, height=500)
463
+ return fig
464
+ elif ptype == "hist" and y and y in plot_df.columns:
465
+ fig = px.histogram(plot_df, x=y, title=title, nbins=30)
466
+ fig.update_layout(width=900, height=500)
467
+ return fig
468
+ elif ptype == "scatter" and x and x in plot_df.columns and y and y in plot_df.columns:
469
+ fig = px.scatter(plot_df, x=x, y=y, title=title)
470
+ fig.update_layout(width=900, height=500)
471
+ return fig
472
+ except:
473
+ pass
474
+ return None
475
+
476
+ def generate_insights(df, dfw, plan):
477
+ try:
478
+ context_parts = []
479
+ for op in plan.get("operations", []):
480
+ if op.get("op") == "describe":
481
+ cols = op.get("columns", [])
482
+ for col in cols:
483
+ if col in df.columns:
484
+ desc = df[col].describe()
485
+ context_parts.append(f"\n{col} Statistics:\n{desc.to_string()}")
486
+ elif op.get("op") == "groupby":
487
+ context_parts.append(f"\nGrouped Results:\n{dfw.head(10).to_string()}")
488
+
489
+ insights_prompt = get_insights_prompt(context_parts, plan.get('narrative', ''))
490
+
491
+ response = llm.invoke([
492
+ {"role": "system", "content": INSIGHTS_SYSTEM_PROMPT},
493
+ {"role": "user", "content": insights_prompt}
494
+ ])
495
+
496
+ return response.content if hasattr(response, 'content') else str(response)
497
+ except Exception as e:
498
+ return f"Error generating insights: {str(e)}"
499
+
500
+ def analyze_question(question, selected_columns):
501
+ global uploaded_df, llm
502
+
503
+ if llm is None:
504
+ return "API not initialized. Please restart.", None, None
505
+
506
+ if uploaded_df is None:
507
+ return "Please upload a dataset first.", None, None
508
+
509
+ if not question.strip():
510
+ return "Please enter a question.", None, None
511
+
512
+ try:
513
+ df_to_analyze = uploaded_df[selected_columns] if selected_columns else uploaded_df
514
+
515
+ sample_data = df_to_analyze.head(3).to_string(max_cols=10, max_colwidth=20)
516
+
517
+ if selected_columns:
518
+ column_context = f"Selected columns for analysis: {', '.join(selected_columns)}\n"
519
+ else:
520
+ column_context = ""
521
+
522
+ data_ctx = f"""{column_context}Dataset: {len(df_to_analyze)} rows, {len(df_to_analyze.columns)} columns
523
+ Columns: {', '.join(df_to_analyze.columns)}
524
+ Sample data:
525
+ {sample_data}"""
526
+
527
+ enhanced_prompt = get_chart_prompt(question, df_to_analyze.columns.tolist(), sample_data)
528
+
529
+ messages = [
530
+ {"role": "system", "content": ENHANCED_SYSTEM_PROMPT},
531
+ {"role": "user", "content": enhanced_prompt}
532
+ ]
533
+
534
+ response = llm.invoke(messages)
535
+ raw_text = response.content if hasattr(response, 'content') else str(response)
536
+
537
+ plan = parse_plan(raw_text)
538
+
539
+ if plan.get("type") == "explain":
540
+ return plan.get("narrative", "No explanation provided"), None, None
541
+
542
+ if plan.get("type") == "error":
543
+ return plan.get("narrative", "Error occurred"), None, None
544
+
545
+ if plan.get("plot"):
546
+ plan["plot"] = validate_plot_spec(plan["plot"], df_to_analyze.columns.tolist())
547
+
548
+ dfw, describe_stats = execute_plan(df_to_analyze, plan)
549
+
550
+ response_text = plan.get("narrative", "Analysis complete")
551
+
552
+ if describe_stats:
553
+ response_text += "\n\nStatistical Summary:\n"
554
+ for col, stats in describe_stats.items():
555
+ response_text += f"\n{col}:\n{stats.to_string()}\n"
556
+
557
+ fig = None
558
+ if plan.get("plot"):
559
+ fig = create_plot(df_to_analyze, dfw, plan, describe_stats, selected_columns)
560
+
561
+ if fig is None and selected_columns and len(selected_columns) >= 2:
562
+ fig = create_multi_column_plot(df_to_analyze, selected_columns, "bar", "Multi-Column Chart")
563
+
564
+ if fig is None:
565
+ fig = create_simple_chart(df_to_analyze, selected_columns)
566
+
567
+ if fig:
568
+ response_text += "\n\nChart generated successfully!"
569
+ if selected_columns and len(selected_columns) >= 1:
570
+ response_text += f"\nUsing selected columns: {', '.join(selected_columns)}"
571
+
572
+ if plan.get("insights_needed") and fig:
573
+ insights = generate_insights(df_to_analyze, dfw, plan)
574
+ response_text += f"\n\nKey Insights:\n{insights}"
575
+
576
+ result_table = None
577
+ if not describe_stats and len(dfw) != len(df_to_analyze):
578
+ result_table = dfw.head(50)
579
+
580
+ return response_text, fig, result_table
581
+
582
+ except Exception as e:
583
+ return f"Error during analysis: {str(e)}", None, None
584
+
585
+ def clear_dataset():
586
+ global uploaded_df, dataset_name
587
+ uploaded_df = None
588
+ dataset_name = None
589
+ return "Dataset cleared. Please upload a new file.", gr.update(visible=False), gr.update(choices=[], value=[]), gr.update(visible=False)
590
+
591
+ custom_css = """
592
+ .gradio-container {
593
+ max-width: 1400px !important;
594
+ margin: 0 auto !important;
595
+ }
596
+ .header-box {
597
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
598
+ border-radius: 15px;
599
+ padding: 25px;
600
+ margin: 20px auto;
601
+ text-align: center;
602
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
603
+ }
604
+ .header-title {
605
+ font-size: 36px;
606
+ font-weight: bold;
607
+ color: white;
608
+ margin: 0;
609
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
610
+ }
611
+ .section-box {
612
+ background-color: #f8f9fa;
613
+ padding: 20px;
614
+ border-radius: 12px;
615
+ margin: 15px 0;
616
+ border: 1px solid #e9ecef;
617
+ }
618
+ """
619
+
620
+ with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
621
+
622
+ gr.HTML("""
623
+ <div class="header-box">
624
+ <h1 class="header-title">SparkNova</h1>
625
+ <p style="color: white; font-size: 18px; margin: 10px 0 0 0;">Advanced Data Analysis Platform</p>
626
+ </div>
627
+ """)
628
+
629
+ with gr.Row():
630
+ with gr.Column(scale=1):
631
+ gr.Markdown("### Upload Dataset")
632
+ file_input = gr.File(label="Choose CSV or Excel File", file_types=[".csv", ".xlsx", ".xls"])
633
+ dataset_info = gr.Markdown()
634
+
635
+ with gr.Row():
636
+ clear_btn = gr.Button("Clear Dataset", variant="secondary", size="sm")
637
+
638
+ column_selector = gr.CheckboxGroup(
639
+ label="Select Columns (optional - for multi-column charts)",
640
+ choices=[],
641
+ visible=False
642
+ )
643
+
644
+ format_selector = gr.Dropdown(
645
+ choices=["None", "DataFrame", "JSON", "Dictionary"],
646
+ value="None",
647
+ label="Display Format"
648
+ )
649
+
650
+ gr.Markdown("### Choose an Analysis Type")
651
+ analysis_selector = gr.Dropdown(
652
+ choices=["None", "Summary", "Describe", "Top 5 Rows", "Bottom 5 Rows", "Missing Values", "Group & Aggregate", "Calculate Expressions", "Highest Correlation"],
653
+ value="None",
654
+ label="Analysis Type"
655
+ )
656
+
657
+ gr.Markdown("### Visualization Types")
658
+ viz_selector = gr.Dropdown(
659
+ choices=["None", "Bar Chart", "Line Chart", "Scatter Plot", "Pie Chart", "Histogram", "Box Plot", "Heat Map"],
660
+ value="None",
661
+ label="Chart Type"
662
+ )
663
+
664
+
665
+
666
+ with gr.Column(scale=2):
667
+ preview_heading = gr.Markdown("### Dataset Preview", visible=False)
668
+ dataset_preview = gr.Dataframe(wrap=True, visible=False)
669
+ text_preview = gr.Textbox(label="Text Preview", lines=15, visible=False)
670
+
671
+ analysis_heading = gr.Markdown("### Analysis Results", visible=False)
672
+ analysis_output = gr.Textbox(label="Analysis Output", lines=10, visible=False, interactive=False)
673
+ analysis_data_table = gr.Dataframe(label="Data Table", visible=False, wrap=True)
674
+ chart_output_new = gr.Plot(label="Chart", visible=False)
675
+ chart_explanation = gr.Textbox(label="Chart Analysis", lines=5, visible=False, interactive=False)
676
+
677
+ gr.Markdown("### Sample Questions")
678
+ with gr.Row():
679
+ for i in range(0, len(SAMPLE_QUESTIONS), 4):
680
+ with gr.Column():
681
+ for j in range(4):
682
+ if i + j < len(SAMPLE_QUESTIONS):
683
+ gr.Markdown(f"• {SAMPLE_QUESTIONS[i + j]}")
684
+
685
+ gr.Markdown("### Ask Your Question")
686
+ user_question = gr.Textbox(
687
+ label="Enter your question",
688
+ placeholder="Ask anything about your data...",
689
+ lines=3
690
+ )
691
+ submit_btn = gr.Button("Analyze", variant="primary", size="lg")
692
+
693
+ gr.Markdown("### Analysis Results")
694
+ with gr.Tabs():
695
+ with gr.Tab("Response"):
696
+ output_text = gr.Textbox(
697
+ label="Analysis Response",
698
+ interactive=False,
699
+ lines=15,
700
+ show_copy_button=True
701
+ )
702
+
703
+ with gr.Tab("Visualization"):
704
+ chart_output = gr.Plot(label="Generated Chart")
705
+
706
+ with gr.Tab("Data"):
707
+ result_table = gr.Dataframe(label="Result Data", wrap=True)
708
+
709
+ file_input.change(
710
+ upload_dataset,
711
+ inputs=file_input,
712
+ outputs=[dataset_info, dataset_preview, column_selector, column_selector]
713
+ )
714
+
715
+ clear_btn.click(
716
+ clear_dataset,
717
+ outputs=[dataset_info, dataset_preview, column_selector, column_selector]
718
+ )
719
+
720
+ def update_preview(format_type, selected_columns):
721
+ if format_type == "None":
722
+ return None, "", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
723
+ elif format_type == "DataFrame":
724
+ return display_data_format(format_type, selected_columns), "", gr.update(visible=True), gr.update(visible=False), gr.update(visible=True)
725
+ else:
726
+ return None, display_text_format(format_type, selected_columns), gr.update(visible=False), gr.update(visible=True), gr.update(visible=True)
727
+
728
+ def handle_analysis_change(analysis_type, selected_columns):
729
+ result_text, heading_update, data_table = run_analysis(analysis_type, selected_columns)
730
+ if result_text and result_text.strip() and analysis_type != "None":
731
+ if data_table is not None:
732
+ return gr.update(value=result_text, visible=True), gr.update(visible=True), gr.update(value=data_table, visible=True)
733
+ else:
734
+ return gr.update(value=result_text, visible=True), gr.update(visible=True), gr.update(visible=False)
735
+ else:
736
+ return gr.update(value="", visible=False), gr.update(visible=False), gr.update(visible=False)
737
+
738
+ analysis_selector.change(
739
+ handle_analysis_change,
740
+ inputs=[analysis_selector, column_selector],
741
+ outputs=[analysis_output, analysis_heading, analysis_data_table]
742
+ )
743
+
744
+ def update_viz_display(viz_type, selected_columns):
745
+ result = create_visualization(viz_type, selected_columns)
746
+ if result and len(result) == 3:
747
+ fig, explanation, chart_visible = result
748
+ return fig, explanation, chart_visible
749
+ else:
750
+ return None, "Error in visualization", gr.update(visible=False)
751
+
752
+ def handle_viz_change(viz_type, selected_columns):
753
+ fig, explanation, chart_visible = update_viz_display(viz_type, selected_columns)
754
+ if explanation:
755
+ return fig, chart_visible, explanation, gr.update(visible=True)
756
+ else:
757
+ return fig, chart_visible, "", gr.update(visible=False)
758
+
759
+ viz_selector.change(
760
+ handle_viz_change,
761
+ inputs=[viz_selector, column_selector],
762
+ outputs=[chart_output_new, chart_output_new, chart_explanation, chart_explanation]
763
+ )
764
+
765
+ format_selector.change(
766
+ update_preview,
767
+ inputs=[format_selector, column_selector],
768
+ outputs=[dataset_preview, text_preview, dataset_preview, text_preview, preview_heading]
769
+ )
770
+
771
+
772
+
773
+ column_selector.change(
774
+ update_preview,
775
+ inputs=[format_selector, column_selector],
776
+ outputs=[dataset_preview, text_preview, dataset_preview, text_preview, preview_heading]
777
+ )
778
+
779
+ column_selector.change(
780
+ handle_analysis_change,
781
+ inputs=[analysis_selector, column_selector],
782
+ outputs=[analysis_output, analysis_heading, analysis_data_table]
783
+ )
784
+
785
+ column_selector.change(
786
+ handle_viz_change,
787
+ inputs=[viz_selector, column_selector],
788
+ outputs=[chart_output_new, chart_output_new, chart_explanation, chart_explanation]
789
+ )
790
+
791
+ submit_btn.click(
792
+ analyze_question,
793
+ inputs=[user_question, column_selector],
794
+ outputs=[output_text, chart_output, result_table]
795
+ )
796
+
797
+ gr.HTML("<div style='text-align: center; margin-top: 20px; color: #666;'>Powered by GROQ LLM & Gradio</div>")
798
+
799
+ if __name__ == "__main__":
800
+ if not initialize_llm():
801
+ print("Warning: Failed to initialize GROQ API")
802
+
803
+ demo.launch(server_name="127.0.0.1",server_port=7860,show_error=True,debug=True,share=True)
prompts.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ENHANCED_SYSTEM_PROMPT = """You are an advanced data analysis assistant. Respond ONLY in valid JSON format.
2
+
3
+ CHART CREATION RULES:
4
+ - For visualization requests (chart, graph, plot, visualize): Always include "plot" object
5
+ - For informational queries (explain, describe, what is, count): Set "plot": null
6
+ - For statistical analysis without charts: Set "plot": null
7
+
8
+ RESPONSE FORMATS:
9
+
10
+ 1. INFORMATIONAL (no visualization):
11
+ {
12
+ "type": "explain",
13
+ "operations": [],
14
+ "plot": null,
15
+ "narrative": "detailed answer",
16
+ "insights_needed": false
17
+ }
18
+
19
+ 2. STATISTICAL DESCRIPTION:
20
+ {
21
+ "type": "describe",
22
+ "operations": [{"op": "describe", "columns": ["col1", "col2"]}],
23
+ "plot": null,
24
+ "narrative": "statistical summary",
25
+ "insights_needed": false
26
+ }
27
+
28
+ 3. VISUALIZATION REQUEST:
29
+ {
30
+ "type": "analysis",
31
+ "operations": [
32
+ {"op": "groupby", "columns": ["category"], "agg_col": "value", "agg_func": "sum"}
33
+ ],
34
+ "plot": {
35
+ "type": "bar|line|pie|hist|scatter",
36
+ "x": "category",
37
+ "y": "sum_value",
38
+ "title": "Chart Title"
39
+ },
40
+ "narrative": "brief explanation",
41
+ "insights_needed": true
42
+ }
43
+
44
+ 4. FILTERING:
45
+ {
46
+ "type": "analysis",
47
+ "operations": [{"op": "filter", "expr": "Age > 25"}],
48
+ "plot": null,
49
+ "narrative": "filtered data explanation",
50
+ "insights_needed": false
51
+ }
52
+
53
+ 5. CALCULATIONS:
54
+ {
55
+ "type": "analysis",
56
+ "operations": [{"op": "calculate", "expr": "Col1 * Col2", "new_col": "Product"}],
57
+ "plot": null,
58
+ "narrative": "calculation explanation",
59
+ "insights_needed": false
60
+ }
61
+
62
+ CHART TYPES:
63
+ - "bar": For categorical comparisons
64
+ - "line": For trends over time/sequence
65
+ - "pie": For proportions/percentages
66
+ - "hist": For distributions
67
+ - "scatter": For correlations
68
+
69
+ Always ensure column names exist in the dataset before referencing them.
70
+ """
71
+
72
+ INSIGHTS_SYSTEM_PROMPT = "You are a data insights expert."
73
+
74
+ SAMPLE_QUESTIONS = [
75
+ "Show me a bar chart of sales by region",
76
+ "What is the average value of each numeric column?",
77
+ "Create a pie chart for category distribution",
78
+ "Display a line chart showing trends over time",
79
+ "Generate a histogram of age distribution",
80
+ "Show correlation between price and quantity",
81
+ "Filter data where sales > 1000",
82
+ "Calculate profit margin (revenue - cost)",
83
+ "What are the top 5 performing products?",
84
+ "Show statistical summary of all columns",
85
+ "Create a scatter plot of price vs sales",
86
+ "Group data by category and sum values"
87
+ ]
88
+
89
+ def get_chart_prompt(question, columns, data_sample):
90
+ return f"""
91
+ Question: {question}
92
+
93
+ Available Columns: {', '.join(columns)}
94
+
95
+ Sample Data:
96
+ {data_sample}
97
+
98
+ Create a JSON response following the format rules. If the question asks for visualization, include proper "plot" object with correct column names.
99
+ """
100
+
101
+ def validate_plot_spec(plot_spec, available_columns):
102
+ if not plot_spec:
103
+ return plot_spec
104
+
105
+ x_col = plot_spec.get('x')
106
+ y_col = plot_spec.get('y')
107
+
108
+ if x_col and x_col not in available_columns:
109
+ for col in available_columns:
110
+ if any(keyword in col.lower() for keyword in ['name', 'category', 'type', 'group']):
111
+ plot_spec['x'] = col
112
+ break
113
+
114
+ if y_col and y_col not in available_columns:
115
+ for col in available_columns:
116
+ if any(keyword in col.lower() for keyword in ['value', 'amount', 'count', 'price', 'sales']):
117
+ plot_spec['y'] = col
118
+ break
119
+
120
+ return plot_spec
121
+
122
+ def get_insights_prompt(context_parts, narrative):
123
+ insights_context = "\n".join(context_parts)
124
+ return f"""Based on this analysis, provide 4-6 detailed bullet points explaining key insights, patterns, and findings.
125
+ Analysis Context:
126
+ {insights_context}
127
+ Original Question Context:
128
+ {narrative}
129
+ Provide insights as bullet points."""
sparknova.py ADDED
@@ -0,0 +1,413 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import pandas as pd
4
+ import numpy as np
5
+ import matplotlib.pyplot as plt
6
+ import plotly.graph_objects as go
7
+ import plotly.express as px
8
+ import traceback
9
+ from io import BytesIO
10
+ from langchain_groq import ChatGroq
11
+ from datetime import datetime
12
+ from prompts import ENHANCED_SYSTEM_PROMPT, SAMPLE_QUESTIONS, get_chart_prompt, validate_plot_spec, INSIGHTS_SYSTEM_PROMPT, get_insights_prompt
13
+
14
+ GROQ_API_KEY = "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"
15
+
16
+ llm = ChatGroq(
17
+ api_key=GROQ_API_KEY,
18
+ model="llama-3.3-70b-versatile",
19
+ temperature=0.0
20
+ )
21
+
22
+ print("GROQ API initialized successfully")
23
+
24
+ def call_groq(messages):
25
+ try:
26
+ res = llm.invoke(messages)
27
+ return res.content if hasattr(res, "content") else str(res)
28
+ except Exception as e:
29
+ raise RuntimeError(f"GROQ API error: {e}")
30
+
31
+ def parse_plan(raw_text):
32
+ txt = raw_text.strip().replace("```json", "").replace("```", "").strip()
33
+ try:
34
+ start = txt.index("{")
35
+ end = txt.rindex("}") + 1
36
+ plan = json.loads(txt[start:end])
37
+ plan.setdefault("type", "analysis")
38
+ plan.setdefault("operations", [])
39
+ plan.setdefault("plot", None)
40
+ plan.setdefault("narrative", "")
41
+ plan.setdefault("insights_needed", False)
42
+ return plan
43
+ except Exception as e:
44
+ return {
45
+ "type": "error",
46
+ "operations": [],
47
+ "plot": None,
48
+ "narrative": f"Error parsing JSON: {str(e)}",
49
+ "insights_needed": False
50
+ }
51
+
52
+ def clean_numeric(df):
53
+ df = df.copy()
54
+ for col in df.columns:
55
+ if pd.api.types.is_string_dtype(df[col]) or df[col].dtype == object:
56
+ s = df[col].astype(str).str.strip()
57
+ if s.str.contains("%", na=False).any():
58
+ numeric_vals = pd.to_numeric(s.str.replace("%", "", regex=False), errors="coerce")
59
+ if numeric_vals.notna().sum() / len(df) > 0.5:
60
+ df[col] = numeric_vals / 100.0
61
+ continue
62
+ cleaned = s.str.replace(",", "", regex=False).str.replace("₹", "", regex=False).str.replace("$", "", regex=False)
63
+ numeric_vals = pd.to_numeric(cleaned, errors="coerce")
64
+ if numeric_vals.notna().sum() / len(df) > 0.5:
65
+ df[col] = numeric_vals
66
+ return df
67
+
68
+ def generate_insights(df, dfw, plan, plot_created):
69
+ context_parts = []
70
+ for op in plan.get("operations", []):
71
+ if op.get("op") == "describe":
72
+ cols = op.get("columns", [])
73
+ for col in cols:
74
+ if col in df.columns:
75
+ desc = df[col].describe()
76
+ context_parts.append(f"\n{col} Statistics:\n{desc.to_string()}")
77
+ elif op.get("op") == "groupby":
78
+ context_parts.append(f"\nGrouped Results:\n{dfw.head(10).to_string()}")
79
+
80
+ plot_spec = plan.get("plot")
81
+ if plot_created and plot_spec:
82
+ context_parts.append(f"\nChart Type: {plot_spec.get('type')}")
83
+ context_parts.append(f"Visualization: {plot_spec.get('title')}")
84
+
85
+ if len(dfw) > 0:
86
+ context_parts.append(f"\nResult Preview:\n{dfw.head(10).to_string()}")
87
+
88
+ insights_prompt = get_insights_prompt(context_parts, plan.get('narrative', ''))
89
+
90
+ try:
91
+ insights_response = call_groq([
92
+ {"role": "system", "content": INSIGHTS_SYSTEM_PROMPT},
93
+ {"role": "user", "content": insights_prompt}
94
+ ])
95
+ return insights_response.strip()
96
+ except Exception as e:
97
+ return f"Analysis completed successfully\n{len(dfw)} records in result\nError generating detailed insights: {str(e)}"
98
+
99
+ def execute_plan(df, plan):
100
+ dfw = df.copy()
101
+ plot_bytes = None
102
+ plot_html = None
103
+ describe_stats = {}
104
+
105
+ try:
106
+ for op in plan.get("operations", []):
107
+ optype = op.get("op", "").lower()
108
+ if optype == "describe":
109
+ cols = op.get("columns", [])
110
+ for col in cols:
111
+ if col in dfw.columns:
112
+ stats = dfw[col].describe()
113
+ describe_stats[col] = stats
114
+ print(f"Described {col}")
115
+ print(f"\n{stats}\n")
116
+ continue
117
+
118
+ elif optype == "groupby":
119
+ cols = op.get("columns", [])
120
+ agg_col = op.get("agg_col")
121
+ agg_func = op.get("agg_func", "count")
122
+
123
+ if not cols:
124
+ raise ValueError("No columns specified for groupby")
125
+
126
+ if agg_func == "count" or not agg_col:
127
+ dfw = dfw.groupby(cols).size().reset_index(name="count")
128
+ print(f"Grouped by {cols} with count")
129
+ else:
130
+ if agg_col not in dfw.columns:
131
+ raise ValueError(f"Column '{agg_col}' not found for aggregation")
132
+ result_col = f"{agg_func}_{agg_col}"
133
+ dfw = dfw.groupby(cols)[agg_col].agg(agg_func).reset_index(name=result_col)
134
+ print(f"Grouped by {cols}, calculated {agg_func} of {agg_col}")
135
+
136
+ elif optype == "filter":
137
+ expr = op.get("expr", "")
138
+ if expr:
139
+ dfw = dfw.query(expr)
140
+ print(f"Filter applied: {expr}")
141
+
142
+ elif optype == "calculate":
143
+ expr = op.get("expr", "")
144
+ new_col = op.get("new_col", "Calculated")
145
+ dfw[new_col] = dfw.eval(expr)
146
+ print(f"Calculated {new_col} = {expr}")
147
+
148
+ plot_spec = plan.get("plot")
149
+ if plot_spec and plot_spec is not None:
150
+ ptype = plot_spec.get("type", "bar")
151
+ x = plot_spec.get("x")
152
+ y = plot_spec.get("y")
153
+ title = plot_spec.get("title", "Chart")
154
+
155
+ plot_df = df if describe_stats else dfw
156
+
157
+ if not x and len(plot_df.columns) > 0:
158
+ categorical_cols = plot_df.select_dtypes(include=['object', 'category']).columns
159
+ x = categorical_cols[0] if len(categorical_cols) > 0 else plot_df.columns[0]
160
+
161
+ if not y:
162
+ numeric_cols = plot_df.select_dtypes(include=[np.number]).columns
163
+ y = numeric_cols[0] if len(numeric_cols) > 0 else None
164
+
165
+ if not y:
166
+ print("No suitable Y column found for plotting.")
167
+ else:
168
+ if ptype == "pie":
169
+ if x and x in plot_df.columns:
170
+ value_counts = plot_df[x].value_counts()
171
+ fig = go.Figure(data=[go.Pie(
172
+ labels=value_counts.index,
173
+ values=value_counts.values,
174
+ hovertemplate='<b>%{label}</b><br>Count: %{value}<br>Percentage: %{percent}<extra></extra>',
175
+ textposition='auto',
176
+ hole=0.3
177
+ )])
178
+ else:
179
+ df_pie = plot_df[y].value_counts()
180
+ fig = go.Figure(data=[go.Pie(
181
+ labels=df_pie.index,
182
+ values=df_pie.values,
183
+ hole=0.3
184
+ )])
185
+
186
+ fig.update_layout(
187
+ title=title,
188
+ title_font_size=16,
189
+ showlegend=True,
190
+ width=950,
191
+ height=550
192
+ )
193
+ plot_html = fig.to_html(include_plotlyjs='cdn')
194
+ print("Enhanced pie chart generated")
195
+
196
+ elif ptype == "bar":
197
+ fig, ax = plt.subplots(figsize=(12, 7))
198
+
199
+ if x and x in plot_df.columns and y and y in plot_df.columns:
200
+ plot_df.plot.bar(x=x, y=y, ax=ax, legend=False, color='steelblue', edgecolor='black', alpha=0.8)
201
+ ax.set_xlabel(x, fontsize=12, fontweight='bold')
202
+
203
+ n_categories = len(plot_df[x].unique())
204
+ if n_categories > 10:
205
+ plt.xticks(rotation=90, ha='right', fontsize=9)
206
+ elif n_categories > 5:
207
+ plt.xticks(rotation=45, ha='right', fontsize=10)
208
+ else:
209
+ plt.xticks(rotation=0, fontsize=10)
210
+ else:
211
+ plot_df[y].plot.bar(ax=ax, color='steelblue', edgecolor='black', alpha=0.8)
212
+
213
+ ax.set_title(title, fontsize=16, fontweight='bold', pad=20)
214
+ ax.set_ylabel(y, fontsize=12, fontweight='bold')
215
+ ax.grid(axis='y', alpha=0.3, linestyle='--')
216
+ plt.tight_layout()
217
+
218
+ buf = BytesIO()
219
+ plt.savefig(buf, format="png", dpi=150, bbox_inches="tight")
220
+ buf.seek(0)
221
+ plot_bytes = buf.read()
222
+ plt.close()
223
+ print("Enhanced bar chart generated")
224
+
225
+ elif ptype == "line":
226
+ fig, ax = plt.subplots(figsize=(12, 7))
227
+
228
+ if x and x in plot_df.columns and y and y in plot_df.columns:
229
+ plot_df.plot.line(x=x, y=y, ax=ax, marker="o", linewidth=3,
230
+ markersize=8, color='darkblue', alpha=0.8)
231
+ ax.set_xlabel(x, fontsize=12, fontweight='bold')
232
+
233
+ if len(plot_df) > 15:
234
+ plt.xticks(rotation=45, ha='right', fontsize=9)
235
+ else:
236
+ plt.xticks(rotation=0, fontsize=10)
237
+ else:
238
+ plot_df[y].plot.line(ax=ax, marker="o", linewidth=3,
239
+ markersize=8, color='darkblue', alpha=0.8)
240
+
241
+ ax.set_title(title, fontsize=16, fontweight='bold', pad=20)
242
+ ax.set_ylabel(y, fontsize=12, fontweight='bold')
243
+ ax.grid(True, alpha=0.3, linestyle='--')
244
+ plt.tight_layout()
245
+
246
+ buf = BytesIO()
247
+ plt.savefig(buf, format="png", dpi=150, bbox_inches="tight")
248
+ buf.seek(0)
249
+ plot_bytes = buf.read()
250
+ plt.close()
251
+ print("Enhanced line chart generated")
252
+
253
+ elif ptype == "hist":
254
+ fig, ax = plt.subplots(figsize=(11, 7))
255
+
256
+ plot_df[y].dropna().plot.hist(ax=ax, bins=25, edgecolor='black',
257
+ alpha=0.7, color='teal')
258
+ ax.set_title(title, fontsize=16, fontweight='bold', pad=20)
259
+ ax.set_xlabel(y, fontsize=12, fontweight='bold')
260
+ ax.set_ylabel("Frequency", fontsize=12, fontweight='bold')
261
+ ax.grid(axis='y', alpha=0.3, linestyle='--')
262
+ plt.tight_layout()
263
+
264
+ buf = BytesIO()
265
+ plt.savefig(buf, format="png", dpi=150, bbox_inches="tight")
266
+ buf.seek(0)
267
+ plot_bytes = buf.read()
268
+ plt.close()
269
+ print("Enhanced histogram generated")
270
+
271
+ elif ptype == "scatter":
272
+ fig, ax = plt.subplots(figsize=(11, 7))
273
+
274
+ if x and x in plot_df.columns and y and y in plot_df.columns:
275
+ plot_df.plot.scatter(x=x, y=y, ax=ax, alpha=0.6, s=60, color='purple')
276
+ ax.set_xlabel(x, fontsize=12, fontweight='bold')
277
+ ax.set_ylabel(y, fontsize=12, fontweight='bold')
278
+
279
+ ax.set_title(title, fontsize=16, fontweight='bold', pad=20)
280
+ ax.grid(True, alpha=0.3, linestyle='--')
281
+ plt.tight_layout()
282
+
283
+ buf = BytesIO()
284
+ plt.savefig(buf, format="png", dpi=150, bbox_inches="tight")
285
+ buf.seek(0)
286
+ plot_bytes = buf.read()
287
+ plt.close()
288
+ print("Enhanced scatter plot generated")
289
+
290
+ return dfw, plot_bytes, plot_html, describe_stats
291
+
292
+ except Exception as e:
293
+ print(f"EXECUTION ERROR: {e}")
294
+ traceback.print_exc()
295
+ raise
296
+
297
+ def make_context(df):
298
+ sample_data = df.head(3).to_string(max_cols=10, max_colwidth=20)
299
+ return f"""Dataset: {len(df)} rows, {len(df.columns)} columns
300
+ Columns: {', '.join(df.columns)}
301
+ Data types: {df.dtypes.value_counts().to_dict()}
302
+ Sample data:
303
+ {sample_data}"""
304
+
305
+ def load_file(file_path):
306
+ if file_path.endswith('.csv'):
307
+ return pd.read_csv(file_path)
308
+ elif file_path.endswith(('.xlsx', '.xls')):
309
+ return pd.read_excel(file_path)
310
+ else:
311
+ raise ValueError("Unsupported file format. Please use CSV or Excel files.")
312
+
313
+ def start_agent():
314
+ print("=" * 80)
315
+ print("SparkNova v5.0 – Advanced Data Analysis & Visualization")
316
+ print("=" * 80)
317
+
318
+ df = None
319
+
320
+ while True:
321
+ if df is None:
322
+ file_path = input("\nEnter file path (CSV or Excel): ").strip()
323
+ if not file_path:
324
+ continue
325
+
326
+ try:
327
+ df = load_file(file_path)
328
+ df = clean_numeric(df)
329
+ print(f"Loaded {file_path} ({len(df)} rows × {len(df.columns)} cols)")
330
+ print("\nFirst 5 rows:")
331
+ print(df.head())
332
+ print(f"\nColumn types:\n{df.dtypes}")
333
+
334
+ print("\nSample Questions You Can Ask:")
335
+ for i, question in enumerate(SAMPLE_QUESTIONS[:8], 1):
336
+ print(f"{i}. {question}")
337
+
338
+ data_ctx = make_context(df)
339
+ except Exception as e:
340
+ print(f"Error loading file: {e}")
341
+ continue
342
+
343
+ q = input("\nYour question (or 'exit'/'reload'): ").strip()
344
+ if not q:
345
+ continue
346
+ if q.lower() in ("exit", "quit"):
347
+ print("Thank you for using SparkNova!")
348
+ break
349
+ if q.lower() == "reload":
350
+ df = None
351
+ continue
352
+
353
+ enhanced_prompt = get_chart_prompt(q, df.columns.tolist(), df.head(3).to_string())
354
+
355
+ try:
356
+ raw = call_groq([
357
+ {"role": "system", "content": ENHANCED_SYSTEM_PROMPT},
358
+ {"role": "user", "content": enhanced_prompt}
359
+ ])
360
+ except Exception as e:
361
+ print(f"LLM call failed: {e}")
362
+ continue
363
+
364
+ plan = parse_plan(raw)
365
+
366
+ if plan.get("type") == "explain":
367
+ print("\nExplanation:")
368
+ print(plan.get("narrative", ""))
369
+ continue
370
+
371
+ if plan.get("type") == "error":
372
+ print("\nError:")
373
+ print(plan.get("narrative", ""))
374
+ continue
375
+
376
+ print("\nAnalysis Plan:")
377
+ print(json.dumps(plan, indent=2))
378
+
379
+ if plan.get("plot"):
380
+ plan["plot"] = validate_plot_spec(plan["plot"], df.columns.tolist())
381
+
382
+ try:
383
+ print("\nExecuting operations...")
384
+ res, plot_img, plot_html, desc_stats = execute_plan(df, plan)
385
+
386
+ if not desc_stats or len(res) != len(df):
387
+ print("\nResult:")
388
+ print(res.head(20))
389
+
390
+ if plot_html:
391
+ print("\nGenerated Interactive Chart (HTML saved as chart.html)")
392
+ with open("chart.html", "w") as f:
393
+ f.write(plot_html)
394
+ elif plot_img:
395
+ print("\nGenerated Chart (saved as chart.png)")
396
+ with open("chart.png", "wb") as f:
397
+ f.write(plot_img)
398
+
399
+ narrative = plan.get("narrative", "")
400
+ if narrative:
401
+ print(f"\nSummary: {narrative}")
402
+
403
+ if plan.get("insights_needed") and (plot_html or plot_img):
404
+ print("\nDetailed Insights:")
405
+ insights = generate_insights(df, res, plan, True)
406
+ print(insights)
407
+
408
+ except Exception as e:
409
+ print(f"Execution failed: {e}")
410
+ continue
411
+
412
+ if __name__ == "__main__":
413
+ start_agent()