""" 🧠 AGENTIC AUTOPILOT ENGINE v1.0 ================================= The world's first fully autonomous data science agent. Given a dataset and a goal (or no goal at all), this engine: 1. Profiles the data autonomously 2. Cleans and prepares it 3. Discovers patterns and insights 4. Trains the best ML model 5. Generates visualizations 6. Produces a complete analysis report 7. Deploys a prediction API All with ZERO human intervention. The user just uploads data and watches. Architecture: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PLANNER │────▢│ EXECUTOR │────▢│ EVALUATOR β”‚ β”‚ (Goal β†’ Plan)β”‚ β”‚ (Run Steps) β”‚ β”‚ (Check/Fix) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–² β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ (Re-plan if needed) """ import logging import json import time import traceback import pandas as pd import numpy as np from typing import Dict, Any, List, Optional, Callable from dataclasses import dataclass, field from datetime import datetime from enum import Enum from pathlib import Path import asyncio logger = logging.getLogger(__name__) class StepStatus(Enum): PENDING = "pending" RUNNING = "running" COMPLETED = "completed" FAILED = "failed" SKIPPED = "skipped" class AutopilotPhase(Enum): PLANNING = "planning" DATA_PROFILING = "data_profiling" DATA_CLEANING = "data_cleaning" FEATURE_DISCOVERY = "feature_discovery" INSIGHT_EXTRACTION = "insight_extraction" VISUALIZATION = "visualization" MODEL_TRAINING = "model_training" MODEL_EVALUATION = "model_evaluation" CODE_GENERATION = "code_generation" REPORT_GENERATION = "report_generation" COMPLETE = "complete" @dataclass class AutopilotStep: """A single step in the autopilot execution plan.""" id: str phase: AutopilotPhase title: str description: str status: StepStatus = StepStatus.PENDING result: Optional[Dict[str, Any]] = None error: Optional[str] = None started_at: Optional[float] = None completed_at: Optional[float] = None progress: float = 0.0 # 0-100 def to_dict(self): return { "id": self.id, "phase": self.phase.value, "title": self.title, "description": self.description, "status": self.status.value, "result": self.result, "error": self.error, "duration_ms": int((self.completed_at - self.started_at) * 1000) if self.started_at and self.completed_at else None, "progress": self.progress } @dataclass class AutopilotState: """Full state of an autopilot run.""" user_id: str session_id: str goal: str filename: str df: Optional[pd.DataFrame] = None target_column: Optional[str] = None task_type: Optional[str] = None steps: List[AutopilotStep] = field(default_factory=list) current_step_index: int = 0 insights: List[str] = field(default_factory=list) charts: List[Dict[str, Any]] = field(default_factory=list) model_result: Optional[Dict[str, Any]] = None report: Optional[str] = None started_at: float = 0.0 completed_at: Optional[float] = None cancelled: bool = False def to_dict(self): return { "session_id": self.session_id, "goal": self.goal, "filename": self.filename, "target_column": self.target_column, "task_type": self.task_type, "current_step": self.current_step_index, "total_steps": len(self.steps), "steps": [s.to_dict() for s in self.steps], "insights": self.insights, "charts": self.charts, "elapsed_seconds": round(time.time() - self.started_at, 1) if self.started_at else 0, } class AgenticAutopilot: """ The Agentic Autopilot β€” an autonomous data science agent. Usage: autopilot = AgenticAutopilot(user_id, df, goal="Predict customer churn") async for event in autopilot.run(): # event is a dict: {"type": "step_start"|"step_complete"|"insight"|"chart"|...} yield event """ def __init__(self, user_id: str, df: pd.DataFrame, filename: str = "data.csv", goal: str = "", target_column: str = None, session_id: str = None): import uuid self.state = AutopilotState( user_id=user_id, session_id=session_id or str(uuid.uuid4()), goal=goal or "Perform comprehensive autonomous data analysis", filename=filename, df=df, target_column=target_column, started_at=time.time() ) self._event_callback: Optional[Callable] = None self._build_plan() def _build_plan(self): """Build the execution plan based on the goal and data.""" steps = [ AutopilotStep( id="plan", phase=AutopilotPhase.PLANNING, title="🧠 Analyzing Goal & Planning Strategy", description="Understanding your data and formulating an optimal analysis plan" ), AutopilotStep( id="profile", phase=AutopilotPhase.DATA_PROFILING, title="πŸ” Deep Data Profiling", description="Scanning every column β€” types, distributions, missing values, correlations" ), AutopilotStep( id="clean", phase=AutopilotPhase.DATA_CLEANING, title="🧹 Autonomous Data Cleaning", description="Fixing missing values, outliers, duplicates, and type errors" ), AutopilotStep( id="features", phase=AutopilotPhase.FEATURE_DISCOVERY, title="⚑ Intelligent Feature Discovery", description="Engineering new features and selecting the most predictive ones" ), AutopilotStep( id="insights", phase=AutopilotPhase.INSIGHT_EXTRACTION, title="πŸ’‘ Extracting Key Insights", description="Finding patterns, trends, anomalies, and correlations in your data" ), AutopilotStep( id="viz", phase=AutopilotPhase.VISUALIZATION, title="πŸ“Š Generating Visualizations", description="Creating the most informative charts and plots for your data" ), AutopilotStep( id="train", phase=AutopilotPhase.MODEL_TRAINING, title="πŸ‹οΈ Training ML Models", description="Testing multiple algorithms with cross-validation to find the best model" ), AutopilotStep( id="eval", phase=AutopilotPhase.MODEL_EVALUATION, title="πŸ“ˆ Evaluating & Explaining Model", description="Computing metrics, feature importance, and reliability scores" ), AutopilotStep( id="code", phase=AutopilotPhase.CODE_GENERATION, title="πŸ’» Generating Deployment Code", description="Creating a production-ready prediction API you can run locally" ), AutopilotStep( id="report", phase=AutopilotPhase.REPORT_GENERATION, title="πŸ“‹ Compiling Analysis Report", description="Summarizing everything into an executive-ready report" ), ] self.state.steps = steps async def run(self): """ Execute the full autopilot pipeline. Yields SSE-compatible event dicts as it progresses. """ yield self._event("session_start", { "session_id": self.state.session_id, "goal": self.state.goal, "total_steps": len(self.state.steps), "filename": self.state.filename, "rows": len(self.state.df), "cols": len(self.state.df.columns), }) for i, step in enumerate(self.state.steps): if self.state.cancelled: yield self._event("cancelled", {"step": step.id}) return self.state.current_step_index = i step.status = StepStatus.RUNNING step.started_at = time.time() yield self._event("step_start", { "step_index": i, "step": step.to_dict() }) try: result = await self._execute_step(step) step.status = StepStatus.COMPLETED step.completed_at = time.time() step.result = result step.progress = 100.0 yield self._event("step_complete", { "step_index": i, "step": step.to_dict() }) except Exception as e: step.status = StepStatus.FAILED step.completed_at = time.time() step.error = str(e) logger.error(f"Autopilot step {step.id} failed: {e}") traceback.print_exc() yield self._event("step_error", { "step_index": i, "step": step.to_dict(), "error": str(e) }) # Continue with next step despite error continue self.state.completed_at = time.time() elapsed = round(self.state.completed_at - self.state.started_at, 1) session_payload = { "session_id": self.state.session_id, "elapsed_seconds": elapsed, "insights_count": len(self.state.insights), "charts_count": len(self.state.charts), "model_trained": self.state.model_result is not None, "model_result": self.state.model_result, "data_profile": self.state.steps[1].result if len(self.state.steps) > 1 and self.state.steps[1].result else None, "insights": self.state.insights, "summary": self.state.report or "Analysis complete.", } yield self._event("session_complete", session_payload) # Fire webhooks asynchronously without blocking the stream asyncio.create_task(self._trigger_webhooks(session_payload)) async def _trigger_webhooks(self, payload: dict): """Fire HTTP requests to the user's registered webhooks.""" try: from database.db import AsyncSessionLocal from database.orm import Webhook from sqlalchemy import select import httpx async with AsyncSessionLocal() as db: result = await db.execute( select(Webhook).filter( Webhook.user_id == self.state.user_id, Webhook.status == "active" ) ) webhooks = result.scalars().all() user_webhooks = [ w for w in webhooks if "autopilot.completed" in (w.events or []) ] if not user_webhooks: return async with httpx.AsyncClient() as client: for wh in user_webhooks: try: await client.post( wh.url, json={"event": "autopilot.completed", "data": payload}, timeout=10.0 ) logger.info(f"Successfully fired webhook to {wh.url}") except Exception as e: logger.warning(f"Webhook {wh.url} failed: {e}") except Exception as e: logger.error(f"Failed to process webhooks: {e}") async def _execute_step(self, step: AutopilotStep) -> Dict[str, Any]: """Route execution to the correct handler.""" handlers = { AutopilotPhase.PLANNING: self._step_planning, AutopilotPhase.DATA_PROFILING: self._step_profiling, AutopilotPhase.DATA_CLEANING: self._step_cleaning, AutopilotPhase.FEATURE_DISCOVERY: self._step_features, AutopilotPhase.INSIGHT_EXTRACTION: self._step_insights, AutopilotPhase.VISUALIZATION: self._step_visualization, AutopilotPhase.MODEL_TRAINING: self._step_training, AutopilotPhase.MODEL_EVALUATION: self._step_evaluation, AutopilotPhase.CODE_GENERATION: self._step_code_generation, AutopilotPhase.REPORT_GENERATION: self._step_report, } handler = handlers.get(step.phase) if handler: # Add a small artificial delay so the user can see the AI "thinking" # and the UI animations have time to run smoothly. await asyncio.sleep(1.5) # Run the handler return await handler(step) return {"status": "skipped"} # ========================================================================= # STEP HANDLERS # ========================================================================= async def _step_planning(self, step: AutopilotStep) -> Dict: """Analyze goal and data to create intelligent plan.""" df = self.state.df # Auto-detect target column if not provided if not self.state.target_column: # Heuristics: last column, or columns with names like "target", "label", "class", "price", "sales" target_keywords = ['target', 'label', 'class', 'churn', 'fraud', 'price', 'sales', 'revenue', 'profit', 'rating', 'score', 'outcome', 'result', 'survival', 'default', 'response', 'converted', 'purchased'] for col in df.columns: if any(kw in col.lower() for kw in target_keywords): self.state.target_column = col break if not self.state.target_column: # Use last numeric column numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist() if numeric_cols: self.state.target_column = numeric_cols[-1] else: self.state.target_column = df.columns[-1] # Detect task type target_col = self.state.target_column if target_col in df.columns: nunique = df[target_col].nunique() dtype = df[target_col].dtype if dtype == 'object' or (dtype in ['int64', 'float64'] and nunique <= 20 and nunique / len(df) < 0.05): self.state.task_type = "classification" else: self.state.task_type = "regression" else: self.state.task_type = "regression" plan_summary = ( f"πŸ“‹ **Analysis Plan:**\n" f"- Dataset: {self.state.filename} ({len(df):,} rows Γ— {len(df.columns)} cols)\n" f"- Target: `{self.state.target_column}`\n" f"- Task: {self.state.task_type}\n" f"- Goal: {self.state.goal}\n" f"- Strategy: Full autonomous pipeline β€” profile β†’ clean β†’ engineer β†’ train β†’ deploy" ) self.state.insights.append(plan_summary) return { "target_column": self.state.target_column, "task_type": self.state.task_type, "rows": len(df), "cols": len(df.columns), "plan": plan_summary } async def _step_profiling(self, step: AutopilotStep) -> Dict: """Deep data profiling.""" df = self.state.df profile = { "shape": {"rows": len(df), "cols": len(df.columns)}, "dtypes": {col: str(dtype) for col, dtype in df.dtypes.items()}, "missing": {col: int(df[col].isnull().sum()) for col in df.columns if df[col].isnull().sum() > 0}, "missing_pct": round(df.isnull().sum().sum() / (len(df) * len(df.columns)) * 100, 2), "duplicates": int(df.duplicated().sum()), "numeric_cols": df.select_dtypes(include=[np.number]).columns.tolist(), "categorical_cols": df.select_dtypes(include=['object', 'category']).columns.tolist(), "memory_mb": round(df.memory_usage(deep=True).sum() / 1024 / 1024, 2), } # Compute basic stats for numerics numeric_stats = {} for col in profile["numeric_cols"][:15]: # Limit to 15 cols for speed numeric_stats[col] = { "mean": round(float(df[col].mean()), 3) if not df[col].isnull().all() else None, "std": round(float(df[col].std()), 3) if not df[col].isnull().all() else None, "min": float(df[col].min()) if not df[col].isnull().all() else None, "max": float(df[col].max()) if not df[col].isnull().all() else None, } profile["numeric_stats"] = numeric_stats # Generate profiling insight insight = ( f"πŸ” **Data Profile:** {len(df):,} rows Γ— {len(df.columns)} cols | " f"{len(profile['numeric_cols'])} numeric, {len(profile['categorical_cols'])} categorical | " f"{profile['missing_pct']}% missing | {profile['duplicates']} duplicates | " f"{profile['memory_mb']} MB" ) self.state.insights.append(insight) return profile async def _step_cleaning(self, step: AutopilotStep) -> Dict: """Autonomous data cleaning.""" df = self.state.df.copy() fixes = [] original_shape = df.shape # 1. Remove duplicates dup_count = df.duplicated().sum() if dup_count > 0: df = df.drop_duplicates() fixes.append(f"Removed {dup_count} duplicate rows") # 2. Handle missing values for col in df.columns: missing = df[col].isnull().sum() if missing > 0: pct = missing / len(df) * 100 if pct > 60: df = df.drop(columns=[col]) fixes.append(f"Dropped column `{col}` ({pct:.0f}% missing)") elif df[col].dtype in ['float64', 'int64']: median_val = df[col].median() df[col] = df[col].fillna(median_val) fixes.append(f"Filled {missing} missing values in `{col}` with median ({median_val:.2f})") else: mode_val = df[col].mode()[0] if len(df[col].mode()) > 0 else "Unknown" df[col] = df[col].fillna(mode_val) fixes.append(f"Filled {missing} missing values in `{col}` with mode ({mode_val})") self.state.df = df insight = f"🧹 **Data Cleaning:** Applied {len(fixes)} fixes. Shape: {original_shape} β†’ {df.shape}" self.state.insights.append(insight) return {"fixes": fixes, "original_shape": list(original_shape), "new_shape": list(df.shape)} async def _step_features(self, step: AutopilotStep) -> Dict: """Feature discovery and engineering.""" df = self.state.df new_features = [] # Auto-detect and engineer date features for col in df.columns: if df[col].dtype == 'object': try: parsed = pd.to_datetime(df[col], errors='coerce') if parsed.notna().sum() > len(df) * 0.5: df[f'{col}_year'] = parsed.dt.year df[f'{col}_month'] = parsed.dt.month df[f'{col}_dayofweek'] = parsed.dt.dayofweek new_features.extend([f'{col}_year', f'{col}_month', f'{col}_dayofweek']) df = df.drop(columns=[col]) except: pass self.state.df = df # Compute correlations with target target = self.state.target_column correlations = {} if target in df.columns and df[target].dtype in ['float64', 'int64']: numeric_df = df.select_dtypes(include=[np.number]) if target in numeric_df.columns: corrs = numeric_df.corr()[target].drop(target, errors='ignore').abs().sort_values(ascending=False) correlations = {col: round(float(val), 3) for col, val in corrs.head(10).items()} insight = f"⚑ **Features:** Engineered {len(new_features)} new features. Top correlations with `{target}`: {', '.join(f'{k}({v})' for k, v in list(correlations.items())[:5])}" self.state.insights.append(insight) return { "new_features": new_features, "top_correlations": correlations, "total_features": len(df.columns) } async def _step_insights(self, step: AutopilotStep) -> Dict: """Extract key data insights using statistical analysis.""" df = self.state.df key_insights = [] # 1. Distribution insights numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist() for col in numeric_cols[:10]: skewness = df[col].skew() if abs(skewness) > 1: direction = "right" if skewness > 0 else "left" key_insights.append(f"πŸ“Š `{col}` is heavily {direction}-skewed (skew={skewness:.2f})") # 2. Categorical insights cat_cols = df.select_dtypes(include=['object', 'category']).columns.tolist() for col in cat_cols[:5]: top_val = df[col].value_counts().head(1) if len(top_val) > 0: pct = top_val.values[0] / len(df) * 100 if pct > 50: key_insights.append(f"⚠️ `{col}` is dominated by '{top_val.index[0]}' ({pct:.0f}% of data)") # 3. Correlation insights if len(numeric_cols) > 1: corr_matrix = df[numeric_cols].corr() for i in range(len(numeric_cols)): for j in range(i+1, len(numeric_cols)): corr_val = corr_matrix.iloc[i, j] if abs(corr_val) > 0.8: key_insights.append( f"πŸ”— Strong {'positive' if corr_val > 0 else 'negative'} correlation between " f"`{numeric_cols[i]}` and `{numeric_cols[j]}` ({corr_val:.2f})" ) self.state.insights.extend(key_insights[:10]) # Cap at 10 return {"insights": key_insights[:10], "count": len(key_insights)} async def _step_visualization(self, step: AutopilotStep) -> Dict: """Generate key visualizations.""" df = self.state.df charts = [] target = self.state.target_column numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist() # 1. Target distribution chart if target in df.columns: if self.state.task_type == "classification": value_counts = df[target].value_counts().head(10) charts.append({ "type": "bar", "title": f"Distribution of {target}", "data": {"labels": value_counts.index.tolist(), "values": value_counts.values.tolist()}, "description": f"Class distribution of target variable `{target}`" }) else: charts.append({ "type": "histogram", "title": f"Distribution of {target}", "data": {"values": df[target].dropna().tolist()[:1000]}, "description": f"Histogram of target variable `{target}`" }) # 2. Correlation heatmap data if len(numeric_cols) > 1: top_cols = numeric_cols[:10] corr = df[top_cols].corr().round(3) charts.append({ "type": "heatmap", "title": "Feature Correlation Matrix", "data": { "labels": top_cols, "values": corr.values.tolist() }, "description": "Correlation heatmap of top numeric features" }) # 3. Top feature bar chart if target in df.columns and target in numeric_cols and len(numeric_cols) > 1: corrs = df[numeric_cols].corr()[target].drop(target, errors='ignore').abs().sort_values(ascending=False).head(8) charts.append({ "type": "bar", "title": f"Top Features Correlated with {target}", "data": {"labels": corrs.index.tolist(), "values": corrs.values.tolist()}, "description": "Features most correlated with the target variable" }) self.state.charts = charts return {"charts_generated": len(charts)} async def _step_training(self, step: AutopilotStep) -> Dict: """Train ML model using the existing God-Level AutoML engine.""" try: from ml.god_level_automl import run_god_level import asyncio # Run the heavy blocking AutoML training in a separate thread # to prevent starving the event loop and freezing the SSE stream. result = await asyncio.to_thread( run_god_level, df=self.state.df, target_column=self.state.target_column, user_id=self.state.user_id, mode="balanced" ) self.state.model_result = { "best_model": result.best_model_name, "metrics": result.best_model_metrics, "reliability": result.best_model_reliability, "leaderboard": result.leaderboard[:5] if result.leaderboard else [], "processing_time": result.processing_time, } insight = ( f"πŸ† **Best Model: {result.best_model_name}** | " f"Reliability: {result.best_model_reliability:.1f}/100 | " f"Trained in {result.processing_time:.1f}s" ) self.state.insights.append(insight) return self.state.model_result except Exception as e: logger.error(f"Model training failed: {e}") # Fallback to simple sklearn return await self._fallback_training(step) async def _fallback_training(self, step: AutopilotStep) -> Dict: """Fallback training with basic sklearn if God-Level fails.""" from sklearn.model_selection import train_test_split, cross_val_score from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor from sklearn.preprocessing import LabelEncoder from sklearn.metrics import accuracy_score, r2_score, mean_squared_error df = self.state.df.copy() target = self.state.target_column if target not in df.columns: return {"error": f"Target column '{target}' not found"} # Prepare data X = df.drop(columns=[target]) y = df[target] # Encode categoricals encoders = {} for col in X.select_dtypes(include=['object']).columns: le = LabelEncoder() X[col] = le.fit_transform(X[col].astype(str)) encoders[col] = le if y.dtype == 'object': le_target = LabelEncoder() y = le_target.fit_transform(y) # Drop remaining non-numeric X = X.select_dtypes(include=[np.number]) # Fill any remaining NaN X = X.fillna(0) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) if self.state.task_type == "classification": model = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1) model.fit(X_train, y_train) y_pred = model.predict(X_test) score = accuracy_score(y_test, y_pred) metric_name = "accuracy" else: model = RandomForestRegressor(n_estimators=100, random_state=42, n_jobs=-1) model.fit(X_train, y_train) y_pred = model.predict(X_test) score = r2_score(y_test, y_pred) metric_name = "r2" # Save model try: from ml.model_persistence import model_persistence model_persistence.save_model( user_id=self.state.user_id, model=model, metadata={ "model_name": "RandomForest", "task_type": self.state.task_type, "target_column": self.state.target_column, "feature_columns": X.columns.tolist(), metric_name: round(score, 4), "source": "autopilot" } ) except Exception as save_err: logger.warning(f"Could not save model: {save_err}") self.state.model_result = { "best_model": "RandomForest", "metrics": {metric_name: round(score, 4)}, "reliability": round(score * 100, 1), "processing_time": round(time.time() - step.started_at, 1), } insight = f"πŸ† **Best Model: RandomForest** | {metric_name}: {score:.4f}" self.state.insights.append(insight) return self.state.model_result async def _step_evaluation(self, step: AutopilotStep) -> Dict: """Evaluate model and generate explanations.""" if not self.state.model_result: return {"status": "skipped", "reason": "No model trained"} eval_result = { "model": self.state.model_result.get("best_model"), "metrics": self.state.model_result.get("metrics", {}), "reliability": self.state.model_result.get("reliability", 0), "verdict": "βœ… Model is production-ready" if self.state.model_result.get("reliability", 0) > 70 else "⚠️ Model needs improvement" } self.state.insights.append(eval_result["verdict"]) return eval_result async def _step_code_generation(self, step: AutopilotStep) -> Dict: """Generate deployment code.""" if not self.state.model_result: return {"status": "skipped", "reason": "No model to deploy"} try: from ml.ml_code_generator import generate_code_zip from config.settings import Settings from ml.model_persistence import model_persistence base_storage = Settings.STORAGE model_path = base_storage / "models" / self.state.user_id / "active_model.pkl" if model_path.exists(): meta_path = model_path.parent / "active_metadata.json" metadata = {} if meta_path.exists(): with open(meta_path, 'r') as f: metadata = json.load(f) buf = generate_code_zip(model_path, metadata) self.state.insights.append("πŸ’» **Deployment code generated** β€” Ready to run as a local API server") return {"status": "generated", "files_count": 5} else: return {"status": "skipped", "reason": "Model file not found at expected path"} except Exception as e: logger.warning(f"Code generation failed: {e}") return {"status": "error", "error": str(e)} async def _step_report(self, step: AutopilotStep) -> Dict: """Generate the final analysis report.""" elapsed = round(time.time() - self.state.started_at, 1) report_lines = [ f"# πŸš€ DataVision Autopilot Report", f"", f"**Dataset:** {self.state.filename}", f"**Goal:** {self.state.goal}", f"**Target:** `{self.state.target_column}` ({self.state.task_type})", f"**Total Time:** {elapsed}s", f"", f"---", f"", f"## Key Insights", f"", ] for i, insight in enumerate(self.state.insights): report_lines.append(f"{i+1}. {insight}") if self.state.model_result: report_lines.extend([ f"", f"## Model Performance", f"", f"- **Best Model:** {self.state.model_result.get('best_model', 'N/A')}", f"- **Metrics:** {json.dumps(self.state.model_result.get('metrics', {}), indent=2)}", f"- **Reliability:** {self.state.model_result.get('reliability', 0)}/100", ]) report_lines.extend([ f"", f"---", f"*Generated by DataVision Agentic Autopilot v5.0*", ]) self.state.report = "\n".join(report_lines) return {"report": self.state.report} def _event(self, event_type: str, data: Dict) -> Dict: """Create a standardized event dict.""" return { "type": event_type, "timestamp": datetime.utcnow().isoformat(), "data": data } def cancel(self): """Cancel the running autopilot.""" self.state.cancelled = True # Global registry of active autopilot sessions active_sessions: Dict[str, AgenticAutopilot] = {}