Tamannathakur commited on
Commit
2ad2aa7
·
verified ·
1 Parent(s): 097537f

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -474
app.py DELETED
@@ -1,474 +0,0 @@
1
- # app.py (SparkNova Gradio Space - FIXED)
2
- import os
3
- import io
4
- import json
5
- import tempfile
6
- import traceback
7
- from io import BytesIO
8
- import pandas as pd
9
- import numpy as np
10
- import matplotlib.pyplot as plt
11
- import plotly.express as px
12
- import plotly.graph_objects as go
13
- import gradio as gr
14
-
15
- from langchain_groq import ChatGroq
16
-
17
- # ---------------- Config ----------------
18
- GROQ_API_KEY = os.getenv("GROQ_API_KEY", "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")
19
- os.environ["GROQ_API_KEY"] = GROQ_API_KEY
20
-
21
- # ---------------- Core Agent (UNCHANGED) ----------------
22
- llm = None
23
- uploaded_df = None
24
- dataset_name = None
25
- last_chart = None # Store last generated chart
26
-
27
- SYSTEM_PROMPT = """You are a data analysis assistant. Respond ONLY in JSON describing a clear analysis plan.
28
- IMPORTANT: Only create plots when the user EXPLICITLY asks for visualization (chart, graph, plot, visualize, show distribution, etc.)
29
- For INFORMATIONAL queries (explain, describe, what is, tell me about, how many, etc.) - NO PLOT:
30
- {
31
- "type": "explain",
32
- "operations": [],
33
- "plot": null,
34
- "narrative": "detailed answer to the user's question",
35
- "insights_needed": false
36
- }
37
- For STATISTICAL description WITHOUT visualization:
38
- {
39
- "type": "describe",
40
- "operations": [
41
- {"op": "describe", "columns": ["col1"]}
42
- ],
43
- "plot": null,
44
- "narrative": "statistical summary explanation",
45
- "insights_needed": false
46
- }
47
- For VISUALIZATION requests (when user asks for chart/graph/plot):
48
- {
49
- "type": "analysis",
50
- "operations": [
51
- {
52
- "op": "groupby",
53
- "columns": ["col1"],
54
- "agg_col": "col2",
55
- "agg_func": "mean"
56
- }
57
- ],
58
- "plot": {"type": "bar"|"line"|"pie"|"hist", "x": "col1", "y": "result_col", "title": "..."},
59
- "narrative": "short explanation",
60
- "insights_needed": true
61
- }
62
- For filtering:
63
- {"op": "filter", "expr": "Age > 25"}
64
- For calculations:
65
- {"op": "calculate", "expr": "Col1 * Col2", "new_col": "Result"}
66
- Set "plot": null when user is asking informational questions without requesting visualization.
67
- """
68
-
69
- def initialize_llm():
70
- global llm
71
- try:
72
- llm = ChatGroq(model="llama-3.3-70b-versatile", api_key=GROQ_API_KEY, temperature=0.0)
73
- print("GROQ API initialized")
74
- return True
75
- except Exception as e:
76
- print("LLM init error:", e)
77
- return False
78
-
79
- def clean_numeric(df):
80
- df = df.copy()
81
- for col in df.columns:
82
- if pd.api.types.is_string_dtype(df[col]) or df[col].dtype == object:
83
- s = df[col].astype(str).str.strip()
84
- if s.str.contains("%", na=False).any():
85
- numeric_vals = pd.to_numeric(s.str.replace("%", "", regex=False), errors="coerce")
86
- if numeric_vals.notna().sum() / len(df) > 0.5:
87
- df[col] = numeric_vals / 100.0
88
- continue
89
- cleaned = s.str.replace(",", "", regex=False).str.replace("₹", "", regex=False).str.replace("$", "", regex=False)
90
- numeric_vals = pd.to_numeric(cleaned, errors="coerce")
91
- if numeric_vals.notna().sum() / len(df) > 0.5:
92
- df[col] = numeric_vals
93
- return df
94
-
95
- def make_context(df):
96
- sample_data = df.head(3).to_string(max_cols=10, max_colwidth=20)
97
- return f"""Dataset: {len(df)} rows, {len(df.columns)} columns
98
- Columns: {', '.join(df.columns)}
99
- Data types: {df.dtypes.to_dict()}
100
- Sample data:
101
- {sample_data}"""
102
-
103
- def parse_plan(raw_text):
104
- txt = raw_text.strip().replace("```json", "").replace("```", "").strip()
105
- try:
106
- start = txt.index("{")
107
- end = txt.rindex("}") + 1
108
- plan = json.loads(txt[start:end])
109
- plan.setdefault("type", "analysis")
110
- plan.setdefault("operations", [])
111
- plan.setdefault("plot", None)
112
- plan.setdefault("narrative", "")
113
- plan.setdefault("insights_needed", False)
114
- return plan
115
- except Exception as e:
116
- return {
117
- "type": "error",
118
- "operations": [],
119
- "plot": None,
120
- "narrative": f"Error parsing LLM response: {str(e)}",
121
- "insights_needed": False
122
- }
123
-
124
- def execute_plan(df, plan):
125
- dfw = df.copy()
126
- describe_stats = {}
127
- try:
128
- for op in plan.get("operations", []):
129
- optype = op.get("op", "").lower()
130
- if optype == "describe":
131
- cols = op.get("columns", [])
132
- for col in cols:
133
- if col in dfw.columns:
134
- stats = dfw[col].describe()
135
- describe_stats[col] = stats
136
- elif optype == "groupby":
137
- cols = op.get("columns", [])
138
- agg_col = op.get("agg_col")
139
- agg_func = op.get("agg_func", "count")
140
- if not cols:
141
- continue
142
- if agg_func == "count" or not agg_col:
143
- dfw = dfw.groupby(cols).size().reset_index(name="count")
144
- else:
145
- if agg_col in dfw.columns:
146
- result_col = f"{agg_func}_{agg_col}"
147
- dfw = dfw.groupby(cols)[agg_col].agg(agg_func).reset_index(name=result_col)
148
- elif optype == "filter":
149
- expr = op.get("expr", "")
150
- if expr:
151
- dfw = dfw.query(expr)
152
- elif optype == "calculate":
153
- expr = op.get("expr", "")
154
- new_col = op.get("new_col", "Calculated")
155
- dfw[new_col] = dfw.eval(expr)
156
- return dfw, describe_stats
157
- except Exception as e:
158
- raise Exception(f"Execution error: {str(e)}")
159
-
160
- def create_plot(df, dfw, plan, describe_stats):
161
- global last_chart
162
- plot_spec = plan.get("plot")
163
- if not plot_spec:
164
- last_chart = None
165
- return None
166
- ptype = plot_spec.get("type", "bar")
167
- x = plot_spec.get("x")
168
- y = plot_spec.get("y")
169
- title = plot_spec.get("title", "Chart")
170
- plot_df = df if describe_stats else dfw
171
- if x and x not in plot_df.columns:
172
- x = None
173
- if y and y not in plot_df.columns:
174
- numeric_cols = plot_df.select_dtypes(include=[np.number]).columns
175
- y = numeric_cols[0] if len(numeric_cols) > 0 else None
176
- if not y:
177
- last_chart = None
178
- return None
179
- try:
180
- if ptype == "pie":
181
- df_pie = plot_df.set_index(x)[y] if x else plot_df[y]
182
- df_pie = df_pie.dropna()
183
- fig = go.Figure(data=[go.Pie(labels=df_pie.index, values=df_pie.values, textposition='none')])
184
- fig.update_layout(title=title, title_font_size=16, showlegend=True, width=900, height=500)
185
- elif ptype == "bar":
186
- if x:
187
- fig = px.bar(plot_df, x=x, y=y, title=title)
188
- else:
189
- fig = px.bar(y=plot_df[y], title=title)
190
- fig.update_layout(width=900, height=500)
191
- elif ptype == "line":
192
- if x:
193
- fig = px.line(plot_df, x=x, y=y, title=title, markers=True)
194
- else:
195
- fig = px.line(y=plot_df[y], title=title, markers=True)
196
- fig.update_layout(width=900, height=500)
197
- elif ptype == "hist":
198
- fig = px.histogram(plot_df, x=y, title=title, nbins=20)
199
- fig.update_layout(width=900, height=500)
200
- elif ptype == "box":
201
- fig = px.box(plot_df, y=y, x=x, title=title) if x else px.box(plot_df, y=y, title=title)
202
- fig.update_layout(width=900, height=500)
203
- elif ptype == "heatmap":
204
- numeric = plot_df.select_dtypes(include=[np.number])
205
- if numeric.shape[1] < 2:
206
- last_chart = None
207
- return None
208
- corr = numeric.corr()
209
- fig = px.imshow(corr, text_auto=True, title=title)
210
- fig.update_layout(width=900, height=500)
211
- else:
212
- last_chart = None
213
- return None
214
-
215
- last_chart = fig
216
- return fig
217
- except Exception as e:
218
- print(f"Plot error: {e}")
219
- last_chart = None
220
- return None
221
-
222
- def generate_insights(df, dfw, plan):
223
- try:
224
- context_parts = []
225
- for op in plan.get("operations", []):
226
- if op.get("op") == "describe":
227
- cols = op.get("columns", [])
228
- for col in cols:
229
- if col in df.columns:
230
- desc = df[col].describe()
231
- context_parts.append(f"\n{col} Statistics:\n{desc.to_string()}")
232
- elif op.get("op") == "groupby":
233
- context_parts.append(f"\nGrouped Results:\n{dfw.head(10).to_string()}")
234
- if len(dfw) > 0:
235
- context_parts.append(f"\nResult Preview:\n{dfw.head(10).to_string()}")
236
- insights_context = "\n".join(context_parts)
237
- insights_prompt = f"""Based on this analysis, provide 4-6 detailed bullet points explaining key insights, patterns, and findings.
238
- Analysis Context:
239
- {insights_context}
240
- Original Question Context:
241
- {plan.get('narrative', '')}
242
- Provide insights as bullet points."""
243
- response = llm.invoke([
244
- {"role": "system", "content": "You are a data insights expert."},
245
- {"role": "user", "content": insights_prompt}
246
- ])
247
- return response.content if hasattr(response, 'content') else str(response)
248
- except Exception as e:
249
- return f"Error generating insights: {str(e)}"
250
-
251
- def analyze_question(question):
252
- global uploaded_df, llm
253
- if llm is None:
254
- return "API not initialized. Please restart the application.", None, None
255
- if uploaded_df is None:
256
- return "Please upload a dataset first.", None, None
257
- if not question.strip():
258
- return "Please enter a question.", None, None
259
- try:
260
- data_ctx = make_context(uploaded_df)
261
- user_msg = f"{question}\n\nDataset Context:\n{data_ctx}"
262
- messages = [
263
- {"role": "system", "content": SYSTEM_PROMPT},
264
- {"role": "user", "content": user_msg}
265
- ]
266
- response = llm.invoke(messages)
267
- raw_text = response.content if hasattr(response, 'content') else str(response)
268
- plan = parse_plan(raw_text)
269
- if plan.get("type") == "explain":
270
- return plan.get("narrative", "No explanation provided"), None, None
271
- if plan.get("type") == "error":
272
- return plan.get("narrative", "Error occurred"), None, None
273
- dfw, describe_stats = execute_plan(uploaded_df, plan)
274
- response_text = plan.get("narrative", "Analysis complete")
275
- if describe_stats:
276
- response_text += "\n\n Statistical Summary:\n"
277
- for col, stats in describe_stats.items():
278
- response_text += f"\n{col}:\n{stats.to_string()}\n"
279
- fig = None
280
- if plan.get("plot"):
281
- fig = create_plot(uploaded_df, dfw, plan, describe_stats)
282
- if fig:
283
- response_text += "\n\n Chart generated successfully!"
284
- if plan.get("insights_needed") and fig:
285
- insights = generate_insights(uploaded_df, dfw, plan)
286
- response_text += f"\n\n Key Insights:\n{insights}"
287
- result_table = None
288
- if not describe_stats and len(dfw) != len(uploaded_df):
289
- result_table = dfw.head(50)
290
- return response_text, fig, result_table
291
- except Exception as e:
292
- return f"Error during analysis: {str(e)}", None, None
293
-
294
- # ---------------- UI (Gradio) ----------------
295
- custom_css = """
296
- .gradio-container { max-width: 1200px !important; margin: 0 auto !important; }
297
- .header-box { background: linear-gradient(135deg, #f6f0ff 0%, #fff0fb 100%); border: 2px solid #e9d8ff; border-radius: 12px; padding: 18px; text-align: center;}
298
- .left-panel { background:#f7f7fb; border-radius:10px; padding:12px; height:100%; }
299
- .right-panel { background:#ffffff; border-radius:10px; padding:12px; height:100%; box-shadow: 0 6px 18px rgba(20,20,20,0.04); }
300
- .section-title { font-weight:600; font-size:16px; margin-bottom:8px; }
301
- .tools-vertical .gr-button { width:100%; margin-bottom:8px; text-align:left; }
302
- .small-muted { font-size:12px; color:#666; }
303
- .result-box { border:1px solid #eee; border-radius:8px; padding:10px; background:#fafafa; min-height:120px; }
304
- """
305
-
306
- with gr.Blocks(theme=gr.themes.Soft(primary_hue="purple"), css=custom_css) as demo:
307
- gr.HTML("<div class='header-box'><h1 style='margin:0;color:#5B21B6;'>SparkNova</h1><div style='margin-top:6px;color:#666;font-style:italic;'>An AI-powered data exploration and insight generator</div></div>")
308
-
309
- with gr.Row():
310
- with gr.Column(scale=2):
311
- file_input = gr.File(label="Upload CSV", file_types=[".csv"])
312
- format_select = gr.Dropdown(["Preview","JSON","CSV","Excel"], value="Preview", label="Show preview as")
313
- dataset_info = gr.Markdown()
314
- dataset_preview = gr.Dataframe(wrap=True, label="Dataset Preview")
315
- with gr.Column(scale=3):
316
- gr.Markdown("### Sample Questions")
317
- gr.Markdown("- What is the average value of numeric columns?\n- Show a bar chart of sales by region\n- Which column correlates most with revenue?\n- Generate a pie chart for product categories\n- Find missing values\n- Create a line chart showing trends over time\n- Top 5 values in the dataset\n- Show statistical summary of all columns", elem_classes="small-muted")
318
-
319
- # Hook upload to handler
320
- def upload_handler(file, fmt):
321
- global uploaded_df, dataset_name
322
- if file is None:
323
- return "No file uploaded", None
324
- try:
325
- uploaded_df = pd.read_csv(file.name)
326
- uploaded_df = clean_numeric(uploaded_df)
327
- dataset_name = os.path.basename(file.name)
328
- info_text = f"**Dataset Loaded:** {dataset_name} — {uploaded_df.shape[0]} rows × {uploaded_df.shape[1]} cols"
329
- preview_df = uploaded_df.head(10)
330
- return info_text, preview_df
331
- except Exception as e:
332
- return f"Error loading file: {e}", None
333
-
334
- file_input.change(upload_handler, inputs=[file_input, format_select], outputs=[dataset_info, dataset_preview])
335
-
336
- with gr.Row():
337
- with gr.Column(scale=1, elem_classes="left-panel"):
338
- gr.Markdown("#### Choose an Analysis Type", elem_classes="section-title")
339
- btn_describe = gr.Button("Describe Dataset")
340
- btn_summary = gr.Button("Summary Statistics")
341
- btn_corr = gr.Button("Show Correlations")
342
- btn_top5 = gr.Button("Top 5 Rows")
343
- btn_missing = gr.Button("Missing Values")
344
- btn_groupby = gr.Button("Group & Aggregate")
345
- btn_calc = gr.Button("Calculate Expression")
346
- btn_find = gr.Button("Find Highest Correlation")
347
- gr.Markdown("#### Visualization Types", elem_classes="section-title")
348
- chart_bar = gr.Button("Bar Chart")
349
- chart_line = gr.Button("Line Chart")
350
- chart_scatter = gr.Button("Scatter Plot")
351
- chart_pie = gr.Button("Pie Chart")
352
- chart_hist = gr.Button("Histogram")
353
- chart_box = gr.Button("Box Plot")
354
- chart_heat = gr.Button("Heatmap")
355
- gr.Markdown("#### Other", elem_classes="section-title")
356
- btn_insights = gr.Button("Generate Insights (LLM)")
357
- btn_reload = gr.Button("Reload Dataset")
358
-
359
- with gr.Column(scale=2, elem_classes="right-panel"):
360
- gr.Markdown("#### Analysis Output", elem_classes="section-title")
361
- output_text = gr.Textbox(label="Detailed Response", interactive=False, lines=10, show_copy_button=True)
362
- gr.Markdown("#### Result Data", elem_classes="section-title")
363
- result_table = gr.Dataframe(wrap=True, interactive=False)
364
- gr.Markdown("#### Chart Preview", elem_classes="section-title")
365
- chart_display = gr.Plot()
366
- chart_explain = gr.Markdown("Chart explanation will appear here.")
367
- gr.Markdown("#### Ask a Question (LLM)", elem_classes="section-title")
368
- question_box = gr.Textbox(label="Type your question here", lines=3, placeholder="Ask the agent (LLM)...")
369
- ask_btn = gr.Button("Ask (LLM)", variant="primary")
370
-
371
- # Map actions to natural language questions
372
- action_map = {
373
- "Describe Dataset": "Describe the dataset statistically.",
374
- "Summary Statistics": "Provide summary statistics for all columns.",
375
- "Show Correlations": "Which columns have the highest correlation? Provide correlation table.",
376
- "Top 5 Rows": "Show the top 5 rows of the dataset.",
377
- "Missing Values": "List missing values in each column.",
378
- "Group & Aggregate": "Group data and compute typical aggregations (provide example groupby).",
379
- "Calculate Expression": "Create a calculated column (example: create Col_Calc = ColA * ColB).",
380
- "Find Highest Correlation": "Which column has highest correlation with revenue?",
381
- "Generate Insights (LLM)": "Generate short insights for the dataset (4-6 bullets)."
382
- }
383
-
384
- def run_action(action_label):
385
- if uploaded_df is None:
386
- return "Please upload a dataset first.", None, None
387
- question = action_map.get(action_label, action_label)
388
- text, fig, table = analyze_question(question)
389
- return text, fig, table
390
-
391
- # Connect buttons
392
- btn_describe.click(lambda: run_action("Describe Dataset"), outputs=[output_text, chart_display, result_table])
393
- btn_summary.click(lambda: run_action("Summary Statistics"), outputs=[output_text, chart_display, result_table])
394
- btn_corr.click(lambda: run_action("Show Correlations"), outputs=[output_text, chart_display, result_table])
395
- btn_top5.click(lambda: run_action("Top 5 Rows"), outputs=[output_text, chart_display, result_table])
396
- btn_missing.click(lambda: run_action("Missing Values"), outputs=[output_text, chart_display, result_table])
397
- btn_groupby.click(lambda: run_action("Group & Aggregate"), outputs=[output_text, chart_display, result_table])
398
- btn_calc.click(lambda: run_action("Calculate Expression"), outputs=[output_text, chart_display, result_table])
399
- btn_find.click(lambda: run_action("Find Highest Correlation"), outputs=[output_text, chart_display, result_table])
400
- btn_insights.click(lambda: run_action("Generate Insights (LLM)"), outputs=[output_text, chart_display, result_table])
401
- btn_reload.click(lambda: upload_handler(file_input, format_select), outputs=[dataset_info, dataset_preview])
402
-
403
- # Chart requests
404
- def request_chart(chart_type):
405
- if uploaded_df is None:
406
- return "Please upload dataset first.", None, "No explanation"
407
- q = f"Please produce a {chart_type} showing something meaningful from the dataset. Include a plot specification in the JSON."
408
- text, fig, table = analyze_question(q)
409
- explanation = f"This is a {chart_type} generated by the agent."
410
- return text, fig, explanation
411
-
412
- chart_bar.click(lambda: request_chart("bar"), outputs=[output_text, chart_display, chart_explain])
413
- chart_line.click(lambda: request_chart("line"), outputs=[output_text, chart_display, chart_explain])
414
- chart_scatter.click(lambda: request_chart("scatter"), outputs=[output_text, chart_display, chart_explain])
415
- chart_pie.click(lambda: request_chart("pie"), outputs=[output_text, chart_display, chart_explain])
416
- chart_hist.click(lambda: request_chart("hist"), outputs=[output_text, chart_display, chart_explain])
417
- chart_box.click(lambda: request_chart("box"), outputs=[output_text, chart_display, chart_explain])
418
- chart_heat.click(lambda: request_chart("heatmap"), outputs=[output_text, chart_display, chart_explain])
419
-
420
- # Freeform question via LLM
421
- def ask_freeform(q):
422
- if uploaded_df is None:
423
- return "Please upload a dataset first.", None, None
424
- return analyze_question(q)
425
-
426
- ask_btn.click(ask_freeform, inputs=[question_box], outputs=[output_text, chart_display, result_table])
427
-
428
- # --------------------- Download Section (FIXED) ---------------------
429
- gr.Markdown("### Download Options")
430
-
431
- # Define download functions with proper state management
432
- def download_result_data():
433
- """Download the result table as CSV"""
434
- if uploaded_df is None:
435
- return None
436
- try:
437
- temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.csv', newline='')
438
- uploaded_df.to_csv(temp_file.name, index=False)
439
- return temp_file.name
440
- except Exception as e:
441
- print(f"Error saving results: {e}")
442
- return None
443
-
444
- def download_chart_image():
445
- """Download the last generated chart as PNG"""
446
- global last_chart
447
- if last_chart is None:
448
- return None
449
- try:
450
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
451
- last_chart.write_image(temp_file.name, width=1200, height=700)
452
- return temp_file.name
453
- except Exception as e:
454
- print(f"Error saving chart: {e}")
455
- return None
456
-
457
- # Create file outputs
458
- result_file_output = gr.File(label="Download Results", visible=True)
459
- chart_file_output = gr.File(label="Download Chart", visible=True)
460
-
461
- # Buttons with proper outputs
462
- download_result_btn = gr.Button(" Download Results CSV", variant="secondary")
463
- download_chart_btn = gr.Button(" Download Chart PNG", variant="secondary")
464
-
465
- # Connect buttons
466
- download_result_btn.click(download_result_data, inputs=[], outputs=[result_file_output])
467
- download_chart_btn.click(download_chart_image, inputs=[], outputs=[chart_file_output])
468
-
469
- gr.HTML("<div style='text-align:center;color:#666;padding-top:10px;'>Powered by GROQ LLM & Gradio — SparkNova</div>")
470
-
471
- # Initialize LLM and launch
472
- if __name__ == "__main__":
473
- initialize_llm()
474
- demo.launch(server_name="0.0.0.0", server_port=7860, debug=True)