Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -8,12 +8,24 @@ import os
|
|
8 |
import json
|
9 |
import zipfile
|
10 |
import tempfile
|
11 |
-
from typing import Dict, List, Tuple, Union
|
12 |
import re
|
13 |
from pathlib import Path
|
14 |
import openpyxl
|
15 |
from dataclasses import dataclass
|
16 |
from enum import Enum
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
# Configuración para HuggingFace
|
19 |
os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
|
@@ -21,6 +33,181 @@ os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
|
|
21 |
# Inicializar cliente Anthropic
|
22 |
client = anthropic.Anthropic()
|
23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
# Enum para tipos de análisis
|
25 |
class AnalysisType(Enum):
|
26 |
MATHEMATICAL_MODEL = "mathematical_model"
|
@@ -91,28 +278,6 @@ class ModelRegistry:
|
|
91 |
category="crecimiento_biomasa",
|
92 |
biological_meaning="Incluye fase de adaptación (lag) seguida de crecimiento exponencial y estacionario"
|
93 |
))
|
94 |
-
|
95 |
-
# Modelos enzimáticos
|
96 |
-
self.register_model(MathematicalModel(
|
97 |
-
name="Michaelis-Menten",
|
98 |
-
equation="v = Vmax × S / (Km + S)",
|
99 |
-
parameters=["Vmax", "Km"],
|
100 |
-
application="Cinética enzimática básica",
|
101 |
-
sources=["Warsaw Univ Tech", "Food Processing"],
|
102 |
-
category="consumo_sustrato",
|
103 |
-
biological_meaning="Describe saturación enzimática con afinidad por sustrato"
|
104 |
-
))
|
105 |
-
|
106 |
-
# Modelos de producto
|
107 |
-
self.register_model(MathematicalModel(
|
108 |
-
name="Luedeking-Piret",
|
109 |
-
equation="dP/dt = α × (dX/dt) + β × X",
|
110 |
-
parameters=["α (asociado)", "β (no asociado)"],
|
111 |
-
application="Producción mixta asociada/no asociada",
|
112 |
-
sources=["Cambridge", "E-Century"],
|
113 |
-
category="formacion_producto",
|
114 |
-
biological_meaning="Separa producción ligada al crecimiento de la producción metabólica"
|
115 |
-
))
|
116 |
|
117 |
# Instancia global del registro
|
118 |
model_registry = ModelRegistry()
|
@@ -121,21 +286,21 @@ model_registry = ModelRegistry()
|
|
121 |
CLAUDE_MODELS = {
|
122 |
"claude-3-5-sonnet-20241022": {
|
123 |
"name": "Claude 3.5 Sonnet",
|
124 |
-
"description": "
|
125 |
"max_tokens": 4000,
|
126 |
-
"best_for": "
|
127 |
},
|
128 |
"claude-3-opus-20240229": {
|
129 |
"name": "Claude 3 Opus",
|
130 |
-
"description": "
|
131 |
"max_tokens": 4000,
|
132 |
-
"best_for": "
|
133 |
},
|
134 |
"claude-3-haiku-20240307": {
|
135 |
"name": "Claude 3 Haiku",
|
136 |
-
"description": "
|
137 |
"max_tokens": 4000,
|
138 |
-
"best_for": "
|
139 |
}
|
140 |
}
|
141 |
|
@@ -152,7 +317,7 @@ class FileProcessor:
|
|
152 |
text += page.extract_text() + "\n"
|
153 |
return text
|
154 |
except Exception as e:
|
155 |
-
return f"Error
|
156 |
|
157 |
@staticmethod
|
158 |
def read_csv(csv_file) -> pd.DataFrame:
|
@@ -181,9 +346,162 @@ class FileProcessor:
|
|
181 |
file_data = zip_ref.read(file_name)
|
182 |
files.append((file_name, file_data))
|
183 |
except Exception as e:
|
184 |
-
print(f"Error
|
185 |
return files
|
186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
class AIAnalyzer:
|
188 |
"""Clase para análisis con IA"""
|
189 |
|
@@ -211,12 +529,12 @@ class AIAnalyzer:
|
|
211 |
return AnalysisType.DATA_FITTING
|
212 |
|
213 |
prompt = """
|
214 |
-
|
215 |
-
1.
|
216 |
-
2.
|
217 |
-
3.
|
218 |
|
219 |
-
|
220 |
"""
|
221 |
|
222 |
try:
|
@@ -227,11 +545,11 @@ class AIAnalyzer:
|
|
227 |
)
|
228 |
|
229 |
result = response.content[0].text.strip().upper()
|
230 |
-
if "
|
231 |
return AnalysisType.MATHEMATICAL_MODEL
|
232 |
-
elif "
|
233 |
return AnalysisType.FITTING_RESULTS
|
234 |
-
elif "
|
235 |
return AnalysisType.DATA_FITTING
|
236 |
else:
|
237 |
return AnalysisType.UNKNOWN
|
@@ -239,206 +557,120 @@ class AIAnalyzer:
|
|
239 |
except:
|
240 |
return AnalysisType.UNKNOWN
|
241 |
|
242 |
-
def
|
243 |
-
"""
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
4. Aplicaciones biotecnológicas
|
251 |
-
5. Microorganismos y procesos
|
252 |
-
|
253 |
-
Formato JSON con estructura:
|
254 |
-
{
|
255 |
-
"modelos": ["nombre1", "nombre2"],
|
256 |
-
"ecuaciones": ["eq1", "eq2"],
|
257 |
-
"parametros": ["param1", "param2"],
|
258 |
-
"aplicaciones": ["app1", "app2"],
|
259 |
-
"microorganismos": ["org1", "org2"]
|
260 |
-
}
|
261 |
-
""",
|
262 |
-
|
263 |
-
"recomendar_implementacion": """
|
264 |
-
Basado en los modelos identificados, proporciona:
|
265 |
-
1. Estrategia de implementación
|
266 |
-
2. Consideraciones experimentales
|
267 |
-
3. Métodos de validación
|
268 |
-
4. Posibles limitaciones
|
269 |
-
"""
|
270 |
}
|
271 |
-
|
272 |
-
try:
|
273 |
-
response = self.client.messages.create(
|
274 |
-
model=claude_model,
|
275 |
-
max_tokens=2000,
|
276 |
-
messages=[{
|
277 |
-
"role": "user",
|
278 |
-
"content": f"{prompts['identificar_modelos']}\n\nTEXTO:\n{text[:3000]}"
|
279 |
-
}]
|
280 |
-
)
|
281 |
-
|
282 |
-
models_info = response.content[0].text
|
283 |
-
|
284 |
-
response2 = self.client.messages.create(
|
285 |
-
model=claude_model,
|
286 |
-
max_tokens=2000,
|
287 |
-
messages=[{
|
288 |
-
"role": "user",
|
289 |
-
"content": f"{prompts['recomendar_implementacion']}\n\nMODELOS:\n{models_info}"
|
290 |
-
}]
|
291 |
-
)
|
292 |
-
|
293 |
-
return {
|
294 |
-
"tipo": "Artículo de Modelos Matemáticos",
|
295 |
-
"modelos": models_info,
|
296 |
-
"recomendaciones": response2.content[0].text
|
297 |
-
}
|
298 |
-
|
299 |
-
except Exception as e:
|
300 |
-
return {"error": str(e)}
|
301 |
|
302 |
-
def
|
303 |
-
"""Analiza
|
304 |
-
data_summary = f"""
|
305 |
-
Columnas: {list(data.columns)}
|
306 |
-
Forma: {data.shape}
|
307 |
-
Primeras filas:
|
308 |
-
{data.head().to_string()}
|
309 |
-
|
310 |
-
Estadísticas:
|
311 |
-
{data.describe().to_string()}
|
312 |
-
"""
|
313 |
-
|
314 |
-
prompt = """
|
315 |
-
Analiza estos datos experimentales y determina:
|
316 |
-
1. Variables independientes y dependientes
|
317 |
-
2. Posibles modelos matemáticos aplicables
|
318 |
-
3. Método de ajuste recomendado
|
319 |
-
4. Parámetros a estimar
|
320 |
-
5. Calidad esperada del ajuste
|
321 |
-
|
322 |
-
Proporciona código Python para el ajuste.
|
323 |
-
"""
|
324 |
-
|
325 |
-
try:
|
326 |
-
response = self.client.messages.create(
|
327 |
-
model=claude_model,
|
328 |
-
max_tokens=3000,
|
329 |
-
messages=[{
|
330 |
-
"role": "user",
|
331 |
-
"content": f"{prompt}\n\nDATOS:\n{data_summary}"
|
332 |
-
}]
|
333 |
-
)
|
334 |
-
|
335 |
-
return {
|
336 |
-
"tipo": "Datos Experimentales para Ajuste",
|
337 |
-
"analisis": response.content[0].text,
|
338 |
-
"resumen_datos": data_summary
|
339 |
-
}
|
340 |
-
|
341 |
-
except Exception as e:
|
342 |
-
return {"error": str(e)}
|
343 |
-
|
344 |
-
def analyze_fitting_results(self, data: pd.DataFrame, claude_model: str, detail_level: str = "detallado") -> Dict:
|
345 |
-
"""Analiza resultados de ajuste de modelos con enfoque mejorado"""
|
346 |
|
347 |
# Preparar resumen completo de los datos
|
348 |
data_summary = f"""
|
349 |
-
|
350 |
|
351 |
-
|
352 |
-
-
|
353 |
-
-
|
354 |
|
355 |
-
|
356 |
{data.to_string()}
|
357 |
|
358 |
-
|
359 |
{data.describe().to_string()}
|
360 |
"""
|
361 |
|
362 |
-
#
|
|
|
|
|
|
|
363 |
prompt = f"""
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
-
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
-
|
379 |
-
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
*
|
386 |
-
*
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
-
|
395 |
-
-
|
|
|
|
|
396 |
|
397 |
-
b) **
|
398 |
-
-
|
399 |
-
-
|
400 |
-
-
|
401 |
-
-
|
402 |
|
403 |
-
c) **
|
404 |
-
-
|
405 |
-
-
|
406 |
-
-
|
407 |
-
-
|
408 |
-
|
409 |
-
5. **
|
410 |
-
-
|
411 |
-
-
|
412 |
-
-
|
413 |
-
-
|
414 |
-
|
415 |
-
6. **
|
416 |
-
-
|
417 |
-
-
|
418 |
-
-
|
419 |
-
-
|
420 |
-
|
421 |
-
7. **
|
422 |
-
-
|
423 |
-
-
|
424 |
-
-
|
425 |
-
-
|
426 |
-
|
427 |
-
8. **
|
428 |
-
|
429 |
-
-
|
430 |
-
|
431 |
-
|
432 |
-
-
|
433 |
-
-
|
434 |
-
|
435 |
-
|
436 |
-
-
|
437 |
-
- **
|
438 |
-
-
|
439 |
-
-
|
440 |
-
|
441 |
-
|
442 |
"""
|
443 |
|
444 |
try:
|
@@ -451,24 +683,26 @@ class AIAnalyzer:
|
|
451 |
}]
|
452 |
)
|
453 |
|
454 |
-
# Análisis adicional para generar código
|
455 |
-
code_prompt = """
|
456 |
-
|
457 |
|
458 |
-
|
459 |
-
2. Crear gráficos comparativos de modelos (barras para R², RMSE)
|
460 |
-
3. Implementar el mejor modelo identificado
|
461 |
-
4. Generar predicciones con el modelo seleccionado
|
462 |
-
5. Análisis de sensibilidad de parámetros
|
463 |
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
- Ejemplo de uso
|
470 |
|
471 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
472 |
"""
|
473 |
|
474 |
code_response = self.client.messages.create(
|
@@ -476,12 +710,12 @@ class AIAnalyzer:
|
|
476 |
max_tokens=3000,
|
477 |
messages=[{
|
478 |
"role": "user",
|
479 |
-
"content": f"{code_prompt}\n\
|
480 |
}]
|
481 |
)
|
482 |
|
483 |
return {
|
484 |
-
"tipo": "
|
485 |
"analisis_completo": response.content[0].text,
|
486 |
"codigo_implementacion": code_response.content[0].text,
|
487 |
"resumen_datos": {
|
@@ -497,8 +731,8 @@ class AIAnalyzer:
|
|
497 |
except Exception as e:
|
498 |
return {"error": str(e)}
|
499 |
|
500 |
-
def process_files(files, claude_model: str, detail_level: str = "
|
501 |
-
"""Procesa múltiples archivos"""
|
502 |
processor = FileProcessor()
|
503 |
analyzer = AIAnalyzer(client, model_registry)
|
504 |
results = []
|
@@ -514,61 +748,11 @@ def process_files(files, claude_model: str, detail_level: str = "detallado") ->
|
|
514 |
with open(file.name, 'rb') as f:
|
515 |
file_content = f.read()
|
516 |
|
517 |
-
if file_ext
|
518 |
-
|
519 |
-
|
520 |
-
results.append(f"Contiene {len(extracted_files)} archivos\n")
|
521 |
-
|
522 |
-
for name, content in extracted_files:
|
523 |
-
sub_ext = Path(name).suffix.lower()
|
524 |
-
results.append(f"### 📄 {name}")
|
525 |
-
|
526 |
-
if sub_ext == '.pdf':
|
527 |
-
text = processor.extract_text_from_pdf(content)
|
528 |
-
analysis_type = analyzer.detect_analysis_type(text)
|
529 |
-
|
530 |
-
if analysis_type == AnalysisType.MATHEMATICAL_MODEL:
|
531 |
-
result = analyzer.analyze_mathematical_article(text, claude_model)
|
532 |
-
else:
|
533 |
-
result = {"tipo": "PDF no reconocido", "contenido": text[:500]}
|
534 |
-
|
535 |
-
results.append(result.get("analisis_completo", json.dumps(result, indent=2, ensure_ascii=False)))
|
536 |
-
|
537 |
-
elif sub_ext in ['.csv', '.xlsx', '.xls']:
|
538 |
-
if sub_ext == '.csv':
|
539 |
-
df = processor.read_csv(content)
|
540 |
-
else:
|
541 |
-
df = processor.read_excel(content)
|
542 |
-
|
543 |
-
if df is not None:
|
544 |
-
analysis_type = analyzer.detect_analysis_type(df)
|
545 |
-
|
546 |
-
if analysis_type == AnalysisType.FITTING_RESULTS:
|
547 |
-
result = analyzer.analyze_fitting_results(df, claude_model, detail_level)
|
548 |
-
results.append(result.get("analisis_completo", ""))
|
549 |
-
if "codigo_implementacion" in result:
|
550 |
-
all_code.append(result["codigo_implementacion"])
|
551 |
-
else:
|
552 |
-
result = analyzer.analyze_fitting_data(df, claude_model)
|
553 |
-
results.append(result.get("analisis", ""))
|
554 |
-
|
555 |
-
results.append("\n---\n")
|
556 |
-
|
557 |
-
elif file_ext == '.pdf':
|
558 |
-
text = processor.extract_text_from_pdf(file_content)
|
559 |
-
analysis_type = analyzer.detect_analysis_type(text)
|
560 |
-
|
561 |
-
results.append(f"## 📄 PDF: {file_name}")
|
562 |
-
|
563 |
-
if analysis_type == AnalysisType.MATHEMATICAL_MODEL:
|
564 |
-
result = analyzer.analyze_mathematical_article(text, claude_model)
|
565 |
-
results.append(result.get("modelos", "") + "\n" + result.get("recomendaciones", ""))
|
566 |
else:
|
567 |
-
|
568 |
-
results.append(json.dumps(result, indent=2, ensure_ascii=False))
|
569 |
-
|
570 |
-
elif file_ext in ['.csv', '.xlsx', '.xls']:
|
571 |
-
results.append(f"## 📊 Análisis de Resultados: {file_name}")
|
572 |
|
573 |
if file_ext == '.csv':
|
574 |
df = processor.read_csv(file_content)
|
@@ -579,15 +763,16 @@ def process_files(files, claude_model: str, detail_level: str = "detallado") ->
|
|
579 |
analysis_type = analyzer.detect_analysis_type(df)
|
580 |
|
581 |
if analysis_type == AnalysisType.FITTING_RESULTS:
|
582 |
-
result = analyzer.analyze_fitting_results(df, claude_model, detail_level)
|
583 |
-
|
|
|
|
|
|
|
|
|
|
|
584 |
results.append(result.get("analisis_completo", ""))
|
585 |
if "codigo_implementacion" in result:
|
586 |
all_code.append(result["codigo_implementacion"])
|
587 |
-
else:
|
588 |
-
result = analyzer.analyze_fitting_data(df, claude_model)
|
589 |
-
results.append("### 📈 ANÁLISIS DE DATOS EXPERIMENTALES")
|
590 |
-
results.append(result.get("analisis", ""))
|
591 |
|
592 |
results.append("\n---\n")
|
593 |
|
@@ -597,7 +782,7 @@ def process_files(files, claude_model: str, detail_level: str = "detallado") ->
|
|
597 |
return analysis_text, code_text
|
598 |
|
599 |
def generate_implementation_code(analysis_results: str) -> str:
|
600 |
-
"""Genera código de implementación
|
601 |
code = """
|
602 |
import numpy as np
|
603 |
import pandas as pd
|
@@ -608,14 +793,14 @@ from sklearn.metrics import r2_score, mean_squared_error
|
|
608 |
import seaborn as sns
|
609 |
from typing import Dict, List, Tuple, Optional
|
610 |
|
611 |
-
#
|
612 |
plt.style.use('seaborn-v0_8-darkgrid')
|
613 |
sns.set_palette("husl")
|
614 |
|
615 |
class ComparativeModelAnalyzer:
|
616 |
\"\"\"
|
617 |
-
|
618 |
-
|
619 |
\"\"\"
|
620 |
|
621 |
def __init__(self):
|
@@ -624,14 +809,14 @@ class ComparativeModelAnalyzer:
|
|
624 |
self.model_rankings = {}
|
625 |
|
626 |
def load_results(self, file_path: str) -> pd.DataFrame:
|
627 |
-
\"\"\"
|
628 |
if file_path.endswith('.csv'):
|
629 |
self.results_df = pd.read_csv(file_path)
|
630 |
else:
|
631 |
self.results_df = pd.read_excel(file_path)
|
632 |
|
633 |
-
print(f"✅
|
634 |
-
print(f"📊
|
635 |
|
636 |
return self.results_df
|
637 |
|
@@ -642,40 +827,40 @@ class ComparativeModelAnalyzer:
|
|
642 |
bic_col: Optional[str] = 'BIC',
|
643 |
model_col: str = 'Model') -> pd.DataFrame:
|
644 |
\"\"\"
|
645 |
-
|
646 |
-
|
647 |
\"\"\"
|
648 |
if self.results_df is None:
|
649 |
-
raise ValueError("
|
650 |
|
651 |
-
#
|
652 |
comparison = self.results_df.copy()
|
653 |
|
654 |
-
#
|
655 |
scores = pd.DataFrame(index=comparison.index)
|
656 |
|
657 |
-
#
|
658 |
if r2_col in comparison.columns:
|
659 |
-
scores['r2_score'] = comparison[r2_col] #
|
660 |
|
661 |
if rmse_col in comparison.columns:
|
662 |
-
#
|
663 |
max_rmse = comparison[rmse_col].max()
|
664 |
scores['rmse_score'] = 1 - (comparison[rmse_col] / max_rmse)
|
665 |
|
666 |
if aic_col and aic_col in comparison.columns:
|
667 |
-
#
|
668 |
min_aic = comparison[aic_col].min()
|
669 |
max_aic = comparison[aic_col].max()
|
670 |
scores['aic_score'] = 1 - ((comparison[aic_col] - min_aic) / (max_aic - min_aic))
|
671 |
|
672 |
if bic_col and bic_col in comparison.columns:
|
673 |
-
#
|
674 |
min_bic = comparison[bic_col].min()
|
675 |
max_bic = comparison[bic_col].max()
|
676 |
scores['bic_score'] = 1 - ((comparison[bic_col] - min_bic) / (max_bic - min_bic))
|
677 |
|
678 |
-
#
|
679 |
weights = {
|
680 |
'r2_score': 0.4,
|
681 |
'rmse_score': 0.3,
|
@@ -688,23 +873,23 @@ class ComparativeModelAnalyzer:
|
|
688 |
if metric in scores.columns:
|
689 |
scores['total_score'] += scores[metric] * weight
|
690 |
|
691 |
-
#
|
692 |
comparison['Score'] = scores['total_score']
|
693 |
comparison['Ranking'] = comparison['Score'].rank(ascending=False).astype(int)
|
694 |
|
695 |
-
#
|
696 |
comparison = comparison.sort_values('Ranking')
|
697 |
|
698 |
-
#
|
699 |
best_idx = comparison['Score'].idxmax()
|
700 |
self.best_models['overall'] = comparison.loc[best_idx]
|
701 |
|
702 |
-
#
|
703 |
print("\\n" + "="*80)
|
704 |
-
print("📊
|
705 |
print("="*80)
|
706 |
|
707 |
-
print(f"\\n{'Rank':<6} {'
|
708 |
print("-"*80)
|
709 |
|
710 |
for idx, row in comparison.iterrows():
|
@@ -727,369 +912,134 @@ class ComparativeModelAnalyzer:
|
|
727 |
print(f"{'N/A':<10} ", end="")
|
728 |
print(f"{score:<8.4f}")
|
729 |
|
730 |
-
print("\\n🏆
|
731 |
print(f" - R² = {comparison.iloc[0].get(r2_col, 0):.4f}")
|
732 |
print(f" - RMSE = {comparison.iloc[0].get(rmse_col, 0):.4f}")
|
733 |
|
734 |
self.model_rankings = comparison
|
735 |
return comparison
|
736 |
-
|
737 |
-
def analyze_by_category(self, category_col: Optional[str] = None) -> Dict:
|
738 |
-
\"\"\"
|
739 |
-
Analiza modelos por categoría (biomasa, sustrato, producto).
|
740 |
-
Si no hay columna de categoría, intenta inferir del nombre del modelo.
|
741 |
-
\"\"\"
|
742 |
-
if self.results_df is None:
|
743 |
-
raise ValueError("Primero carga los datos")
|
744 |
-
|
745 |
-
categories = {}
|
746 |
-
|
747 |
-
if category_col and category_col in self.results_df.columns:
|
748 |
-
# Usar columna de categoría existente
|
749 |
-
for cat in self.results_df[category_col].unique():
|
750 |
-
cat_data = self.results_df[self.results_df[category_col] == cat]
|
751 |
-
categories[cat] = cat_data
|
752 |
-
else:
|
753 |
-
# Inferir categorías del nombre del modelo
|
754 |
-
biomass_keywords = ['monod', 'logistic', 'gompertz', 'baranyi', 'growth']
|
755 |
-
substrate_keywords = ['michaelis', 'menten', 'substrate', 'consumption']
|
756 |
-
product_keywords = ['luedeking', 'piret', 'product', 'formation']
|
757 |
-
|
758 |
-
for idx, row in self.results_df.iterrows():
|
759 |
-
model_name = str(row.get('Model', '')).lower()
|
760 |
-
|
761 |
-
if any(kw in model_name for kw in biomass_keywords):
|
762 |
-
if 'biomasa' not in categories:
|
763 |
-
categories['biomasa'] = []
|
764 |
-
categories['biomasa'].append(row)
|
765 |
-
elif any(kw in model_name for kw in substrate_keywords):
|
766 |
-
if 'sustrato' not in categories:
|
767 |
-
categories['sustrato'] = []
|
768 |
-
categories['sustrato'].append(row)
|
769 |
-
elif any(kw in model_name for kw in product_keywords):
|
770 |
-
if 'producto' not in categories:
|
771 |
-
categories['producto'] = []
|
772 |
-
categories['producto'].append(row)
|
773 |
-
else:
|
774 |
-
if 'otros' not in categories:
|
775 |
-
categories['otros'] = []
|
776 |
-
categories['otros'].append(row)
|
777 |
-
|
778 |
-
# Convertir listas a DataFrames
|
779 |
-
for cat in categories:
|
780 |
-
if isinstance(categories[cat], list):
|
781 |
-
categories[cat] = pd.DataFrame(categories[cat])
|
782 |
-
|
783 |
-
# Analizar cada categoría
|
784 |
-
print("\\n" + "="*80)
|
785 |
-
print("📈 ANÁLISIS POR CATEGORÍA")
|
786 |
-
print("="*80)
|
787 |
-
|
788 |
-
for cat, data in categories.items():
|
789 |
-
if len(data) > 0:
|
790 |
-
print(f"\\n### {cat.upper()}")
|
791 |
-
print(f"Modelos analizados: {len(data)}")
|
792 |
-
|
793 |
-
if 'R2' in data.columns:
|
794 |
-
best_idx = data['R2'].idxmax()
|
795 |
-
best_model = data.loc[best_idx]
|
796 |
-
|
797 |
-
print(f"Mejor modelo: {best_model.get('Model', 'N/A')}")
|
798 |
-
print(f" - R² = {best_model.get('R2', 0):.4f}")
|
799 |
-
print(f" - RMSE = {best_model.get('RMSE', 0):.4f}")
|
800 |
-
|
801 |
-
self.best_models[cat] = best_model
|
802 |
-
|
803 |
-
return categories
|
804 |
-
|
805 |
-
def plot_comparison(self, save_path: Optional[str] = None) -> plt.Figure:
|
806 |
-
\"\"\"
|
807 |
-
Genera visualizaciones comparativas de los modelos.
|
808 |
-
\"\"\"
|
809 |
-
if self.model_rankings is None:
|
810 |
-
raise ValueError("Primero ejecuta analyze_model_quality()")
|
811 |
-
|
812 |
-
fig = plt.figure(figsize=(16, 10))
|
813 |
-
|
814 |
-
# Configurar grid de subplots
|
815 |
-
gs = fig.add_gridspec(3, 2, height_ratios=[1, 1, 1], hspace=0.3, wspace=0.3)
|
816 |
-
|
817 |
-
# 1. Gráfico de barras R²
|
818 |
-
ax1 = fig.add_subplot(gs[0, 0])
|
819 |
-
models = self.model_rankings.get('Model', range(len(self.model_rankings)))
|
820 |
-
r2_values = self.model_rankings.get('R2', [])
|
821 |
-
|
822 |
-
bars1 = ax1.bar(range(len(models)), r2_values, color='skyblue', edgecolor='navy', alpha=0.7)
|
823 |
-
ax1.set_title('Comparación de R² por Modelo', fontsize=14, fontweight='bold')
|
824 |
-
ax1.set_ylabel('R² (Coeficiente de Determinación)', fontsize=12)
|
825 |
-
ax1.set_ylim(0, 1.05)
|
826 |
-
ax1.set_xticks(range(len(models)))
|
827 |
-
ax1.set_xticklabels(models, rotation=45, ha='right')
|
828 |
-
ax1.grid(True, alpha=0.3)
|
829 |
-
|
830 |
-
# Añadir valores en las barras
|
831 |
-
for bar, val in zip(bars1, r2_values):
|
832 |
-
height = bar.get_height()
|
833 |
-
ax1.text(bar.get_x() + bar.get_width()/2., height + 0.01,
|
834 |
-
f'{val:.3f}', ha='center', va='bottom', fontsize=9)
|
835 |
-
|
836 |
-
# 2. Gráfico de barras RMSE
|
837 |
-
ax2 = fig.add_subplot(gs[0, 1])
|
838 |
-
rmse_values = self.model_rankings.get('RMSE', [])
|
839 |
-
|
840 |
-
bars2 = ax2.bar(range(len(models)), rmse_values, color='lightcoral', edgecolor='darkred', alpha=0.7)
|
841 |
-
ax2.set_title('Comparación de RMSE por Modelo', fontsize=14, fontweight='bold')
|
842 |
-
ax2.set_ylabel('RMSE (Error Cuadrático Medio)', fontsize=12)
|
843 |
-
ax2.set_xticks(range(len(models)))
|
844 |
-
ax2.set_xticklabels(models, rotation=45, ha='right')
|
845 |
-
ax2.grid(True, alpha=0.3)
|
846 |
-
|
847 |
-
# 3. Gráfico de puntuación total
|
848 |
-
ax3 = fig.add_subplot(gs[1, :])
|
849 |
-
scores = self.model_rankings.get('Score', [])
|
850 |
-
rankings = self.model_rankings.get('Ranking', [])
|
851 |
-
|
852 |
-
# Crear gradiente de colores basado en ranking
|
853 |
-
colors = plt.cm.RdYlGn(1 - (rankings - 1) / (len(rankings) - 1))
|
854 |
-
|
855 |
-
bars3 = ax3.bar(range(len(models)), scores, color=colors, edgecolor='black', alpha=0.8)
|
856 |
-
ax3.set_title('Puntuación Total Compuesta (Mayor es Mejor)', fontsize=16, fontweight='bold')
|
857 |
-
ax3.set_ylabel('Puntuación Total', fontsize=12)
|
858 |
-
ax3.set_xticks(range(len(models)))
|
859 |
-
ax3.set_xticklabels(models, rotation=45, ha='right')
|
860 |
-
ax3.grid(True, alpha=0.3)
|
861 |
-
|
862 |
-
# Marcar el mejor modelo
|
863 |
-
best_idx = scores.argmax()
|
864 |
-
bars3[best_idx].set_linewidth(3)
|
865 |
-
bars3[best_idx].set_edgecolor('gold')
|
866 |
-
|
867 |
-
# 4. Tabla de métricas
|
868 |
-
ax4 = fig.add_subplot(gs[2, :])
|
869 |
-
ax4.axis('tight')
|
870 |
-
ax4.axis('off')
|
871 |
-
|
872 |
-
# Preparar datos para la tabla
|
873 |
-
table_data = []
|
874 |
-
for idx, row in self.model_rankings.head(5).iterrows():
|
875 |
-
table_data.append([
|
876 |
-
row.get('Ranking', ''),
|
877 |
-
row.get('Model', '')[:20],
|
878 |
-
f"{row.get('R2', 0):.4f}",
|
879 |
-
f"{row.get('RMSE', 0):.4f}",
|
880 |
-
f"{row.get('AIC', 'N/A'):.2f}" if isinstance(row.get('AIC'), (int, float)) else 'N/A',
|
881 |
-
f"{row.get('BIC', 'N/A'):.2f}" if isinstance(row.get('BIC'), (int, float)) else 'N/A',
|
882 |
-
f"{row.get('Score', 0):.4f}"
|
883 |
-
])
|
884 |
-
|
885 |
-
table = ax4.table(cellText=table_data,
|
886 |
-
colLabels=['Rank', 'Modelo', 'R²', 'RMSE', 'AIC', 'BIC', 'Score'],
|
887 |
-
cellLoc='center',
|
888 |
-
loc='center',
|
889 |
-
colWidths=[0.08, 0.25, 0.12, 0.12, 0.12, 0.12, 0.12])
|
890 |
-
|
891 |
-
table.auto_set_font_size(False)
|
892 |
-
table.set_fontsize(10)
|
893 |
-
table.scale(1.2, 1.5)
|
894 |
-
|
895 |
-
# Colorear la primera fila (mejor modelo)
|
896 |
-
for j in range(len(table_data[0])):
|
897 |
-
table[(1, j)].set_facecolor('#90EE90')
|
898 |
-
|
899 |
-
ax4.set_title('Top 5 Modelos - Tabla Resumen', fontsize=14, fontweight='bold', pad=20)
|
900 |
-
|
901 |
-
plt.suptitle('Análisis Comparativo de Modelos Biotecnológicos', fontsize=18, fontweight='bold')
|
902 |
-
|
903 |
-
if save_path:
|
904 |
-
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
905 |
-
print(f"\\n💾 Gráfico guardado en: {save_path}")
|
906 |
-
|
907 |
-
return fig
|
908 |
-
|
909 |
-
def generate_report(self, output_file: str = 'informe_comparativo.txt'):
|
910 |
-
\"\"\"
|
911 |
-
Genera un informe detallado con todas las conclusiones numéricas.
|
912 |
-
\"\"\"
|
913 |
-
if self.model_rankings is None:
|
914 |
-
raise ValueError("Primero ejecuta analyze_model_quality()")
|
915 |
-
|
916 |
-
report = []
|
917 |
-
report.append("="*80)
|
918 |
-
report.append("INFORME DE ANÁLISIS COMPARATIVO DE MODELOS MATEMÁTICOS")
|
919 |
-
report.append("="*80)
|
920 |
-
report.append(f"\\nFecha: {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
921 |
-
report.append(f"Número de modelos analizados: {len(self.results_df)}")
|
922 |
-
|
923 |
-
# Resumen ejecutivo
|
924 |
-
report.append("\\n" + "-"*40)
|
925 |
-
report.append("RESUMEN EJECUTIVO")
|
926 |
-
report.append("-"*40)
|
927 |
-
|
928 |
-
best_model = self.model_rankings.iloc[0]
|
929 |
-
report.append(f"\\nMEJOR MODELO GLOBAL: {best_model.get('Model', 'N/A')}")
|
930 |
-
report.append(f" - R² = {best_model.get('R2', 0):.4f} (explica {best_model.get('R2', 0)*100:.1f}% de la variabilidad)")
|
931 |
-
report.append(f" - RMSE = {best_model.get('RMSE', 0):.4f}")
|
932 |
-
|
933 |
-
if 'AIC' in best_model:
|
934 |
-
report.append(f" - AIC = {best_model.get('AIC'):.2f}")
|
935 |
-
if 'BIC' in best_model:
|
936 |
-
report.append(f" - BIC = {best_model.get('BIC'):.2f}")
|
937 |
-
|
938 |
-
# Análisis detallado
|
939 |
-
report.append("\\n" + "-"*40)
|
940 |
-
report.append("ANÁLISIS DETALLADO POR MODELO")
|
941 |
-
report.append("-"*40)
|
942 |
-
|
943 |
-
for idx, row in self.model_rankings.iterrows():
|
944 |
-
report.append(f"\\n{row.get('Ranking')}. {row.get('Model', 'N/A')}")
|
945 |
-
report.append(f" Métricas de ajuste:")
|
946 |
-
report.append(f" - R² = {row.get('R2', 0):.4f}")
|
947 |
-
report.append(f" - RMSE = {row.get('RMSE', 0):.4f}")
|
948 |
-
|
949 |
-
# Interpretación
|
950 |
-
r2_val = row.get('R2', 0)
|
951 |
-
if r2_val > 0.95:
|
952 |
-
quality = "EXCELENTE"
|
953 |
-
elif r2_val > 0.90:
|
954 |
-
quality = "MUY BUENO"
|
955 |
-
elif r2_val > 0.80:
|
956 |
-
quality = "BUENO"
|
957 |
-
elif r2_val > 0.70:
|
958 |
-
quality = "ACEPTABLE"
|
959 |
-
else:
|
960 |
-
quality = "POBRE"
|
961 |
-
|
962 |
-
report.append(f" - Calidad del ajuste: {quality}")
|
963 |
-
|
964 |
-
# Análisis por categorías si está disponible
|
965 |
-
if hasattr(self, 'best_models') and len(self.best_models) > 1:
|
966 |
-
report.append("\\n" + "-"*40)
|
967 |
-
report.append("MEJORES MODELOS POR CATEGORÍA")
|
968 |
-
report.append("-"*40)
|
969 |
-
|
970 |
-
for cat, model in self.best_models.items():
|
971 |
-
if cat != 'overall':
|
972 |
-
report.append(f"\\n{cat.upper()}:")
|
973 |
-
report.append(f" Mejor modelo: {model.get('Model', 'N/A')}")
|
974 |
-
report.append(f" - R² = {model.get('R2', 0):.4f}")
|
975 |
-
report.append(f" - RMSE = {model.get('RMSE', 0):.4f}")
|
976 |
-
|
977 |
-
# Recomendaciones
|
978 |
-
report.append("\\n" + "-"*40)
|
979 |
-
report.append("RECOMENDACIONES")
|
980 |
-
report.append("-"*40)
|
981 |
-
|
982 |
-
report.append(f"\\n1. Para predicciones generales, usar: {best_model.get('Model', 'N/A')}")
|
983 |
-
report.append("2. Validar con conjunto de datos independiente")
|
984 |
-
report.append("3. Considerar análisis de residuos")
|
985 |
-
report.append("4. Evaluar estabilidad de parámetros")
|
986 |
-
|
987 |
-
# Guardar informe
|
988 |
-
with open(output_file, 'w', encoding='utf-8') as f:
|
989 |
-
f.write("\\n".join(report))
|
990 |
-
|
991 |
-
print(f"\\n📄 Informe guardado en: {output_file}")
|
992 |
-
|
993 |
-
return "\\n".join(report)
|
994 |
-
|
995 |
-
# Implementación de modelos específicos
|
996 |
-
class BiotechModels:
|
997 |
-
\"\"\"Biblioteca de modelos biotecnológicos comunes\"\"\"
|
998 |
-
|
999 |
-
@staticmethod
|
1000 |
-
def monod(S, mu_max, Ks):
|
1001 |
-
\"\"\"Modelo de Monod para crecimiento\"\"\"
|
1002 |
-
return mu_max * S / (Ks + S)
|
1003 |
-
|
1004 |
-
@staticmethod
|
1005 |
-
def logistic(t, K, r, t0):
|
1006 |
-
\"\"\"Modelo logístico\"\"\"
|
1007 |
-
return K / (1 + np.exp(-r * (t - t0)))
|
1008 |
-
|
1009 |
-
@staticmethod
|
1010 |
-
def gompertz(t, A, mu, lambda_param):
|
1011 |
-
\"\"\"Modelo de Gompertz\"\"\"
|
1012 |
-
return A * np.exp(-np.exp(mu * np.e / A * (lambda_param - t) + 1))
|
1013 |
-
|
1014 |
-
@staticmethod
|
1015 |
-
def michaelis_menten(S, Vmax, Km):
|
1016 |
-
\"\"\"Modelo de Michaelis-Menten\"\"\"
|
1017 |
-
return Vmax * S / (Km + S)
|
1018 |
-
|
1019 |
-
@staticmethod
|
1020 |
-
def luedeking_piret_integrated(t, X0, mu_max, alpha, beta):
|
1021 |
-
\"\"\"Modelo de Luedeking-Piret integrado\"\"\"
|
1022 |
-
X = X0 * np.exp(mu_max * t)
|
1023 |
-
P = alpha * (X - X0) + beta * X0 * (np.exp(mu_max * t) - 1) / mu_max
|
1024 |
-
return P
|
1025 |
|
1026 |
-
#
|
1027 |
if __name__ == "__main__":
|
1028 |
-
print("🧬
|
1029 |
print("="*60)
|
1030 |
|
1031 |
-
#
|
1032 |
analyzer = ComparativeModelAnalyzer()
|
1033 |
|
1034 |
-
#
|
1035 |
-
print("\\n📋
|
1036 |
-
print("1. analyzer.load_results('
|
1037 |
print("2. analyzer.analyze_model_quality()")
|
1038 |
-
print("3. analyzer.
|
1039 |
-
print("4. analyzer.
|
1040 |
-
print("5. analyzer.generate_report()")
|
1041 |
|
1042 |
-
print("\\n✨
|
1043 |
"""
|
1044 |
|
1045 |
return code
|
1046 |
|
1047 |
-
#
|
1048 |
-
|
1049 |
-
|
1050 |
-
|
1051 |
-
|
1052 |
-
|
1053 |
-
|
1054 |
-
|
1055 |
-
|
1056 |
-
|
1057 |
-
|
1058 |
-
|
1059 |
-
|
1060 |
-
|
|
|
|
|
|
|
|
|
1061 |
}
|
1062 |
-
.
|
1063 |
-
|
1064 |
-
|
1065 |
-
|
1066 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1067 |
}
|
1068 |
-
|
1069 |
-
|
1070 |
-
|
1071 |
-
|
1072 |
-
|
1073 |
-
|
1074 |
-
|
1075 |
-
|
1076 |
-
|
1077 |
-
|
1078 |
-
|
1079 |
-
|
1080 |
-
|
1081 |
-
|
1082 |
-
|
1083 |
-
|
1084 |
-
|
1085 |
-
|
1086 |
-
|
1087 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1088 |
|
1089 |
with gr.Row():
|
1090 |
with gr.Column(scale=1):
|
1091 |
files_input = gr.File(
|
1092 |
-
label=
|
1093 |
file_count="multiple",
|
1094 |
file_types=[".csv", ".xlsx", ".xls", ".pdf", ".zip"],
|
1095 |
type="filepath"
|
@@ -1098,138 +1048,140 @@ def create_interface():
|
|
1098 |
model_selector = gr.Dropdown(
|
1099 |
choices=list(CLAUDE_MODELS.keys()),
|
1100 |
value="claude-3-5-sonnet-20241022",
|
1101 |
-
label=
|
1102 |
-
info="
|
1103 |
)
|
1104 |
|
1105 |
detail_level = gr.Radio(
|
1106 |
-
choices=[
|
1107 |
-
|
1108 |
-
|
1109 |
-
|
|
|
|
|
1110 |
)
|
1111 |
|
1112 |
analyze_btn = gr.Button(
|
1113 |
-
|
1114 |
variant="primary",
|
1115 |
size="lg"
|
1116 |
)
|
1117 |
|
1118 |
-
|
1119 |
-
model_info = gr.Markdown()
|
1120 |
|
1121 |
-
|
1122 |
-
|
1123 |
-
|
1124 |
-
|
1125 |
-
|
1126 |
-
{info['description']}
|
1127 |
-
|
1128 |
-
*Mejor para: {info['best_for']}*
|
1129 |
-
"""
|
1130 |
|
1131 |
-
|
1132 |
-
|
1133 |
-
|
1134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1135 |
)
|
1136 |
|
1137 |
with gr.Column(scale=2):
|
1138 |
analysis_output = gr.Markdown(
|
1139 |
-
label=
|
1140 |
-
elem_classes=["highlight-results"]
|
1141 |
)
|
1142 |
|
1143 |
code_output = gr.Code(
|
1144 |
-
label=
|
1145 |
language="python",
|
1146 |
interactive=True,
|
1147 |
lines=20
|
1148 |
)
|
1149 |
|
1150 |
-
|
1151 |
-
|
|
|
|
|
|
|
|
|
1152 |
gr.Markdown("""
|
1153 |
-
###
|
1154 |
|
1155 |
| Model | R2 | RMSE | AIC | BIC | mu_max | Ks | Parameters |
|
1156 |
|-------|-----|------|-----|-----|--------|-------|------------|
|
1157 |
| Monod | 0.985 | 0.023 | -45.2 | -42.1 | 0.45 | 2.1 | {...} |
|
1158 |
| Logistic | 0.976 | 0.031 | -42.1 | -39.5 | 0.42 | - | {...} |
|
1159 |
| Gompertz | 0.992 | 0.018 | -48.5 | -45.2 | 0.48 | - | {...} |
|
1160 |
-
|
1161 |
-
**Columnas mínimas requeridas:**
|
1162 |
-
- `Model` o `Modelo`: Nombre del modelo
|
1163 |
-
- `R2` o `R_squared`: Coeficiente de determinación
|
1164 |
-
- `RMSE` o `MSE`: Error de ajuste
|
1165 |
-
|
1166 |
-
**Columnas opcionales pero recomendadas:**
|
1167 |
-
- `AIC`, `BIC`: Criterios de información
|
1168 |
-
- Parámetros específicos del modelo
|
1169 |
-
- `SE` o `Standard_Error`: Errores estándar
|
1170 |
-
- `CI_lower`, `CI_upper`: Intervalos de confianza
|
1171 |
""")
|
1172 |
|
1173 |
-
|
1174 |
-
gr.Examples(
|
1175 |
examples=[
|
1176 |
-
[["examples/biomass_models_comparison.csv"], "claude-3-5-sonnet-20241022", "
|
1177 |
-
[["examples/substrate_kinetics_results.xlsx"], "claude-3-5-sonnet-20241022", "
|
1178 |
-
[["examples/product_formation_fits.csv"], "claude-3-opus-20240229", "detallado"]
|
1179 |
],
|
1180 |
inputs=[files_input, model_selector, detail_level],
|
1181 |
-
label=
|
1182 |
)
|
1183 |
|
1184 |
# Eventos
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1185 |
analyze_btn.click(
|
1186 |
-
fn=
|
1187 |
-
|
1188 |
-
""
|
1189 |
-
),
|
1190 |
-
inputs=[files_input, model_selector, detail_level],
|
1191 |
outputs=[analysis_output, code_output]
|
1192 |
)
|
1193 |
|
1194 |
-
|
1195 |
-
|
1196 |
-
|
1197 |
-
|
1198 |
-
|
1199 |
-
|
1200 |
-
|
1201 |
-
|
1202 |
-
|
1203 |
-
|
1204 |
-
|
1205 |
-
4. **Significado biológico**: Interpretación de parámetros
|
1206 |
-
5. **Conclusiones numéricas**: Valores óptimos, rangos de operación
|
1207 |
-
6. **Código Python**: Implementación lista para usar
|
1208 |
-
|
1209 |
-
### 💡 Tips para mejores resultados:
|
1210 |
-
- Incluye todas las métricas de ajuste disponibles
|
1211 |
-
- Usa nombres descriptivos para los modelos
|
1212 |
-
- Incluye errores estándar si están disponibles
|
1213 |
-
- Añade información de condiciones experimentales si es relevante
|
1214 |
-
""")
|
1215 |
-
|
1216 |
-
# Cargar info inicial del modelo
|
1217 |
-
demo.load(
|
1218 |
-
fn=lambda: update_model_info("claude-3-5-sonnet-20241022"),
|
1219 |
-
outputs=[model_info]
|
1220 |
)
|
1221 |
|
1222 |
return demo
|
1223 |
|
1224 |
-
# Función principal
|
1225 |
def main():
|
1226 |
if not os.getenv("ANTHROPIC_API_KEY"):
|
1227 |
-
print("⚠️
|
1228 |
return gr.Interface(
|
1229 |
-
fn=lambda x:
|
1230 |
inputs=gr.Textbox(),
|
1231 |
outputs=gr.Textbox(),
|
1232 |
-
title="Error
|
1233 |
)
|
1234 |
|
1235 |
return create_interface()
|
|
|
8 |
import json
|
9 |
import zipfile
|
10 |
import tempfile
|
11 |
+
from typing import Dict, List, Tuple, Union, Optional
|
12 |
import re
|
13 |
from pathlib import Path
|
14 |
import openpyxl
|
15 |
from dataclasses import dataclass
|
16 |
from enum import Enum
|
17 |
+
from docx import Document
|
18 |
+
from docx.shared import Inches, Pt, RGBColor
|
19 |
+
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
20 |
+
from reportlab.lib import colors
|
21 |
+
from reportlab.lib.pagesizes import letter, A4
|
22 |
+
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, PageBreak
|
23 |
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
24 |
+
from reportlab.lib.units import inch
|
25 |
+
from reportlab.pdfbase import pdfmetrics
|
26 |
+
from reportlab.pdfbase.ttfonts import TTFont
|
27 |
+
import matplotlib.pyplot as plt
|
28 |
+
from datetime import datetime
|
29 |
|
30 |
# Configuración para HuggingFace
|
31 |
os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
|
|
|
33 |
# Inicializar cliente Anthropic
|
34 |
client = anthropic.Anthropic()
|
35 |
|
36 |
+
# Sistema de traducción
|
37 |
+
TRANSLATIONS = {
|
38 |
+
'en': {
|
39 |
+
'title': '🧬 Comparative Analyzer of Biotechnological Models',
|
40 |
+
'subtitle': 'Specialized in comparative analysis of mathematical model fitting results',
|
41 |
+
'upload_files': '📁 Upload fitting results (CSV/Excel)',
|
42 |
+
'select_model': '🤖 Claude Model',
|
43 |
+
'select_language': '🌐 Language',
|
44 |
+
'select_theme': '🎨 Theme',
|
45 |
+
'detail_level': '📋 Analysis detail level',
|
46 |
+
'detailed': 'Detailed',
|
47 |
+
'summarized': 'Summarized',
|
48 |
+
'analyze_button': '🚀 Analyze and Compare Models',
|
49 |
+
'export_format': '📄 Export format',
|
50 |
+
'export_button': '💾 Export Report',
|
51 |
+
'comparative_analysis': '📊 Comparative Analysis',
|
52 |
+
'implementation_code': '💻 Implementation Code',
|
53 |
+
'data_format': '📋 Expected data format',
|
54 |
+
'examples': '📚 Analysis examples',
|
55 |
+
'light': 'Light',
|
56 |
+
'dark': 'Dark',
|
57 |
+
'best_for': 'Best for',
|
58 |
+
'loading': 'Loading...',
|
59 |
+
'error_no_api': 'Please configure ANTHROPIC_API_KEY in HuggingFace Space secrets',
|
60 |
+
'error_no_files': 'Please upload fitting result files to analyze',
|
61 |
+
'report_exported': 'Report exported successfully as',
|
62 |
+
'specialized_in': '🎯 Specialized in:',
|
63 |
+
'metrics_analyzed': '📊 Analyzed metrics:',
|
64 |
+
'what_analyzes': '🔍 What it specifically analyzes:',
|
65 |
+
'tips': '💡 Tips for better results:'
|
66 |
+
},
|
67 |
+
'es': {
|
68 |
+
'title': '🧬 Analizador Comparativo de Modelos Biotecnológicos',
|
69 |
+
'subtitle': 'Especializado en análisis comparativo de resultados de ajuste de modelos matemáticos',
|
70 |
+
'upload_files': '📁 Subir resultados de ajuste (CSV/Excel)',
|
71 |
+
'select_model': '🤖 Modelo Claude',
|
72 |
+
'select_language': '🌐 Idioma',
|
73 |
+
'select_theme': '🎨 Tema',
|
74 |
+
'detail_level': '📋 Nivel de detalle del análisis',
|
75 |
+
'detailed': 'Detallado',
|
76 |
+
'summarized': 'Resumido',
|
77 |
+
'analyze_button': '🚀 Analizar y Comparar Modelos',
|
78 |
+
'export_format': '📄 Formato de exportación',
|
79 |
+
'export_button': '💾 Exportar Reporte',
|
80 |
+
'comparative_analysis': '📊 Análisis Comparativo',
|
81 |
+
'implementation_code': '💻 Código de Implementación',
|
82 |
+
'data_format': '📋 Formato de datos esperado',
|
83 |
+
'examples': '📚 Ejemplos de análisis',
|
84 |
+
'light': 'Claro',
|
85 |
+
'dark': 'Oscuro',
|
86 |
+
'best_for': 'Mejor para',
|
87 |
+
'loading': 'Cargando...',
|
88 |
+
'error_no_api': 'Por favor configura ANTHROPIC_API_KEY en los secretos del Space',
|
89 |
+
'error_no_files': 'Por favor sube archivos con resultados de ajuste para analizar',
|
90 |
+
'report_exported': 'Reporte exportado exitosamente como',
|
91 |
+
'specialized_in': '🎯 Especializado en:',
|
92 |
+
'metrics_analyzed': '📊 Métricas analizadas:',
|
93 |
+
'what_analyzes': '🔍 Qué analiza específicamente:',
|
94 |
+
'tips': '💡 Tips para mejores resultados:'
|
95 |
+
},
|
96 |
+
'fr': {
|
97 |
+
'title': '🧬 Analyseur Comparatif de Modèles Biotechnologiques',
|
98 |
+
'subtitle': 'Spécialisé dans l\'analyse comparative des résultats d\'ajustement',
|
99 |
+
'upload_files': '📁 Télécharger les résultats (CSV/Excel)',
|
100 |
+
'select_model': '🤖 Modèle Claude',
|
101 |
+
'select_language': '🌐 Langue',
|
102 |
+
'select_theme': '🎨 Thème',
|
103 |
+
'detail_level': '📋 Niveau de détail',
|
104 |
+
'detailed': 'Détaillé',
|
105 |
+
'summarized': 'Résumé',
|
106 |
+
'analyze_button': '🚀 Analyser et Comparer',
|
107 |
+
'export_format': '📄 Format d\'export',
|
108 |
+
'export_button': '💾 Exporter le Rapport',
|
109 |
+
'comparative_analysis': '📊 Analyse Comparative',
|
110 |
+
'implementation_code': '💻 Code d\'Implémentation',
|
111 |
+
'data_format': '📋 Format de données attendu',
|
112 |
+
'examples': '📚 Exemples d\'analyse',
|
113 |
+
'light': 'Clair',
|
114 |
+
'dark': 'Sombre',
|
115 |
+
'best_for': 'Meilleur pour',
|
116 |
+
'loading': 'Chargement...',
|
117 |
+
'error_no_api': 'Veuillez configurer ANTHROPIC_API_KEY',
|
118 |
+
'error_no_files': 'Veuillez télécharger des fichiers à analyser',
|
119 |
+
'report_exported': 'Rapport exporté avec succès comme',
|
120 |
+
'specialized_in': '🎯 Spécialisé dans:',
|
121 |
+
'metrics_analyzed': '📊 Métriques analysées:',
|
122 |
+
'what_analyzes': '🔍 Ce qu\'il analyse spécifiquement:',
|
123 |
+
'tips': '💡 Conseils pour de meilleurs résultats:'
|
124 |
+
},
|
125 |
+
'de': {
|
126 |
+
'title': '🧬 Vergleichender Analysator für Biotechnologische Modelle',
|
127 |
+
'subtitle': 'Spezialisiert auf vergleichende Analyse von Modellanpassungsergebnissen',
|
128 |
+
'upload_files': '📁 Ergebnisse hochladen (CSV/Excel)',
|
129 |
+
'select_model': '🤖 Claude Modell',
|
130 |
+
'select_language': '🌐 Sprache',
|
131 |
+
'select_theme': '🎨 Thema',
|
132 |
+
'detail_level': '📋 Detailgrad der Analyse',
|
133 |
+
'detailed': 'Detailliert',
|
134 |
+
'summarized': 'Zusammengefasst',
|
135 |
+
'analyze_button': '🚀 Analysieren und Vergleichen',
|
136 |
+
'export_format': '📄 Exportformat',
|
137 |
+
'export_button': '💾 Bericht Exportieren',
|
138 |
+
'comparative_analysis': '📊 Vergleichende Analyse',
|
139 |
+
'implementation_code': '💻 Implementierungscode',
|
140 |
+
'data_format': '📋 Erwartetes Datenformat',
|
141 |
+
'examples': '📚 Analysebeispiele',
|
142 |
+
'light': 'Hell',
|
143 |
+
'dark': 'Dunkel',
|
144 |
+
'best_for': 'Am besten für',
|
145 |
+
'loading': 'Laden...',
|
146 |
+
'error_no_api': 'Bitte konfigurieren Sie ANTHROPIC_API_KEY',
|
147 |
+
'error_no_files': 'Bitte laden Sie Dateien zur Analyse hoch',
|
148 |
+
'report_exported': 'Bericht erfolgreich exportiert als',
|
149 |
+
'specialized_in': '🎯 Spezialisiert auf:',
|
150 |
+
'metrics_analyzed': '📊 Analysierte Metriken:',
|
151 |
+
'what_analyzes': '🔍 Was spezifisch analysiert wird:',
|
152 |
+
'tips': '💡 Tipps für bessere Ergebnisse:'
|
153 |
+
},
|
154 |
+
'pt': {
|
155 |
+
'title': '🧬 Analisador Comparativo de Modelos Biotecnológicos',
|
156 |
+
'subtitle': 'Especializado em análise comparativa de resultados de ajuste',
|
157 |
+
'upload_files': '📁 Carregar resultados (CSV/Excel)',
|
158 |
+
'select_model': '🤖 Modelo Claude',
|
159 |
+
'select_language': '🌐 Idioma',
|
160 |
+
'select_theme': '🎨 Tema',
|
161 |
+
'detail_level': '📋 Nível de detalhe',
|
162 |
+
'detailed': 'Detalhado',
|
163 |
+
'summarized': 'Resumido',
|
164 |
+
'analyze_button': '🚀 Analisar e Comparar',
|
165 |
+
'export_format': '📄 Formato de exportação',
|
166 |
+
'export_button': '💾 Exportar Relatório',
|
167 |
+
'comparative_analysis': '📊 Análise Comparativa',
|
168 |
+
'implementation_code': '💻 Código de Implementação',
|
169 |
+
'data_format': '📋 Formato de dados esperado',
|
170 |
+
'examples': '📚 Exemplos de análise',
|
171 |
+
'light': 'Claro',
|
172 |
+
'dark': 'Escuro',
|
173 |
+
'best_for': 'Melhor para',
|
174 |
+
'loading': 'Carregando...',
|
175 |
+
'error_no_api': 'Por favor configure ANTHROPIC_API_KEY',
|
176 |
+
'error_no_files': 'Por favor carregue arquivos para analisar',
|
177 |
+
'report_exported': 'Relatório exportado com sucesso como',
|
178 |
+
'specialized_in': '🎯 Especializado em:',
|
179 |
+
'metrics_analyzed': '📊 Métricas analisadas:',
|
180 |
+
'what_analyzes': '🔍 O que analisa especificamente:',
|
181 |
+
'tips': '💡 Dicas para melhores resultados:'
|
182 |
+
}
|
183 |
+
}
|
184 |
+
|
185 |
+
# Temas disponibles
|
186 |
+
THEMES = {
|
187 |
+
'light': gr.themes.Soft(),
|
188 |
+
'dark': gr.themes.Base(
|
189 |
+
primary_hue="blue",
|
190 |
+
secondary_hue="gray",
|
191 |
+
neutral_hue="gray",
|
192 |
+
font=["Arial", "sans-serif"]
|
193 |
+
).set(
|
194 |
+
body_background_fill="dark",
|
195 |
+
body_background_fill_dark="*neutral_950",
|
196 |
+
button_primary_background_fill="*primary_600",
|
197 |
+
button_primary_background_fill_hover="*primary_500",
|
198 |
+
button_primary_text_color="white",
|
199 |
+
block_background_fill="*neutral_800",
|
200 |
+
block_border_color="*neutral_700",
|
201 |
+
block_label_text_color="*neutral_200",
|
202 |
+
block_title_text_color="*neutral_100",
|
203 |
+
checkbox_background_color="*neutral_700",
|
204 |
+
checkbox_background_color_selected="*primary_600",
|
205 |
+
input_background_fill="*neutral_700",
|
206 |
+
input_border_color="*neutral_600",
|
207 |
+
input_placeholder_color="*neutral_400"
|
208 |
+
)
|
209 |
+
}
|
210 |
+
|
211 |
# Enum para tipos de análisis
|
212 |
class AnalysisType(Enum):
|
213 |
MATHEMATICAL_MODEL = "mathematical_model"
|
|
|
278 |
category="crecimiento_biomasa",
|
279 |
biological_meaning="Incluye fase de adaptación (lag) seguida de crecimiento exponencial y estacionario"
|
280 |
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
281 |
|
282 |
# Instancia global del registro
|
283 |
model_registry = ModelRegistry()
|
|
|
286 |
CLAUDE_MODELS = {
|
287 |
"claude-3-5-sonnet-20241022": {
|
288 |
"name": "Claude 3.5 Sonnet",
|
289 |
+
"description": "Fast and efficient model",
|
290 |
"max_tokens": 4000,
|
291 |
+
"best_for": "General analysis"
|
292 |
},
|
293 |
"claude-3-opus-20240229": {
|
294 |
"name": "Claude 3 Opus",
|
295 |
+
"description": "Most powerful model",
|
296 |
"max_tokens": 4000,
|
297 |
+
"best_for": "Complex analysis"
|
298 |
},
|
299 |
"claude-3-haiku-20240307": {
|
300 |
"name": "Claude 3 Haiku",
|
301 |
+
"description": "Fastest model",
|
302 |
"max_tokens": 4000,
|
303 |
+
"best_for": "Quick analysis"
|
304 |
}
|
305 |
}
|
306 |
|
|
|
317 |
text += page.extract_text() + "\n"
|
318 |
return text
|
319 |
except Exception as e:
|
320 |
+
return f"Error reading PDF: {str(e)}"
|
321 |
|
322 |
@staticmethod
|
323 |
def read_csv(csv_file) -> pd.DataFrame:
|
|
|
346 |
file_data = zip_ref.read(file_name)
|
347 |
files.append((file_name, file_data))
|
348 |
except Exception as e:
|
349 |
+
print(f"Error processing ZIP: {e}")
|
350 |
return files
|
351 |
|
352 |
+
class ReportExporter:
|
353 |
+
"""Clase para exportar reportes a diferentes formatos"""
|
354 |
+
|
355 |
+
@staticmethod
|
356 |
+
def export_to_docx(content: str, filename: str, language: str = 'en') -> str:
|
357 |
+
"""Exporta el contenido a un archivo DOCX"""
|
358 |
+
doc = Document()
|
359 |
+
|
360 |
+
# Configurar estilos
|
361 |
+
title_style = doc.styles['Title']
|
362 |
+
title_style.font.size = Pt(24)
|
363 |
+
title_style.font.bold = True
|
364 |
+
|
365 |
+
heading_style = doc.styles['Heading 1']
|
366 |
+
heading_style.font.size = Pt(18)
|
367 |
+
heading_style.font.bold = True
|
368 |
+
|
369 |
+
# Título
|
370 |
+
title_text = {
|
371 |
+
'en': 'Comparative Analysis Report - Biotechnological Models',
|
372 |
+
'es': 'Informe de Análisis Comparativo - Modelos Biotecnológicos',
|
373 |
+
'fr': 'Rapport d\'Analyse Comparative - Modèles Biotechnologiques',
|
374 |
+
'de': 'Vergleichsanalysebericht - Biotechnologische Modelle',
|
375 |
+
'pt': 'Relatório de Análise Comparativa - Modelos Biotecnológicos'
|
376 |
+
}
|
377 |
+
|
378 |
+
doc.add_heading(title_text.get(language, title_text['en']), 0)
|
379 |
+
|
380 |
+
# Fecha
|
381 |
+
date_text = {
|
382 |
+
'en': 'Generated on',
|
383 |
+
'es': 'Generado el',
|
384 |
+
'fr': 'Généré le',
|
385 |
+
'de': 'Erstellt am',
|
386 |
+
'pt': 'Gerado em'
|
387 |
+
}
|
388 |
+
doc.add_paragraph(f"{date_text.get(language, date_text['en'])}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
389 |
+
doc.add_paragraph()
|
390 |
+
|
391 |
+
# Procesar contenido
|
392 |
+
lines = content.split('\n')
|
393 |
+
current_paragraph = None
|
394 |
+
|
395 |
+
for line in lines:
|
396 |
+
line = line.strip()
|
397 |
+
|
398 |
+
if line.startswith('###'):
|
399 |
+
doc.add_heading(line.replace('###', '').strip(), level=2)
|
400 |
+
elif line.startswith('##'):
|
401 |
+
doc.add_heading(line.replace('##', '').strip(), level=1)
|
402 |
+
elif line.startswith('#'):
|
403 |
+
doc.add_heading(line.replace('#', '').strip(), level=0)
|
404 |
+
elif line.startswith('**') and line.endswith('**'):
|
405 |
+
# Texto en negrita
|
406 |
+
p = doc.add_paragraph()
|
407 |
+
run = p.add_run(line.replace('**', ''))
|
408 |
+
run.bold = True
|
409 |
+
elif line.startswith('- ') or line.startswith('* '):
|
410 |
+
# Lista
|
411 |
+
doc.add_paragraph(line[2:], style='List Bullet')
|
412 |
+
elif line.startswith(tuple('0123456789')):
|
413 |
+
# Lista numerada
|
414 |
+
doc.add_paragraph(line, style='List Number')
|
415 |
+
elif line == '---' or line.startswith('==='):
|
416 |
+
# Separador
|
417 |
+
doc.add_paragraph('_' * 50)
|
418 |
+
elif line:
|
419 |
+
# Párrafo normal
|
420 |
+
doc.add_paragraph(line)
|
421 |
+
|
422 |
+
# Guardar documento
|
423 |
+
doc.save(filename)
|
424 |
+
return filename
|
425 |
+
|
426 |
+
@staticmethod
|
427 |
+
def export_to_pdf(content: str, filename: str, language: str = 'en') -> str:
|
428 |
+
"""Exporta el contenido a un archivo PDF"""
|
429 |
+
# Crear documento PDF
|
430 |
+
doc = SimpleDocTemplate(filename, pagesize=letter)
|
431 |
+
story = []
|
432 |
+
styles = getSampleStyleSheet()
|
433 |
+
|
434 |
+
# Estilos personalizados
|
435 |
+
title_style = ParagraphStyle(
|
436 |
+
'CustomTitle',
|
437 |
+
parent=styles['Title'],
|
438 |
+
fontSize=24,
|
439 |
+
textColor=colors.HexColor('#1f4788'),
|
440 |
+
spaceAfter=30
|
441 |
+
)
|
442 |
+
|
443 |
+
heading_style = ParagraphStyle(
|
444 |
+
'CustomHeading',
|
445 |
+
parent=styles['Heading1'],
|
446 |
+
fontSize=16,
|
447 |
+
textColor=colors.HexColor('#2e5090'),
|
448 |
+
spaceAfter=12
|
449 |
+
)
|
450 |
+
|
451 |
+
# Título
|
452 |
+
title_text = {
|
453 |
+
'en': 'Comparative Analysis Report - Biotechnological Models',
|
454 |
+
'es': 'Informe de Análisis Comparativo - Modelos Biotecnológicos',
|
455 |
+
'fr': 'Rapport d\'Analyse Comparative - Modèles Biotechnologiques',
|
456 |
+
'de': 'Vergleichsanalysebericht - Biotechnologische Modelle',
|
457 |
+
'pt': 'Relatório de Análise Comparativa - Modelos Biotecnológicos'
|
458 |
+
}
|
459 |
+
|
460 |
+
story.append(Paragraph(title_text.get(language, title_text['en']), title_style))
|
461 |
+
|
462 |
+
# Fecha
|
463 |
+
date_text = {
|
464 |
+
'en': 'Generated on',
|
465 |
+
'es': 'Generado el',
|
466 |
+
'fr': 'Généré le',
|
467 |
+
'de': 'Erstellt am',
|
468 |
+
'pt': 'Gerado em'
|
469 |
+
}
|
470 |
+
story.append(Paragraph(f"{date_text.get(language, date_text['en'])}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", styles['Normal']))
|
471 |
+
story.append(Spacer(1, 0.5*inch))
|
472 |
+
|
473 |
+
# Procesar contenido
|
474 |
+
lines = content.split('\n')
|
475 |
+
|
476 |
+
for line in lines:
|
477 |
+
line = line.strip()
|
478 |
+
|
479 |
+
if not line:
|
480 |
+
story.append(Spacer(1, 0.2*inch))
|
481 |
+
elif line.startswith('###'):
|
482 |
+
story.append(Paragraph(line.replace('###', '').strip(), styles['Heading3']))
|
483 |
+
elif line.startswith('##'):
|
484 |
+
story.append(Paragraph(line.replace('##', '').strip(), styles['Heading2']))
|
485 |
+
elif line.startswith('#'):
|
486 |
+
story.append(Paragraph(line.replace('#', '').strip(), heading_style))
|
487 |
+
elif line.startswith('**') and line.endswith('**'):
|
488 |
+
text = line.replace('**', '')
|
489 |
+
story.append(Paragraph(f"<b>{text}</b>", styles['Normal']))
|
490 |
+
elif line.startswith('- ') or line.startswith('* '):
|
491 |
+
story.append(Paragraph(f"• {line[2:]}", styles['Normal']))
|
492 |
+
elif line == '---' or line.startswith('==='):
|
493 |
+
story.append(Spacer(1, 0.3*inch))
|
494 |
+
story.append(Paragraph("_" * 70, styles['Normal']))
|
495 |
+
story.append(Spacer(1, 0.3*inch))
|
496 |
+
else:
|
497 |
+
# Limpiar caracteres especiales para PDF
|
498 |
+
clean_line = line.replace('📊', '[GRAPH]').replace('🎯', '[TARGET]').replace('🔍', '[SEARCH]').replace('💡', '[TIP]')
|
499 |
+
story.append(Paragraph(clean_line, styles['Normal']))
|
500 |
+
|
501 |
+
# Construir PDF
|
502 |
+
doc.build(story)
|
503 |
+
return filename
|
504 |
+
|
505 |
class AIAnalyzer:
|
506 |
"""Clase para análisis con IA"""
|
507 |
|
|
|
529 |
return AnalysisType.DATA_FITTING
|
530 |
|
531 |
prompt = """
|
532 |
+
Analyze this content and determine if it is:
|
533 |
+
1. A scientific article describing biotechnological mathematical models
|
534 |
+
2. Experimental data for parameter fitting
|
535 |
+
3. Model fitting results (with parameters, R², RMSE, etc.)
|
536 |
|
537 |
+
Reply only with: "MODEL", "DATA" or "RESULTS"
|
538 |
"""
|
539 |
|
540 |
try:
|
|
|
545 |
)
|
546 |
|
547 |
result = response.content[0].text.strip().upper()
|
548 |
+
if "MODEL" in result:
|
549 |
return AnalysisType.MATHEMATICAL_MODEL
|
550 |
+
elif "RESULTS" in result:
|
551 |
return AnalysisType.FITTING_RESULTS
|
552 |
+
elif "DATA" in result:
|
553 |
return AnalysisType.DATA_FITTING
|
554 |
else:
|
555 |
return AnalysisType.UNKNOWN
|
|
|
557 |
except:
|
558 |
return AnalysisType.UNKNOWN
|
559 |
|
560 |
+
def get_language_prompt_prefix(self, language: str) -> str:
|
561 |
+
"""Obtiene el prefijo del prompt según el idioma"""
|
562 |
+
prefixes = {
|
563 |
+
'en': "Please respond in English. ",
|
564 |
+
'es': "Por favor responde en español. ",
|
565 |
+
'fr': "Veuillez répondre en français. ",
|
566 |
+
'de': "Bitte antworten Sie auf Deutsch. ",
|
567 |
+
'pt': "Por favor responda em português. "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
568 |
}
|
569 |
+
return prefixes.get(language, prefixes['en'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
570 |
|
571 |
+
def analyze_fitting_results(self, data: pd.DataFrame, claude_model: str, detail_level: str = "detailed", language: str = "en") -> Dict:
|
572 |
+
"""Analiza resultados de ajuste de modelos con soporte multiidioma"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
573 |
|
574 |
# Preparar resumen completo de los datos
|
575 |
data_summary = f"""
|
576 |
+
FITTING RESULTS DATA:
|
577 |
|
578 |
+
Data structure:
|
579 |
+
- Columns: {list(data.columns)}
|
580 |
+
- Number of models evaluated: {len(data)}
|
581 |
|
582 |
+
Complete data:
|
583 |
{data.to_string()}
|
584 |
|
585 |
+
Descriptive statistics:
|
586 |
{data.describe().to_string()}
|
587 |
"""
|
588 |
|
589 |
+
# Obtener prefijo de idioma
|
590 |
+
lang_prefix = self.get_language_prompt_prefix(language)
|
591 |
+
|
592 |
+
# Prompt mejorado con soporte de idioma
|
593 |
prompt = f"""
|
594 |
+
{lang_prefix}
|
595 |
+
|
596 |
+
You are an expert in biotechnology and mathematical modeling. Analyze these kinetic/biotechnological model fitting results.
|
597 |
+
|
598 |
+
REQUESTED DETAIL LEVEL: {detail_level}
|
599 |
+
|
600 |
+
PERFORM A COMPREHENSIVE COMPARATIVE ANALYSIS:
|
601 |
+
|
602 |
+
1. **MODEL IDENTIFICATION AND CLASSIFICATION**
|
603 |
+
- Identify ALL fitted mathematical models
|
604 |
+
- Classify them by type: biomass, substrate, product
|
605 |
+
- Indicate the mathematical equation of each model if possible
|
606 |
+
|
607 |
+
2. **COMPARATIVE ANALYSIS OF FIT QUALITY**
|
608 |
+
- Compare ALL available indicators: R², RMSE, AIC, BIC, etc.
|
609 |
+
- Create a ranking from best to worst model
|
610 |
+
- Identify significant differences between models
|
611 |
+
- Detect possible overfitting or underfitting
|
612 |
+
|
613 |
+
3. **DETERMINATION OF THE BEST MODEL**
|
614 |
+
- Select the BEST model based on MULTIPLE criteria:
|
615 |
+
* Highest R² (closest to 1)
|
616 |
+
* Lowest RMSE/MSE
|
617 |
+
* Lowest AIC/BIC (if available)
|
618 |
+
* Parsimony (fewer parameters if fit is similar)
|
619 |
+
- Justify NUMERICALLY why it is the best
|
620 |
+
- If there's a technical tie, explain the advantages of each
|
621 |
+
|
622 |
+
4. **SPECIFIC ANALYSIS BY VARIABLE TYPE**
|
623 |
+
a) **BIOMASS (if applicable)**:
|
624 |
+
- Growth parameters (μmax, Xmax, etc.)
|
625 |
+
- Doubling time
|
626 |
+
- Biomass productivity
|
627 |
+
- Numerical comparison between models
|
628 |
|
629 |
+
b) **SUBSTRATE (if applicable)**:
|
630 |
+
- Affinity constants (Ks, Km)
|
631 |
+
- Consumption rates
|
632 |
+
- Yield Yx/s
|
633 |
+
- Utilization efficiency
|
634 |
|
635 |
+
c) **PRODUCT (if applicable)**:
|
636 |
+
- Production parameters (α, β)
|
637 |
+
- Specific productivity
|
638 |
+
- Yield Yp/x
|
639 |
+
- Production type (associated/non-associated)
|
640 |
+
|
641 |
+
5. **BIOLOGICAL INTERPRETATION OF PARAMETERS**
|
642 |
+
- Explain what EACH parameter means biologically
|
643 |
+
- Compare values between models
|
644 |
+
- Evaluate if they are realistic for the system
|
645 |
+
- Identify critical process parameters
|
646 |
+
|
647 |
+
6. **CONCLUSIONS WITH NUMERICAL CONTENT**
|
648 |
+
- Summarize key findings with SPECIFIC NUMBERS
|
649 |
+
- Provide confidence intervals if available
|
650 |
+
- Indicate optimal operating conditions
|
651 |
+
- Suggest design values for scale-up
|
652 |
+
|
653 |
+
7. **PRACTICAL RECOMMENDATIONS**
|
654 |
+
- Which model(s) to use for prediction
|
655 |
+
- Limitations of the selected model
|
656 |
+
- Recommended additional experiments
|
657 |
+
- Considerations for industrial implementation
|
658 |
+
|
659 |
+
8. **FINAL COMPARATIVE TABLE**
|
660 |
+
Create a summary table with:
|
661 |
+
- Model | R² | RMSE | AIC/BIC | Key Parameters | Ranking
|
662 |
+
|
663 |
+
RESPONSE FORMAT:
|
664 |
+
- If level is "detailed": include ALL points with complete explanations
|
665 |
+
- If level is "summarized": focus on points 3, 6 and 8 with key numerical values
|
666 |
+
|
667 |
+
Use Markdown format with:
|
668 |
+
- Clear titles and subtitles
|
669 |
+
- **Bold** for important values
|
670 |
+
- Tables when appropriate
|
671 |
+
- Numbered and bulleted lists
|
672 |
+
|
673 |
+
IMPORTANT: Base ALL conclusions on the SPECIFIC NUMBERS from the provided data.
|
674 |
"""
|
675 |
|
676 |
try:
|
|
|
683 |
}]
|
684 |
)
|
685 |
|
686 |
+
# Análisis adicional para generar código
|
687 |
+
code_prompt = f"""
|
688 |
+
{lang_prefix}
|
689 |
|
690 |
+
Based on the previous analysis, generate Python code for:
|
|
|
|
|
|
|
|
|
691 |
|
692 |
+
1. Load and visualize these fitting results
|
693 |
+
2. Create comparative model graphs (bars for R², RMSE)
|
694 |
+
3. Implement the best identified model
|
695 |
+
4. Generate predictions with the selected model
|
696 |
+
5. Parameter sensitivity analysis
|
|
|
697 |
|
698 |
+
Include:
|
699 |
+
- Necessary imports
|
700 |
+
- Well-documented functions
|
701 |
+
- Professional visualizations
|
702 |
+
- Error handling
|
703 |
+
- Usage example
|
704 |
+
|
705 |
+
The code should be executable and modular.
|
706 |
"""
|
707 |
|
708 |
code_response = self.client.messages.create(
|
|
|
710 |
max_tokens=3000,
|
711 |
messages=[{
|
712 |
"role": "user",
|
713 |
+
"content": f"{code_prompt}\n\nBased on these models:\n{response.content[0].text[:1000]}"
|
714 |
}]
|
715 |
)
|
716 |
|
717 |
return {
|
718 |
+
"tipo": "Comparative Analysis of Mathematical Models",
|
719 |
"analisis_completo": response.content[0].text,
|
720 |
"codigo_implementacion": code_response.content[0].text,
|
721 |
"resumen_datos": {
|
|
|
731 |
except Exception as e:
|
732 |
return {"error": str(e)}
|
733 |
|
734 |
+
def process_files(files, claude_model: str, detail_level: str = "detailed", language: str = "en") -> Tuple[str, str]:
|
735 |
+
"""Procesa múltiples archivos con soporte de idioma"""
|
736 |
processor = FileProcessor()
|
737 |
analyzer = AIAnalyzer(client, model_registry)
|
738 |
results = []
|
|
|
748 |
with open(file.name, 'rb') as f:
|
749 |
file_content = f.read()
|
750 |
|
751 |
+
if file_ext in ['.csv', '.xlsx', '.xls']:
|
752 |
+
if language == 'es':
|
753 |
+
results.append(f"## 📊 Análisis de Resultados: {file_name}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
754 |
else:
|
755 |
+
results.append(f"## 📊 Results Analysis: {file_name}")
|
|
|
|
|
|
|
|
|
756 |
|
757 |
if file_ext == '.csv':
|
758 |
df = processor.read_csv(file_content)
|
|
|
763 |
analysis_type = analyzer.detect_analysis_type(df)
|
764 |
|
765 |
if analysis_type == AnalysisType.FITTING_RESULTS:
|
766 |
+
result = analyzer.analyze_fitting_results(df, claude_model, detail_level, language)
|
767 |
+
|
768 |
+
if language == 'es':
|
769 |
+
results.append("### 🎯 ANÁLISIS COMPARATIVO DE MODELOS MATEMÁTICOS")
|
770 |
+
else:
|
771 |
+
results.append("### 🎯 COMPARATIVE ANALYSIS OF MATHEMATICAL MODELS")
|
772 |
+
|
773 |
results.append(result.get("analisis_completo", ""))
|
774 |
if "codigo_implementacion" in result:
|
775 |
all_code.append(result["codigo_implementacion"])
|
|
|
|
|
|
|
|
|
776 |
|
777 |
results.append("\n---\n")
|
778 |
|
|
|
782 |
return analysis_text, code_text
|
783 |
|
784 |
def generate_implementation_code(analysis_results: str) -> str:
|
785 |
+
"""Genera código de implementación"""
|
786 |
code = """
|
787 |
import numpy as np
|
788 |
import pandas as pd
|
|
|
793 |
import seaborn as sns
|
794 |
from typing import Dict, List, Tuple, Optional
|
795 |
|
796 |
+
# Visualization configuration
|
797 |
plt.style.use('seaborn-v0_8-darkgrid')
|
798 |
sns.set_palette("husl")
|
799 |
|
800 |
class ComparativeModelAnalyzer:
|
801 |
\"\"\"
|
802 |
+
Class for comparative analysis of biotechnological model fitting results.
|
803 |
+
Specialized in comparing biomass, substrate and product models.
|
804 |
\"\"\"
|
805 |
|
806 |
def __init__(self):
|
|
|
809 |
self.model_rankings = {}
|
810 |
|
811 |
def load_results(self, file_path: str) -> pd.DataFrame:
|
812 |
+
\"\"\"Load fitting results from CSV or Excel file\"\"\"
|
813 |
if file_path.endswith('.csv'):
|
814 |
self.results_df = pd.read_csv(file_path)
|
815 |
else:
|
816 |
self.results_df = pd.read_excel(file_path)
|
817 |
|
818 |
+
print(f"✅ Data loaded: {len(self.results_df)} models")
|
819 |
+
print(f"📊 Available columns: {list(self.results_df.columns)}")
|
820 |
|
821 |
return self.results_df
|
822 |
|
|
|
827 |
bic_col: Optional[str] = 'BIC',
|
828 |
model_col: str = 'Model') -> pd.DataFrame:
|
829 |
\"\"\"
|
830 |
+
Analyze and compare the fit quality of all models.
|
831 |
+
Create a ranking based on multiple metrics.
|
832 |
\"\"\"
|
833 |
if self.results_df is None:
|
834 |
+
raise ValueError("First load data with load_results()")
|
835 |
|
836 |
+
# Create comparison DataFrame
|
837 |
comparison = self.results_df.copy()
|
838 |
|
839 |
+
# Calculate composite score
|
840 |
scores = pd.DataFrame(index=comparison.index)
|
841 |
|
842 |
+
# Normalize metrics (0-1)
|
843 |
if r2_col in comparison.columns:
|
844 |
+
scores['r2_score'] = comparison[r2_col] # Already between 0-1
|
845 |
|
846 |
if rmse_col in comparison.columns:
|
847 |
+
# Invert and normalize RMSE (lower is better)
|
848 |
max_rmse = comparison[rmse_col].max()
|
849 |
scores['rmse_score'] = 1 - (comparison[rmse_col] / max_rmse)
|
850 |
|
851 |
if aic_col and aic_col in comparison.columns:
|
852 |
+
# Invert and normalize AIC (lower is better)
|
853 |
min_aic = comparison[aic_col].min()
|
854 |
max_aic = comparison[aic_col].max()
|
855 |
scores['aic_score'] = 1 - ((comparison[aic_col] - min_aic) / (max_aic - min_aic))
|
856 |
|
857 |
if bic_col and bic_col in comparison.columns:
|
858 |
+
# Invert and normalize BIC (lower is better)
|
859 |
min_bic = comparison[bic_col].min()
|
860 |
max_bic = comparison[bic_col].max()
|
861 |
scores['bic_score'] = 1 - ((comparison[bic_col] - min_bic) / (max_bic - min_bic))
|
862 |
|
863 |
+
# Calculate total score (weighted average)
|
864 |
weights = {
|
865 |
'r2_score': 0.4,
|
866 |
'rmse_score': 0.3,
|
|
|
873 |
if metric in scores.columns:
|
874 |
scores['total_score'] += scores[metric] * weight
|
875 |
|
876 |
+
# Add score to comparison DataFrame
|
877 |
comparison['Score'] = scores['total_score']
|
878 |
comparison['Ranking'] = comparison['Score'].rank(ascending=False).astype(int)
|
879 |
|
880 |
+
# Sort by ranking
|
881 |
comparison = comparison.sort_values('Ranking')
|
882 |
|
883 |
+
# Identify best model
|
884 |
best_idx = comparison['Score'].idxmax()
|
885 |
self.best_models['overall'] = comparison.loc[best_idx]
|
886 |
|
887 |
+
# Print comparison table
|
888 |
print("\\n" + "="*80)
|
889 |
+
print("📊 MODEL COMPARISON TABLE")
|
890 |
print("="*80)
|
891 |
|
892 |
+
print(f"\\n{'Rank':<6} {'Model':<20} {'R²':<8} {'RMSE':<10} {'AIC':<10} {'BIC':<10} {'Score':<8}")
|
893 |
print("-"*80)
|
894 |
|
895 |
for idx, row in comparison.iterrows():
|
|
|
912 |
print(f"{'N/A':<10} ", end="")
|
913 |
print(f"{score:<8.4f}")
|
914 |
|
915 |
+
print("\\n🏆 BEST MODEL: " + comparison.iloc[0].get(model_col, 'Not specified'))
|
916 |
print(f" - R² = {comparison.iloc[0].get(r2_col, 0):.4f}")
|
917 |
print(f" - RMSE = {comparison.iloc[0].get(rmse_col, 0):.4f}")
|
918 |
|
919 |
self.model_rankings = comparison
|
920 |
return comparison
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
921 |
|
922 |
+
# Example usage
|
923 |
if __name__ == "__main__":
|
924 |
+
print("🧬 Biotechnological Model Comparative Analysis System")
|
925 |
print("="*60)
|
926 |
|
927 |
+
# Create analyzer
|
928 |
analyzer = ComparativeModelAnalyzer()
|
929 |
|
930 |
+
# Instructions
|
931 |
+
print("\\n📋 USAGE INSTRUCTIONS:")
|
932 |
+
print("1. analyzer.load_results('your_file.csv')")
|
933 |
print("2. analyzer.analyze_model_quality()")
|
934 |
+
print("3. analyzer.plot_comparison()")
|
935 |
+
print("4. analyzer.generate_report()")
|
|
|
936 |
|
937 |
+
print("\\n✨ System ready for analysis!")
|
938 |
"""
|
939 |
|
940 |
return code
|
941 |
|
942 |
+
# Estado global para almacenar resultados
|
943 |
+
class AppState:
|
944 |
+
def __init__(self):
|
945 |
+
self.current_analysis = ""
|
946 |
+
self.current_code = ""
|
947 |
+
self.current_language = "en"
|
948 |
+
|
949 |
+
app_state = AppState()
|
950 |
+
|
951 |
+
def export_report(export_format: str, language: str) -> Tuple[str, str]:
|
952 |
+
"""Exporta el reporte al formato seleccionado"""
|
953 |
+
if not app_state.current_analysis:
|
954 |
+
error_msg = {
|
955 |
+
'en': "No analysis available to export",
|
956 |
+
'es': "No hay análisis disponible para exportar",
|
957 |
+
'fr': "Aucune analyse disponible pour exporter",
|
958 |
+
'de': "Keine Analyse zum Exportieren verfügbar",
|
959 |
+
'pt': "Nenhuma análise disponível para exportar"
|
960 |
}
|
961 |
+
return error_msg.get(language, error_msg['en']), ""
|
962 |
+
|
963 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
964 |
+
|
965 |
+
try:
|
966 |
+
if export_format == "DOCX":
|
967 |
+
filename = f"biotech_analysis_report_{timestamp}.docx"
|
968 |
+
ReportExporter.export_to_docx(app_state.current_analysis, filename, language)
|
969 |
+
else: # PDF
|
970 |
+
filename = f"biotech_analysis_report_{timestamp}.pdf"
|
971 |
+
ReportExporter.export_to_pdf(app_state.current_analysis, filename, language)
|
972 |
+
|
973 |
+
success_msg = TRANSLATIONS[language]['report_exported']
|
974 |
+
return f"{success_msg} {filename}", filename
|
975 |
+
except Exception as e:
|
976 |
+
return f"Error: {str(e)}", ""
|
977 |
+
|
978 |
+
# Interfaz Gradio con soporte multiidioma y temas
|
979 |
+
def create_interface():
|
980 |
+
# Estado inicial
|
981 |
+
current_theme = "light"
|
982 |
+
current_language = "en"
|
983 |
+
|
984 |
+
def update_interface_language(language):
|
985 |
+
"""Actualiza el idioma de la interfaz"""
|
986 |
+
app_state.current_language = language
|
987 |
+
t = TRANSLATIONS[language]
|
988 |
+
|
989 |
+
return {
|
990 |
+
title_text: gr.update(value=f"# {t['title']}"),
|
991 |
+
subtitle_text: gr.update(value=t['subtitle']),
|
992 |
+
files_input: gr.update(label=t['upload_files']),
|
993 |
+
model_selector: gr.update(label=t['select_model']),
|
994 |
+
language_selector: gr.update(label=t['select_language']),
|
995 |
+
theme_selector: gr.update(label=t['select_theme']),
|
996 |
+
detail_level: gr.update(label=t['detail_level']),
|
997 |
+
analyze_btn: gr.update(value=t['analyze_button']),
|
998 |
+
export_format: gr.update(label=t['export_format']),
|
999 |
+
export_btn: gr.update(value=t['export_button']),
|
1000 |
+
analysis_output: gr.update(label=t['comparative_analysis']),
|
1001 |
+
code_output: gr.update(label=t['implementation_code']),
|
1002 |
+
examples_label: gr.update(label=t['examples']),
|
1003 |
+
data_format_accordion: gr.update(label=t['data_format'])
|
1004 |
}
|
1005 |
+
|
1006 |
+
def process_and_store(files, model, detail, language):
|
1007 |
+
"""Procesa archivos y almacena resultados"""
|
1008 |
+
if not files:
|
1009 |
+
error_msg = TRANSLATIONS[language]['error_no_files']
|
1010 |
+
return error_msg, ""
|
1011 |
+
|
1012 |
+
analysis, code = process_files(files, model, detail, language)
|
1013 |
+
app_state.current_analysis = analysis
|
1014 |
+
app_state.current_code = code
|
1015 |
+
return analysis, code
|
1016 |
+
|
1017 |
+
with gr.Blocks(theme=THEMES[current_theme]) as demo:
|
1018 |
+
# Componentes de UI
|
1019 |
+
with gr.Row():
|
1020 |
+
with gr.Column(scale=3):
|
1021 |
+
title_text = gr.Markdown(f"# {TRANSLATIONS[current_language]['title']}")
|
1022 |
+
subtitle_text = gr.Markdown(TRANSLATIONS[current_language]['subtitle'])
|
1023 |
+
with gr.Column(scale=1):
|
1024 |
+
with gr.Row():
|
1025 |
+
language_selector = gr.Dropdown(
|
1026 |
+
choices=[("English", "en"), ("Español", "es"), ("Français", "fr"),
|
1027 |
+
("Deutsch", "de"), ("Português", "pt")],
|
1028 |
+
value="en",
|
1029 |
+
label=TRANSLATIONS[current_language]['select_language'],
|
1030 |
+
interactive=True
|
1031 |
+
)
|
1032 |
+
theme_selector = gr.Dropdown(
|
1033 |
+
choices=[("Light", "light"), ("Dark", "dark")],
|
1034 |
+
value="light",
|
1035 |
+
label=TRANSLATIONS[current_language]['select_theme'],
|
1036 |
+
interactive=True
|
1037 |
+
)
|
1038 |
|
1039 |
with gr.Row():
|
1040 |
with gr.Column(scale=1):
|
1041 |
files_input = gr.File(
|
1042 |
+
label=TRANSLATIONS[current_language]['upload_files'],
|
1043 |
file_count="multiple",
|
1044 |
file_types=[".csv", ".xlsx", ".xls", ".pdf", ".zip"],
|
1045 |
type="filepath"
|
|
|
1048 |
model_selector = gr.Dropdown(
|
1049 |
choices=list(CLAUDE_MODELS.keys()),
|
1050 |
value="claude-3-5-sonnet-20241022",
|
1051 |
+
label=TRANSLATIONS[current_language]['select_model'],
|
1052 |
+
info=f"{TRANSLATIONS[current_language]['best_for']}: {CLAUDE_MODELS['claude-3-5-sonnet-20241022']['best_for']}"
|
1053 |
)
|
1054 |
|
1055 |
detail_level = gr.Radio(
|
1056 |
+
choices=[
|
1057 |
+
(TRANSLATIONS[current_language]['detailed'], "detailed"),
|
1058 |
+
(TRANSLATIONS[current_language]['summarized'], "summarized")
|
1059 |
+
],
|
1060 |
+
value="detailed",
|
1061 |
+
label=TRANSLATIONS[current_language]['detail_level']
|
1062 |
)
|
1063 |
|
1064 |
analyze_btn = gr.Button(
|
1065 |
+
TRANSLATIONS[current_language]['analyze_button'],
|
1066 |
variant="primary",
|
1067 |
size="lg"
|
1068 |
)
|
1069 |
|
1070 |
+
gr.Markdown("---")
|
|
|
1071 |
|
1072 |
+
export_format = gr.Radio(
|
1073 |
+
choices=["DOCX", "PDF"],
|
1074 |
+
value="PDF",
|
1075 |
+
label=TRANSLATIONS[current_language]['export_format']
|
1076 |
+
)
|
|
|
|
|
|
|
|
|
1077 |
|
1078 |
+
export_btn = gr.Button(
|
1079 |
+
TRANSLATIONS[current_language]['export_button'],
|
1080 |
+
variant="secondary"
|
1081 |
+
)
|
1082 |
+
|
1083 |
+
export_status = gr.Textbox(
|
1084 |
+
label="Export Status",
|
1085 |
+
interactive=False,
|
1086 |
+
visible=False
|
1087 |
+
)
|
1088 |
+
|
1089 |
+
export_file = gr.File(
|
1090 |
+
label="Download Report",
|
1091 |
+
visible=False
|
1092 |
)
|
1093 |
|
1094 |
with gr.Column(scale=2):
|
1095 |
analysis_output = gr.Markdown(
|
1096 |
+
label=TRANSLATIONS[current_language]['comparative_analysis']
|
|
|
1097 |
)
|
1098 |
|
1099 |
code_output = gr.Code(
|
1100 |
+
label=TRANSLATIONS[current_language]['implementation_code'],
|
1101 |
language="python",
|
1102 |
interactive=True,
|
1103 |
lines=20
|
1104 |
)
|
1105 |
|
1106 |
+
data_format_accordion = gr.Accordion(
|
1107 |
+
label=TRANSLATIONS[current_language]['data_format'],
|
1108 |
+
open=False
|
1109 |
+
)
|
1110 |
+
|
1111 |
+
with data_format_accordion:
|
1112 |
gr.Markdown("""
|
1113 |
+
### Expected CSV/Excel structure:
|
1114 |
|
1115 |
| Model | R2 | RMSE | AIC | BIC | mu_max | Ks | Parameters |
|
1116 |
|-------|-----|------|-----|-----|--------|-------|------------|
|
1117 |
| Monod | 0.985 | 0.023 | -45.2 | -42.1 | 0.45 | 2.1 | {...} |
|
1118 |
| Logistic | 0.976 | 0.031 | -42.1 | -39.5 | 0.42 | - | {...} |
|
1119 |
| Gompertz | 0.992 | 0.018 | -48.5 | -45.2 | 0.48 | - | {...} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1120 |
""")
|
1121 |
|
1122 |
+
examples_label = gr.Examples(
|
|
|
1123 |
examples=[
|
1124 |
+
[["examples/biomass_models_comparison.csv"], "claude-3-5-sonnet-20241022", "detailed"],
|
1125 |
+
[["examples/substrate_kinetics_results.xlsx"], "claude-3-5-sonnet-20241022", "summarized"]
|
|
|
1126 |
],
|
1127 |
inputs=[files_input, model_selector, detail_level],
|
1128 |
+
label=TRANSLATIONS[current_language]['examples']
|
1129 |
)
|
1130 |
|
1131 |
# Eventos
|
1132 |
+
language_selector.change(
|
1133 |
+
update_interface_language,
|
1134 |
+
inputs=[language_selector],
|
1135 |
+
outputs=[
|
1136 |
+
title_text, subtitle_text, files_input, model_selector,
|
1137 |
+
language_selector, theme_selector, detail_level, analyze_btn,
|
1138 |
+
export_format, export_btn, analysis_output, code_output,
|
1139 |
+
examples_label, data_format_accordion
|
1140 |
+
]
|
1141 |
+
)
|
1142 |
+
|
1143 |
+
def change_theme(theme_name):
|
1144 |
+
"""Cambia el tema de la interfaz"""
|
1145 |
+
# Nota: En Gradio actual, cambiar el tema dinámicamente requiere recargar
|
1146 |
+
# Esta es una limitación conocida
|
1147 |
+
return gr.Info("Theme will be applied on next page load")
|
1148 |
+
|
1149 |
+
theme_selector.change(
|
1150 |
+
change_theme,
|
1151 |
+
inputs=[theme_selector],
|
1152 |
+
outputs=[]
|
1153 |
+
)
|
1154 |
+
|
1155 |
analyze_btn.click(
|
1156 |
+
fn=process_and_store,
|
1157 |
+
inputs=[files_input, model_selector, detail_level, language_selector],
|
|
|
|
|
|
|
1158 |
outputs=[analysis_output, code_output]
|
1159 |
)
|
1160 |
|
1161 |
+
def handle_export(format, language):
|
1162 |
+
status, file = export_report(format, language)
|
1163 |
+
if file:
|
1164 |
+
return gr.update(value=status, visible=True), gr.update(value=file, visible=True)
|
1165 |
+
else:
|
1166 |
+
return gr.update(value=status, visible=True), gr.update(visible=False)
|
1167 |
+
|
1168 |
+
export_btn.click(
|
1169 |
+
fn=handle_export,
|
1170 |
+
inputs=[export_format, language_selector],
|
1171 |
+
outputs=[export_status, export_file]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1172 |
)
|
1173 |
|
1174 |
return demo
|
1175 |
|
1176 |
+
# Función principal
|
1177 |
def main():
|
1178 |
if not os.getenv("ANTHROPIC_API_KEY"):
|
1179 |
+
print("⚠️ Configure ANTHROPIC_API_KEY in HuggingFace Space secrets")
|
1180 |
return gr.Interface(
|
1181 |
+
fn=lambda x: TRANSLATIONS['en']['error_no_api'],
|
1182 |
inputs=gr.Textbox(),
|
1183 |
outputs=gr.Textbox(),
|
1184 |
+
title="Configuration Error"
|
1185 |
)
|
1186 |
|
1187 |
return create_interface()
|