import gradio as gr import pandas as pd import joblib import os from huggingface_hub import hf_hub_download from dotenv import load_dotenv load_dotenv() # ============================================================================== # 1. CARGA DEL MODELO DESDE HUGGING FACE (CLOUD) # ============================================================================== # Cambia 'TU_USUARIO' por tu nombre de usuario en Hugging Face repo_id = "larxmind/student-dropout-predictor-rf" nombre_archivo = "modelo_rf_optimizado.pkl" try: print(f"⏳ Conectando con Hugging Face para descargar el modelo '{repo_id}'...") ruta_modelo_cache = hf_hub_download(repo_id=repo_id, filename=nombre_archivo) modelo = joblib.load(ruta_modelo_cache) print("✅ Modelo Cloud cargado correctamente y listo para inferencia.") except Exception as e: print(f"❌ Error crítico al cargar el modelo desde la nube: {e}") print("Asegúrate de que el repositorio es público o de que tu HF_TOKEN está en el .env") # ============================================================================== # 2. FUNCIÓN DE PREDICCIÓN # ============================================================================== def predecir_abandono(genero, region, nivel_educativo, nivel_economico, rango_edad, intentos_previos, creditos, discapacidad, clics, entregas, nota, asistencia): # Preparamos el dataframe emulando las columnas que espera tu modelo datos_entrada = pd.DataFrame([{ 'code_module': 0, 'code_presentation': 0, 'genero': 0 if genero == 'F' else 1, 'region': 0, 'nivel_educativo': 1, 'nivel_economico': 5, 'rango_edad': 0 if rango_edad == '0-35' else (1 if rango_edad == '35-55' else 2), 'intentos_previos': intentos_previos, 'creditos_matriculados': creditos, 'discapacidad': 1 if discapacidad == 'Sí' else 0, 'total_clics': clics, 'evaluaciones_entregadas': entregas, 'nota_media': nota, 'dias_asistencia_mes': asistencia }]) # ------------------------------------------------------------------------- # 🛡️ BLINDAJE DE COLUMNAS: Reordenar exactamente como en el entrenamiento # ------------------------------------------------------------------------- if hasattr(modelo, 'feature_names_in_'): # Rellenamos columnas que falten (por si el encoder creó más de la cuenta) for col in modelo.feature_names_in_: if col not in datos_entrada.columns: datos_entrada[col] = 0 # Forzamos el orden estricto datos_entrada = datos_entrada[modelo.feature_names_in_] # ------------------------------------------------------------------------- # Extraemos probabilidad (clase 1 = Abandono) y la predicción final probabilidad = modelo.predict_proba(datos_entrada)[0][1] prediccion = modelo.predict(datos_entrada)[0] if prediccion == 1: return f"⚠️ RIESGO ALTO DE ABANDONO ({probabilidad:.1%})", "Se recomienda intervención inmediata por parte del tutor." else: return f"✅ ESTUDIANTE SEGURO ({1 - probabilidad:.1%} de permanencia)", "El ritmo de estudio es adecuado." # ============================================================================== # 3. INTERFAZ GRÁFICA (GRADIO BLOCKS) # ============================================================================== with gr.Blocks() as demo: gr.Markdown( """ # 🎓 Sistema de Alerta Temprana (Early Warning System) ### Predicción de Abandono Estudiantil basada en Telemetría (Primeros 45 días) """ ) with gr.Row(): # COLUMNA 1: Perfil with gr.Column(): gr.Markdown("### 👤 Perfil del Estudiante") genero = gr.Radio(choices=["M", "F"], label="Género", value="F") rango_edad = gr.Dropdown(choices=["0-35", "35-55", "55<="], label="Rango de Edad", value="0-35") discapacidad = gr.Radio(choices=["Sí", "No"], label="¿Discapacidad declarada?", value="No") region = gr.Textbox(label="Región (Ej. Scotland)", placeholder="Opcional") nivel_educativo = gr.Textbox(label="Nivel Educativo", placeholder="Opcional") nivel_economico = gr.Textbox(label="Nivel Socioeconómico", placeholder="Opcional") # COLUMNA 2: Telemetría y Notas with gr.Column(): gr.Markdown("### 📚 Historial Académico") intentos_previos = gr.Slider(minimum=0, maximum=5, step=1, label="Intentos Previos", value=0) creditos = gr.Slider(minimum=30, maximum=300, step=30, label="Créditos Matriculados", value=60) gr.Markdown("### 🖱️ Telemetría (Primeros 45 días)") clics = gr.Number(label="Total de Clics en el Campus", value=120) entregas = gr.Slider(minimum=0, maximum=3, step=1, label="Evaluaciones Entregadas", value=2) nota = gr.Slider(minimum=0, maximum=100, step=1, label="Nota Media Provisional", value=65) asistencia = gr.Slider(minimum=0, maximum=30, step=1, label="Días de Asistencia en el Mes", value=12) btn_predecir = gr.Button("🔍 Analizar Riesgo", variant="primary") with gr.Row(): # Quitamos el text_color="red" para evitar el TypeError en Gradio 6.0 resultado_estado = gr.Textbox(label="Estado de Alerta") resultado_accion = gr.Textbox(label="Acción Recomendada") btn_predecir.click( fn=predecir_abandono, inputs=[genero, region, nivel_educativo, nivel_economico, rango_edad, intentos_previos, creditos, discapacidad, clics, entregas, nota, asistencia], outputs=[resultado_estado, resultado_accion] ) # 4. Lanzar el servidor if __name__ == "__main__": # Pasamos el tema directamente a la función de lanzamiento demo.launch(share=True, theme=gr.themes.Soft())