Spaces:
Sleeping
Sleeping
| # -*- coding: utf-8 -*- | |
| """Untitled0.ipynb | |
| Automatically generated by Colab. | |
| Original file is located at | |
| https://colab.research.google.com/drive/1lA4vvx9sbWFfjQHAmGs8ADgwhNOypfsM | |
| """ | |
| import pandas as pd | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import gradio as gr | |
| import io | |
| import base64 | |
| from datetime import datetime | |
| from sklearn.ensemble import RandomForestClassifier | |
| from sklearn.model_selection import train_test_split | |
| from sklearn.metrics import classification_report, accuracy_score | |
| import tempfile | |
| import os | |
| print("🚀 Iniciando CorrosionPredict en Hugging Face...") | |
| # ============================================================================== | |
| # BLOQUE 1: CARGA Y ENTRENAMIENTO DEL MODELO | |
| # ============================================================================== | |
| print("📂 Cargando y entrenando modelo de corrosión...") | |
| try: | |
| # Cargar datos | |
| df = pd.read_excel("Acero_bajo_OFICIAL.xlsx") | |
| print(f"✅ Datos cargados: {len(df)} registros") | |
| # Limpiar datos | |
| df['Humedad relativa (%)'] = df['Humedad relativa (%)'].astype(str).str.replace('%', '').astype(float) | |
| df['Clasificacion ISO 9223'] = df['Clasificacion ISO 9223'].str.upper() | |
| # Preparar variables | |
| X = df[['Distancia al mar (km)', 'Altitud (msnm)', 'Humedad relativa (%)']] | |
| y = df['Clasificacion ISO 9223'] | |
| # Añadir ruido controlado para robustez | |
| np.random.seed(42) | |
| X_noisy = X.copy() | |
| X_noisy['Distancia al mar (km)'] += np.random.uniform(-2, 2, size=len(X)) | |
| X_noisy['Altitud (msnm)'] += np.random.uniform(-10, 10, size=len(X)) | |
| X_noisy['Humedad relativa (%)'] += np.random.uniform(-3, 3, size=len(X)) | |
| # Asegurar rangos razonables | |
| X_noisy['Distancia al mar (km)'] = X_noisy['Distancia al mar (km)'].clip(lower=0, upper=25) | |
| X_noisy['Altitud (msnm)'] = X_noisy['Altitud (msnm)'].clip(lower=0, upper=400) | |
| X_noisy['Humedad relativa (%)'] = X_noisy['Humedad relativa (%)'].clip(lower=65, upper=95) | |
| # Entrenar modelo final con todos los datos | |
| modelo = RandomForestClassifier(n_estimators=100, random_state=42, class_weight='balanced') | |
| modelo.fit(X_noisy, y) | |
| print("✅ Modelo entrenado correctamente") | |
| except Exception as e: | |
| print(f"❌ Error cargando datos: {e}") | |
| # Crear datos de ejemplo para demo | |
| modelo = None | |
| # ============================================================================== | |
| # BLOQUE 2: CONFIGURACIÓN Y DATOS | |
| # ============================================================================== | |
| # Configuración profesional de matplotlib | |
| plt.rcParams['font.size'] = 12 | |
| plt.rcParams['axes.grid'] = True | |
| plt.rcParams['grid.alpha'] = 0.3 | |
| # Diccionario mejorado de recomendaciones | |
| RECOMENDACIONES = { | |
| "C1": { | |
| "nivel": "MUY BAJA", | |
| "color": "#2E8B57", # Verde | |
| "ejemplos": "Oficinas climatizadas, escuelas, museos, bibliotecas, hospitales.", | |
| "descripcion": "Ambientes interiores controlados con baja contaminación.", | |
| "protecciones": [ | |
| {"proteccion": "Pintura anticorrosiva básica", "costo": "30-45", "vida_util": "5-8", "frecuencia": "Cada 5-10 años", "notas": "Protección adecuada para ambiente controlado"}, | |
| {"proteccion": "Recubrimiento ligero", "costo": "15-25", "vida_util": "3-5", "frecuencia": "Cada 3 años", "notas": "Opción económica para baja exposición"} | |
| ] | |
| }, | |
| "C2": { | |
| "nivel": "BAJA", | |
| "color": "#9ACD32", # Amarillo verdoso | |
| "ejemplos": "Zonas rurales, áreas suburbanas, almacenamiento techado, interiores no climatizados.", | |
| "descripcion": "Ambientes con exposición mínima a contaminantes corrosivos.", | |
| "protecciones": [ | |
| {"proteccion": "Pintura anticorrosiva estándar", "costo": "35-50", "vida_util": "5-8", "frecuencia": "Cada 3-4 años", "notas": "Balance óptimo costo-beneficio"}, | |
| {"proteccion": "Galvanizado ligero", "costo": "60-80", "vida_util": "10-15", "frecuencia": "Inspección cada 5 años", "notas": "Mayor durabilidad con inversión moderada"} | |
| ] | |
| }, | |
| "C3": { | |
| "nivel": "MODERADA", | |
| "color": "#FFA500", # Naranja | |
| "ejemplos": "Zonas urbanas, áreas costeras con baja salinidad, plantas de procesamiento de alimentos.", | |
| "descripcion": "Ambientes urbanos y semi-industriales con exposición moderada.", | |
| "protecciones": [ | |
| {"proteccion": "Galvanizado en caliente", "costo": "80-120", "vida_util": "15-20", "frecuencia": "Inspección cada 5 años", "notas": "Protección robusta y duradera"}, | |
| {"proteccion": "Pintura epóxica industrial", "costo": "50-70", "vida_util": "8-12", "frecuencia": "Cada 4-6 años", "notas": "Excelente resistencia química"} | |
| ] | |
| }, | |
| "C4": { | |
| "nivel": "ALTA", | |
| "color": "#FF4500", # Rojo naranja | |
| "ejemplos": "Áreas industriales, zonas costeras sin rociado de sal, plantas químicas.", | |
| "descripcion": "Ambientes industriales y costeros con alta contaminación.", | |
| "protecciones": [ | |
| {"proteccion": "Sistema duplex (galvanizado + pintura)", "costo": "120-180", "vida_util": "20-25", "frecuencia": "Inspección cada 5-7 años", "notas": "Máxima protección sin acero inoxidable"}, | |
| {"proteccion": "Recubrimiento de zinc-aleación", "costo": "100-150", "vida_util": "15-20", "frecuencia": "Inspección cada 5 años", "notas": "Alta resistencia a la corrosión"} | |
| ] | |
| }, | |
| "C5": { | |
| "nivel": "MUY ALTA", | |
| "color": "#DC143C", # Rojo | |
| "ejemplos": "Áreas costeras con rociado de sal, industrias pesadas, plataformas costeras.", | |
| "descripcion": "Ambientes marinos e industriales extremadamente agresivos.", | |
| "protecciones": [ | |
| {"proteccion": "Acero inoxidable 316", "costo": "300-500", "vida_util": "50+", "frecuencia": "Limpieza periódica", "notas": "Solución premium para ambientes severos"}, | |
| {"proteccion": "Sistema de protección catódica", "costo": "200-350", "vida_util": "25-30", "frecuencia": "Mantenimiento anual", "notas": "Ideal para estructuras fijas"} | |
| ] | |
| }, | |
| "CX": { | |
| "nivel": "EXTREMA", | |
| "color": "#8B008B", # Púrpura | |
| "ejemplos": "Zonas tropicales con humedad permanente, industrias extremas, offshore.", | |
| "descripcion": "Ambientes tropicales y offshore con exposición constante.", | |
| "protecciones": [ | |
| {"proteccion": "Acero inoxidable superduplex", "costo": "400-600", "vida_util": "50+", "frecuencia": "Limpieza periódica", "notas": "Máxima resistencia para ambientes extremos"}, | |
| {"proteccion": "Recubrimientos especiales marinos", "costo": "250-400", "vida_util": "20-30", "frecuencia": "Inspección cada 3-5 años", "notas": "Tecnología avanzada de protección"} | |
| ] | |
| } | |
| } | |
| # ============================================================================== | |
| # BLOQUE 3: FUNCIONES PRINCIPALES | |
| # ============================================================================== | |
| def generar_graficos(clase_predicha, estacion): | |
| """Genera gráficos profesionales para el reporte""" | |
| graficos_base64 = [] | |
| try: | |
| # 1. Gráfico de Barras: Vida Útil | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| protecciones = RECOMENDACIONES[clase_predicha]["protecciones"] | |
| sistemas = [p['proteccion'] for p in protecciones] | |
| vida_util = [float(p['vida_util'].split('-')[1].replace('+', '')) if '-' in p['vida_util'] else float(p['vida_util'].replace('+', '')) for p in protecciones] | |
| bars = ax.bar(sistemas, vida_util, color=[RECOMENDACIONES[clase_predicha]["color"], '#FFD700']) | |
| ax.set_title('Comparación de Vida Útil entre Sistemas de Protección', fontsize=14, fontweight='bold') | |
| ax.set_ylabel('Vida Útil (años)', fontsize=12) | |
| ax.set_xlabel('Sistemas de Protección', fontsize=12) | |
| # Añadir valores en las barras | |
| for bar in bars: | |
| height = bar.get_height() | |
| ax.text(bar.get_x() + bar.get_width()/2., height + 0.5, | |
| f'{height} años', ha='center', va='bottom', fontweight='bold') | |
| plt.xticks(rotation=15) | |
| plt.tight_layout() | |
| # Convertir a base64 | |
| buf = io.BytesIO() | |
| plt.savefig(buf, format='png', dpi=120, bbox_inches='tight') | |
| buf.seek(0) | |
| graficos_base64.append(base64.b64encode(buf.getvalue()).decode('utf-8')) | |
| plt.close() | |
| # 2. Gráfico Circular: Distribución de Costos | |
| fig, ax = plt.subplots(figsize=(8, 8)) | |
| costos = [float(p['costo'].split('-')[0]) for p in protecciones] | |
| colors = [RECOMENDACIONES[clase_predicha]["color"], '#FFD700'] | |
| wedges, texts, autotexts = ax.pie(costos, labels=sistemas, autopct='%1.1f%%', | |
| colors=colors, startangle=90) | |
| for autotext in autotexts: | |
| autotext.set_color('white') | |
| autotext.set_fontweight('bold') | |
| ax.set_title('Distribución de Costos Iniciales\n(S/ m²)', fontsize=14, fontweight='bold') | |
| plt.tight_layout() | |
| buf = io.BytesIO() | |
| plt.savefig(buf, format='png', dpi=120, bbox_inches='tight') | |
| buf.seek(0) | |
| graficos_base64.append(base64.b64encode(buf.getvalue()).decode('utf-8')) | |
| plt.close() | |
| # 3. Gráfico de Líneas: Degradación | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| tiempo = np.arange(0, 26, 5) # 0 a 25 años | |
| for i, proteccion in enumerate(protecciones): | |
| vida = float(proteccion['vida_util'].split('-')[1].replace('+', '')) if '-' in proteccion['vida_util'] else float(proteccion['vida_util'].replace('+', '')) | |
| eficacia = [100 - (100/vida) * t for t in tiempo] | |
| eficacia = [max(0, e) for e in eficacia] # No menor que 0 | |
| ax.plot(tiempo, eficacia, marker='o', linewidth=2, | |
| label=proteccion['proteccion'], color=colors[i]) | |
| ax.set_title('Degradación de Protección en el Tiempo', fontsize=14, fontweight='bold') | |
| ax.set_xlabel('Años', fontsize=12) | |
| ax.set_ylabel('Eficacia de Protección (%)', fontsize=12) | |
| ax.legend() | |
| ax.grid(True, alpha=0.3) | |
| plt.tight_layout() | |
| buf = io.BytesIO() | |
| plt.savefig(buf, format='png', dpi=120, bbox_inches='tight') | |
| buf.seek(0) | |
| graficos_base64.append(base64.b64encode(buf.getvalue()).decode('utf-8')) | |
| plt.close() | |
| # 4. NUEVO GRÁFICO: Real vs Predicho | |
| grafico_comparativo = generar_grafico_real_vs_predicho() | |
| if grafico_comparativo: | |
| graficos_base64.append(grafico_comparativo) | |
| except Exception as e: | |
| print(f"Error generando gráficos: {e}") | |
| return graficos_base64 | |
| def generar_grafico_real_vs_predicho(): | |
| """Genera gráfico comparativo entre valores reales y predichos""" | |
| try: | |
| print("🔄 Generando gráfico real vs predicho...") | |
| # Obtener datos reales del Excel | |
| df_real = pd.read_excel("Acero_bajo_OFICIAL.xlsx") | |
| print(f"✅ Datos cargados: {len(df_real)} registros") | |
| # Preparar datos para predicción | |
| X_real = df_real[['Distancia al mar (km)', 'Altitud (msnm)', 'Humedad relativa (%)']] | |
| y_real = df_real['Clasificacion ISO 9223'] | |
| # Generar predicciones para todos los datos reales | |
| y_pred = modelo.predict(X_real) | |
| # Crear gráfico comparativo | |
| fig, ax = plt.subplots(figsize=(12, 8)) | |
| # Convertir clases categóricas a numéricas para gráfico | |
| clases_ordenadas = ['C1', 'C2', 'C3', 'C4', 'C5', 'CX'] | |
| y_real_num = [clases_ordenadas.index(clase) for clase in y_real] | |
| y_pred_num = [clases_ordenadas.index(clase) for clase in y_pred] | |
| # Gráfico de dispersión | |
| scatter = ax.scatter(y_real_num, y_pred_num, alpha=0.6, c=y_real_num, | |
| cmap='viridis', s=60) | |
| # Línea de perfecta predicción | |
| min_val = min(min(y_real_num), min(y_pred_num)) | |
| max_val = max(max(y_real_num), max(y_pred_num)) | |
| ax.plot([min_val, max_val], [min_val, max_val], 'r--', alpha=0.8, | |
| label='Predicción Perfecta') | |
| ax.set_xlabel('Valores Reales', fontsize=12, fontweight='bold') | |
| ax.set_ylabel('Valores Predichos', fontsize=12, fontweight='bold') | |
| ax.set_title('Comparación: Valores Reales vs Predichos\nModelo Random Forest', | |
| fontsize=14, fontweight='bold') | |
| # Configurar ejes con etiquetas de clases | |
| ax.set_xticks(range(len(clases_ordenadas))) | |
| ax.set_yticks(range(len(clases_ordenadas))) | |
| ax.set_xticklabels(clases_ordenadas) | |
| ax.set_yticklabels(clases_ordenadas) | |
| # Añadir grid y leyenda | |
| ax.grid(True, alpha=0.3) | |
| ax.legend() | |
| # Añadir barra de color | |
| cbar = plt.colorbar(scatter) | |
| cbar.set_label('Nivel de Corrosión', rotation=270, labelpad=15) | |
| # Calcular y mostrar precisión | |
| accuracy = accuracy_score(y_real, y_pred) | |
| ax.text(0.05, 0.95, f'Precisión del Modelo: {accuracy:.2%}', | |
| transform=ax.transAxes, fontsize=12, | |
| bbox=dict(boxstyle="round,pad=0.3", facecolor="white", alpha=0.8)) | |
| plt.tight_layout() | |
| # Convertir a base64 para HTML | |
| buf = io.BytesIO() | |
| plt.savefig(buf, format='png', dpi=120, bbox_inches='tight') | |
| buf.seek(0) | |
| img_base64 = base64.b64encode(buf.getvalue()).decode('utf-8') | |
| plt.close() | |
| print("✅ Gráfico real vs predicho generado exitosamente") | |
| return img_base64 | |
| except Exception as e: | |
| print(f"❌ Error generando gráfico real vs predicho: {e}") | |
| return None | |
| def predecir_corrosion(estacion, distancia, altitud, humedad, nombre_ing, proyecto, ubicacion): | |
| """Función principal para predecir corrosión""" | |
| try: | |
| # Validaciones | |
| if not all([estacion, nombre_ing, proyecto, ubicacion]): | |
| return "❌ Complete todos los campos obligatorios", "", "", "" | |
| distancia = float(distancia) | |
| altitud = float(altitud) | |
| humedad = float(humedad) | |
| # Validar rangos | |
| if not (0 <= distancia <= 25): | |
| return "❌ Distancia al mar debe estar entre 0-25 km", "", "", "" | |
| if not (0 <= altitud <= 400): | |
| return "❌ Altitud debe estar entre 0-400 msnm", "", "", "" | |
| if not (65 <= humedad <= 95): | |
| return "❌ Humedad relativa debe estar entre 65-95%", "", "", "" | |
| if modelo is None: | |
| return "❌ Modelo no disponible. Verifique los datos de entrenamiento.", "", "", "" | |
| # Realizar predicción | |
| nuevo_dato = pd.DataFrame({ | |
| 'Distancia al mar (km)': [distancia], | |
| 'Altitud (msnm)': [altitud], | |
| 'Humedad relativa (%)': [humedad] | |
| }) | |
| prediccion = modelo.predict(nuevo_dato)[0].upper() | |
| probabilidades = modelo.predict_proba(nuevo_dato)[0] | |
| clases = modelo.classes_ | |
| # Generar gráficos | |
| graficos_base64 = generar_graficos(prediccion, estacion) | |
| # Generar reporte COMPLETO | |
| reporte_completo_html = generar_reporte_completo( | |
| estacion, distancia, altitud, humedad, prediccion, | |
| probabilidades, clases, graficos_base64, | |
| nombre_ing, proyecto, ubicacion | |
| ) | |
| # Generar reporte SOLO GRÁFICOS | |
| reporte_graficos_html = generar_reporte_solo_graficos( | |
| estacion, distancia, altitud, humedad, prediccion, | |
| probabilidades, clases, graficos_base64, | |
| nombre_ing, proyecto, ubicacion | |
| ) | |
| # Resultados para mostrar en interfaz | |
| resultados_html = generar_resultados_html( | |
| estacion, distancia, altitud, humedad, prediccion, | |
| probabilidades, clases | |
| ) | |
| graficas_html = generar_graficas_html(graficos_base64) | |
| return resultados_html, graficas_html, reporte_completo_html, reporte_graficos_html | |
| except Exception as e: | |
| return f"❌ Error en la predicción: {str(e)}", "", "", "" | |
| # ============================================================================== | |
| # BLOQUE 4: GENERACIÓN DE HTML Y REPORTES | |
| # ============================================================================== | |
| def generar_resultados_html(estacion, distancia, altitud, humedad, prediccion, probabilidades, clases): | |
| """Genera HTML para mostrar resultados en la interfaz""" | |
| info_clase = RECOMENDACIONES.get(prediccion, {}) | |
| # Tabla de probabilidades | |
| prob_table = "" | |
| for clase, prob in zip(clases, probabilidades): | |
| prob_table += f"<tr><td>{clase}</td><td style='text-align: center;'>{prob:.2%}</td></tr>" | |
| return f""" | |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; padding: 20px; border-radius: 10px; margin-bottom: 20px;"> | |
| <h2 style="margin: 0; text-align: center;">🏭 CorrosionPredict Pro</h2> | |
| <p style="text-align: center; margin: 5px 0 0 0; opacity: 0.9;"> | |
| {estacion} | {datetime.now().strftime('%d/%m/%Y %H:%M')} | |
| </p> | |
| </div> | |
| <div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 15px 0;"> | |
| <h3 style="color: #2c3e50; margin-top: 0;">📊 Resultado de Predicción</h3> | |
| <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px;"> | |
| <div style="background: white; padding: 15px; border-radius: 8px; text-align: center; border-left: 4px solid {info_clase.get('color', '#666')};"> | |
| <div style="font-weight: bold; color: #2c3e50;">Clasificación ISO 9223</div> | |
| <div style="font-size: 1.8em; font-weight: bold; color: {info_clase.get('color', '#666')};">{prediccion}</div> | |
| <div style="font-size: 0.9em; color: #666;">{info_clase.get('nivel', 'N/A')}</div> | |
| </div> | |
| </div> | |
| <div style="background: white; padding: 15px; border-radius: 8px; margin-bottom: 15px;"> | |
| <h4 style="margin-top: 0; color: #2c3e50;">📈 Probabilidades por Clase</h4> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <tr style="background: #f8f9fa;"> | |
| <th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6;">Clase</th> | |
| <th style="padding: 10px; text-align: center; border-bottom: 2px solid #dee2e6;">Probabilidad</th> | |
| </tr> | |
| {prob_table} | |
| </table> | |
| </div> | |
| <div style="background: white; padding: 15px; border-radius: 8px;"> | |
| <h4 style="margin-top: 0; color: #2c3e50;">📍 Datos de Entrada</h4> | |
| <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px;"> | |
| <div style="text-align: center;"> | |
| <div style="font-size: 0.9em; color: #666;">Distancia al mar</div> | |
| <div style="font-size: 1.2em; font-weight: bold;">{distancia} km</div> | |
| </div> | |
| <div style="text-align: center;"> | |
| <div style="font-size: 0.9em; color: #666;">Altitud</div> | |
| <div style="font-size: 1.2em; font-weight: bold;">{altitud} msnm</div> | |
| </div> | |
| <div style="text-align: center;"> | |
| <div style="font-size: 0.9em; color: #666;">Humedad</div> | |
| <div style="font-size: 1.2em; font-weight: bold;">{humedad}%</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def generar_graficas_html(graficos_base64): | |
| """Genera HTML para mostrar gráficas""" | |
| if not graficos_base64: | |
| return "<p>No se pudieron generar las gráficas</p>" | |
| graficas_html = "<div style='margin-top: 20px;'>" | |
| titulos = [ | |
| "Comparación de Vida Útil entre Sistemas de Protección", | |
| "Distribución de Costos Iniciales", | |
| "Degradación de Protección en el Tiempo", | |
| "Validación del Modelo: Valores Reales vs Predichos" | |
| ] | |
| for i, img_base64 in enumerate(graficos_base64): | |
| graficas_html += f""" | |
| <div style="background: white; padding: 20px; border-radius: 8px; margin: 15px 0; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1);"> | |
| <h4 style="margin-top: 0; color: #2c3e50;">{titulos[i] if i < len(titulos) else 'Gráfico'}</h4> | |
| <img src="data:image/png;base64,{img_base64}" | |
| style="max-width: 100%; height: auto; border-radius: 5px; border: 1px solid #dee2e6;"> | |
| </div> | |
| """ | |
| graficas_html += "</div>" | |
| return graficas_html | |
| def generar_reporte_solo_graficos(estacion, distancia, altitud, humedad, prediccion, | |
| probabilidades, clases, graficos_base64, nombre_ing, proyecto, ubicacion): | |
| """Genera reporte HTML SOLO con el gráfico Real vs Predicho""" | |
| # Tomar el cuarto gráfico (Real vs Predicho) | |
| grafico_real_vs_predicho = graficos_base64[3] if len(graficos_base64) >= 4 else None | |
| if not grafico_real_vs_predicho: | |
| return "<p>No se pudo generar el gráfico de validación</p>" | |
| html_content = f"""<!DOCTYPE html> | |
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Validación del Modelo - {estacion}</title> | |
| <style> | |
| body {{ | |
| font-family: 'Arial', sans-serif; | |
| line-height: 1.6; | |
| color: #333; | |
| max-width: 1000px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| background: #f8f9fa; | |
| }} | |
| .header {{ | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 25px; | |
| border-radius: 15px; | |
| text-align: center; | |
| margin-bottom: 25px; | |
| }} | |
| .section {{ | |
| background: white; | |
| padding: 25px; | |
| margin: 20px 0; | |
| border-radius: 10px; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| }} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <h1>📊 Validación del Modelo Predictivo</h1> | |
| <p>Análisis Real vs Predicho - CorrosionPredict Pro</p> | |
| <p>{estacion} | {datetime.now().strftime('%d/%m/%Y %H:%M')}</p> | |
| </div> | |
| <div class="section"> | |
| <h2>📈 Gráfico de Validación: Real vs Predicho</h2> | |
| <img src="data:image/png;base64,{grafico_real_vs_predicho}" | |
| style="max-width: 90%; height: auto; border: 1px solid #ddd; border-radius: 8px;"> | |
| </div> | |
| <div class="section" style="text-align: center; background: #f8f9fa;"> | |
| <h3>CorrosionPredict Pro - Módulo de Validación</h3> | |
| <p>Reporte de validación generado el {datetime.now().strftime('%d/%m/%Y')}</p> | |
| </div> | |
| </body> | |
| </html>""" | |
| return html_content | |
| def generar_reporte_completo(estacion, distancia, altitud, humedad, prediccion, | |
| probabilidades, clases, graficos_base64, nombre_ing, proyecto, ubicacion): | |
| """Genera reporte HTML completo para descargar""" | |
| info_clase = RECOMENDACIONES.get(prediccion, {}) | |
| # Tabla de protecciones | |
| protecciones_html = "" | |
| for proteccion in info_clase.get("protecciones", []): | |
| protecciones_html += f""" | |
| <tr> | |
| <td>{proteccion['proteccion']}</td> | |
| <td>S/ {proteccion['costo']}</td> | |
| <td>{proteccion['vida_util']} años</td> | |
| <td>{proteccion['frecuencia']}</td> | |
| <td>{proteccion['notas']}</td> | |
| </tr> | |
| """ | |
| # Tabla de probabilidades | |
| prob_html = "" | |
| for clase, prob in zip(clases, probabilidades): | |
| prob_html += f"<tr><td>{clase}</td><td>{prob:.2%}</td></tr>" | |
| # Gráficos en base64 | |
| graficos_html = "" | |
| titulos_graficos = [ | |
| "Comparación de Vida Útil entre Sistemas de Protección", | |
| "Distribución de Costos Iniciales", | |
| "Degradación de Protección en el Tiempo", | |
| "Validación del Modelo: Valores Reales vs Predichos" | |
| ] | |
| for i, img_base64 in enumerate(graficos_base64): | |
| graficos_html += f""" | |
| <div style="page-break-inside: avoid; margin: 20px 0;"> | |
| <h3>{titulos_graficos[i]}</h3> | |
| <img src="data:image/png;base64,{img_base64}" | |
| style="max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 5px;"> | |
| </div> | |
| """ | |
| html_content = f"""<!DOCTYPE html> | |
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Reporte de Corrosión - {estacion}</title> | |
| <style> | |
| body {{ | |
| font-family: 'Arial', sans-serif; | |
| line-height: 1.6; | |
| color: #333; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| background: #f8f9fa; | |
| }} | |
| .header {{ | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 30px; | |
| border-radius: 15px; | |
| text-align: center; | |
| margin-bottom: 30px; | |
| }} | |
| .section {{ | |
| background: white; | |
| padding: 25px; | |
| margin: 20px 0; | |
| border-radius: 10px; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| }} | |
| .clasificacion {{ | |
| background: {info_clase.get('color', '#666')}; | |
| color: white; | |
| padding: 20px; | |
| border-radius: 10px; | |
| text-align: center; | |
| margin: 15px 0; | |
| }} | |
| table {{ | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin: 15px 0; | |
| }} | |
| th, td {{ | |
| border: 1px solid #ddd; | |
| padding: 12px; | |
| text-align: left; | |
| }} | |
| th {{ | |
| background-color: #f8f9fa; | |
| font-weight: bold; | |
| }} | |
| .proteccion-card {{ | |
| background: #f8f9fa; | |
| padding: 15px; | |
| margin: 10px 0; | |
| border-radius: 8px; | |
| border-left: 4px solid {info_clase.get('color', '#666')}; | |
| }} | |
| @media print {{ | |
| body {{ background: white; }} | |
| .section {{ box-shadow: none; border: 1px solid #ddd; }} | |
| .header {{ background: #667eea; }} | |
| }} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <h1>🏭 CorrosionPredict Pro</h1> | |
| <p>Sistema de Predicción de Corrosión ISO 9223</p> | |
| <p>{estacion} | {datetime.now().strftime('%d/%m/%Y %H:%M')}</p> | |
| </div> | |
| <div class="section"> | |
| <h2>📋 Información del Proyecto</h2> | |
| <table> | |
| <tr><th>Proyecto:</th><td>{proyecto}</td></tr> | |
| <tr><th>Ubicación:</th><td>{ubicacion}</td></tr> | |
| <tr><th>Ingeniero:</th><td>{nombre_ing}</td></tr> | |
| <tr><th>Estación/Distrito:</th><td>{estacion}</td></tr> | |
| <tr><th>Fecha:</th><td>{datetime.now().strftime('%d/%m/%Y %H:%M')}</td></tr> | |
| </table> | |
| </div> | |
| <div class="section"> | |
| <h2>📊 Datos de Entrada</h2> | |
| <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;"> | |
| <div style="text-align: center; padding: 15px; background: #f8f9fa; border-radius: 8px;"> | |
| <h3>🌊 Distancia al mar</h3> | |
| <p style="font-size: 1.5em; font-weight: bold; margin: 0;">{distancia} km</p> | |
| </div> | |
| <div style="text-align: center; padding: 15px; background: #f8f9fa; border-radius: 8px;"> | |
| <h3>⛰️ Altitud</h3> | |
| <p style="font-size: 1.5em; font-weight: bold; margin: 0;">{altitud} msnm</p> | |
| </div> | |
| <div style="text-align: center; padding: 15px; background: #f8f9fa; border-radius: 8px;"> | |
| <h3>💧 Humedad relativa</h3> | |
| <p style="font-size: 1.5em; font-weight: bold; margin: 0;">{humedad}%</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="section"> | |
| <h2>🎯 Resultado de Predicción</h2> | |
| <div class="clasificacion"> | |
| <h3 style="margin: 0; font-size: 2em;">CLASE {prediccion}</h3> | |
| <p style="margin: 10px 0 0 0; font-size: 1.2em;">Corrosión {info_clase.get('nivel', 'N/A')}</p> | |
| </div> | |
| <h3>Probabilidades por Clase:</h3> | |
| <table> | |
| <tr><th>Clase ISO 9223</th><th>Probabilidad</th></tr> | |
| {prob_html} | |
| </table> | |
| </div> | |
| <div class="section"> | |
| <h2>🛡️ Recomendaciones de Protección</h2> | |
| <p><strong>Descripción del ambiente:</strong> {info_clase.get('descripcion', 'N/A')}</p> | |
| <p><strong>Ejemplos típicos:</strong> {info_clase.get('ejemplos', 'N/A')}</p> | |
| <h3>Sistemas de Protección Recomendados:</h3> | |
| <table> | |
| <tr> | |
| <th>Sistema de Protección</th> | |
| <th>Costo (S/ m²)</th> | |
| <th>Vida Útil</th> | |
| <th>Mantenimiento</th> | |
| <th>Notas</th> | |
| </tr> | |
| {protecciones_html} | |
| </table> | |
| </div> | |
| <div class="section"> | |
| <h2>📈 Análisis Gráfico</h2> | |
| {graficos_html} | |
| </div> | |
| <div class="section" style="text-align: center; background: #f8f9fa;"> | |
| <h3>CorrosionPredict Pro</h3> | |
| <p>Sistema de predicción de corrosión basado en Machine Learning</p> | |
| <p>Reporte generado automáticamente el {datetime.now().strftime('%d/%m/%Y')}</p> | |
| </div> | |
| </body> | |
| </html>""" | |
| return html_content | |
| # ============================================================================== | |
| # BLOQUE 5: INTERFAZ GRADIO | |
| # ============================================================================== | |
| with gr.Blocks(theme=gr.themes.Soft(), title="CorrosionPredict Pro") as demo: | |
| gr.Markdown(""" | |
| # 🏭 CorrosionPredict Pro | |
| ### Sistema de Predicción de Corrosión ISO 9223 con Machine Learning | |
| Predice la clasificación de corrosividad según ISO 9223 para acero al carbono | |
| basado en condiciones ambientales. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### ⚙️ Parámetros de Entrada") | |
| estacion = gr.Textbox( | |
| label="🏢 Estación/Distrito", | |
| placeholder="Ej: Miraflores, Callao, Ate", | |
| info="Nombre de la ubicación de estudio" | |
| ) | |
| with gr.Row(): | |
| distancia = gr.Number( | |
| label="🌊 Distancia al mar (km)", | |
| value=5.0, | |
| minimum=0, | |
| maximum=25, | |
| info="0-25 km" | |
| ) | |
| altitud = gr.Number( | |
| label="⛰️ Altitud (msnm)", | |
| value=100, | |
| minimum=0, | |
| maximum=400, | |
| info="0-400 msnm" | |
| ) | |
| humedad = gr.Slider( | |
| label="💧 Humedad relativa (%)", | |
| value=80, | |
| minimum=65, | |
| maximum=95, | |
| step=1, | |
| info="65-95%" | |
| ) | |
| nombre_ing = gr.Textbox( | |
| label="👤 Ingeniero Responsable", | |
| value="Ing. Ambiental", | |
| placeholder="Nombre del profesional" | |
| ) | |
| proyecto = gr.Textbox( | |
| label="📋 Nombre del Proyecto", | |
| value="Estudio de Corrosión", | |
| placeholder="Nombre del proyecto" | |
| ) | |
| ubicacion = gr.Textbox( | |
| label="📍 Ubicación", | |
| value="Lima Metropolitana", | |
| placeholder="Región/Departamento" | |
| ) | |
| btn_predict = gr.Button( | |
| "🚀 Ejecutar Predicción", | |
| size="lg", | |
| variant="primary" | |
| ) | |
| with gr.Column(scale=2): | |
| gr.Markdown("### 📊 Resultados del Análisis") | |
| resultados = gr.HTML(label="Predicciones") | |
| graficas = gr.HTML(label="Gráficas del Análisis") | |
| gr.Markdown("### 💾 Descargar Reportes") | |
| descarga_html = gr.HTML(""" | |
| <div style="background: #d4edda; color: #155724; padding: 15px; border-radius: 8px; | |
| border: 1px solid #c3e6cb; text-align: center;"> | |
| <p style="margin: 0;">📄 Ejecuta una predicción para generar los reportes</p> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| descarga_btn_completo = gr.Button( | |
| "📥 Reporte Completo", | |
| size="lg", | |
| variant="primary", | |
| visible=False | |
| ) | |
| descarga_btn_graficos = gr.Button( | |
| "📊 Solo Validación", | |
| size="lg", | |
| variant="secondary", | |
| visible=False | |
| ) | |
| archivo_descarga_completo = gr.File( | |
| label="Reporte Completo", | |
| visible=False | |
| ) | |
| archivo_descarga_graficos = gr.File( | |
| label="Reporte de Validación", | |
| visible=False | |
| ) | |
| # Ejemplos rápidos | |
| gr.Markdown("### 🚀 Ejemplos Rápidos") | |
| with gr.Row(): | |
| gr.Examples( | |
| examples=[ | |
| ["Miraflores", 2.5, 80, 85, "Ing. Ejemplo", "Edificio Costero", "Lima"], | |
| ["Ate", 15.0, 350, 75, "Ing. Ejemplo", "Planta Industrial", "Lima"], | |
| ["Callao", 0.5, 5, 90, "Ing. Ejemplo", "Puerto Marítimo", "Callao"] | |
| ], | |
| inputs=[estacion, distancia, altitud, humedad, nombre_ing, proyecto, ubicacion] | |
| ) | |
| # Estado para almacenar reportes | |
| reporte_actual_completo = gr.State("") | |
| reporte_actual_graficos = gr.State("") | |
| # Funciones de conexión | |
| def procesar_prediccion(estacion, distancia, altitud, humedad, nombre_ing, proyecto, ubicacion): | |
| resultados_html, graficas_html, reporte_completo, reporte_graficos = predecir_corrosion( | |
| estacion, distancia, altitud, humedad, nombre_ing, proyecto, ubicacion | |
| ) | |
| mostrar_descarga = reporte_completo != "" | |
| return resultados_html, graficas_html, gr.Button(visible=mostrar_descarga), gr.Button(visible=mostrar_descarga), reporte_completo, reporte_graficos | |
| def generar_descarga_completo(reporte_html): | |
| if reporte_html: | |
| filename = f"reporte_completo_{datetime.now().strftime('%Y%m%d_%H%M')}.html" | |
| temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.html', mode='w', encoding='utf-8') | |
| temp_file.write(reporte_html) | |
| temp_file.close() | |
| return gr.File(value=temp_file.name, label=filename, visible=True) | |
| return gr.File(visible=False) | |
| def generar_descarga_graficos(reporte_html): | |
| if reporte_html: | |
| filename = f"validacion_modelo_{datetime.now().strftime('%Y%m%d_%H%M')}.html" | |
| temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.html', mode='w', encoding='utf-8') | |
| temp_file.write(reporte_html) | |
| temp_file.close() | |
| return gr.File(value=temp_file.name, label=filename, visible=True) | |
| return gr.File(visible=False) | |
| # Conectar componentes | |
| btn_predict.click( | |
| fn=procesar_prediccion, | |
| inputs=[estacion, distancia, altitud, humedad, nombre_ing, proyecto, ubicacion], | |
| outputs=[resultados, graficas, descarga_btn_completo, descarga_btn_graficos, reporte_actual_completo, reporte_actual_graficos] | |
| ) | |
| descarga_btn_completo.click( | |
| fn=generar_descarga_completo, | |
| inputs=[reporte_actual_completo], | |
| outputs=[archivo_descarga_completo] | |
| ) | |
| descarga_btn_graficos.click( | |
| fn=generar_descarga_graficos, | |
| inputs=[reporte_actual_graficos], | |
| outputs=[archivo_descarga_graficos] | |
| ) | |
| # ============================================================================== | |
| # EJECUCIÓN | |
| # ============================================================================== | |
| if __name__ == "__main__": | |
| demo.launch( | |
| debug=False, | |
| show_error=True, | |
| server_name="0.0.0.0", | |
| server_port=7860 | |
| ) |