import React from 'react'; import { StatusModal, StatusModalSection } from '@/components/ui/StatusModal/StatusModal'; import { Badge } from '@/components/ui/Badge'; import { Tooltip } from '@/components/ui/Tooltip'; import { TrainedModelResponse, TrainingMetrics } from '@/types/training'; import { Activity, TrendingUp, Calendar, Settings, Lightbulb, AlertTriangle, CheckCircle, Target } from 'lucide-react'; interface ModelDetailsModalProps { isOpen: boolean; onClose: () => void; model: TrainedModelResponse; } // Helper function to determine performance color based on accuracy const getPerformanceColor = (accuracy: number): string => { if (accuracy >= 90) return 'var(--color-success)'; if (accuracy >= 80) return 'var(--color-info)'; if (accuracy >= 70) return 'var(--color-warning)'; if (accuracy >= 60) return 'var(--color-warning-dark)'; return 'var(--color-error)'; }; // Helper function to determine performance message for bakery owners const getPerformanceMessage = (accuracy: number): string => { if (accuracy >= 90) return 'Excelente: Las predicciones son muy precisas, ideales para planificar producción'; if (accuracy >= 80) return 'Bueno: Las predicciones son confiables para planificación diaria'; if (accuracy >= 70) return 'Aceptable: Predicciones útiles pero considera monitorear de cerca'; if (accuracy >= 60) return 'Necesita mejora: Predicciones menos precisas, considera reentrenar'; return 'Poco confiable: El modelo necesita ser reentrenado con más datos'; }; // Helper function to format date const formatDate = (dateString: string): string => { const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); }; // Helper function to calculate accuracy from MAPE const calculateAccuracy = (mape: number | undefined): number => { if (mape === undefined || mape === null || isNaN(mape)) return 0; // MAPE in the API response is already in percentage form (e.g., 8.5 = 8.5%) const accuracy = Math.max(0, Math.min(100, 100 - mape)); return accuracy; }; // Feature tag component const FeatureTag: React.FC<{ feature: string }> = ({ feature }) => { const getIcon = (feature: string) => { const featureLower = feature.toLowerCase(); if (featureLower.includes('temperature')) return '☀️'; if (featureLower.includes('weekend')) return '📅'; if (featureLower.includes('holiday')) return '🎉'; if (featureLower.includes('precipitation')) return '🌧️'; if (featureLower.includes('traffic')) return '🚗'; return '📊'; }; const getTooltip = (feature: string) => { const featureLower = feature.toLowerCase(); if (featureLower.includes('temperature')) return 'El clima afecta lo que los clientes quieren comprar - días calurosos significan menos productos horneados calientes'; if (featureLower.includes('weekend')) return 'Tus patrones de ventas son diferentes los fines de semana vs días de semana'; if (featureLower.includes('holiday')) return 'Días especiales y festivos cambian cuánto compra la gente'; if (featureLower.includes('precipitation')) return 'La lluvia o nieve afecta cuántos clientes visitan tu panadería'; if (featureLower.includes('traffic')) return 'Calles transitadas usualmente significan más clientes'; return `${feature}: Esto ayuda a predecir cuánto venderás`; }; return ( {feature} ); }; const ModelDetailsModal: React.FC = ({ isOpen, onClose, model }) => { // Early return if model is not provided if (!model) { return ( ); } // Calculate business metrics from technical metrics // The API response has metrics directly on the model object, not nested in training_metrics const accuracy = calculateAccuracy((model as any).mape || model.training_metrics?.mape); const performanceColor = getPerformanceColor(accuracy); const performanceMessage = getPerformanceMessage(accuracy); // Prepare sections for StatusModal const sections: StatusModalSection[] = [ { title: "Resumen", icon: Activity, fields: [ { label: "Rendimiento del Modelo", value: (
Precisión {accuracy.toFixed(0)}%
{performanceMessage}
), span: 2 }, { label: "Estado", value: ( {(model as any).is_active ? 'Activo' : 'Inactivo'} ) }, { label: "Última Actualización", value: formatDate(model.created_at || new Date().toISOString()) }, { label: "Período de Entrenamiento", value: `${formatDate((model as any).training_start_date || model.training_period?.start_date || new Date().toISOString())} - ${formatDate((model as any).training_end_date || model.training_period?.end_date || new Date().toISOString())}` } ] }, { title: "Métricas de Rendimiento", icon: TrendingUp, fields: [ { label: "Tasa de Precisión", value: `${accuracy.toFixed(0)}%`, highlight: true }, { label: "Diferencia Típica", value: (() => { const maeValue = ((model as any).mae || model.training_metrics?.mae || 0).toFixed(0); return (
±{maeValue} productos
En promedio, las predicciones se desvían por {maeValue} productos
); })() }, { label: "Confiabilidad General", value: `${(((model as any).r2_score || model.training_metrics?.r2_score || 0) * 100).toFixed(0)}% confiable` } ] }, { title: "Qué Significa Esto para Tu Panadería", icon: Target, fields: [ { label: "Precisión de las Predicciones", value: (() => { const mapeValue = Math.round((model as any).mape || model.training_metrics?.mape || 0); return (
Error promedio: {mapeValue}%
Ejemplo práctico: Si el modelo predice que venderás 100 panes, es muy probable que vendas entre {100 - mapeValue} y {100 + mapeValue} panes. Mientras menor sea este porcentaje, más precisas son las predicciones.
); })(), span: 2 }, { label: "Potencial de Reducción de Desperdicio", value: accuracy >= 80 ? `Podría ayudar a reducir el desperdicio diario en ~${Math.round(accuracy / 8)}% mediante mejor planificación` : accuracy >= 60 ? 'Alguna reducción de desperdicio posible con monitoreo cuidadoso' : 'Precisión del modelo demasiado baja para reducción confiable de desperdicio', highlight: true } ] }, { title: "Factores que Considera el Modelo", icon: Settings, fields: [ { label: "Información que Analiza", value: (() => { const features = ((model as any).features_used || model.features_used || []); const featureCount = features.length; if (featureCount === 0) { return (
Usando datos básicos de ventas e historial
); } return (
El modelo analiza {featureCount} factores diferentes para hacer predicciones más precisas.
📅 Temporales:
Días de semana, festivos, estaciones
🌤️ Clima:
Temperatura, lluvia, humedad
🚶 Tráfico:
Peatones, congestión, velocidad
📊 Ventas:
Historial, patrones, tendencias
💡 En resumen: Cuantos más factores analiza el modelo, más precisas son las predicciones para tu panadería.
); })(), span: 2 } ] }, { title: "Detalles Técnicos", icon: Calendar, fields: [ { label: "Método de Predicción", value: model.model_type || 'Pronóstico con IA' }, { label: "Última Actualización", value: formatDate(model.created_at || new Date().toISOString()) }, { label: "Período de Entrenamiento", value: `${formatDate((model as any).training_start_date || model.training_period?.start_date || new Date().toISOString())} a ${formatDate((model as any).training_end_date || model.training_period?.end_date || new Date().toISOString())}`, span: 2 } ] }, { title: "Perspectivas de Negocio", icon: Lightbulb, fields: [ { label: "Qué funciona mejor", value: (
Este modelo te da las predicciones más precisas para días de semana regulares
), span: 2 }, { label: "Ten cuidado con", value: (
Las predicciones pueden ser menos confiables durante festivos y eventos especiales
), span: 2 }, { label: "Patrones descubiertos", value: ((model as any).features_used || model.features_used || []).some((f: string) => f.toLowerCase().includes('weekend')) ? "Tu negocio muestra patrones diferentes entre días de semana y fines de semana" : "Este modelo ha aprendido tus patrones regulares de ventas", span: 2 }, { label: "Próximos pasos", value: accuracy < 80 ? "Considera reentrenar con datos más recientes para mejorar la precisión" : "Tu modelo está funcionando bien. Continúa monitoreando las predicciones", span: 2 } ] } ]; // Actions for the modal - removed console.log placeholders for production readiness const actions = [ { label: 'Actualizar Modelo', variant: 'primary' as const, onClick: () => { // TODO: Implement model retraining functionality // This should trigger a new training job for the product } }, { label: 'Ver Predicciones', variant: 'secondary' as const, onClick: () => { // TODO: Navigate to forecast history or predictions view // This should show historical predictions vs actual sales } } ]; // Only show deactivate if model is active if ((model as any).is_active) { actions.push({ label: 'Desactivar', variant: 'secondary' as const, onClick: () => { // TODO: Implement model deactivation // This should set model status to inactive } }); } return ( ); }; export default ModelDetailsModal;