RM0303 commited on
Commit
b4b5155
·
verified ·
1 Parent(s): 84b4eeb

Upload 3 files

Browse files
Files changed (3) hide show
  1. Acero_bajo_OFICIAL.xlsx +0 -0
  2. app.py +719 -0
  3. requirements.txt +6 -0
Acero_bajo_OFICIAL.xlsx ADDED
Binary file (25.3 kB). View file
 
app.py ADDED
@@ -0,0 +1,719 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """Untitled0.ipynb
3
+
4
+ Automatically generated by Colab.
5
+
6
+ Original file is located at
7
+ https://colab.research.google.com/drive/1lA4vvx9sbWFfjQHAmGs8ADgwhNOypfsM
8
+ """
9
+
10
+ import pandas as pd
11
+ import numpy as np
12
+ import matplotlib.pyplot as plt
13
+ import gradio as gr
14
+ import io
15
+ import base64
16
+ from datetime import datetime
17
+ from sklearn.ensemble import RandomForestClassifier
18
+ from sklearn.model_selection import train_test_split
19
+ from sklearn.metrics import classification_report, accuracy_score
20
+ import tempfile
21
+ import os
22
+
23
+ print("🚀 Iniciando CorrosionPredict en Hugging Face...")
24
+
25
+ # ==============================================================================
26
+ # BLOQUE 1: CARGA Y ENTRENAMIENTO DEL MODELO
27
+ # ==============================================================================
28
+
29
+ print("📂 Cargando y entrenando modelo de corrosión...")
30
+
31
+ try:
32
+ # Cargar datos
33
+ df = pd.read_excel("Acero_bajo_OFICIAL.xlsx")
34
+ print(f"✅ Datos cargados: {len(df)} registros")
35
+
36
+ # Limpiar datos
37
+ df['Humedad relativa (%)'] = df['Humedad relativa (%)'].astype(str).str.replace('%', '').astype(float)
38
+ df['Clasificacion ISO 9223'] = df['Clasificacion ISO 9223'].str.upper()
39
+
40
+ # Preparar variables
41
+ X = df[['Distancia al mar (km)', 'Altitud (msnm)', 'Humedad relativa (%)']]
42
+ y = df['Clasificacion ISO 9223']
43
+
44
+ # Añadir ruido controlado para robustez
45
+ np.random.seed(42)
46
+ X_noisy = X.copy()
47
+ X_noisy['Distancia al mar (km)'] += np.random.uniform(-2, 2, size=len(X))
48
+ X_noisy['Altitud (msnm)'] += np.random.uniform(-10, 10, size=len(X))
49
+ X_noisy['Humedad relativa (%)'] += np.random.uniform(-3, 3, size=len(X))
50
+
51
+ # Asegurar rangos razonables
52
+ X_noisy['Distancia al mar (km)'] = X_noisy['Distancia al mar (km)'].clip(lower=0, upper=25)
53
+ X_noisy['Altitud (msnm)'] = X_noisy['Altitud (msnm)'].clip(lower=0, upper=400)
54
+ X_noisy['Humedad relativa (%)'] = X_noisy['Humedad relativa (%)'].clip(lower=65, upper=95)
55
+
56
+ # Entrenar modelo final con todos los datos
57
+ modelo = RandomForestClassifier(n_estimators=100, random_state=42, class_weight='balanced')
58
+ modelo.fit(X_noisy, y)
59
+
60
+ print("✅ Modelo entrenado correctamente")
61
+
62
+ except Exception as e:
63
+ print(f"❌ Error cargando datos: {e}")
64
+ # Crear datos de ejemplo para demo
65
+ modelo = None
66
+
67
+ # ==============================================================================
68
+ # BLOQUE 2: CONFIGURACIÓN Y DATOS
69
+ # ==============================================================================
70
+
71
+ # Configuración profesional de matplotlib
72
+ plt.rcParams['font.size'] = 12
73
+ plt.rcParams['axes.grid'] = True
74
+ plt.rcParams['grid.alpha'] = 0.3
75
+
76
+ # Diccionario mejorado de recomendaciones
77
+ RECOMENDACIONES = {
78
+ "C1": {
79
+ "nivel": "MUY BAJA",
80
+ "color": "#2E8B57", # Verde
81
+ "ejemplos": "Oficinas climatizadas, escuelas, museos, bibliotecas, hospitales.",
82
+ "descripcion": "Ambientes interiores controlados con baja contaminación.",
83
+ "protecciones": [
84
+ {"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"},
85
+ {"proteccion": "Recubrimiento ligero", "costo": "15-25", "vida_util": "3-5", "frecuencia": "Cada 3 años", "notas": "Opción económica para baja exposición"}
86
+ ]
87
+ },
88
+ "C2": {
89
+ "nivel": "BAJA",
90
+ "color": "#9ACD32", # Amarillo verdoso
91
+ "ejemplos": "Zonas rurales, áreas suburbanas, almacenamiento techado, interiores no climatizados.",
92
+ "descripcion": "Ambientes con exposición mínima a contaminantes corrosivos.",
93
+ "protecciones": [
94
+ {"proteccion": "Pintura anticorrosiva estándar", "costo": "35-50", "vida_util": "5-8", "frecuencia": "Cada 3-4 años", "notas": "Balance óptimo costo-beneficio"},
95
+ {"proteccion": "Galvanizado ligero", "costo": "60-80", "vida_util": "10-15", "frecuencia": "Inspección cada 5 años", "notas": "Mayor durabilidad con inversión moderada"}
96
+ ]
97
+ },
98
+ "C3": {
99
+ "nivel": "MODERADA",
100
+ "color": "#FFA500", # Naranja
101
+ "ejemplos": "Zonas urbanas, áreas costeras con baja salinidad, plantas de procesamiento de alimentos.",
102
+ "descripcion": "Ambientes urbanos y semi-industriales con exposición moderada.",
103
+ "protecciones": [
104
+ {"proteccion": "Galvanizado en caliente", "costo": "80-120", "vida_util": "15-20", "frecuencia": "Inspección cada 5 años", "notas": "Protección robusta y duradera"},
105
+ {"proteccion": "Pintura epóxica industrial", "costo": "50-70", "vida_util": "8-12", "frecuencia": "Cada 4-6 años", "notas": "Excelente resistencia química"}
106
+ ]
107
+ },
108
+ "C4": {
109
+ "nivel": "ALTA",
110
+ "color": "#FF4500", # Rojo naranja
111
+ "ejemplos": "Áreas industriales, zonas costeras sin rociado de sal, plantas químicas.",
112
+ "descripcion": "Ambientes industriales y costeros con alta contaminación.",
113
+ "protecciones": [
114
+ {"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"},
115
+ {"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"}
116
+ ]
117
+ },
118
+ "C5": {
119
+ "nivel": "MUY ALTA",
120
+ "color": "#DC143C", # Rojo
121
+ "ejemplos": "Áreas costeras con rociado de sal, industrias pesadas, plataformas costeras.",
122
+ "descripcion": "Ambientes marinos e industriales extremadamente agresivos.",
123
+ "protecciones": [
124
+ {"proteccion": "Acero inoxidable 316", "costo": "300-500", "vida_util": "50+", "frecuencia": "Limpieza periódica", "notas": "Solución premium para ambientes severos"},
125
+ {"proteccion": "Sistema de protección catódica", "costo": "200-350", "vida_util": "25-30", "frecuencia": "Mantenimiento anual", "notas": "Ideal para estructuras fijas"}
126
+ ]
127
+ },
128
+ "CX": {
129
+ "nivel": "EXTREMA",
130
+ "color": "#8B008B", # Púrpura
131
+ "ejemplos": "Zonas tropicales con humedad permanente, industrias extremas, offshore.",
132
+ "descripcion": "Ambientes tropicales y offshore con exposición constante.",
133
+ "protecciones": [
134
+ {"proteccion": "Acero inoxidable superduplex", "costo": "400-600", "vida_util": "50+", "frecuencia": "Limpieza periódica", "notas": "Máxima resistencia para ambientes extremos"},
135
+ {"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"}
136
+ ]
137
+ }
138
+ }
139
+
140
+ # ==============================================================================
141
+ # BLOQUE 3: FUNCIONES PRINCIPALES
142
+ # ==============================================================================
143
+
144
+ def generar_graficos(clase_predicha, estacion):
145
+ """Genera gráficos profesionales para el reporte"""
146
+ graficos_base64 = []
147
+
148
+ try:
149
+ # 1. Gráfico de Barras: Vida Útil
150
+ fig, ax = plt.subplots(figsize=(10, 6))
151
+ protecciones = RECOMENDACIONES[clase_predicha]["protecciones"]
152
+ sistemas = [p['proteccion'] for p in protecciones]
153
+ vida_util = [float(p['vida_util'].split('-')[1].replace('+', '')) if '-' in p['vida_util'] else float(p['vida_util'].replace('+', '')) for p in protecciones]
154
+
155
+ bars = ax.bar(sistemas, vida_util, color=[RECOMENDACIONES[clase_predicha]["color"], '#FFD700'])
156
+ ax.set_title('Comparación de Vida Útil entre Sistemas de Protección', fontsize=14, fontweight='bold')
157
+ ax.set_ylabel('Vida Útil (años)', fontsize=12)
158
+ ax.set_xlabel('Sistemas de Protección', fontsize=12)
159
+
160
+ # Añadir valores en las barras
161
+ for bar in bars:
162
+ height = bar.get_height()
163
+ ax.text(bar.get_x() + bar.get_width()/2., height + 0.5,
164
+ f'{height} años', ha='center', va='bottom', fontweight='bold')
165
+
166
+ plt.xticks(rotation=15)
167
+ plt.tight_layout()
168
+
169
+ # Convertir a base64
170
+ buf = io.BytesIO()
171
+ plt.savefig(buf, format='png', dpi=120, bbox_inches='tight')
172
+ buf.seek(0)
173
+ graficos_base64.append(base64.b64encode(buf.getvalue()).decode('utf-8'))
174
+ plt.close()
175
+
176
+ # 2. Gráfico Circular: Distribución de Costos
177
+ fig, ax = plt.subplots(figsize=(8, 8))
178
+ costos = [float(p['costo'].split('-')[0]) for p in protecciones]
179
+ colors = [RECOMENDACIONES[clase_predicha]["color"], '#FFD700']
180
+
181
+ wedges, texts, autotexts = ax.pie(costos, labels=sistemas, autopct='%1.1f%%',
182
+ colors=colors, startangle=90)
183
+
184
+ for autotext in autotexts:
185
+ autotext.set_color('white')
186
+ autotext.set_fontweight('bold')
187
+
188
+ ax.set_title('Distribución de Costos Iniciales\n(S/ m²)', fontsize=14, fontweight='bold')
189
+ plt.tight_layout()
190
+
191
+ buf = io.BytesIO()
192
+ plt.savefig(buf, format='png', dpi=120, bbox_inches='tight')
193
+ buf.seek(0)
194
+ graficos_base64.append(base64.b64encode(buf.getvalue()).decode('utf-8'))
195
+ plt.close()
196
+
197
+ # 3. Gráfico de Líneas: Degradación
198
+ fig, ax = plt.subplots(figsize=(10, 6))
199
+ tiempo = np.arange(0, 26, 5) # 0 a 25 años
200
+
201
+ for i, proteccion in enumerate(protecciones):
202
+ vida = float(proteccion['vida_util'].split('-')[1].replace('+', '')) if '-' in proteccion['vida_util'] else float(proteccion['vida_util'].replace('+', ''))
203
+ eficacia = [100 - (100/vida) * t for t in tiempo]
204
+ eficacia = [max(0, e) for e in eficacia] # No menor que 0
205
+
206
+ ax.plot(tiempo, eficacia, marker='o', linewidth=2,
207
+ label=proteccion['proteccion'], color=colors[i])
208
+
209
+ ax.set_title('Degradación de Protección en el Tiempo', fontsize=14, fontweight='bold')
210
+ ax.set_xlabel('Años', fontsize=12)
211
+ ax.set_ylabel('Eficacia de Protección (%)', fontsize=12)
212
+ ax.legend()
213
+ ax.grid(True, alpha=0.3)
214
+ plt.tight_layout()
215
+
216
+ buf = io.BytesIO()
217
+ plt.savefig(buf, format='png', dpi=120, bbox_inches='tight')
218
+ buf.seek(0)
219
+ graficos_base64.append(base64.b64encode(buf.getvalue()).decode('utf-8'))
220
+ plt.close()
221
+
222
+ except Exception as e:
223
+ print(f"Error generando gráficos: {e}")
224
+
225
+ return graficos_base64
226
+
227
+ def predecir_corrosion(estacion, distancia, altitud, humedad, nombre_ing, proyecto, ubicacion):
228
+ """Función principal para predecir corrosión"""
229
+ try:
230
+ # Validaciones
231
+ if not all([estacion, nombre_ing, proyecto, ubicacion]):
232
+ return "❌ Complete todos los campos obligatorios", "", ""
233
+
234
+ distancia = float(distancia)
235
+ altitud = float(altitud)
236
+ humedad = float(humedad)
237
+
238
+ # Validar rangos
239
+ if not (0 <= distancia <= 25):
240
+ return "❌ Distancia al mar debe estar entre 0-25 km", "", ""
241
+ if not (0 <= altitud <= 400):
242
+ return "❌ Altitud debe estar entre 0-400 msnm", "", ""
243
+ if not (65 <= humedad <= 95):
244
+ return "❌ Humedad relativa debe estar entre 65-95%", "", ""
245
+
246
+ if modelo is None:
247
+ return "❌ Modelo no disponible. Verifique los datos de entrenamiento.", "", ""
248
+
249
+ # Realizar predicción
250
+ nuevo_dato = pd.DataFrame({
251
+ 'Distancia al mar (km)': [distancia],
252
+ 'Altitud (msnm)': [altitud],
253
+ 'Humedad relativa (%)': [humedad]
254
+ })
255
+
256
+ prediccion = modelo.predict(nuevo_dato)[0].upper()
257
+ probabilidades = modelo.predict_proba(nuevo_dato)[0]
258
+ clases = modelo.classes_
259
+
260
+ # Generar gráficos
261
+ graficos_base64 = generar_graficos(prediccion, estacion)
262
+
263
+ # Generar reporte HTML
264
+ reporte_html = generar_reporte_completo(
265
+ estacion, distancia, altitud, humedad, prediccion,
266
+ probabilidades, clases, graficos_base64,
267
+ nombre_ing, proyecto, ubicacion
268
+ )
269
+
270
+ # Resultados para mostrar en interfaz
271
+ resultados_html = generar_resultados_html(
272
+ estacion, distancia, altitud, humedad, prediccion,
273
+ probabilidades, clases
274
+ )
275
+
276
+ graficas_html = generar_graficas_html(graficos_base64)
277
+
278
+ return resultados_html, graficas_html, reporte_html
279
+
280
+ except Exception as e:
281
+ return f"❌ Error en la predicción: {str(e)}", "", ""
282
+
283
+ # ==============================================================================
284
+ # BLOQUE 4: GENERACIÓN DE HTML Y REPORTES
285
+ # ==============================================================================
286
+
287
+ def generar_resultados_html(estacion, distancia, altitud, humedad, prediccion, probabilidades, clases):
288
+ """Genera HTML para mostrar resultados en la interfaz"""
289
+
290
+ info_clase = RECOMENDACIONES.get(prediccion, {})
291
+
292
+ # Tabla de probabilidades
293
+ prob_table = ""
294
+ for clase, prob in zip(clases, probabilidades):
295
+ prob_table += f"<tr><td>{clase}</td><td style='text-align: center;'>{prob:.2%}</td></tr>"
296
+
297
+ return f"""
298
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
299
+ color: white; padding: 20px; border-radius: 10px; margin-bottom: 20px;">
300
+ <h2 style="margin: 0; text-align: center;">🏭 CorrosionPredict Pro</h2>
301
+ <p style="text-align: center; margin: 5px 0 0 0; opacity: 0.9;">
302
+ {estacion} | {datetime.now().strftime('%d/%m/%Y %H:%M')}
303
+ </p>
304
+ </div>
305
+
306
+ <div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 15px 0;">
307
+ <h3 style="color: #2c3e50; margin-top: 0;">📊 Resultado de Predicción</h3>
308
+
309
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px;">
310
+ <div style="background: white; padding: 15px; border-radius: 8px; text-align: center; border-left: 4px solid {info_clase.get('color', '#666')};">
311
+ <div style="font-weight: bold; color: #2c3e50;">Clasificación ISO 9223</div>
312
+ <div style="font-size: 1.8em; font-weight: bold; color: {info_clase.get('color', '#666')};">{prediccion}</div>
313
+ <div style="font-size: 0.9em; color: #666;">{info_clase.get('nivel', 'N/A')}</div>
314
+ </div>
315
+ </div>
316
+
317
+ <div style="background: white; padding: 15px; border-radius: 8px; margin-bottom: 15px;">
318
+ <h4 style="margin-top: 0; color: #2c3e50;">📈 Probabilidades por Clase</h4>
319
+ <table style="width: 100%; border-collapse: collapse;">
320
+ <tr style="background: #f8f9fa;">
321
+ <th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6;">Clase</th>
322
+ <th style="padding: 10px; text-align: center; border-bottom: 2px solid #dee2e6;">Probabilidad</th>
323
+ </tr>
324
+ {prob_table}
325
+ </table>
326
+ </div>
327
+
328
+ <div style="background: white; padding: 15px; border-radius: 8px;">
329
+ <h4 style="margin-top: 0; color: #2c3e50;">📍 Datos de Entrada</h4>
330
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px;">
331
+ <div style="text-align: center;">
332
+ <div style="font-size: 0.9em; color: #666;">Distancia al mar</div>
333
+ <div style="font-size: 1.2em; font-weight: bold;">{distancia} km</div>
334
+ </div>
335
+ <div style="text-align: center;">
336
+ <div style="font-size: 0.9em; color: #666;">Altitud</div>
337
+ <div style="font-size: 1.2em; font-weight: bold;">{altitud} msnm</div>
338
+ </div>
339
+ <div style="text-align: center;">
340
+ <div style="font-size: 0.9em; color: #666;">Humedad</div>
341
+ <div style="font-size: 1.2em; font-weight: bold;">{humedad}%</div>
342
+ </div>
343
+ </div>
344
+ </div>
345
+ </div>
346
+ """
347
+
348
+ def generar_graficas_html(graficos_base64):
349
+ """Genera HTML para mostrar gráficas"""
350
+ if not graficos_base64:
351
+ return "<p>No se pudieron generar las gráficas</p>"
352
+
353
+ graficas_html = "<div style='margin-top: 20px;'>"
354
+ titulos = [
355
+ "Comparación de Vida Útil entre Sistemas de Protección",
356
+ "Distribución de Costos Iniciales",
357
+ "Degradación de Protección en el Tiempo"
358
+ ]
359
+
360
+ for i, img_base64 in enumerate(graficos_base64):
361
+ graficas_html += f"""
362
+ <div style="background: white; padding: 20px; border-radius: 8px; margin: 15px 0;
363
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
364
+ <h4 style="margin-top: 0; color: #2c3e50;">{titulos[i] if i < len(titulos) else 'Gráfico'}</h4>
365
+ <img src="data:image/png;base64,{img_base64}"
366
+ style="max-width: 100%; height: auto; border-radius: 5px; border: 1px solid #dee2e6;">
367
+ </div>
368
+ """
369
+ graficas_html += "</div>"
370
+ return graficas_html
371
+
372
+ def generar_reporte_completo(estacion, distancia, altitud, humedad, prediccion,
373
+ probabilidades, clases, graficos_base64, nombre_ing, proyecto, ubicacion):
374
+ """Genera reporte HTML completo para descargar"""
375
+
376
+ info_clase = RECOMENDACIONES.get(prediccion, {})
377
+
378
+ # Tabla de protecciones
379
+ protecciones_html = ""
380
+ for proteccion in info_clase.get("protecciones", []):
381
+ protecciones_html += f"""
382
+ <tr>
383
+ <td>{proteccion['proteccion']}</td>
384
+ <td>S/ {proteccion['costo']}</td>
385
+ <td>{proteccion['vida_util']} años</td>
386
+ <td>{proteccion['frecuencia']}</td>
387
+ <td>{proteccion['notas']}</td>
388
+ </tr>
389
+ """
390
+
391
+ # Tabla de probabilidades
392
+ prob_html = ""
393
+ for clase, prob in zip(clases, probabilidades):
394
+ prob_html += f"<tr><td>{clase}</td><td>{prob:.2%}</td></tr>"
395
+
396
+ # Gráficos en base64
397
+ graficos_html = ""
398
+ titulos_graficos = [
399
+ "Comparación de Vida Útil entre Sistemas de Protección",
400
+ "Distribución de Costos Iniciales",
401
+ "Degradación de Protección en el Tiempo"
402
+ ]
403
+
404
+ for i, img_base64 in enumerate(graficos_base64):
405
+ graficos_html += f"""
406
+ <div style="page-break-inside: avoid; margin: 20px 0;">
407
+ <h3>{titulos_graficos[i]}</h3>
408
+ <img src="data:image/png;base64,{img_base64}"
409
+ style="max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 5px;">
410
+ </div>
411
+ """
412
+
413
+ html_content = f"""<!DOCTYPE html>
414
+ <html lang="es">
415
+ <head>
416
+ <meta charset="UTF-8">
417
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
418
+ <title>Reporte de Corrosión - {estacion}</title>
419
+ <style>
420
+ body {{
421
+ font-family: 'Arial', sans-serif;
422
+ line-height: 1.6;
423
+ color: #333;
424
+ max-width: 1200px;
425
+ margin: 0 auto;
426
+ padding: 20px;
427
+ background: #f8f9fa;
428
+ }}
429
+ .header {{
430
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
431
+ color: white;
432
+ padding: 30px;
433
+ border-radius: 15px;
434
+ text-align: center;
435
+ margin-bottom: 30px;
436
+ }}
437
+ .section {{
438
+ background: white;
439
+ padding: 25px;
440
+ margin: 20px 0;
441
+ border-radius: 10px;
442
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
443
+ }}
444
+ .clasificacion {{
445
+ background: {info_clase.get('color', '#666')};
446
+ color: white;
447
+ padding: 20px;
448
+ border-radius: 10px;
449
+ text-align: center;
450
+ margin: 15px 0;
451
+ }}
452
+ table {{
453
+ width: 100%;
454
+ border-collapse: collapse;
455
+ margin: 15px 0;
456
+ }}
457
+ th, td {{
458
+ border: 1px solid #ddd;
459
+ padding: 12px;
460
+ text-align: left;
461
+ }}
462
+ th {{
463
+ background-color: #f8f9fa;
464
+ font-weight: bold;
465
+ }}
466
+ .proteccion-card {{
467
+ background: #f8f9fa;
468
+ padding: 15px;
469
+ margin: 10px 0;
470
+ border-radius: 8px;
471
+ border-left: 4px solid {info_clase.get('color', '#666')};
472
+ }}
473
+ @media print {{
474
+ body {{ background: white; }}
475
+ .section {{ box-shadow: none; border: 1px solid #ddd; }}
476
+ .header {{ background: #667eea; }}
477
+ }}
478
+ </style>
479
+ </head>
480
+ <body>
481
+ <div class="header">
482
+ <h1>🏭 CorrosionPredict Pro</h1>
483
+ <p>Sistema de Predicción de Corrosión ISO 9223</p>
484
+ <p>{estacion} | {datetime.now().strftime('%d/%m/%Y %H:%M')}</p>
485
+ </div>
486
+
487
+ <div class="section">
488
+ <h2>📋 Información del Proyecto</h2>
489
+ <table>
490
+ <tr><th>Proyecto:</th><td>{proyecto}</td></tr>
491
+ <tr><th>Ubicación:</th><td>{ubicacion}</td></tr>
492
+ <tr><th>Ingeniero:</th><td>{nombre_ing}</td></tr>
493
+ <tr><th>Estación/Distrito:</th><td>{estacion}</td></tr>
494
+ <tr><th>Fecha:</th><td>{datetime.now().strftime('%d/%m/%Y %H:%M')}</td></tr>
495
+ </table>
496
+ </div>
497
+
498
+ <div class="section">
499
+ <h2>📊 Datos de Entrada</h2>
500
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
501
+ <div style="text-align: center; padding: 15px; background: #f8f9fa; border-radius: 8px;">
502
+ <h3>🌊 Distancia al mar</h3>
503
+ <p style="font-size: 1.5em; font-weight: bold; margin: 0;">{distancia} km</p>
504
+ </div>
505
+ <div style="text-align: center; padding: 15px; background: #f8f9fa; border-radius: 8px;">
506
+ <h3>⛰️ Altitud</h3>
507
+ <p style="font-size: 1.5em; font-weight: bold; margin: 0;">{altitud} msnm</p>
508
+ </div>
509
+ <div style="text-align: center; padding: 15px; background: #f8f9fa; border-radius: 8px;">
510
+ <h3>💧 Humedad relativa</h3>
511
+ <p style="font-size: 1.5em; font-weight: bold; margin: 0;">{humedad}%</p>
512
+ </div>
513
+ </div>
514
+ </div>
515
+
516
+ <div class="section">
517
+ <h2>🎯 Resultado de Predicción</h2>
518
+ <div class="clasificacion">
519
+ <h3 style="margin: 0; font-size: 2em;">CLASE {prediccion}</h3>
520
+ <p style="margin: 10px 0 0 0; font-size: 1.2em;">Corrosión {info_clase.get('nivel', 'N/A')}</p>
521
+ </div>
522
+
523
+ <h3>Probabilidades por Clase:</h3>
524
+ <table>
525
+ <tr><th>Clase ISO 9223</th><th>Probabilidad</th></tr>
526
+ {prob_html}
527
+ </table>
528
+ </div>
529
+
530
+ <div class="section">
531
+ <h2>🛡️ Recomendaciones de Protección</h2>
532
+ <p><strong>Descripción del ambiente:</strong> {info_clase.get('descripcion', 'N/A')}</p>
533
+ <p><strong>Ejemplos típicos:</strong> {info_clase.get('ejemplos', 'N/A')}</p>
534
+
535
+ <h3>Sistemas de Protección Recomendados:</h3>
536
+ <table>
537
+ <tr>
538
+ <th>Sistema de Protección</th>
539
+ <th>Costo (S/ m²)</th>
540
+ <th>Vida Útil</th>
541
+ <th>Mantenimiento</th>
542
+ <th>Notas</th>
543
+ </tr>
544
+ {protecciones_html}
545
+ </table>
546
+ </div>
547
+
548
+ <div class="section">
549
+ <h2>📈 Análisis Gráfico</h2>
550
+ {graficos_html}
551
+ </div>
552
+
553
+ <div class="section" style="text-align: center; background: #f8f9fa;">
554
+ <h3>CorrosionPredict Pro</h3>
555
+ <p>Sistema de predicción de corrosión basado en Machine Learning</p>
556
+ <p>Reporte generado automáticamente el {datetime.now().strftime('%d/%m/%Y')}</p>
557
+ </div>
558
+ </body>
559
+ </html>"""
560
+
561
+ return html_content
562
+
563
+ # ==============================================================================
564
+ # BLOQUE 5: INTERFAZ GRADIO
565
+ # ==============================================================================
566
+
567
+ with gr.Blocks(theme=gr.themes.Soft(), title="CorrosionPredict Pro") as demo:
568
+
569
+ gr.Markdown("""
570
+ # 🏭 CorrosionPredict Pro
571
+ ### Sistema de Predicción de Corrosión ISO 9223 con Machine Learning
572
+
573
+ Predice la clasificación de corrosividad según ISO 9223 para acero al carbono
574
+ basado en condiciones ambientales.
575
+ """)
576
+
577
+ with gr.Row():
578
+ with gr.Column(scale=1):
579
+ gr.Markdown("### ⚙️ Parámetros de Entrada")
580
+
581
+ estacion = gr.Textbox(
582
+ label="🏢 Estación/Distrito",
583
+ placeholder="Ej: Miraflores, Callao, Ate",
584
+ info="Nombre de la ubicación de estudio"
585
+ )
586
+
587
+ with gr.Row():
588
+ distancia = gr.Number(
589
+ label="🌊 Distancia al mar (km)",
590
+ value=5.0,
591
+ minimum=0,
592
+ maximum=25,
593
+ info="0-25 km"
594
+ )
595
+
596
+ altitud = gr.Number(
597
+ label="⛰️ Altitud (msnm)",
598
+ value=100,
599
+ minimum=0,
600
+ maximum=400,
601
+ info="0-400 msnm"
602
+ )
603
+
604
+ humedad = gr.Slider(
605
+ label="💧 Humedad relativa (%)",
606
+ value=80,
607
+ minimum=65,
608
+ maximum=95,
609
+ step=1,
610
+ info="65-95%"
611
+ )
612
+
613
+ nombre_ing = gr.Textbox(
614
+ label="👤 Ingeniero Responsable",
615
+ value="Ing. Ambiental",
616
+ placeholder="Nombre del profesional"
617
+ )
618
+
619
+ proyecto = gr.Textbox(
620
+ label="📋 Nombre del Proyecto",
621
+ value="Estudio de Corrosión",
622
+ placeholder="Nombre del proyecto"
623
+ )
624
+
625
+ ubicacion = gr.Textbox(
626
+ label="📍 Ubicación",
627
+ value="Lima Metropolitana",
628
+ placeholder="Región/Departamento"
629
+ )
630
+
631
+ btn_predict = gr.Button(
632
+ "🚀 Ejecutar Predicción",
633
+ size="lg",
634
+ variant="primary"
635
+ )
636
+
637
+ with gr.Column(scale=2):
638
+ gr.Markdown("### 📊 Resultados del Análisis")
639
+ resultados = gr.HTML(label="Predicciones")
640
+ graficas = gr.HTML(label="Gráficas del Análisis")
641
+
642
+ gr.Markdown("### 💾 Descargar Reporte Completo")
643
+ descarga_html = gr.HTML("""
644
+ <div style="background: #d4edda; color: #155724; padding: 15px; border-radius: 8px;
645
+ border: 1px solid #c3e6cb; text-align: center;">
646
+ <p style="margin: 0;">📄 Ejecuta una predicción para generar el reporte</p>
647
+ </div>
648
+ """)
649
+
650
+ descarga_btn = gr.Button(
651
+ "📥 Descargar Reporte HTML",
652
+ size="lg",
653
+ variant="secondary",
654
+ visible=False
655
+ )
656
+
657
+ archivo_descarga = gr.File(
658
+ label="Reporte de Corrosión",
659
+ visible=False
660
+ )
661
+
662
+ # Ejemplos rápidos
663
+ gr.Markdown("### 🚀 Ejemplos Rápidos")
664
+ with gr.Row():
665
+ gr.Examples(
666
+ examples=[
667
+ ["Miraflores", 2.5, 80, 85, "Ing. Ejemplo", "Edificio Costero", "Lima"],
668
+ ["Ate", 15.0, 350, 75, "Ing. Ejemplo", "Planta Industrial", "Lima"],
669
+ ["Callao", 0.5, 5, 90, "Ing. Ejemplo", "Puerto Marítimo", "Callao"]
670
+ ],
671
+ inputs=[estacion, distancia, altitud, humedad, nombre_ing, proyecto, ubicacion]
672
+ )
673
+
674
+ # Estado para almacenar reporte
675
+ reporte_actual = gr.State("")
676
+
677
+ # Funciones de conexión
678
+ def procesar_prediccion(estacion, distancia, altitud, humedad, nombre_ing, proyecto, ubicacion):
679
+ resultados_html, graficas_html, reporte_html = predecir_corrosion(
680
+ estacion, distancia, altitud, humedad, nombre_ing, proyecto, ubicacion
681
+ )
682
+
683
+ mostrar_descarga = reporte_html != ""
684
+
685
+ return resultados_html, graficas_html, gr.Button(visible=mostrar_descarga), reporte_html
686
+
687
+ def generar_descarga(reporte_html):
688
+ if reporte_html:
689
+ filename = f"reporte_corrosion_{datetime.now().strftime('%Y%m%d_%H%M')}.html"
690
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.html', mode='w', encoding='utf-8')
691
+ temp_file.write(reporte_html)
692
+ temp_file.close()
693
+ return gr.File(value=temp_file.name, label=filename, visible=True)
694
+ return gr.File(visible=False)
695
+
696
+ # Conectar componentes
697
+ btn_predict.click(
698
+ fn=procesar_prediccion,
699
+ inputs=[estacion, distancia, altitud, humedad, nombre_ing, proyecto, ubicacion],
700
+ outputs=[resultados, graficas, descarga_btn, reporte_actual]
701
+ )
702
+
703
+ descarga_btn.click(
704
+ fn=generar_descarga,
705
+ inputs=[reporte_actual],
706
+ outputs=[archivo_descarga]
707
+ )
708
+
709
+ # ==============================================================================
710
+ # EJECUCIÓN
711
+ # ==============================================================================
712
+
713
+ if __name__ == "__main__":
714
+ demo.launch(
715
+ debug=False,
716
+ show_error=True,
717
+ server_name="0.0.0.0",
718
+ server_port=7860
719
+ )
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ pandas==1.5.3
2
+ scikit-learn==1.2.2
3
+ matplotlib==3.7.1
4
+ openpyxl==3.1.2
5
+ numpy==1.24.3
6
+ gradio==4.13.0