Spaces:
Running
Running
| """ | |
| 🤖 AUTONOMOUS API - Model Management & Auto-Fix Endpoints | |
| ========================================================== | |
| Provides API endpoints for: | |
| 1. Model persistence (list, load, delete, rollback) | |
| 2. Autonomous data operations (auto-fix, detect issues) | |
| """ | |
| import logging | |
| from typing import Optional, List | |
| from fastapi import APIRouter, HTTPException, UploadFile, File, Form, Header | |
| from pydantic import BaseModel | |
| import pandas as pd | |
| import io | |
| logger = logging.getLogger(__name__) | |
| router = APIRouter() | |
| # ============================================================================= | |
| # SECURITY HELPER - JWT Authentication | |
| # ============================================================================= | |
| def get_secure_user_id(form_user_id: str, x_user_id: Optional[str], authorization: Optional[str]) -> str: | |
| """ | |
| Get verified user_id from JWT token or headers. | |
| Priority: JWT token > X-User-ID header > Form data | |
| """ | |
| # 1. Try JWT token first (most secure) | |
| if authorization: | |
| try: | |
| token = authorization.replace("Bearer ", "") | |
| from core.auth import decode_jwt_token | |
| payload = decode_jwt_token(token) | |
| if payload and payload.get("sub"): | |
| return payload["sub"] | |
| except Exception as e: | |
| logger.debug(f"JWT decode failed: {e}") | |
| # 2. Try X-User-ID header (from authenticated frontend) | |
| if x_user_id and x_user_id != "default": | |
| return x_user_id | |
| # 3. Fallback to form data (least secure) | |
| if form_user_id and form_user_id != "default": | |
| logger.warning(f"Using form user_id: {form_user_id} - consider using JWT") | |
| return form_user_id | |
| # 4. Generate guest fingerprint | |
| import hashlib | |
| import time | |
| return f"guest_{hashlib.md5(str(time.time()).encode()).hexdigest()[:8]}" | |
| # ============================================================================= | |
| # REQUEST/RESPONSE MODELS | |
| # ============================================================================= | |
| class AutoFixRequest(BaseModel): | |
| user_id: str | |
| fix_missing: bool = True | |
| fix_outliers: bool = True | |
| fix_duplicates: bool = True | |
| fix_types: bool = True | |
| enrich_dates: bool = True | |
| aggressive: bool = False | |
| class ModelRollbackRequest(BaseModel): | |
| user_id: str | |
| version: int | |
| # ============================================================================= | |
| # MODEL MANAGEMENT ENDPOINTS | |
| # ============================================================================= | |
| async def list_user_models( | |
| user_id: str, | |
| x_user_id: Optional[str] = Header(None, alias="X-User-ID"), | |
| authorization: Optional[str] = Header(None, alias="Authorization") | |
| ): | |
| """ | |
| List all trained models for a user. SECURED. | |
| Returns active model and version history with metrics. | |
| """ | |
| try: | |
| # SECURITY: Verify user_id matches authenticated user | |
| secure_user_id = get_secure_user_id(user_id, x_user_id, authorization) | |
| from ml.model_persistence import model_persistence | |
| models = model_persistence.list_models(secure_user_id) | |
| if not models: | |
| return { | |
| "success": True, | |
| "user_id": secure_user_id, | |
| "has_models": False, | |
| "models": [], | |
| "message": "No trained models found. Train a model first." | |
| } | |
| return { | |
| "success": True, | |
| "user_id": secure_user_id, | |
| "has_models": True, | |
| "active_model": models[0].to_dict() if models else None, | |
| "total_versions": len(models), | |
| "models": [m.to_dict() for m in models] | |
| } | |
| except Exception as e: | |
| logger.error(f"List models error: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def get_active_model( | |
| user_id: str, | |
| x_user_id: Optional[str] = Header(None, alias="X-User-ID"), | |
| authorization: Optional[str] = Header(None, alias="Authorization") | |
| ): | |
| """Get the currently active model for a user. SECURED.""" | |
| try: | |
| # SECURITY: Verify user_id matches authenticated user | |
| secure_user_id = get_secure_user_id(user_id, x_user_id, authorization) | |
| from ml.model_persistence import model_persistence | |
| metadata = model_persistence.get_metadata(secure_user_id) | |
| if not metadata: | |
| raise HTTPException(status_code=404, detail="No active model found") | |
| return { | |
| "success": True, | |
| "model": metadata.to_dict() | |
| } | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| logger.error(f"Get active model error: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def rollback_model( | |
| request: ModelRollbackRequest, | |
| x_user_id: Optional[str] = Header(None, alias="X-User-ID"), | |
| authorization: Optional[str] = Header(None, alias="Authorization") | |
| ): | |
| """Rollback to a previous model version. SECURED.""" | |
| try: | |
| # SECURITY: Verify user_id matches authenticated user | |
| secure_user_id = get_secure_user_id(request.user_id, x_user_id, authorization) | |
| from ml.model_persistence import model_persistence | |
| success = model_persistence.rollback_to_version(secure_user_id, request.version) | |
| if not success: | |
| raise HTTPException( | |
| status_code=404, | |
| detail=f"Version {request.version} not found" | |
| ) | |
| return { | |
| "success": True, | |
| "message": f"Rolled back to version {request.version}", | |
| "active_version": request.version | |
| } | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| logger.error(f"Rollback error: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def delete_all_models( | |
| user_id: str, | |
| x_user_id: Optional[str] = Header(None, alias="X-User-ID"), | |
| authorization: Optional[str] = Header(None, alias="Authorization") | |
| ): | |
| """Delete all models for a user. SECURED.""" | |
| try: | |
| # SECURITY: Verify user_id matches authenticated user | |
| secure_user_id = get_secure_user_id(user_id, x_user_id, authorization) | |
| from ml.model_persistence import model_persistence | |
| success = model_persistence.delete_model(secure_user_id) | |
| return { | |
| "success": success, | |
| "message": f"All models deleted for user {secure_user_id}" if success else "No models found" | |
| } | |
| except Exception as e: | |
| logger.error(f"Delete models error: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def delete_model_version( | |
| user_id: str, | |
| version: int, | |
| x_user_id: Optional[str] = Header(None, alias="X-User-ID"), | |
| authorization: Optional[str] = Header(None, alias="Authorization") | |
| ): | |
| """Delete a specific model version. SECURED.""" | |
| try: | |
| # SECURITY: Verify user_id matches authenticated user | |
| secure_user_id = get_secure_user_id(user_id, x_user_id, authorization) | |
| from ml.model_persistence import model_persistence | |
| success = model_persistence.delete_model(secure_user_id, version) | |
| if not success: | |
| raise HTTPException(status_code=404, detail=f"Version {version} not found") | |
| return { | |
| "success": True, | |
| "message": f"Deleted version {version}" | |
| } | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| logger.error(f"Delete version error: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # ============================================================================= | |
| # AUTONOMOUS DATA OPERATIONS ENDPOINTS | |
| # ============================================================================= | |
| async def auto_fix_data( | |
| file: UploadFile = File(...), | |
| user_id: str = Form("default"), | |
| fix_missing: bool = Form(True), | |
| fix_outliers: bool = Form(True), | |
| fix_duplicates: bool = Form(True), | |
| fix_types: bool = Form(True), | |
| enrich_dates: bool = Form(True), | |
| aggressive: bool = Form(False), | |
| x_user_id: Optional[str] = Header(None, alias="X-User-ID"), | |
| authorization: Optional[str] = Header(None, alias="Authorization") | |
| ): | |
| """ | |
| 🤖 Automatically fix data quality issues. SECURED. | |
| Fixes applied: | |
| - Missing value imputation (median/mode) | |
| - Outlier capping (IQR method) | |
| - Duplicate removal | |
| - Data type corrections | |
| - Date feature enrichment | |
| Returns the fixed data and a detailed report. | |
| """ | |
| try: | |
| # SECURITY: Get verified user_id | |
| secure_user_id = get_secure_user_id(user_id, x_user_id, authorization) | |
| from core.autonomous_data_ops import autonomous_data_ops | |
| # Load file | |
| content = await file.read() | |
| filename = file.filename or "data.csv" | |
| if filename.endswith('.csv'): | |
| df = pd.read_csv(io.BytesIO(content)) | |
| else: | |
| df = pd.read_excel(io.BytesIO(content)) | |
| logger.info(f"🤖 Auto-fix request: {filename} ({df.shape[0]} rows, {df.shape[1]} cols)") | |
| # Apply autonomous fixes | |
| df_fixed, report = autonomous_data_ops.auto_fix( | |
| df, | |
| fix_missing=fix_missing, | |
| fix_outliers=fix_outliers, | |
| fix_duplicates=fix_duplicates, | |
| fix_types=fix_types, | |
| enrich_dates=enrich_dates, | |
| aggressive=aggressive | |
| ) | |
| # Save fixed data to user storage with SECURE user_id | |
| from pathlib import Path | |
| save_dir = Path(f"./storage/users/{secure_user_id}/fixed_data") | |
| save_dir.mkdir(parents=True, exist_ok=True) | |
| fixed_filename = f"fixed_{filename}" | |
| save_path = save_dir / fixed_filename | |
| df_fixed.to_csv(save_path, index=False) | |
| return { | |
| "success": True, | |
| "original_file": filename, | |
| "fixed_file": fixed_filename, | |
| "report": report.to_dict(), | |
| "preview": { | |
| "columns": list(df_fixed.columns), | |
| "rows": df_fixed.head(10).to_dict(orient="records") | |
| }, | |
| "message": f"Applied {len(report.fixes_applied)} fixes, quality improved from {report.quality_score_before:.0%} to {report.quality_score_after:.0%}" | |
| } | |
| except Exception as e: | |
| logger.error(f"Auto-fix error: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def detect_data_issues( | |
| file: UploadFile = File(...) | |
| ): | |
| """ | |
| Detect data quality issues without fixing them. | |
| Returns a report of issues and recommendations. | |
| """ | |
| try: | |
| from core.autonomous_data_ops import autonomous_data_ops | |
| # Load file | |
| content = await file.read() | |
| filename = file.filename or "data.csv" | |
| if filename.endswith('.csv'): | |
| df = pd.read_csv(io.BytesIO(content)) | |
| else: | |
| df = pd.read_excel(io.BytesIO(content)) | |
| # Detect issues | |
| issues = autonomous_data_ops.detect_issues(df) | |
| recommendation = autonomous_data_ops.get_fix_recommendation(df) | |
| return { | |
| "success": True, | |
| "filename": filename, | |
| "shape": {"rows": len(df), "columns": len(df.columns)}, | |
| "issues": issues, | |
| "recommendation": recommendation, | |
| "issue_summary": { | |
| "missing_value_columns": len(issues["missing_values"]), | |
| "duplicate_rows": len(issues["duplicates"]) > 0, | |
| "outlier_columns": len(issues["outliers"]), | |
| "type_issues": len(issues["type_issues"]) | |
| } | |
| } | |
| except Exception as e: | |
| logger.error(f"Detect issues error: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def get_all_users_with_models(): | |
| """Get list of all users who have trained models.""" | |
| try: | |
| from ml.model_persistence import model_persistence | |
| users = model_persistence.get_all_users_with_models() | |
| return { | |
| "success": True, | |
| "total_users": len(users), | |
| "users": users | |
| } | |
| except Exception as e: | |
| logger.error(f"Get users error: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |