/** * 🔍 Explain Modal - Shows SHAP explanation for predictions * * Features: * - Waterfall chart showing feature contributions * - Plain English explanation * - Top contributing features list */ import React, { useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { X, HelpCircle, TrendingUp, TrendingDown, Lightbulb, RefreshCw, AlertCircle } from 'lucide-react'; import { useUserStore } from '@/store/userStore'; import { getUserIdSync } from '@/utils/userId'; interface ContributionItem { feature: string; value: number; shap_value: number; direction: string; } interface ExplainResponse { success: boolean; base_value?: number; prediction?: any; prediction_contribution?: number; contributions: ContributionItem[]; waterfall_chart?: string; explanation_text?: string; } interface ExplainModalProps { isOpen: boolean; onClose: () => void; inputValues: Record; mode?: string; } const ExplainModal: React.FC = ({ isOpen, onClose, inputValues, mode = 'traditional' }) => { const { isDark } = useUserStore(); const [loading, setLoading] = useState(true); // Start with loading=true const [explanation, setExplanation] = useState(null); const [error, setError] = useState(null); const fetchExplanation = async () => { try { setLoading(true); setError(null); const userId = getUserIdSync(); console.log('[ExplainModal] Fetching explanation for:', inputValues); const response = await fetch('/api/v1/automl/explain', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input_values: inputValues, user_id: userId, mode: mode }) }); const data = await response.json(); console.log('[ExplainModal] Response:', data); if (response.ok && data.success) { setExplanation(data); } else { setError(data.detail || data.error || 'Explanation failed'); } } catch (err: any) { console.error('[ExplainModal] Error:', err); setError(err.message || 'Failed to get explanation'); } finally { setLoading(false); } }; // Fetch when modal opens React.useEffect(() => { if (isOpen) { setLoading(true); setExplanation(null); setError(null); if (Object.keys(inputValues).length > 0) { fetchExplanation(); } else { setLoading(false); setError('No input values provided. Please fill in the prediction form first.'); } } }, [isOpen]); if (!isOpen) return null; return ( e.stopPropagation()} > {/* Header */}

🔍 Why This Prediction?

SHAP values show how each feature influenced the prediction

{/* Content */}
{loading && (

Calculating SHAP values...

This may take a few seconds...

)} {error && !loading && (

{error}

Make sure SHAP is installed: pip install shap

)} {explanation && !loading && (
{/* Plain English Explanation */} {explanation.explanation_text && (

Quick Explanation

{explanation.explanation_text}

)} {/* Waterfall Chart */} {explanation.waterfall_chart && (

📊 Feature Impact (Waterfall Chart)

SHAP Waterfall Chart
)} {/* Feature Contributions List */}

🎯 Top Contributing Features

{explanation.contributions.slice(0, 10).map((contrib, i) => (
{contrib.direction === 'positive' ? ( ) : ( )}

{contrib.feature}

Value: {typeof contrib.value === 'number' ? contrib.value.toFixed(2) : contrib.value}

{contrib.shap_value > 0 ? '+' : ''}{contrib.shap_value.toFixed(3)}
))}
{/* Base Value */} {explanation.base_value !== undefined && (

Base value: {explanation.base_value.toFixed(4)} {explanation.prediction !== undefined && ( <> → Prediction: {explanation.prediction} )}

)}
)}
); }; export default ExplainModal;