Datavision / backend /core /autonomous_dashboard.py
DataVision CI/CD Bot
release: clean production build for HuggingFace Space
09801ca
Raw
History Blame Contribute Delete
11 kB
"""
AUTONOMOUS DASHBOARD GENERATOR - Like Power BI but AI-Driven
=============================================================
Generates complete dashboards AUTONOMOUSLY from user data.
NO HARDCODING - LLM decides everything:
- Which KPIs to show
- What charts to generate
- Colors and themes
- Layout and organization
- Insights and recommendations
This is a $50B Silicon Valley feature!
"""
import json
import logging
from typing import Dict, List, Optional, Any
from dataclasses import dataclass
import pandas as pd
from core.llm import chat
logger = logging.getLogger(__name__)
@dataclass
class DashboardWidget:
"""A single widget in the dashboard"""
widget_type: str # kpi, chart, table, insight
title: str
data: Dict
config: Dict
position: int
@dataclass
class AutonomousDashboard:
"""Complete autonomous dashboard"""
title: str
theme: Dict
kpis: List[Dict]
charts: List[Dict]
insights: List[str]
recommendations: List[str]
generated_at: str
def analyze_data_for_dashboard(df: pd.DataFrame) -> Dict:
"""
Analyze data to understand what kind of dashboard to generate.
"""
analysis = {
"row_count": len(df),
"column_count": len(df.columns),
"columns": [],
"numeric_summary": {},
"categorical_summary": {},
"date_columns": [],
"suggested_kpis": [],
"suggested_charts": []
}
for col in df.columns:
col_info = {
"name": col,
"dtype": str(df[col].dtype),
"unique_count": df[col].nunique(),
"null_count": df[col].isnull().sum()
}
if df[col].dtype in ['int64', 'float64']:
col_info["is_numeric"] = True
analysis["numeric_summary"][col] = {
"sum": float(df[col].sum()),
"mean": float(df[col].mean()),
"min": float(df[col].min()),
"max": float(df[col].max())
}
else:
col_info["is_numeric"] = False
if df[col].nunique() < 20: # Categorical
analysis["categorical_summary"][col] = df[col].value_counts().head(10).to_dict()
# Detect date columns
if 'date' in col.lower() or 'time' in col.lower():
analysis["date_columns"].append(col)
analysis["columns"].append(col_info)
return analysis
def generate_autonomous_dashboard(df: pd.DataFrame, user_id: str) -> Dict:
"""
Generate a COMPLETE dashboard autonomously using LLM.
The LLM decides:
- Dashboard title and theme
- Which KPIs to highlight
- What charts to generate
- Layout and colors
- Insights and recommendations
"""
if df is None or df.empty:
return {"error": "No data available. Please upload files first."}
try:
# Step 1: Analyze data structure
analysis = analyze_data_for_dashboard(df)
# Step 2: Use LLM to design the dashboard
prompt = f"""You are an expert dashboard designer like Power BI. Design an AUTONOMOUS dashboard for this data.
DATA ANALYSIS:
- Total Rows: {analysis['row_count']}
- Columns: {[c['name'] for c in analysis['columns']]}
- Numeric Columns: {list(analysis['numeric_summary'].keys())}
- Categorical Columns: {list(analysis['categorical_summary'].keys())}
- Date Columns: {analysis['date_columns']}
NUMERIC DATA SUMMARY:
{json.dumps(analysis['numeric_summary'], indent=2)[:1000]}
CATEGORICAL DATA SUMMARY:
{json.dumps(analysis['categorical_summary'], default=str, indent=2)[:1000]}
SAMPLE DATA:
{df.head(5).to_string()[:800]}
GENERATE A COMPLETE DASHBOARD with this JSON structure:
{{
"dashboard_title": "Descriptive title based on data",
"theme": {{
"primary_color": "#hex",
"secondary_color": "#hex",
"accent_color": "#hex",
"background": "dark or light"
}},
"kpis": [
{{
"title": "KPI Name",
"value": actual_number_from_data,
"format": "currency/number/percentage",
"trend": "up/down/neutral",
"comparison": "vs previous period text"
}}
],
"charts": [
{{
"chart_id": "chart_1",
"title": "Chart Title",
"type": "bar/line/pie/donut/area",
"x_column": "column_name",
"y_column": "column_name",
"color": "#hex"
}}
],
"insights": [
"Key insight 1 with specific numbers",
"Key insight 2 with specific numbers"
],
"recommendations": [
"Action recommendation 1",
"Action recommendation 2"
]
}}
Generate 3-5 KPIs, 4-6 charts, 3-5 insights, 2-3 recommendations.
Use REAL values from the data summary above.
Make it look like a professional Power BI dashboard!
DASHBOARD JSON:"""
dashboard_config = chat(prompt, temperature=0.3, max_tokens=2500)
# Parse the response
config = dashboard_config.strip()
if '```json' in config:
config = config.split('```json')[1].split('```')[0]
elif '```' in config:
config = config.split('```')[1].split('```')[0]
start = config.find('{')
end = config.rfind('}') + 1
if start >= 0 and end > start:
config = config[start:end]
dashboard = json.loads(config)
# Step 3: Generate actual Plotly charts for each chart config
charts_with_data = []
for chart_config in dashboard.get("charts", []):
plotly_chart = generate_dashboard_chart(df, chart_config, dashboard.get("theme", {}))
if plotly_chart:
charts_with_data.append({
**chart_config,
"plotly_config": plotly_chart
})
dashboard["charts"] = charts_with_data
dashboard["generated_at"] = pd.Timestamp.now().isoformat()
dashboard["data_source"] = f"{analysis['row_count']} rows, {analysis['column_count']} columns"
logger.info(f"✅ Generated autonomous dashboard: {dashboard.get('dashboard_title', 'Dashboard')}")
return dashboard
except json.JSONDecodeError as e:
logger.error(f"Dashboard JSON parse error: {e}")
return {"error": f"Failed to parse dashboard config: {str(e)}"}
except Exception as e:
logger.error(f"Dashboard generation error: {e}")
import traceback
traceback.print_exc()
return {"error": str(e)}
def generate_dashboard_chart(df: pd.DataFrame, chart_config: Dict, theme: Dict) -> Optional[Dict]:
"""
Generate a Plotly chart configuration for a dashboard widget.
"""
try:
chart_type = chart_config.get("type", "bar")
x_col = chart_config.get("x_column")
y_col = chart_config.get("y_column")
title = chart_config.get("title", "Chart")
color = chart_config.get("color", theme.get("primary_color", "#14b8a6"))
# Validate columns
if x_col not in df.columns:
x_col = df.columns[0]
if y_col and y_col not in df.columns:
y_col = df.select_dtypes(include=['int64', 'float64']).columns[0] if len(df.select_dtypes(include=['int64', 'float64']).columns) > 0 else df.columns[-1]
# Prepare data
if y_col and df[y_col].dtype in ['int64', 'float64']:
grouped = df.groupby(x_col)[y_col].sum().head(10)
labels = [str(l) for l in grouped.index.tolist()]
values = grouped.values.tolist()
else:
counts = df[x_col].value_counts().head(10)
labels = [str(l) for l in counts.index.tolist()]
values = counts.values.tolist()
# Base layout with dark theme
base_layout = {
"title": {"text": title, "font": {"color": "#fff", "size": 14}},
"paper_bgcolor": "rgba(0,0,0,0)",
"plot_bgcolor": "rgba(0,0,0,0)",
"font": {"color": "#9ca3af", "size": 11},
"margin": {"l": 40, "r": 20, "t": 40, "b": 40},
"xaxis": {"gridcolor": "#374151", "showgrid": True},
"yaxis": {"gridcolor": "#374151", "showgrid": True}
}
if chart_type == "pie" or chart_type == "donut":
return {
"data": [{
"type": "pie",
"labels": labels,
"values": values,
"hole": 0.4 if chart_type == "donut" else 0,
"marker": {"colors": ["#14b8a6", "#6366f1", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4"]}
}],
"layout": {**base_layout, "showlegend": True, "legend": {"font": {"color": "#9ca3af"}}}
}
elif chart_type == "line":
return {
"data": [{
"type": "scatter",
"mode": "lines+markers",
"x": labels,
"y": values,
"line": {"color": color, "width": 2},
"marker": {"size": 6}
}],
"layout": base_layout
}
elif chart_type == "area":
return {
"data": [{
"type": "scatter",
"mode": "lines",
"x": labels,
"y": values,
"fill": "tozeroy",
"fillcolor": f"{color}40",
"line": {"color": color, "width": 2}
}],
"layout": base_layout
}
else: # bar
return {
"data": [{
"type": "bar",
"x": labels,
"y": values,
"marker": {"color": color}
}],
"layout": base_layout
}
except Exception as e:
logger.warning(f"Chart generation failed: {e}")
return None
def get_dashboard_summary(df: pd.DataFrame) -> Dict:
"""
Get a quick summary for dashboard header.
"""
if df is None or df.empty:
return {"error": "No data"}
summary = {
"total_rows": len(df),
"total_columns": len(df.columns),
"numeric_columns": len(df.select_dtypes(include=['int64', 'float64']).columns),
"categorical_columns": len(df.select_dtypes(include=['object']).columns),
"date_columns": len([c for c in df.columns if 'date' in c.lower()])
}
# Get top metric
numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns
if len(numeric_cols) > 0:
top_col = numeric_cols[0]
summary["top_metric"] = {
"name": top_col,
"total": float(df[top_col].sum()),
"average": float(df[top_col].mean())
}
return summary