C2MV commited on
Commit
fd48c2d
·
verified ·
1 Parent(s): 3442269

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +632 -276
app.py CHANGED
@@ -25,7 +25,7 @@ client = anthropic.Anthropic()
25
  class AnalysisType(Enum):
26
  MATHEMATICAL_MODEL = "mathematical_model"
27
  DATA_FITTING = "data_fitting"
28
- FITTING_RESULTS = "fitting_results" # Nuevo: resultados de ajuste
29
  UNKNOWN = "unknown"
30
 
31
  # Estructura modular para modelos
@@ -194,10 +194,8 @@ class AIAnalyzer:
194
  def detect_analysis_type(self, content: Union[str, pd.DataFrame]) -> AnalysisType:
195
  """Detecta el tipo de análisis necesario"""
196
  if isinstance(content, pd.DataFrame):
197
- # Analizar si son datos experimentales o resultados de ajuste
198
  columns = [col.lower() for col in content.columns]
199
 
200
- # Indicadores de resultados de ajuste
201
  fitting_indicators = [
202
  'r2', 'r_squared', 'rmse', 'mse', 'aic', 'bic',
203
  'parameter', 'param', 'coefficient', 'fit',
@@ -205,7 +203,6 @@ class AIAnalyzer:
205
  'p_value', 'confidence', 'standard_error', 'se'
206
  ]
207
 
208
- # Verificar si hay indicadores de resultados de ajuste
209
  has_fitting_results = any(indicator in ' '.join(columns) for indicator in fitting_indicators)
210
 
211
  if has_fitting_results:
@@ -213,7 +210,6 @@ class AIAnalyzer:
213
  else:
214
  return AnalysisType.DATA_FITTING
215
 
216
- # Analizar texto para determinar tipo
217
  prompt = """
218
  Analiza este contenido y determina si es:
219
  1. Un artículo científico que describe modelos matemáticos biotecnológicos
@@ -274,7 +270,6 @@ class AIAnalyzer:
274
  }
275
 
276
  try:
277
- # Identificar modelos
278
  response = self.client.messages.create(
279
  model=claude_model,
280
  max_tokens=2000,
@@ -286,7 +281,6 @@ class AIAnalyzer:
286
 
287
  models_info = response.content[0].text
288
 
289
- # Recomendaciones
290
  response2 = self.client.messages.create(
291
  model=claude_model,
292
  max_tokens=2000,
@@ -307,7 +301,6 @@ class AIAnalyzer:
307
 
308
  def analyze_fitting_data(self, data: pd.DataFrame, claude_model: str) -> Dict:
309
  """Analiza datos experimentales para ajuste de parámetros"""
310
- # Preparar resumen de datos
311
  data_summary = f"""
312
  Columnas: {list(data.columns)}
313
  Forma: {data.shape}
@@ -348,14 +341,16 @@ class AIAnalyzer:
348
  except Exception as e:
349
  return {"error": str(e)}
350
 
351
- def analyze_fitting_results(self, data: pd.DataFrame, claude_model: str) -> Dict:
352
- """Analiza resultados de ajuste de modelos cinéticos"""
353
- # Preparar resumen detallado de resultados
 
354
  data_summary = f"""
355
- DATOS DE RESULTADOS DE AJUSTE:
356
 
357
- Columnas disponibles: {list(data.columns)}
358
- Forma de los datos: {data.shape}
 
359
 
360
  Datos completos:
361
  {data.to_string()}
@@ -364,39 +359,86 @@ class AIAnalyzer:
364
  {data.describe().to_string()}
365
  """
366
 
367
- prompt = """
368
- Estos son RESULTADOS DE AJUSTE DE MODELOS CINÉTICOS/BIOTECNOLÓGICOS ya calculados.
369
-
370
- Necesito que analices:
371
-
372
- 1. **IDENTIFICACIÓN DE MODELOS**: ¿Qué modelos matemáticos se ajustaron? (Monod, Logístico, Gompertz, Michaelis-Menten, etc.)
373
-
374
- 2. **CALIDAD DEL AJUSTE**:
375
- - Compara R², RMSE, AIC, BIC entre modelos
376
- - ¿Cuál ajusta mejor y por qué?
377
- - ¿Hay sobreajuste o subajuste?
378
-
379
- 3. **SIGNIFICADO BIOLÓGICO**:
380
- - ¿Qué significan los parámetros estimados biológicamente?
381
- - ¿Son valores realistas para el sistema biológico?
382
- - ¿Qué información nos dan sobre el proceso?
383
-
384
- 4. **INFERENCIA DEL DISEÑO EXPERIMENTAL**:
385
- - ¿Qué tipo de experimento se realizó?
386
- - ¿Qué variables se midieron?
387
- - ¿Batch, continuo, fed-batch?
388
-
389
- 5. **INTERPRETACIÓN EN LENGUAJE HUMANO**:
390
- - Traduce los resultados técnicos a conclusiones comprensibles
391
- - ¿Qué nos dicen sobre el comportamiento del microorganismo/proceso?
392
- - ¿Cuáles son las implicaciones prácticas?
393
-
394
- 6. **RECOMENDACIONES**:
395
- - ¿Qué modelo(s) recomiendas usar?
396
- - ¿Qué limitaciones tienen?
397
- - ¿Qué experimentos adicionales serían útiles?
398
-
399
- Estructura tu respuesta de forma clara y detallada.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  """
401
 
402
  try:
@@ -405,27 +447,62 @@ class AIAnalyzer:
405
  max_tokens=4000,
406
  messages=[{
407
  "role": "user",
408
- "content": f"{prompt}\n\nRESULTADOS DE AJUSTE:\n{data_summary}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  }]
410
  )
411
 
412
  return {
413
- "tipo": "Análisis de Resultados de Ajuste de Modelos Cinéticos",
414
  "analisis_completo": response.content[0].text,
415
- "resumen_datos": data_summary,
416
- "n_modelos": len(data),
417
- "metricas_disponibles": [col for col in data.columns if any(metric in col.lower()
418
- for metric in ['r2', 'rmse', 'aic', 'bic', 'mse'])]
 
 
 
 
 
419
  }
420
 
421
  except Exception as e:
422
  return {"error": str(e)}
423
 
424
- def process_files(files, claude_model: str) -> str:
425
  """Procesa múltiples archivos"""
426
  processor = FileProcessor()
427
  analyzer = AIAnalyzer(client, model_registry)
428
  results = []
 
429
 
430
  for file in files:
431
  if file is None:
@@ -434,13 +511,10 @@ def process_files(files, claude_model: str) -> str:
434
  file_name = file.name if hasattr(file, 'name') else "archivo"
435
  file_ext = Path(file_name).suffix.lower()
436
 
437
- # Leer contenido del archivo
438
  with open(file.name, 'rb') as f:
439
  file_content = f.read()
440
 
441
- # Procesar según tipo
442
  if file_ext == '.zip':
443
- # Extraer y procesar archivos del ZIP
444
  extracted_files = processor.extract_from_zip(file_content)
445
  results.append(f"## 📦 Archivo ZIP: {file_name}")
446
  results.append(f"Contiene {len(extracted_files)} archivos\n")
@@ -458,7 +532,7 @@ def process_files(files, claude_model: str) -> str:
458
  else:
459
  result = {"tipo": "PDF no reconocido", "contenido": text[:500]}
460
 
461
- results.append(json.dumps(result, indent=2, ensure_ascii=False))
462
 
463
  elif sub_ext in ['.csv', '.xlsx', '.xls']:
464
  if sub_ext == '.csv':
@@ -470,11 +544,13 @@ def process_files(files, claude_model: str) -> str:
470
  analysis_type = analyzer.detect_analysis_type(df)
471
 
472
  if analysis_type == AnalysisType.FITTING_RESULTS:
473
- result = analyzer.analyze_fitting_results(df, claude_model)
 
 
 
474
  else:
475
  result = analyzer.analyze_fitting_data(df, claude_model)
476
-
477
- results.append(json.dumps(result, indent=2, ensure_ascii=False))
478
 
479
  results.append("\n---\n")
480
 
@@ -486,13 +562,13 @@ def process_files(files, claude_model: str) -> str:
486
 
487
  if analysis_type == AnalysisType.MATHEMATICAL_MODEL:
488
  result = analyzer.analyze_mathematical_article(text, claude_model)
 
489
  else:
490
  result = {"tipo": "PDF - Contenido no identificado", "texto": text[:1000]}
491
-
492
- results.append(json.dumps(result, indent=2, ensure_ascii=False))
493
 
494
  elif file_ext in ['.csv', '.xlsx', '.xls']:
495
- results.append(f"## 📊 Archivo de datos: {file_name}")
496
 
497
  if file_ext == '.csv':
498
  df = processor.read_csv(file_content)
@@ -503,17 +579,22 @@ def process_files(files, claude_model: str) -> str:
503
  analysis_type = analyzer.detect_analysis_type(df)
504
 
505
  if analysis_type == AnalysisType.FITTING_RESULTS:
506
- result = analyzer.analyze_fitting_results(df, claude_model)
507
- results.append("### 🎯 ANÁLISIS DE RESULTADOS DE AJUSTE")
 
 
 
508
  else:
509
  result = analyzer.analyze_fitting_data(df, claude_model)
510
  results.append("### 📈 ANÁLISIS DE DATOS EXPERIMENTALES")
511
-
512
- results.append(json.dumps(result, indent=2, ensure_ascii=False))
513
 
514
  results.append("\n---\n")
515
 
516
- return "\n".join(results)
 
 
 
517
 
518
  def generate_implementation_code(analysis_results: str) -> str:
519
  """Genera código de implementación basado en el análisis"""
@@ -525,183 +606,448 @@ from scipy.integrate import odeint
525
  from scipy.optimize import curve_fit, differential_evolution
526
  from sklearn.metrics import r2_score, mean_squared_error
527
  import seaborn as sns
 
528
 
529
  # Configuración de visualización
530
  plt.style.use('seaborn-v0_8-darkgrid')
531
  sns.set_palette("husl")
532
 
533
- class BiotechModelAnalyzer:
534
- \"\"\"Clase para análisis de resultados de ajuste de modelos biotecnológicos\"\"\"
 
 
 
535
 
536
  def __init__(self):
537
- self.models = {}
538
- self.fitted_params = {}
539
- self.results = {}
540
- self.biological_interpretations = {}
541
-
542
- def load_fitting_results(self, data_path):
543
- \"\"\"Carga resultados de ajuste desde CSV/Excel\"\"\"
544
- if data_path.endswith('.csv'):
545
- self.results_df = pd.read_csv(data_path)
546
  else:
547
- self.results_df = pd.read_excel(data_path)
 
 
 
548
 
549
  return self.results_df
550
 
551
- def compare_models(self, r2_col='R2', rmse_col='RMSE', model_col='Model'):
552
- \"\"\"Compara modelos basado en métricas de ajuste\"\"\"
553
- if not hasattr(self, 'results_df'):
554
- raise ValueError("Primero carga los datos con load_fitting_results()")
555
-
556
- # Ordenar por descendente
557
- comparison = self.results_df.sort_values(by=r2_col, ascending=False)
558
-
559
- print("=== COMPARACIÓN DE MODELOS ===")
560
- print(f"{'Modelo':<20} {'R²':<10} {'RMSE':<10} {'Ranking':<10}")
561
- print("-" * 60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
 
563
- for i, (idx, row) in enumerate(comparison.iterrows()):
564
- model_name = row[model_col] if model_col in row else f"Modelo_{idx}"
565
- r2_val = row[r2_col] if r2_col in row else "N/A"
566
- rmse_val = row[rmse_col] if rmse_col in row else "N/A"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
 
568
- print(f"{model_name:<20} {r2_val:<10.4f} {rmse_val:<10.4f} {i+1:<10}")
 
 
 
 
 
 
 
 
 
569
 
570
- return comparison
571
-
572
- def interpret_biological_meaning(self, model_name, parameters):
573
- \"\"\"Interpreta el significado biológico de parámetros\"\"\"
574
- interpretations = {
575
- 'monod': {
576
- 'mu_max': 'Velocidad máxima específica de crecimiento',
577
- 'Ks': 'Constante de saturación (afinidad por sustrato)',
578
- 'biological_meaning': 'Crecimiento limitado por sustrato único'
579
- },
580
- 'logistic': {
581
- 'K': 'Capacidad de carga del sistema',
582
- 'r': 'Tasa intrínseca de crecimiento',
583
- 'biological_meaning': 'Crecimiento limitado por densidad poblacional'
584
- },
585
- 'gompertz': {
586
- 'A': 'Asíntota superior (biomasa máxima)',
587
- 'mu': 'Velocidad máxima de crecimiento',
588
- 'lambda': 'Tiempo de fase lag',
589
- 'biological_meaning': 'Crecimiento con adaptación inicial'
590
- }
591
- }
592
 
593
- model_key = model_name.lower()
594
- if model_key in interpretations:
595
- return interpretations[model_key]
596
- else:
597
- return {'biological_meaning': 'Modelo no reconocido en base de datos'}
598
 
599
- def generate_human_readable_report(self):
600
- \"\"\"Genera reporte en lenguaje humano\"\"\"
601
- if not hasattr(self, 'results_df'):
 
 
 
602
  raise ValueError("Primero carga los datos")
603
 
604
- report = []
605
- report.append("# 🧬 REPORTE DE ANÁLISIS BIOTECNOLÓGICO")
606
- report.append("=" * 50)
607
- report.append("")
608
-
609
- # Mejor modelo
610
- best_model = self.results_df.loc[self.results_df['R2'].idxmax()]
611
- report.append(f"## 🏆 MEJOR MODELO IDENTIFICADO")
612
- report.append(f"**Modelo:** {best_model.get('Model', 'No especificado')}")
613
- report.append(f"**Calidad del ajuste:** R² = {best_model.get('R2', 'N/A'):.4f}")
614
- report.append(f"**Error:** RMSE = {best_model.get('RMSE', 'N/A'):.4f}")
615
- report.append("")
616
-
617
- # Interpretación biológica
618
- report.append("## 🔬 SIGNIFICADO BIOLÓGICO")
619
- report.append("Los parámetros ajustados nos indican:")
620
-
621
- # Aquí puedes expandir según los modelos específicos encontrados
622
- report.append("- El sistema estudiado muestra un comportamiento predecible")
623
- report.append("- Los parámetros están dentro de rangos biológicamente plausibles")
624
- report.append("")
625
 
626
- # Recomendaciones
627
- report.append("## 💡 RECOMENDACIONES")
628
- report.append("1. Validar el modelo con datos independientes")
629
- report.append("2. Considerar factores ambientales adicionales")
630
- report.append("3. Evaluar la robustez del modelo")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
 
632
- return "\\n".join(report)
633
 
634
- def plot_model_comparison(self):
635
- \"\"\"Visualiza comparación de modelos\"\"\"
636
- if not hasattr(self, 'results_df'):
637
- raise ValueError("Primero carga los datos")
 
 
 
 
638
 
639
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
 
640
 
641
- # Gráfico de R²
642
- models = self.results_df.get('Model', range(len(self.results_df)))
643
- r2_values = self.results_df.get('R2', [])
 
644
 
645
- ax1.bar(models, r2_values, color='skyblue', edgecolor='navy', alpha=0.7)
646
  ax1.set_title('Comparación de R² por Modelo', fontsize=14, fontweight='bold')
647
  ax1.set_ylabel('R² (Coeficiente de Determinación)', fontsize=12)
648
- ax1.set_ylim(0, 1)
 
 
649
  ax1.grid(True, alpha=0.3)
650
 
651
- # Rotar etiquetas si es necesario
652
- plt.setp(ax1.get_xticklabels(), rotation=45, ha='right')
 
 
 
653
 
654
- # Gráfico de RMSE
655
- rmse_values = self.results_df.get('RMSE', [])
656
- ax2.bar(models, rmse_values, color='lightcoral', edgecolor='darkred', alpha=0.7)
 
 
657
  ax2.set_title('Comparación de RMSE por Modelo', fontsize=14, fontweight='bold')
658
  ax2.set_ylabel('RMSE (Error Cuadrático Medio)', fontsize=12)
 
 
659
  ax2.grid(True, alpha=0.3)
660
 
661
- plt.setp(ax2.get_xticklabels(), rotation=45, ha='right')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662
 
663
- plt.tight_layout()
664
  return fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
665
 
666
- # Modelos predefinidos comunes
667
- def monod_model(S, mu_max, Ks):
668
- return mu_max * S / (Ks + S)
669
-
670
- def logistic_growth(t, K, r, t0):
671
- return K / (1 + np.exp(-r * (t - t0)))
672
-
673
- def gompertz_model(t, A, mu, lambda_param):
674
- return A * np.exp(-np.exp(mu * np.e / A * (lambda_param - t) + 1))
675
-
676
- def michaelis_menten(S, Vmax, Km):
677
- return Vmax * S / (Km + S)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
 
679
- # Ejemplo de uso para análisis de resultados
680
  if __name__ == "__main__":
681
- # Crear instancia del analizador
682
- analyzer = BiotechModelAnalyzer()
683
-
684
- # Ejemplo de carga de datos
685
- # analyzer.load_fitting_results('resultados_ajuste.csv')
686
 
687
- # Ejemplo de comparación
688
- # comparison = analyzer.compare_models()
689
 
690
- # Generar reporte
691
- # report = analyzer.generate_human_readable_report()
692
- # print(report)
 
 
 
 
693
 
694
- print("🔬 Sistema de análisis de resultados listo!")
695
- print("📊 Carga tus resultados CSV y utiliza analyzer.load_fitting_results()")
696
- print("📈 Luego usa analyzer.compare_models() para comparar")
697
  """
698
 
699
  return code
700
 
701
- # Interfaz Gradio optimizada para HuggingFace
702
  def create_interface():
703
  with gr.Blocks(
704
- title="Analizador Inteligente de Modelos Biotecnológicos",
705
  theme=gr.themes.Soft(),
706
  css="""
707
  .gradio-container {
@@ -713,30 +1059,31 @@ def create_interface():
713
  border-radius: 10px;
714
  border-left: 5px solid #4CAF50;
715
  }
 
 
 
 
 
 
716
  """
717
  ) as demo:
718
 
719
  gr.Markdown("""
720
- # 🧬 Analizador Inteligente de Modelos Biotecnológicos
721
-
722
- ### 🎯 Especializado en Análisis de Resultados de Ajuste:
723
- - **Análisis automático** de resultados de ajuste de modelos cinéticos
724
- - **Interpretación biológica** de parámetros y métricas
725
- - **Comparación inteligente** entre modelos (R², RMSE, AIC, BIC)
726
- - **Traducción a lenguaje humano** de resultados técnicos
727
- - **Inferencia del diseño experimental** a partir de los datos
728
- - **Recomendaciones** sobre qué modelos usar y por qué
729
-
730
- ### 📁 Tipos de archivo soportados:
731
- - **CSV/Excel** con resultados de ajuste (parámetros, R², RMSE, etc.)
732
- - **PDF** con artículos científicos o reportes
733
- - **ZIP** con múltiples archivos
734
-
735
- ### 🔍 ¿Qué analiza específicamente?
736
- - Calidad del ajuste y comparación entre modelos
737
- - Significado biológico de parámetros estimados
738
- - Detección de sobreajuste o problemas en el ajuste
739
- - Interpretación de resultados en contexto biotecnológico
740
  """)
741
 
742
  with gr.Row():
@@ -755,8 +1102,15 @@ def create_interface():
755
  info="Selecciona el modelo de IA"
756
  )
757
 
 
 
 
 
 
 
 
758
  analyze_btn = gr.Button(
759
- "🚀 Analizar Resultados",
760
  variant="primary",
761
  size="lg"
762
  )
@@ -782,81 +1136,83 @@ def create_interface():
782
 
783
  with gr.Column(scale=2):
784
  analysis_output = gr.Markdown(
785
- label="📊 Análisis de Resultados",
786
  elem_classes=["highlight-results"]
787
  )
788
 
789
  code_output = gr.Code(
790
- label="💻 Código de Análisis",
791
  language="python",
792
- interactive=True
 
793
  )
794
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
  # Ejemplos
796
  gr.Examples(
797
  examples=[
798
- [["examples/fitting_results.csv"]],
799
- [["examples/model_comparison.xlsx"]],
800
- [["examples/kinetic_parameters.csv"]]
801
  ],
802
- inputs=[files_input],
803
- label="📚 Ejemplos de resultados de ajuste"
804
  )
805
 
806
- # Guía de uso
807
- gr.Markdown("""
808
- ---
809
- ### 📋 Guía de uso para resultados de ajuste:
810
-
811
- **Para obtener el mejor análisis, asegúrate que tu CSV/Excel contenga:**
812
-
813
- 1. **Columna de modelos**: Nombres de los modelos ajustados (Monod, Logístico, Gompertz, etc.)
814
- 2. **Métricas de ajuste**: R², RMSE, AIC, BIC, MSE, etc.
815
- 3. **Parámetros**: Valores de parámetros estimados (μmax, Ks, K, etc.)
816
- 4. **Errores estándar**: Si están disponibles
817
-
818
- **Ejemplo de estructura ideal:**
819
- ```
820
- Model | R2 | RMSE | mu_max | Ks | AIC
821
- Monod | 0.985 | 0.023 | 0.45 | 2.1 | -45.2
822
- Logistic | 0.976 | 0.031 | 0.42 | 15.3 | -42.1
823
- Gompertz | 0.992 | 0.018 | 0.48 | 1.8 | -48.5
824
- ```
825
-
826
- ### 🔬 Lo que obtendrás:
827
- - **Ranking de modelos** basado en calidad de ajuste
828
- - **Interpretación biológica** de cada parámetro
829
- - **Análisis del diseño experimental** inferido
830
- - **Recomendaciones** sobre cuál modelo usar
831
- - **Explicación en lenguaje simple** de los resultados
832
- """)
833
-
834
- # Footer
835
- gr.Markdown("""
836
- ---
837
- ### 🔧 Características técnicas:
838
- - **Detección automática** de tipo de análisis (datos vs resultados)
839
- - **Interpretación contextual** de parámetros biotecnológicos
840
- - **Análisis comparativo** inteligente entre modelos
841
- - **Traducción técnica** a lenguaje comprensible
842
-
843
- ### 💡 Casos de uso:
844
- - Análisis de resultados de ajustes de crecimiento microbiano
845
- - Comparación de modelos de consumo de sustrato
846
- - Evaluación de modelos de formación de producto
847
- - Interpretación de parámetros cinéticos
848
- """)
849
-
850
  # Eventos
851
  analyze_btn.click(
852
- fn=lambda files, model: (
853
- process_files(files, model) if files else "Por favor sube archivos con resultados de ajuste para analizar",
854
- generate_implementation_code("") if files else ""
855
  ),
856
- inputs=[files_input, model_selector],
857
  outputs=[analysis_output, code_output]
858
  )
859
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
860
  # Cargar info inicial del modelo
861
  demo.load(
862
  fn=lambda: update_model_info("claude-3-5-sonnet-20241022"),
 
25
  class AnalysisType(Enum):
26
  MATHEMATICAL_MODEL = "mathematical_model"
27
  DATA_FITTING = "data_fitting"
28
+ FITTING_RESULTS = "fitting_results"
29
  UNKNOWN = "unknown"
30
 
31
  # Estructura modular para modelos
 
194
  def detect_analysis_type(self, content: Union[str, pd.DataFrame]) -> AnalysisType:
195
  """Detecta el tipo de análisis necesario"""
196
  if isinstance(content, pd.DataFrame):
 
197
  columns = [col.lower() for col in content.columns]
198
 
 
199
  fitting_indicators = [
200
  'r2', 'r_squared', 'rmse', 'mse', 'aic', 'bic',
201
  'parameter', 'param', 'coefficient', 'fit',
 
203
  'p_value', 'confidence', 'standard_error', 'se'
204
  ]
205
 
 
206
  has_fitting_results = any(indicator in ' '.join(columns) for indicator in fitting_indicators)
207
 
208
  if has_fitting_results:
 
210
  else:
211
  return AnalysisType.DATA_FITTING
212
 
 
213
  prompt = """
214
  Analiza este contenido y determina si es:
215
  1. Un artículo científico que describe modelos matemáticos biotecnológicos
 
270
  }
271
 
272
  try:
 
273
  response = self.client.messages.create(
274
  model=claude_model,
275
  max_tokens=2000,
 
281
 
282
  models_info = response.content[0].text
283
 
 
284
  response2 = self.client.messages.create(
285
  model=claude_model,
286
  max_tokens=2000,
 
301
 
302
  def analyze_fitting_data(self, data: pd.DataFrame, claude_model: str) -> Dict:
303
  """Analiza datos experimentales para ajuste de parámetros"""
 
304
  data_summary = f"""
305
  Columnas: {list(data.columns)}
306
  Forma: {data.shape}
 
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
+ RESULTADOS DE AJUSTE DE MODELOS MATEMÁTICOS:
350
 
351
+ Estructura de datos:
352
+ - Columnas: {list(data.columns)}
353
+ - Número de modelos evaluados: {len(data)}
354
 
355
  Datos completos:
356
  {data.to_string()}
 
359
  {data.describe().to_string()}
360
  """
361
 
362
+ # Prompt mejorado y especializado
363
+ prompt = f"""
364
+ Eres un experto en biotecnología y modelado matemático. Analiza estos resultados de ajuste de modelos cinéticos/biotecnológicos.
365
+
366
+ NIVEL DE DETALLE SOLICITADO: {detail_level}
367
+
368
+ REALIZA UN ANÁLISIS COMPARATIVO EXHAUSTIVO:
369
+
370
+ 1. **IDENTIFICACIÓN Y CLASIFICACIÓN DE MODELOS**
371
+ - Identifica TODOS los modelos matemáticos ajustados
372
+ - Clasifícalos por tipo: biomasa, sustrato, producto
373
+ - Indica la ecuación matemática de cada modelo si es posible inferirla
374
+
375
+ 2. **ANÁLISIS COMPARATIVO DE CALIDAD DE AJUSTE**
376
+ - Compara TODOS los indicadores disponibles: R², RMSE, AIC, BIC, etc.
377
+ - Crea un ranking ordenado de mejor a peor modelo
378
+ - Identifica diferencias significativas entre modelos
379
+ - Detecta posible sobreajuste o subajuste
380
+
381
+ 3. **DETERMINACIÓN DEL MEJOR MODELO**
382
+ - Selecciona el MEJOR modelo basándote en MÚLTIPLES criterios:
383
+ * Mayor R² (más cercano a 1)
384
+ * Menor RMSE/MSE
385
+ * Menor AIC/BIC (si están disponibles)
386
+ * Parsimonia (menos parámetros si el ajuste es similar)
387
+ - Justifica NUMÉRICAMENTE por qué es el mejor
388
+ - Si hay empate técnico, explica las ventajas de cada uno
389
+
390
+ 4. **ANÁLISIS ESPECÍFICO POR TIPO DE VARIABLE**
391
+ a) **BIOMASA (si aplica)**:
392
+ - Parámetros de crecimiento (μmax, Xmax, etc.)
393
+ - Tiempo de duplicación
394
+ - Productividad de biomasa
395
+ - Comparación numérica entre modelos
396
+
397
+ b) **SUSTRATO (si aplica)**:
398
+ - Constantes de afinidad (Ks, Km)
399
+ - Velocidades de consumo
400
+ - Rendimiento Yx/s
401
+ - Eficiencia de utilización
402
+
403
+ c) **PRODUCTO (si aplica)**:
404
+ - Parámetros de producción (α, β)
405
+ - Productividad específica
406
+ - Rendimiento Yp/x
407
+ - Tipo de producción (asociada/no asociada)
408
+
409
+ 5. **INTERPRETACIÓN BIOLÓGICA DE PARÁMETROS**
410
+ - Explica qué significa CADA parámetro biológicamente
411
+ - Compara valores entre modelos
412
+ - Evalúa si son realistas para el sistema
413
+ - Identifica parámetros críticos del proceso
414
+
415
+ 6. **CONCLUSIONES CON CONTENIDO NUMÉRICO**
416
+ - Resume los hallazgos clave con NÚMEROS específicos
417
+ - Proporciona rangos de confianza si están disponibles
418
+ - Indica condiciones óptimas de operación
419
+ - Sugiere valores de diseño para escalamiento
420
+
421
+ 7. **RECOMENDACIONES PRÁCTICAS**
422
+ - Qué modelo(s) usar para predicción
423
+ - Limitaciones del modelo seleccionado
424
+ - Experimentos adicionales recomendados
425
+ - Consideraciones para implementación industrial
426
+
427
+ 8. **TABLA COMPARATIVA FINAL**
428
+ Crea una tabla resumen con:
429
+ - Modelo | R² | RMSE | AIC/BIC | Parámetros clave | Ranking
430
+
431
+ FORMATO DE RESPUESTA:
432
+ - Si el nivel es "detallado": incluye TODOS los puntos con explicaciones completas
433
+ - Si el nivel es "resumido": enfócate en puntos 3, 6 y 8 con valores numéricos clave
434
+
435
+ Usa formato Markdown con:
436
+ - Títulos y subtítulos claros
437
+ - **Negritas** para valores importantes
438
+ - Tablas cuando sea apropiado
439
+ - Listas numeradas y con viñetas
440
+
441
+ IMPORTANTE: Basa TODAS las conclusiones en los NÚMEROS específicos de los datos proporcionados.
442
  """
443
 
444
  try:
 
447
  max_tokens=4000,
448
  messages=[{
449
  "role": "user",
450
+ "content": f"{prompt}\n\n{data_summary}"
451
+ }]
452
+ )
453
+
454
+ # Análisis adicional para generar código si es necesario
455
+ code_prompt = """
456
+ Basándote en el análisis anterior, genera código Python para:
457
+
458
+ 1. Cargar y visualizar estos resultados de ajuste
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
+ Incluye:
465
+ - Imports necesarios
466
+ - Funciones bien documentadas
467
+ - Visualizaciones profesionales
468
+ - Manejo de errores
469
+ - Ejemplo de uso
470
+
471
+ El código debe ser ejecutable y modular.
472
+ """
473
+
474
+ code_response = self.client.messages.create(
475
+ model=claude_model,
476
+ max_tokens=3000,
477
+ messages=[{
478
+ "role": "user",
479
+ "content": f"{code_prompt}\n\nBasado en estos modelos:\n{response.content[0].text[:1000]}"
480
  }]
481
  )
482
 
483
  return {
484
+ "tipo": "Análisis Comparativo de Modelos Matemáticos",
485
  "analisis_completo": response.content[0].text,
486
+ "codigo_implementacion": code_response.content[0].text,
487
+ "resumen_datos": {
488
+ "n_modelos": len(data),
489
+ "columnas": list(data.columns),
490
+ "metricas_disponibles": [col for col in data.columns if any(metric in col.lower()
491
+ for metric in ['r2', 'rmse', 'aic', 'bic', 'mse'])],
492
+ "mejor_r2": data['R2'].max() if 'R2' in data.columns else None,
493
+ "mejor_modelo_r2": data.loc[data['R2'].idxmax()]['Model'] if 'R2' in data.columns and 'Model' in data.columns else None
494
+ }
495
  }
496
 
497
  except Exception as e:
498
  return {"error": str(e)}
499
 
500
+ def process_files(files, claude_model: str, detail_level: str = "detallado") -> Tuple[str, str]:
501
  """Procesa múltiples archivos"""
502
  processor = FileProcessor()
503
  analyzer = AIAnalyzer(client, model_registry)
504
  results = []
505
+ all_code = []
506
 
507
  for file in files:
508
  if file is None:
 
511
  file_name = file.name if hasattr(file, 'name') else "archivo"
512
  file_ext = Path(file_name).suffix.lower()
513
 
 
514
  with open(file.name, 'rb') as f:
515
  file_content = f.read()
516
 
 
517
  if file_ext == '.zip':
 
518
  extracted_files = processor.extract_from_zip(file_content)
519
  results.append(f"## 📦 Archivo ZIP: {file_name}")
520
  results.append(f"Contiene {len(extracted_files)} archivos\n")
 
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':
 
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
 
 
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
  result = {"tipo": "PDF - Contenido no identificado", "texto": text[:1000]}
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
  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
+ results.append("### 🎯 ANÁLISIS COMPARATIVO DE MODELOS MATEMÁTICOS")
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
 
594
+ analysis_text = "\n".join(results)
595
+ code_text = "\n\n# " + "="*50 + "\n\n".join(all_code) if all_code else generate_implementation_code(analysis_text)
596
+
597
+ return analysis_text, code_text
598
 
599
  def generate_implementation_code(analysis_results: str) -> str:
600
  """Genera código de implementación basado en el análisis"""
 
606
  from scipy.optimize import curve_fit, differential_evolution
607
  from sklearn.metrics import r2_score, mean_squared_error
608
  import seaborn as sns
609
+ from typing import Dict, List, Tuple, Optional
610
 
611
  # Configuración de visualización
612
  plt.style.use('seaborn-v0_8-darkgrid')
613
  sns.set_palette("husl")
614
 
615
+ class ComparativeModelAnalyzer:
616
+ \"\"\"
617
+ Clase para análisis comparativo de resultados de ajuste de modelos biotecnológicos.
618
+ Especializada en comparar modelos de biomasa, sustrato y producto.
619
+ \"\"\"
620
 
621
  def __init__(self):
622
+ self.results_df = None
623
+ self.best_models = {}
624
+ self.model_rankings = {}
625
+
626
+ def load_results(self, file_path: str) -> pd.DataFrame:
627
+ \"\"\"Carga resultados de ajuste desde archivo CSV o Excel\"\"\"
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"✅ Datos cargados: {len(self.results_df)} modelos")
634
+ print(f"📊 Columnas disponibles: {list(self.results_df.columns)}")
635
 
636
  return self.results_df
637
 
638
+ def analyze_model_quality(self,
639
+ r2_col: str = 'R2',
640
+ rmse_col: str = 'RMSE',
641
+ aic_col: Optional[str] = 'AIC',
642
+ bic_col: Optional[str] = 'BIC',
643
+ model_col: str = 'Model') -> pd.DataFrame:
644
+ \"\"\"
645
+ Analiza y compara la calidad de ajuste de todos los modelos.
646
+ Crea un ranking basado en múltiples métricas.
647
+ \"\"\"
648
+ if self.results_df is None:
649
+ raise ValueError("Primero carga los datos con load_results()")
650
+
651
+ # Crear DataFrame de comparación
652
+ comparison = self.results_df.copy()
653
+
654
+ # Calcular puntuación compuesta
655
+ scores = pd.DataFrame(index=comparison.index)
656
+
657
+ # Normalizar métricas (0-1)
658
+ if r2_col in comparison.columns:
659
+ scores['r2_score'] = comparison[r2_col] # Ya está entre 0-1
660
+
661
+ if rmse_col in comparison.columns:
662
+ # Invertir y normalizar RMSE (menor es mejor)
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
+ # Invertir y normalizar AIC (menor es mejor)
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
+ # Invertir y normalizar BIC (menor es mejor)
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
+ # Calcular puntuación total (promedio ponderado)
679
+ weights = {
680
+ 'r2_score': 0.4,
681
+ 'rmse_score': 0.3,
682
+ 'aic_score': 0.15,
683
+ 'bic_score': 0.15
684
+ }
685
 
686
+ scores['total_score'] = 0
687
+ for metric, weight in weights.items():
688
+ if metric in scores.columns:
689
+ scores['total_score'] += scores[metric] * weight
690
+
691
+ # Añadir puntuación al DataFrame de comparación
692
+ comparison['Score'] = scores['total_score']
693
+ comparison['Ranking'] = comparison['Score'].rank(ascending=False).astype(int)
694
+
695
+ # Ordenar por ranking
696
+ comparison = comparison.sort_values('Ranking')
697
+
698
+ # Identificar mejor modelo
699
+ best_idx = comparison['Score'].idxmax()
700
+ self.best_models['overall'] = comparison.loc[best_idx]
701
+
702
+ # Imprimir tabla de comparación
703
+ print("\\n" + "="*80)
704
+ print("📊 TABLA COMPARATIVA DE MODELOS")
705
+ print("="*80)
706
+
707
+ print(f"\\n{'Rank':<6} {'Modelo':<20} {'R²':<8} {'RMSE':<10} {'AIC':<10} {'BIC':<10} {'Score':<8}")
708
+ print("-"*80)
709
+
710
+ for idx, row in comparison.iterrows():
711
+ rank = row['Ranking']
712
+ model = row.get(model_col, f'Model_{idx}')[:20]
713
+ r2 = row.get(r2_col, 0)
714
+ rmse = row.get(rmse_col, 0)
715
+ aic = row.get(aic_col, 'N/A')
716
+ bic = row.get(bic_col, 'N/A')
717
+ score = row['Score']
718
 
719
+ print(f"{rank:<6} {model:<20} {r2:<8.4f} {rmse:<10.4f} ", end="")
720
+ if isinstance(aic, (int, float)):
721
+ print(f"{aic:<10.2f} ", end="")
722
+ else:
723
+ print(f"{'N/A':<10} ", end="")
724
+ if isinstance(bic, (int, float)):
725
+ print(f"{bic:<10.2f} ", end="")
726
+ else:
727
+ print(f"{'N/A':<10} ", end="")
728
+ print(f"{score:<8.4f}")
729
 
730
+ print("\\n🏆 MEJOR MODELO: " + comparison.iloc[0].get(model_col, 'No especificado'))
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
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
+ # Ejemplo de uso
1027
  if __name__ == "__main__":
1028
+ print("🧬 Sistema de Análisis Comparativo de Modelos Biotecnológicos")
1029
+ print("="*60)
 
 
 
1030
 
1031
+ # Crear analizador
1032
+ analyzer = ComparativeModelAnalyzer()
1033
 
1034
+ # Instrucciones
1035
+ print("\\n📋 INSTRUCCIONES DE USO:")
1036
+ print("1. analyzer.load_results('tu_archivo.csv')")
1037
+ print("2. analyzer.analyze_model_quality()")
1038
+ print("3. analyzer.analyze_by_category()")
1039
+ print("4. analyzer.plot_comparison()")
1040
+ print("5. analyzer.generate_report()")
1041
 
1042
+ print("\\n✨ ¡Sistema listo para análisis!")
 
 
1043
  """
1044
 
1045
  return code
1046
 
1047
+ # Interfaz Gradio optimizada
1048
  def create_interface():
1049
  with gr.Blocks(
1050
+ title="Analizador Comparativo de Modelos Biotecnológicos",
1051
  theme=gr.themes.Soft(),
1052
  css="""
1053
  .gradio-container {
 
1059
  border-radius: 10px;
1060
  border-left: 5px solid #4CAF50;
1061
  }
1062
+ .comparison-table {
1063
+ background-color: #f9f9f9;
1064
+ padding: 10px;
1065
+ border-radius: 8px;
1066
+ font-family: monospace;
1067
+ }
1068
  """
1069
  ) as demo:
1070
 
1071
  gr.Markdown("""
1072
+ # 🧬 Analizador Comparativo de Modelos Biotecnológicos
1073
+
1074
+ ### 🎯 Especializado en:
1075
+ - **Análisis comparativo** de resultados de ajuste de modelos matemáticos
1076
+ - **Determinación del mejor modelo** con justificación numérica
1077
+ - **Análisis específico** para biomasa, sustrato y producto
1078
+ - **Conclusiones numéricas** detalladas y ordenadas
1079
+ - **Generación de código** para implementación y análisis
1080
+
1081
+ ### 📊 Métricas analizadas:
1082
+ - (Coeficiente de determinación)
1083
+ - RMSE (Error cuadrático medio)
1084
+ - AIC/BIC (Criterios de información)
1085
+ - Parámetros específicos del modelo
1086
+ - Intervalos de confianza
 
 
 
 
 
1087
  """)
1088
 
1089
  with gr.Row():
 
1102
  info="Selecciona el modelo de IA"
1103
  )
1104
 
1105
+ detail_level = gr.Radio(
1106
+ choices=["detallado", "resumido"],
1107
+ value="detallado",
1108
+ label="📋 Nivel de detalle del análisis",
1109
+ info="Detallado: análisis completo | Resumido: puntos clave"
1110
+ )
1111
+
1112
  analyze_btn = gr.Button(
1113
+ "🚀 Analizar y Comparar Modelos",
1114
  variant="primary",
1115
  size="lg"
1116
  )
 
1136
 
1137
  with gr.Column(scale=2):
1138
  analysis_output = gr.Markdown(
1139
+ label="📊 Análisis Comparativo",
1140
  elem_classes=["highlight-results"]
1141
  )
1142
 
1143
  code_output = gr.Code(
1144
+ label="💻 Código de Implementación",
1145
  language="python",
1146
+ interactive=True,
1147
+ lines=20
1148
  )
1149
 
1150
+ # Guía de formato de datos
1151
+ with gr.Accordion("📋 Formato de datos esperado", open=False):
1152
+ gr.Markdown("""
1153
+ ### Estructura ideal del CSV/Excel:
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
  # Ejemplos
1174
  gr.Examples(
1175
  examples=[
1176
+ [["examples/biomass_models_comparison.csv"], "claude-3-5-sonnet-20241022", "detallado"],
1177
+ [["examples/substrate_kinetics_results.xlsx"], "claude-3-5-sonnet-20241022", "resumido"],
1178
+ [["examples/product_formation_fits.csv"], "claude-3-opus-20240229", "detallado"]
1179
  ],
1180
+ inputs=[files_input, model_selector, detail_level],
1181
+ label="📚 Ejemplos de análisis"
1182
  )
1183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1184
  # Eventos
1185
  analyze_btn.click(
1186
+ fn=lambda files, model, detail: process_files(files, model, detail) if files else (
1187
+ "Por favor sube archivos con resultados de ajuste para analizar",
1188
+ ""
1189
  ),
1190
+ inputs=[files_input, model_selector, detail_level],
1191
  outputs=[analysis_output, code_output]
1192
  )
1193
 
1194
+ # Footer con información adicional
1195
+ gr.Markdown("""
1196
+ ---
1197
+ ### 🔍 Qué analiza específicamente:
1198
+
1199
+ 1. **Comparación de modelos**: Ranking basado en múltiples métricas
1200
+ 2. **Mejor modelo**: Identificación y justificación numérica
1201
+ 3. **Análisis por tipo**:
1202
+ - **Biomasa**: μmax, Xmax, tiempo de duplicación
1203
+ - **Sustrato**: Ks, Km, velocidades de consumo, Yx/s
1204
+ - **Producto**: α, β, productividad, Yp/x
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"),