C2MV commited on
Commit
acfb117
·
verified ·
1 Parent(s): a7929b6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1213 -514
app.py CHANGED
@@ -1,5 +1,4 @@
1
  import gradio as gr
2
- from openai import OpenAI
3
  import PyPDF2
4
  import pandas as pd
5
  import numpy as np
@@ -18,227 +17,218 @@ 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
22
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
23
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
24
  from reportlab.lib.units import inch
 
 
25
  import matplotlib.pyplot as plt
26
  from datetime import datetime
 
27
 
28
- # --- Configuración para la API de Qwen ---
29
-
30
- # Configuración de Gradio
31
  os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
32
 
33
- # Inicializar cliente OpenAI para apuntar a la API de Nebius (Qwen)
34
- try:
35
- if "NEBIUS_API_KEY" not in os.environ:
36
- print("⚠️ ADVERTENCIA: La variable de entorno NEBIUS_API_KEY no está configurada. El analizador no funcionará.")
37
- client = None
38
- else:
39
- client = OpenAI(
40
- base_url="https://api.studio.nebius.com/v1/",
41
- api_key=os.environ.get("NEBIUS_API_KEY")
42
- )
43
- except Exception as e:
44
- print(f"Error al inicializar el cliente OpenAI para Nebius: {e}")
45
- client = None
46
-
47
- # --- Sistema de Traducción Completo ---
48
 
 
49
  TRANSLATIONS = {
50
- 'en': {
51
- 'title': '🧬 Comparative Analyzer of Biotechnological Models',
52
- 'subtitle': 'Specialized in comparative analysis of mathematical model fitting results',
53
- 'upload_files': '📁 Upload fitting results (CSV/Excel)',
54
- 'select_model': '🤖 Qwen Model',
55
- 'select_language': '🌐 Language',
56
- 'select_theme': '🎨 Theme',
57
- 'detail_level': '📋 Analysis detail level',
58
- 'detailed': 'Detailed',
59
- 'summarized': 'Summarized',
60
- 'analyze_button': '🚀 Analyze and Compare Models',
61
- 'export_format': '📄 Export format',
62
- 'export_button': '💾 Export Report',
63
- 'comparative_analysis': '📊 Comparative Analysis',
64
- 'implementation_code': '💻 Implementation Code',
65
- 'data_format': '📋 Expected data format',
66
- 'examples': '📚 Analysis examples',
67
- 'light': 'Light',
68
- 'dark': 'Dark',
69
- 'best_for': 'Best for',
70
- 'loading': 'Loading...',
71
- 'error_no_api': 'Please configure NEBIUS_API_KEY in the environment variables or Space secrets',
72
- 'error_no_files': 'Please upload fitting result files to analyze',
73
- 'report_exported': 'Report exported successfully as',
74
- 'specialized_in': '🎯 Specialized in:',
75
- 'metrics_analyzed': '📊 Analyzed metrics:',
76
- 'what_analyzes': '🔍 What it specifically analyzes:',
77
- 'tips': '💡 Tips for better results:',
78
- 'additional_specs': '📝 Additional specifications for analysis',
79
- 'additional_specs_placeholder': 'Add any specific requirements or focus areas for the analysis...'
80
- },
81
- 'es': {
82
- 'title': '🧬 Analizador Comparativo de Modelos Biotecnológicos',
83
- 'subtitle': 'Especializado en análisis comparativo de resultados de ajuste de modelos matemáticos',
84
- 'upload_files': '📁 Subir resultados de ajuste (CSV/Excel)',
85
- 'select_model': '🤖 Modelo Qwen',
86
- 'select_language': '🌐 Idioma',
87
- 'select_theme': '🎨 Tema',
88
- 'detail_level': '📋 Nivel de detalle del análisis',
89
- 'detailed': 'Detallado',
90
- 'summarized': 'Resumido',
91
- 'analyze_button': '🚀 Analizar y Comparar Modelos',
92
- 'export_format': '📄 Formato de exportación',
93
- 'export_button': '💾 Exportar Reporte',
94
- 'comparative_analysis': '📊 Análisis Comparativo',
95
- 'implementation_code': '💻 Código de Implementación',
96
- 'data_format': '📋 Formato de datos esperado',
97
- 'examples': '📚 Ejemplos de análisis',
98
- 'light': 'Claro',
99
- 'dark': 'Oscuro',
100
- 'best_for': 'Mejor para',
101
- 'loading': 'Cargando...',
102
- 'error_no_api': 'Por favor configura NEBIUS_API_KEY en las variables de entorno o secretos del Space',
103
- 'error_no_files': 'Por favor sube archivos con resultados de ajuste para analizar',
104
- 'report_exported': 'Reporte exportado exitosamente como',
105
- 'specialized_in': '🎯 Especializado en:',
106
- 'metrics_analyzed': '📊 Métricas analizadas:',
107
- 'what_analyzes': '🔍 Qué analiza específicamente:',
108
- 'tips': '💡 Tips para mejores resultados:',
109
- 'additional_specs': '📝 Especificaciones adicionales para el análisis',
110
- 'additional_specs_placeholder': 'Agregue cualquier requerimiento específico o áreas de enfoque para el análisis...'
111
- },
112
- 'fr': {
113
- 'title': '🧬 Analyseur Comparatif de Modèles Biotechnologiques',
114
- 'subtitle': 'Spécialisé dans l\'analyse comparative des résultats d\'ajustement',
115
- 'upload_files': '📁 Télécharger les résultats (CSV/Excel)',
116
- 'select_model': '🤖 Modèle Qwen',
117
- 'select_language': '🌐 Langue',
118
- 'select_theme': '🎨 Thème',
119
- 'detail_level': '📋 Niveau de détail',
120
- 'detailed': 'Détaillé',
121
- 'summarized': 'Résumé',
122
- 'analyze_button': '🚀 Analyser et Comparer',
123
- 'export_format': '📄 Format d\'export',
124
- 'export_button': '💾 Exporter le Rapport',
125
- 'comparative_analysis': '📊 Analyse Comparative',
126
- 'implementation_code': '💻 Code d\'Implémentation',
127
- 'data_format': '📋 Format de données attendu',
128
- 'examples': '📚 Exemples d\'analyse',
129
- 'light': 'Clair',
130
- 'dark': 'Sombre',
131
- 'best_for': 'Meilleur pour',
132
- 'loading': 'Chargement...',
133
- 'error_no_api': 'Veuillez configurer NEBIUS_API_KEY',
134
- 'error_no_files': 'Veuillez télécharger des fichiers à analyser',
135
- 'report_exported': 'Rapport exporté avec succès comme',
136
- 'specialized_in': '🎯 Spécialisé dans:',
137
- 'metrics_analyzed': '📊 Métriques analysées:',
138
- 'what_analyzes': '🔍 Ce qu\'il analyse spécifiquement:',
139
- 'tips': '💡 Conseils pour de meilleurs résultats:',
140
- 'additional_specs': '📝 Spécifications supplémentaires pour l\'analyse',
141
- 'additional_specs_placeholder': 'Ajoutez des exigences spécifiques ou des domaines d\'intérêt pour l\'analyse...'
142
- },
143
- 'de': {
144
- 'title': '🧬 Vergleichender Analysator für Biotechnologische Modelle',
145
- 'subtitle': 'Spezialisiert auf vergleichende Analyse von Modellanpassungsergebnissen',
146
- 'upload_files': '📁 Ergebnisse hochladen (CSV/Excel)',
147
- 'select_model': '🤖 Qwen Modell',
148
- 'select_language': '🌐 Sprache',
149
- 'select_theme': '🎨 Thema',
150
- 'detail_level': '📋 Detailgrad der Analyse',
151
- 'detailed': 'Detailliert',
152
- 'summarized': 'Zusammengefasst',
153
- 'analyze_button': '🚀 Analysieren und Vergleichen',
154
- 'export_format': '📄 Exportformat',
155
- 'export_button': '💾 Bericht Exportieren',
156
- 'comparative_analysis': '📊 Vergleichende Analyse',
157
- 'implementation_code': '💻 Implementierungscode',
158
- 'data_format': '📋 Erwartetes Datenformat',
159
- 'examples': '📚 Analysebeispiele',
160
- 'light': 'Hell',
161
- 'dark': 'Dunkel',
162
- 'best_for': 'Am besten für',
163
- 'loading': 'Laden...',
164
- 'error_no_api': 'Bitte konfigurieren Sie NEBIUS_API_KEY',
165
- 'error_no_files': 'Bitte laden Sie Dateien zur Analyse hoch',
166
- 'report_exported': 'Bericht erfolgreich exportiert als',
167
- 'specialized_in': '🎯 Spezialisiert auf:',
168
- 'metrics_analyzed': '📊 Analysierte Metriken:',
169
- 'what_analyzes': '🔍 Was spezifisch analysiert wird:',
170
- 'tips': '💡 Tipps für bessere Ergebnisse:',
171
- 'additional_specs': '📝 Zusätzliche Spezifikationen für die Analyse',
172
- 'additional_specs_placeholder': 'Fügen Sie spezifische Anforderungen oder Schwerpunktbereiche für die Analyse hinzu...'
173
- },
174
- 'pt': {
175
- 'title': '🧬 Analisador Comparativo de Modelos Biotecnológicos',
176
- 'subtitle': 'Especializado em análise comparativa de resultados de ajuste',
177
- 'upload_files': '📁 Carregar resultados (CSV/Excel)',
178
- 'select_model': '🤖 Modelo Qwen',
179
- 'select_language': '🌐 Idioma',
180
- 'select_theme': '🎨 Tema',
181
- 'detail_level': '📋 Nível de detalhe',
182
- 'detailed': 'Detalhado',
183
- 'summarized': 'Resumido',
184
- 'analyze_button': '🚀 Analisar e Comparar',
185
- 'export_format': '📄 Formato de exportação',
186
- 'export_button': '💾 Exportar Relatório',
187
- 'comparative_analysis': '📊 Análise Comparativa',
188
- 'implementation_code': '💻 Código de Implementação',
189
- 'data_format': '📋 Formato de dados esperado',
190
- 'examples': '📚 Exemplos de análise',
191
- 'light': 'Claro',
192
- 'dark': 'Escuro',
193
- 'best_for': 'Melhor para',
194
- 'loading': 'Carregando...',
195
- 'error_no_api': 'Por favor configure NEBIUS_API_KEY',
196
- 'error_no_files': 'Por favor carregue arquivos para analisar',
197
- 'report_exported': 'Relatório exportado com sucesso como',
198
- 'specialized_in': '🎯 Especializado em:',
199
- 'metrics_analyzed': '📊 Métricas analisadas:',
200
- 'what_analyzes': '🔍 O que analisa especificamente:',
201
- 'tips': '💡 Dicas para melhores resultados:',
202
- 'additional_specs': '📝 Especificações adicionais para a análise',
203
- 'additional_specs_placeholder': 'Adicione requisitos específicos ou áreas de foco para a análise...'
204
- }
205
  }
206
 
207
- # --- Temas de la Interfaz ---
208
-
209
  THEMES = {
210
- 'light': gr.themes.Soft(),
211
- 'dark': gr.themes.Base(
212
- primary_hue="blue",
213
- secondary_hue="gray",
214
- neutral_hue="gray",
215
- font=["Arial", "sans-serif"]
216
- ).set(
217
- body_background_fill="dark",
218
- body_background_fill_dark="*neutral_950",
219
- button_primary_background_fill="*primary_600",
220
- button_primary_background_fill_hover="*primary_500",
221
- button_primary_text_color="white",
222
- block_background_fill="*neutral_800",
223
- block_border_color="*neutral_700",
224
- block_label_text_color="*neutral_200",
225
- block_title_text_color="*neutral_100",
226
- checkbox_background_color="*neutral_700",
227
- checkbox_background_color_selected="*primary_600",
228
- input_background_fill="*neutral_700",
229
- input_border_color="*neutral_600",
230
- input_placeholder_color="*neutral_400"
231
- )
232
  }
233
 
234
- # --- Clases de Datos y Estructuras ---
235
-
236
  class AnalysisType(Enum):
237
  MATHEMATICAL_MODEL = "mathematical_model"
238
  DATA_FITTING = "data_fitting"
239
  FITTING_RESULTS = "fitting_results"
240
  UNKNOWN = "unknown"
241
 
 
242
  @dataclass
243
  class MathematicalModel:
244
  name: str
@@ -249,23 +239,29 @@ class MathematicalModel:
249
  category: str
250
  biological_meaning: str
251
 
 
252
  class ModelRegistry:
253
  def __init__(self):
254
  self.models = {}
255
  self._initialize_default_models()
256
-
257
  def register_model(self, model: MathematicalModel):
 
258
  if model.category not in self.models:
259
  self.models[model.category] = {}
260
  self.models[model.category][model.name] = model
261
-
262
- def get_model(self, category: str, name: str) -> Optional[MathematicalModel]:
 
263
  return self.models.get(category, {}).get(name)
264
-
265
  def get_all_models(self) -> Dict:
 
266
  return self.models
267
-
268
  def _initialize_default_models(self):
 
 
269
  self.register_model(MathematicalModel(
270
  name="Monod",
271
  equation="μ = μmax × (S / (Ks + S))",
@@ -296,54 +292,70 @@ class ModelRegistry:
296
  biological_meaning="Incluye fase de adaptación (lag) seguida de crecimiento exponencial y estacionario"
297
  ))
298
 
 
299
  model_registry = ModelRegistry()
300
 
 
301
  QWEN_MODELS = {
302
  "Qwen/Qwen3-14B": {
303
  "name": "Qwen 3 14B",
304
- "description": "Modelo potente y versátil de la serie Qwen.",
305
- "max_tokens": 4096,
306
- "best_for": "Análisis complejos y generación de código detallado."
 
 
 
 
 
 
 
 
 
 
 
 
307
  }
308
  }
309
 
310
- # --- Clases de Procesamiento y Exportación ---
311
-
312
  class FileProcessor:
 
 
313
  @staticmethod
314
- def extract_text_from_pdf(pdf_file: bytes) -> str:
 
315
  try:
316
  pdf_reader = PyPDF2.PdfReader(io.BytesIO(pdf_file))
317
  text = ""
318
  for page in pdf_reader.pages:
319
- page_text = page.extract_text()
320
- if page_text:
321
- text += page_text + "\n"
322
  return text
323
  except Exception as e:
324
  return f"Error reading PDF: {str(e)}"
325
-
326
  @staticmethod
327
- def read_csv(csv_file: bytes) -> Optional[pd.DataFrame]:
 
328
  try:
329
  return pd.read_csv(io.BytesIO(csv_file))
330
- except Exception:
331
  return None
332
-
333
  @staticmethod
334
- def read_excel(excel_file: bytes) -> Optional[pd.DataFrame]:
 
335
  try:
336
  return pd.read_excel(io.BytesIO(excel_file))
337
- except Exception:
338
  return None
339
-
340
  @staticmethod
341
- def extract_from_zip(zip_file: bytes) -> List[Tuple[str, bytes]]:
 
342
  files = []
343
  try:
344
  with zipfile.ZipFile(io.BytesIO(zip_file), 'r') as zip_ref:
345
  for file_name in zip_ref.namelist():
346
- if not file_name.startswith('__MACOSX') and not file_name.endswith('/'):
347
  file_data = zip_ref.read(file_name)
348
  files.append((file_name, file_data))
349
  except Exception as e:
@@ -351,65 +363,134 @@ class FileProcessor:
351
  return files
352
 
353
  class ReportExporter:
 
 
354
  @staticmethod
355
  def export_to_docx(content: str, filename: str, language: str = 'en') -> str:
 
356
  doc = Document()
 
 
357
  title_style = doc.styles['Title']
358
  title_style.font.size = Pt(24)
359
  title_style.font.bold = True
360
 
361
- heading1_style = doc.styles['Heading 1']
362
- heading1_style.font.size = Pt(18)
363
- heading1_style.font.bold = True
 
 
 
 
 
 
 
 
 
364
 
365
- title_text = TRANSLATIONS[language]['title']
366
- doc.add_heading(title_text, 0)
367
 
368
- date_text = {'en': 'Generated on', 'es': 'Generado el'}
 
 
 
 
 
 
 
369
  doc.add_paragraph(f"{date_text.get(language, date_text['en'])}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
370
  doc.add_paragraph()
371
 
 
372
  lines = content.split('\n')
 
 
373
  for line in lines:
374
  line = line.strip()
 
375
  if line.startswith('###'):
376
- doc.add_heading(line.replace('###', '').strip(), level=3)
377
  elif line.startswith('##'):
378
- doc.add_heading(line.replace('##', '').strip(), level=2)
379
  elif line.startswith('#'):
380
- doc.add_heading(line.replace('#', '').strip(), level=1)
381
  elif line.startswith('**') and line.endswith('**'):
 
382
  p = doc.add_paragraph()
383
- p.add_run(line.replace('**', '')).bold = True
 
384
  elif line.startswith('- ') or line.startswith('* '):
 
385
  doc.add_paragraph(line[2:], style='List Bullet')
 
 
 
 
 
 
386
  elif line:
 
387
  doc.add_paragraph(line)
388
 
 
389
  doc.save(filename)
390
  return filename
391
-
392
  @staticmethod
393
  def export_to_pdf(content: str, filename: str, language: str = 'en') -> str:
 
 
394
  doc = SimpleDocTemplate(filename, pagesize=letter)
395
  story = []
396
  styles = getSampleStyleSheet()
397
 
398
- title_style = ParagraphStyle('CustomTitle', parent=styles['Title'], fontSize=24, textColor=colors.HexColor('#1f4788'), spaceAfter=20)
399
- heading_style = ParagraphStyle('CustomHeading1', parent=styles['Heading1'], fontSize=16, textColor=colors.HexColor('#2e5090'), spaceAfter=12)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
 
401
- title_text = TRANSLATIONS[language]['title']
402
- story.append(Paragraph(title_text, title_style))
403
 
404
- date_text = {'en': 'Generated on', 'es': 'Generado el'}
 
 
 
 
 
 
 
405
  story.append(Paragraph(f"{date_text.get(language, date_text['en'])}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", styles['Normal']))
406
- story.append(Spacer(1, 0.3 * inch))
407
 
 
408
  lines = content.split('\n')
 
409
  for line in lines:
410
  line = line.strip()
 
411
  if not line:
412
- continue
413
  elif line.startswith('###'):
414
  story.append(Paragraph(line.replace('###', '').strip(), styles['Heading3']))
415
  elif line.startswith('##'):
@@ -417,206 +498,737 @@ class ReportExporter:
417
  elif line.startswith('#'):
418
  story.append(Paragraph(line.replace('#', '').strip(), heading_style))
419
  elif line.startswith('**') and line.endswith('**'):
420
- story.append(Paragraph(f"<b>{line.replace('**', '')}</b>", styles['Normal']))
 
421
  elif line.startswith('- ') or line.startswith('* '):
422
- story.append(Paragraph(f"• {line[2:]}", styles['Normal'], bulletText='•'))
 
 
 
 
423
  else:
424
- # Limpiar caracteres que pueden dar problemas en ReportLab
425
- clean_line = line.replace('🧬', '[DNA]').replace('🤖', '[BOT]').replace('📁', '[FILE]').replace('🚀', '[ROCKET]').replace('📊', '[GRAPH]').replace('💻', '[CODE]').replace('💾', '[SAVE]').replace('🎯', '[TARGET]').replace('🔍', '[SEARCH]').replace('💡', '[TIP]')
426
  story.append(Paragraph(clean_line, styles['Normal']))
427
- story.append(Spacer(1, 0.1 * inch))
428
 
 
429
  doc.build(story)
430
  return filename
431
 
432
- # --- Clase del Analizador de IA con Qwen ---
433
-
434
  class AIAnalyzer:
 
 
435
  def __init__(self, client, model_registry):
436
  self.client = client
437
  self.model_registry = model_registry
438
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
  def get_language_prompt_prefix(self, language: str) -> str:
440
- return TRANSLATIONS.get(language, TRANSLATIONS['en']).get('response_prefix', f"Please respond in {language}. ")
441
-
442
- def analyze_fitting_results(self, data: pd.DataFrame, qwen_model: str, detail_level: str, language: str, additional_specs: str) -> Dict:
443
- if self.client is None:
444
- return {"error": "AI client not initialized. Check NEBIUS_API_KEY."}
445
-
446
- data_summary = f"FITTING RESULTS DATA:\n- Columns: {list(data.columns)}\n- Number of models/entries: {len(data)}\n- Full Data:\n{data.to_string()}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
  lang_prefix = self.get_language_prompt_prefix(language)
448
- user_specs_section = f"\nADDITIONAL USER SPECIFICATIONS: {additional_specs}\n" if additional_specs else ""
449
 
 
 
 
 
 
 
 
 
 
 
450
  if detail_level == "detailed":
451
- prompt = f"""{lang_prefix}{user_specs_section}
452
- You are an expert biotechnologist and data scientist. Analyze the provided model fitting results in detail.
453
- 1. **Overall Summary:** Briefly describe the dataset (number of experiments, models, metrics).
454
- 2. **Analysis by Experiment/Condition:** For each unique experiment/condition found in the data:
455
- - Identify the best performing model for each category (e.g., Biomass, Substrate, Product) based on R2 (higher is better) and RMSE (lower is better).
456
- - List the best model's name, its key metrics (R2, RMSE), and its fitted parameters.
457
- - Provide a brief biological interpretation of the parameters for the best model in that context.
458
- 3. **Overall Best Models:** Identify the most robust models across all experiments for each category. Justify your choice (e.g., "Model X was best in 3 out of 4 experiments").
459
- 4. **Parameter Trends:** If possible, comment on how key parameters (like μmax, Ks) change across different experimental conditions.
460
- 5. **Conclusion and Recommendations:** Summarize the findings and recommend which models to use for future predictions under specific conditions.
461
- Format the entire response using clear Markdown headers, lists, and bold text."""
462
- else: # summarized
463
- prompt = f"""{lang_prefix}{user_specs_section}
464
- You are an expert biotechnologist. Provide a concise, summarized analysis of the provided model fitting results.
465
- 1. **Best Models Summary Table:** Create a table that shows the best model for each Experiment and Type (Biomass, Substrate, etc.) along with its R2 value.
466
- 2. **Overall Winners:** State the single best model for Biomass, Substrate, and Product across all experiments.
467
- 3. **Key Insights:** In 2-3 bullet points, what are the most important findings? (e.g., "Gompertz model consistently outperforms others for biomass.", "μmax is highest at pH 7.5.").
468
- Format as a brief and clear Markdown report."""
469
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  try:
471
  # Análisis principal
472
- analysis_response = self.client.chat.completions.create(
473
  model=qwen_model,
474
- messages=[{"role": "user", "content": f"{prompt}\n\n{data_summary}"}],
475
  max_tokens=4000,
476
- temperature=0.6,
477
- top_p=0.95
 
 
 
478
  )
479
- analysis_text = analysis_response.choices[0].message.content
480
-
 
481
  # Generación de código
482
- code_prompt = f"""{lang_prefix}
483
- Based on the provided data, generate a single, complete, and executable Python script for analysis.
484
- The script must:
485
- 1. Contain the provided data hardcoded into a pandas DataFrame.
486
- 2. Define functions to analyze the data to find the best model per experiment and type.
487
- 3. Include functions to create visualizations using matplotlib or seaborn, such as:
488
- - A bar chart comparing the R2 values of the best models across experiments.
489
- - A summary table of the best models.
490
- 4. Have a `if __name__ == "__main__":` block that runs the analysis and shows the plots.
491
- The code should be well-commented and self-contained."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
 
493
  code_response = self.client.chat.completions.create(
494
  model=qwen_model,
495
- messages=[{"role": "user", "content": f"{code_prompt}\n\n{data_summary}"}],
496
- max_tokens=3500,
497
- temperature=0.4, # Más determinista para código
498
- top_p=0.95
 
 
499
  )
500
- code_text = code_response.choices[0].message.content
501
-
 
502
  return {
503
- "analisis_completo": analysis_text,
504
- "codigo_implementacion": code_text,
 
 
 
 
 
 
 
 
 
 
505
  }
 
506
  except Exception as e:
507
- return {"error": f"An error occurred with the AI API: {str(e)}"}
508
-
509
- # --- Lógica de la Aplicación ---
510
-
511
- def process_files(files, model_name: str, detail_level: str, language: str, additional_specs: str) -> Tuple[str, str]:
512
- if not files:
513
- return TRANSLATIONS[language]['error_no_files'], "Please upload files first."
514
- if client is None:
515
- return TRANSLATIONS[language]['error_no_api'], "AI client is not configured."
516
 
 
 
 
 
517
  analyzer = AIAnalyzer(client, model_registry)
518
- all_analysis_parts = []
519
- all_code_parts = []
520
-
521
  for file in files:
522
- try:
523
- with open(file.name, 'rb') as f:
524
- file_content = f.read()
 
 
 
 
 
 
 
 
 
 
 
525
 
526
- file_name = os.path.basename(file.name)
527
- file_ext = Path(file_name).suffix.lower()
528
- df = None
529
-
530
  if file_ext == '.csv':
531
- df = FileProcessor.read_csv(file_content)
532
- elif file_ext in ['.xlsx', '.xls']:
533
- df = FileProcessor.read_excel(file_content)
534
 
535
  if df is not None:
536
- all_analysis_parts.append(f"# Analysis for: {file_name}")
537
- result = analyzer.analyze_fitting_results(df, model_name, detail_level, language, additional_specs)
538
 
539
- if "error" in result:
540
- all_analysis_parts.append(f"An error occurred: {result['error']}")
541
- else:
542
- all_analysis_parts.append(result.get("analisis_completo", "No analysis generated."))
543
- all_code_parts.append(f"# Code generated from: {file_name}\n{result.get('codigo_implementacion', '# No code generated.')}")
544
- else:
545
- all_analysis_parts.append(f"# Could not process file: {file_name}")
546
- except Exception as e:
547
- all_analysis_parts.append(f"# Error processing {file.name}: {str(e)}")
548
-
549
- final_analysis = "\n\n---\n\n".join(all_analysis_parts)
550
- final_code = "\n\n" + "="*80 + "\n\n".join(all_code_parts) if all_code_parts else generate_implementation_code(final_analysis)
 
 
 
551
 
552
- return final_analysis, final_code
553
-
 
 
554
 
555
  def generate_implementation_code(analysis_results: str) -> str:
556
- # This is a fallback in case the API fails to generate code.
557
- # It provides a generic template.
558
- return """
559
  import pandas as pd
560
  import matplotlib.pyplot as plt
 
 
 
561
  import seaborn as sns
 
562
 
563
- def analyze_data(df):
 
 
 
 
564
  \"\"\"
565
- Analyzes the dataframe to find the best model per experiment and type.
566
- This is a template function. You may need to adapt it to your specific column names.
567
  \"\"\"
568
- # Assuming columns 'Experiment', 'Type', 'R2', 'Model' exist
569
- if not all(col in df.columns for col in ['Experiment', 'Type', 'R2', 'Model']):
570
- print("DataFrame is missing required columns: 'Experiment', 'Type', 'R2', 'Model'")
571
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
 
573
- # Find the index of the max R2 for each group
574
- best_models_idx = df.loc[df.groupby(['Experiment', 'Type'])['R2'].idxmax()]
575
 
576
- print("--- Best Models by Experiment and Type ---")
577
- print(best_models_idx[['Experiment', 'Type', 'Model', 'R2']].to_string(index=False))
578
- return best_models_idx
579
-
580
- def visualize_results(best_models_df):
581
- \"\"\"
582
- Creates a bar plot to visualize the R2 scores of the best models.
583
- \"\"\"
584
- if best_models_df is None:
585
- print("Cannot visualize results. Analysis failed.")
586
- return
587
-
588
- plt.figure(figsize=(12, 7))
589
- sns.barplot(data=best_models_df, x='Experiment', y='R2', hue='Type', palette='viridis')
590
-
591
- plt.title('Best Model Performance (R²) by Experiment and Type', fontsize=16)
592
- plt.xlabel('Experiment', fontsize=12)
593
- plt.ylabel('R² Score', fontsize=12)
594
- plt.xticks(rotation=45, ha='right')
595
- plt.ylim(bottom=max(0, best_models_df['R2'].min() - 0.05), top=1.0)
596
- plt.legend(title='Variable Type')
597
- plt.tight_layout()
598
- plt.show()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
599
 
600
- if __name__ == '__main__':
601
- # TODO: Replace this with your actual data
602
- # This is placeholder data.
603
- data = {
604
- 'Experiment': ['pH_7.0', 'pH_7.0', 'pH_7.5', 'pH_7.5', 'pH_7.0', 'pH_7.5'],
605
- 'Model': ['Monod', 'Logistic', 'Monod', 'Logistic', 'FirstOrder', 'FirstOrder'],
606
- 'Type': ['Biomass', 'Biomass', 'Biomass', 'Biomass', 'Substrate', 'Substrate'],
607
- 'R2': [0.98, 0.99, 0.97, 0.96, 0.95, 0.99],
608
- 'RMSE': [0.1, 0.05, 0.12, 0.15, 0.2, 0.08]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
609
  }
610
- df = pd.DataFrame(data)
611
 
612
- print("--- Input Data ---")
613
- print(df)
614
- print("\\n")
615
-
616
- best_models = analyze_data(df)
617
- visualize_results(best_models)
 
 
 
 
 
 
 
 
 
 
618
  """
 
 
619
 
 
620
  class AppState:
621
  def __init__(self):
622
  self.current_analysis = ""
@@ -625,12 +1237,20 @@ class AppState:
625
 
626
  app_state = AppState()
627
 
628
- def export_report(export_format: str, language: str) -> Tuple[str, Optional[str]]:
 
629
  if not app_state.current_analysis:
630
- error_msg = TRANSLATIONS[language]['error_no_files'].replace('analizar', 'exportar')
631
- return error_msg, None
632
-
 
 
 
 
 
 
633
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
 
634
  try:
635
  if export_format == "DOCX":
636
  filename = f"biotech_analysis_report_{timestamp}.docx"
@@ -642,164 +1262,243 @@ def export_report(export_format: str, language: str) -> Tuple[str, Optional[str]
642
  success_msg = TRANSLATIONS[language]['report_exported']
643
  return f"{success_msg} {filename}", filename
644
  except Exception as e:
645
- return f"Error during export: {str(e)}", None
646
-
647
- # --- Interfaz de Gradio ---
648
 
 
649
  def create_interface():
 
 
650
  current_language = "en"
651
-
652
- def update_interface_language(language: str):
 
653
  app_state.current_language = language
654
  t = TRANSLATIONS[language]
 
655
  return [
656
- gr.update(value=f"# {t['title']}"),
657
- gr.update(value=t['subtitle']),
658
- gr.update(label=t['upload_files']),
659
- gr.update(label=t['select_model']),
660
- gr.update(label=t['select_language']),
661
- gr.update(label=t['select_theme']),
662
- gr.update(label=t['detail_level'], choices=[(t['detailed'], "detailed"), (t['summarized'], "summarized")]),
663
- gr.update(label=t['additional_specs'], placeholder=t['additional_specs_placeholder']),
664
- gr.update(value=t['analyze_button']),
665
- gr.update(label=t['export_format']),
666
- gr.update(value=t['export_button']),
667
- gr.update(label=t['comparative_analysis']),
668
- gr.update(label=t['implementation_code']),
669
- gr.update(label=t['data_format']),
670
- gr.update(label=t['examples'])
671
  ]
672
-
673
  def process_and_store(files, model, detail, language, additional_specs):
 
674
  if not files:
675
- return TRANSLATIONS[language]['error_no_files'], ""
 
676
 
677
  analysis, code = process_files(files, model, detail, language, additional_specs)
678
  app_state.current_analysis = analysis
679
  app_state.current_code = code
680
  return analysis, code
681
-
682
- with gr.Blocks(theme=THEMES['light']) as demo:
683
- # Estructura de la UI
684
- with gr.Column():
685
- with gr.Row():
686
- with gr.Column(scale=3):
687
- title_text = gr.Markdown(f"# {TRANSLATIONS[current_language]['title']}")
688
- subtitle_text = gr.Markdown(TRANSLATIONS[current_language]['subtitle'])
689
- with gr.Column(scale=1):
690
- language_selector = gr.Dropdown(choices=[("English", "en"), ("Español", "es"), ("Français", "fr"), ("Deutsch", "de"), ("Português", "pt")], value=current_language, label="Language", interactive=True)
691
- theme_selector = gr.Dropdown(choices=["Light", "Dark"], value="Light", label="Theme", interactive=True, visible=False) # Theme switching is complex, hiding for now
692
-
693
- with gr.Row():
694
- with gr.Column(scale=1):
695
- files_input = gr.File(label=TRANSLATIONS[current_language]['upload_files'], file_count="multiple", file_types=[".csv", ".xlsx", ".xls"], type="filepath")
696
- model_selector = gr.Dropdown(choices=list(QWEN_MODELS.keys()), value="Qwen/Qwen3-14B", label=TRANSLATIONS[current_language]['select_model'], info=QWEN_MODELS["Qwen/Qwen3-14B"]['best_for'])
697
- detail_level = gr.Radio(choices=[(TRANSLATIONS[current_language]['detailed'], "detailed"), (TRANSLATIONS[current_language]['summarized'], "summarized")], value="detailed", label=TRANSLATIONS[current_language]['detail_level'])
698
- additional_specs = gr.Textbox(label=TRANSLATIONS[current_language]['additional_specs'], placeholder=TRANSLATIONS[current_language]['additional_specs_placeholder'], lines=3, max_lines=5, interactive=True)
699
- analyze_btn = gr.Button(TRANSLATIONS[current_language]['analyze_button'], variant="primary", size="lg")
700
-
701
- gr.Markdown("---")
702
-
703
- export_format = gr.Radio(choices=["PDF", "DOCX"], value="PDF", label=TRANSLATIONS[current_language]['export_format'])
704
- export_btn = gr.Button(TRANSLATIONS[current_language]['export_button'], variant="secondary")
705
- export_status = gr.Textbox(label="Export Status", interactive=False, visible=False)
706
- export_file = gr.File(label="Download Report", visible=False)
707
-
708
- with gr.Column(scale=2):
709
- analysis_output = gr.Markdown(label=TRANSLATIONS[current_language]['comparative_analysis'])
710
- code_output = gr.Code(label=TRANSLATIONS[current_language]['implementation_code'], language="python", interactive=True, lines=20)
711
-
712
- with gr.Accordion(label=TRANSLATIONS[current_language]['data_format'], open=False) as data_format_accordion:
713
- gr.Markdown("""
714
- ### Expected CSV/Excel Structure:
715
- The file should contain columns representing your model fitting results. Essential columns are:
716
- - **Experiment**: An identifier for the experimental condition (e.g., `pH_7.0`, `Temp_30C`).
717
- - **Model**: The name of the mathematical model (e.g., `Monod`, `Logistic`).
718
- - **Type**: The type of process being modeled (e.g., `Biomass`, `Substrate`, `Product`). This is crucial for categorical analysis.
719
- - **R2 / R_squared**: The coefficient of determination. Higher is better.
720
- - **RMSE**: Root Mean Squared Error. Lower is better.
721
- - **[Parameter_Columns]**: Additional columns for each model parameter (e.g., `mu_max`, `Ks`, `Xmax`).
722
-
723
- | Experiment | Model | Type | R2 | RMSE | mu_max |
724
- |:-----------|:---------|:----------|------:|-------:|--------:|
725
- | pH_7.0 | Monod | Biomass | 0.985 | 0.023 | 0.45 |
726
- | pH_7.0 | Logistic | Biomass | 0.991 | 0.018 | 0.48 |
727
- | pH_7.5 | Monod | Biomass | 0.978 | 0.027 | 0.43 |
728
- """)
729
-
730
- examples_ui = gr.Examples(
731
- examples=[
732
- [["examples/biomass_models_comparison.csv"], "Qwen/Qwen3-14B", "detailed", ""],
733
- [["examples/substrate_kinetics_results.xlsx"], "Qwen/Qwen3-14B", "summarized", "Focus on temperature effects and which temp is optimal."]
734
- ],
735
- inputs=[files_input, model_selector, detail_level, additional_specs],
736
- label=TRANSLATIONS[current_language]['examples']
737
- )
738
-
739
- # Event Handlers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
740
  language_selector.change(
741
  update_interface_language,
742
  inputs=[language_selector],
743
- outputs=[title_text, subtitle_text, files_input, model_selector, language_selector, theme_selector, detail_level, additional_specs, analyze_btn, export_format, export_btn, analysis_output, code_output, data_format_accordion, examples_ui]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
  )
745
 
746
  analyze_btn.click(
747
  fn=process_and_store,
748
  inputs=[files_input, model_selector, detail_level, language_selector, additional_specs],
749
- outputs=[analysis_output, code_output],
750
- api_name="analyze"
751
  )
752
 
753
- def handle_export(fmt, lang):
754
- status, file_path = export_report(fmt, lang)
755
- if file_path:
756
- return gr.update(value=status, visible=True), gr.update(value=file_path, visible=True)
757
  else:
758
  return gr.update(value=status, visible=True), gr.update(visible=False)
759
-
760
  export_btn.click(
761
  fn=handle_export,
762
  inputs=[export_format, language_selector],
763
  outputs=[export_status, export_file]
764
  )
765
-
766
  return demo
767
 
768
- # --- Punto de Entrada Principal ---
769
-
770
  def main():
771
- if client is None:
 
772
  return gr.Interface(
773
- fn=lambda: TRANSLATIONS['en']['error_no_api'],
774
- inputs=[],
775
- outputs=gr.Textbox(label="Error"),
776
  title="Configuration Error"
777
  )
 
778
  return create_interface()
779
 
 
780
  if __name__ == "__main__":
781
- # Crear archivos y carpetas de ejemplo si no existen para que la UI no falle
782
- if not os.path.exists("examples"):
783
- os.makedirs("examples")
784
- if not os.path.exists("examples/biomass_models_comparison.csv"):
785
- pd.DataFrame({
786
- 'Experiment': ['Exp1_pH7', 'Exp1_pH7', 'Exp2_pH8', 'Exp2_pH8'],
787
- 'Model': ['Monod', 'Logistic', 'Monod', 'Logistic'],
788
- 'Type': ['Biomass', 'Biomass', 'Biomass', 'Biomass'],
789
- 'R2': [0.98, 0.99, 0.97, 0.96],
790
- 'RMSE': [0.1, 0.05, 0.12, 0.15],
791
- 'mu_max': [0.5, 0.52, 0.45, 0.46]
792
- }).to_csv("examples/biomass_models_comparison.csv", index=False)
793
- if not os.path.exists("examples/substrate_kinetics_results.xlsx"):
794
- pd.DataFrame({
795
- 'Experiment': ['T30C', 'T30C', 'T37C', 'T37C'],
796
- 'Model': ['FirstOrder', 'MichaelisMenten', 'FirstOrder', 'MichaelisMenten'],
797
- 'Type': ['Substrate', 'Substrate', 'Substrate', 'Substrate'],
798
- 'R2': [0.95, 0.94, 0.99, 0.98],
799
- 'RMSE': [0.2, 0.25, 0.08, 0.1],
800
- 'Ks': [None, 1.5, None, 1.2]
801
- }).to_excel("examples/substrate_kinetics_results.xlsx", index=False)
802
-
803
  demo = main()
804
  if demo:
805
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
 
 
 
 
 
1
  import gradio as gr
 
2
  import PyPDF2
3
  import pandas as pd
4
  import numpy as np
 
17
  from docx.shared import Inches, Pt, RGBColor
18
  from docx.enum.text import WD_ALIGN_PARAGRAPH
19
  from reportlab.lib import colors
20
+ from reportlab.lib.pagesizes import letter, A4
21
+ from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, PageBreak
22
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
23
  from reportlab.lib.units import inch
24
+ from reportlab.pdfbase import pdfmetrics
25
+ from reportlab.pdfbase.ttfonts import TTFont
26
  import matplotlib.pyplot as plt
27
  from datetime import datetime
28
+ from openai import OpenAI # Replaced Anthropic with OpenAI for Qwen
29
 
30
+ # Configuración para HuggingFace
 
 
31
  os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
32
 
33
+ # Inicializar cliente Qwen
34
+ client = OpenAI(
35
+ base_url="https://api.studio.nebius.com/v1/",
36
+ api_key=os.environ.get("NEBIUS_API_KEY")
37
+ )
 
 
 
 
 
 
 
 
 
 
38
 
39
+ # Sistema de traducción - Actualizado con nuevas entradas
40
  TRANSLATIONS = {
41
+ 'en': {
42
+ 'title': '🧬 Comparative Analyzer of Biotechnological Models',
43
+ 'subtitle': 'Specialized in comparative analysis of mathematical model fitting results',
44
+ 'upload_files': '📁 Upload fitting results (CSV/Excel)',
45
+ 'select_model': '🤖 Qwen Model',
46
+ 'select_language': '🌐 Language',
47
+ 'select_theme': '🎨 Theme',
48
+ 'detail_level': '📋 Analysis detail level',
49
+ 'detailed': 'Detailed',
50
+ 'summarized': 'Summarized',
51
+ 'analyze_button': '🚀 Analyze and Compare Models',
52
+ 'export_format': '📄 Export format',
53
+ 'export_button': '💾 Export Report',
54
+ 'comparative_analysis': '📊 Comparative Analysis',
55
+ 'implementation_code': '💻 Implementation Code',
56
+ 'data_format': '📋 Expected data format',
57
+ 'examples': '📚 Analysis examples',
58
+ 'light': 'Light',
59
+ 'dark': 'Dark',
60
+ 'best_for': 'Best for',
61
+ 'loading': 'Loading...',
62
+ 'error_no_api': 'Please configure NEBIUS_API_KEY in HuggingFace Space secrets',
63
+ 'error_no_files': 'Please upload fitting result files to analyze',
64
+ 'report_exported': 'Report exported successfully as',
65
+ 'specialized_in': '🎯 Specialized in:',
66
+ 'metrics_analyzed': '📊 Analyzed metrics:',
67
+ 'what_analyzes': '🔍 What it specifically analyzes:',
68
+ 'tips': '💡 Tips for better results:',
69
+ 'additional_specs': '📝 Additional specifications for analysis',
70
+ 'additional_specs_placeholder': 'Add any specific requirements or focus areas for the analysis...'
71
+ },
72
+ 'es': {
73
+ 'title': '🧬 Analizador Comparativo de Modelos Biotecnológicos',
74
+ 'subtitle': 'Especializado en análisis comparativo de resultados de ajuste de modelos matemáticos',
75
+ 'upload_files': '📁 Subir resultados de ajuste (CSV/Excel)',
76
+ 'select_model': '🤖 Modelo Qwen',
77
+ 'select_language': '🌐 Idioma',
78
+ 'select_theme': '🎨 Tema',
79
+ 'detail_level': '📋 Nivel de detalle del análisis',
80
+ 'detailed': 'Detallado',
81
+ 'summarized': 'Resumido',
82
+ 'analyze_button': '🚀 Analizar y Comparar Modelos',
83
+ 'export_format': '📄 Formato de exportación',
84
+ 'export_button': '💾 Exportar Reporte',
85
+ 'comparative_analysis': '📊 Análisis Comparativo',
86
+ 'implementation_code': '💻 Código de Implementación',
87
+ 'data_format': '📋 Formato de datos esperado',
88
+ 'examples': '📚 Ejemplos de análisis',
89
+ 'light': 'Claro',
90
+ 'dark': 'Oscuro',
91
+ 'best_for': 'Mejor para',
92
+ 'loading': 'Cargando...',
93
+ 'error_no_api': 'Por favor configura NEBIUS_API_KEY en los secretos del Space',
94
+ 'error_no_files': 'Por favor sube archivos con resultados de ajuste para analizar',
95
+ 'report_exported': 'Reporte exportado exitosamente como',
96
+ 'specialized_in': '🎯 Especializado en:',
97
+ 'metrics_analyzed': '📊 Métricas analizadas:',
98
+ 'what_analyzes': '🔍 Qué analiza específicamente:',
99
+ 'tips': '💡 Tips para mejores resultados:',
100
+ 'additional_specs': '📝 Especificaciones adicionales para el análisis',
101
+ 'additional_specs_placeholder': 'Agregue cualquier requerimiento específico o áreas de enfoque para el análisis...'
102
+ },
103
+ 'fr': {
104
+ 'title': '🧬 Analyseur Comparatif de Modèles Biotechnologiques',
105
+ 'subtitle': 'Spécialisé dans l\'analyse comparative des résultats d\'ajustement',
106
+ 'upload_files': '📁 Télécharger les résultats (CSV/Excel)',
107
+ 'select_model': '🤖 Modèle Qwen',
108
+ 'select_language': '🌐 Langue',
109
+ 'select_theme': '🎨 Thème',
110
+ 'detail_level': '📋 Niveau de détail',
111
+ 'detailed': 'Détaillé',
112
+ 'summarized': 'Résumé',
113
+ 'analyze_button': '🚀 Analyser et Comparer',
114
+ 'export_format': '📄 Format d\'export',
115
+ 'export_button': '💾 Exporter le Rapport',
116
+ 'comparative_analysis': '📊 Analyse Comparative',
117
+ 'implementation_code': '💻 Code d\'Implémentation',
118
+ 'data_format': '📋 Format de données attendu',
119
+ 'examples': '📚 Exemples d\'analyse',
120
+ 'light': 'Clair',
121
+ 'dark': 'Sombre',
122
+ 'best_for': 'Meilleur pour',
123
+ 'loading': 'Chargement...',
124
+ 'error_no_api': 'Veuillez configurer NEBIUS_API_KEY',
125
+ 'error_no_files': 'Veuillez télécharger des fichiers à analyser',
126
+ 'report_exported': 'Rapport exporté avec succès comme',
127
+ 'specialized_in': '🎯 Spécialisé dans:',
128
+ 'metrics_analyzed': '📊 Métriques analysées:',
129
+ 'what_analyzes': '🔍 Ce qu\'il analyse spécifiquement:',
130
+ 'tips': '💡 Conseils pour de meilleurs résultats:',
131
+ 'additional_specs': '📝 Spécifications supplémentaires pour l\'analyse',
132
+ 'additional_specs_placeholder': 'Ajoutez des exigences spécifiques ou des domaines d\'intérêt pour l\'analyse...'
133
+ },
134
+ 'de': {
135
+ 'title': '🧬 Vergleichender Analysator für Biotechnologische Modelle',
136
+ 'subtitle': 'Spezialisiert auf vergleichende Analyse von Modellanpassungsergebnissen',
137
+ 'upload_files': '📁 Ergebnisse hochladen (CSV/Excel)',
138
+ 'select_model': '🤖 Qwen Modell',
139
+ 'select_language': '🌐 Sprache',
140
+ 'select_theme': '🎨 Thema',
141
+ 'detail_level': '📋 Detailgrad der Analyse',
142
+ 'detailed': 'Detailliert',
143
+ 'summarized': 'Zusammengefasst',
144
+ 'analyze_button': '🚀 Analysieren und Vergleichen',
145
+ 'export_format': '📄 Exportformat',
146
+ 'export_button': '💾 Bericht Exportieren',
147
+ 'comparative_analysis': '📊 Vergleichende Analyse',
148
+ 'implementation_code': '💻 Implementierungscode',
149
+ 'data_format': '📋 Erwartetes Datenformat',
150
+ 'examples': '📚 Analysebeispiele',
151
+ 'light': 'Hell',
152
+ 'dark': 'Dunkel',
153
+ 'best_for': 'Am besten für',
154
+ 'loading': 'Laden...',
155
+ 'error_no_api': 'Bitte konfigurieren Sie NEBIUS_API_KEY',
156
+ 'error_no_files': 'Bitte laden Sie Dateien zur Analyse hoch',
157
+ 'report_exported': 'Bericht erfolgreich exportiert als',
158
+ 'specialized_in': '🎯 Spezialisiert auf:',
159
+ 'metrics_analyzed': '📊 Analysierte Metriken:',
160
+ 'what_analyzes': '🔍 Was spezifisch analysiert wird:',
161
+ 'tips': '💡 Tipps für bessere Ergebnisse:',
162
+ 'additional_specs': '📝 Zusätzliche Spezifikationen für die Analyse',
163
+ 'additional_specs_placeholder': 'Fügen Sie spezifische Anforderungen oder Schwerpunktbereiche für die Analyse hinzu...'
164
+ },
165
+ 'pt': {
166
+ 'title': '🧬 Analisador Comparativo de Modelos Biotecnológicos',
167
+ 'subtitle': 'Especializado em análise comparativa de resultados de ajuste',
168
+ 'upload_files': '📁 Carregar resultados (CSV/Excel)',
169
+ 'select_model': '🤖 Modelo Qwen',
170
+ 'select_language': '🌐 Idioma',
171
+ 'select_theme': '🎨 Tema',
172
+ 'detail_level': '📋 Nível de detalhe',
173
+ 'detailed': 'Detalhado',
174
+ 'summarized': 'Resumido',
175
+ 'analyze_button': '🚀 Analisar e Comparar',
176
+ 'export_format': '📄 Formato de exportação',
177
+ 'export_button': '💾 Exportar Relatório',
178
+ 'comparative_analysis': '📊 Análise Comparativa',
179
+ 'implementation_code': '💻 Código de Implementação',
180
+ 'data_format': '📋 Formato de dados esperado',
181
+ 'examples': '📚 Exemplos de análise',
182
+ 'light': 'Claro',
183
+ 'dark': 'Escuro',
184
+ 'best_for': 'Melhor para',
185
+ 'loading': 'Carregando...',
186
+ 'error_no_api': 'Por favor configure NEBIUS_API_KEY',
187
+ 'error_no_files': 'Por favor carregue arquivos para analisar',
188
+ 'report_exported': 'Relatório exportado com sucesso como',
189
+ 'specialized_in': '🎯 Especializado em:',
190
+ 'metrics_analyzed': '📊 Métricas analisadas:',
191
+ 'what_analyzes': '🔍 O que analisa especificamente:',
192
+ 'tips': '💡 Dicas para melhores resultados:',
193
+ 'additional_specs': '📝 Especificações adicionais para a análise',
194
+ 'additional_specs_placeholder': 'Adicione requisitos específicos ou áreas de foco para a análise...'
195
+ }
196
  }
197
 
198
+ # Temas disponibles
 
199
  THEMES = {
200
+ 'light': gr.themes.Soft(),
201
+ 'dark': gr.themes.Base(
202
+ primary_hue="blue",
203
+ secondary_hue="gray",
204
+ neutral_hue="gray",
205
+ font=["Arial", "sans-serif"]
206
+ ).set(
207
+ body_background_fill="dark",
208
+ body_background_fill_dark="*neutral_950",
209
+ button_primary_background_fill="*primary_600",
210
+ button_primary_background_fill_hover="*primary_500",
211
+ button_primary_text_color="white",
212
+ block_background_fill="*neutral_800",
213
+ block_border_color="*neutral_700",
214
+ block_label_text_color="*neutral_200",
215
+ block_title_text_color="*neutral_100",
216
+ checkbox_background_color="*neutral_700",
217
+ checkbox_background_color_selected="*primary_600",
218
+ input_background_fill="*neutral_700",
219
+ input_border_color="*neutral_600",
220
+ input_placeholder_color="*neutral_400"
221
+ )
222
  }
223
 
224
+ # Enum para tipos de análisis
 
225
  class AnalysisType(Enum):
226
  MATHEMATICAL_MODEL = "mathematical_model"
227
  DATA_FITTING = "data_fitting"
228
  FITTING_RESULTS = "fitting_results"
229
  UNKNOWN = "unknown"
230
 
231
+ # Estructura modular para modelos
232
  @dataclass
233
  class MathematicalModel:
234
  name: str
 
239
  category: str
240
  biological_meaning: str
241
 
242
+ # Sistema de registro de modelos escalable
243
  class ModelRegistry:
244
  def __init__(self):
245
  self.models = {}
246
  self._initialize_default_models()
247
+
248
  def register_model(self, model: MathematicalModel):
249
+ """Registra un nuevo modelo matemático"""
250
  if model.category not in self.models:
251
  self.models[model.category] = {}
252
  self.models[model.category][model.name] = model
253
+
254
+ def get_model(self, category: str, name: str) -> MathematicalModel:
255
+ """Obtiene un modelo específico"""
256
  return self.models.get(category, {}).get(name)
257
+
258
  def get_all_models(self) -> Dict:
259
+ """Retorna todos los modelos registrados"""
260
  return self.models
261
+
262
  def _initialize_default_models(self):
263
+ """Inicializa los modelos por defecto"""
264
+ # Modelos de crecimiento
265
  self.register_model(MathematicalModel(
266
  name="Monod",
267
  equation="μ = μmax × (S / (Ks + S))",
 
292
  biological_meaning="Incluye fase de adaptación (lag) seguida de crecimiento exponencial y estacionario"
293
  ))
294
 
295
+ # Instancia global del registro
296
  model_registry = ModelRegistry()
297
 
298
+ # Modelos de Qwen disponibles
299
  QWEN_MODELS = {
300
  "Qwen/Qwen3-14B": {
301
  "name": "Qwen 3 14B",
302
+ "description": "Modelo potente multilingüe de Alibaba",
303
+ "max_tokens": 4000,
304
+ "best_for": "Análisis complejos y detallados"
305
+ },
306
+ "Qwen/Qwen3-7B": {
307
+ "name": "Qwen 3 7B",
308
+ "description": "Modelo equilibrado para uso general",
309
+ "max_tokens": 4000,
310
+ "best_for": "Análisis rápidos y precisos"
311
+ },
312
+ "Qwen/Qwen1.5-14B": {
313
+ "name": "Qwen 1.5 14B",
314
+ "description": "Modelo avanzado para tareas complejas",
315
+ "max_tokens": 4000,
316
+ "best_for": "Análisis técnicos detallados"
317
  }
318
  }
319
 
 
 
320
  class FileProcessor:
321
+ """Clase para procesar diferentes tipos de archivos"""
322
+
323
  @staticmethod
324
+ def extract_text_from_pdf(pdf_file) -> str:
325
+ """Extrae texto de un archivo PDF"""
326
  try:
327
  pdf_reader = PyPDF2.PdfReader(io.BytesIO(pdf_file))
328
  text = ""
329
  for page in pdf_reader.pages:
330
+ text += page.extract_text() + "\n"
 
 
331
  return text
332
  except Exception as e:
333
  return f"Error reading PDF: {str(e)}"
334
+
335
  @staticmethod
336
+ def read_csv(csv_file) -> pd.DataFrame:
337
+ """Lee archivo CSV"""
338
  try:
339
  return pd.read_csv(io.BytesIO(csv_file))
340
+ except Exception as e:
341
  return None
342
+
343
  @staticmethod
344
+ def read_excel(excel_file) -> pd.DataFrame:
345
+ """Lee archivo Excel"""
346
  try:
347
  return pd.read_excel(io.BytesIO(excel_file))
348
+ except Exception as e:
349
  return None
350
+
351
  @staticmethod
352
+ def extract_from_zip(zip_file) -> List[Tuple[str, bytes]]:
353
+ """Extrae archivos de un ZIP"""
354
  files = []
355
  try:
356
  with zipfile.ZipFile(io.BytesIO(zip_file), 'r') as zip_ref:
357
  for file_name in zip_ref.namelist():
358
+ if not file_name.startswith('__MACOSX'):
359
  file_data = zip_ref.read(file_name)
360
  files.append((file_name, file_data))
361
  except Exception as e:
 
363
  return files
364
 
365
  class ReportExporter:
366
+ """Clase para exportar reportes a diferentes formatos"""
367
+
368
  @staticmethod
369
  def export_to_docx(content: str, filename: str, language: str = 'en') -> str:
370
+ """Exporta el contenido a un archivo DOCX"""
371
  doc = Document()
372
+
373
+ # Configurar estilos
374
  title_style = doc.styles['Title']
375
  title_style.font.size = Pt(24)
376
  title_style.font.bold = True
377
 
378
+ heading_style = doc.styles['Heading 1']
379
+ heading_style.font.size = Pt(18)
380
+ heading_style.font.bold = True
381
+
382
+ # Título
383
+ title_text = {
384
+ 'en': 'Comparative Analysis Report - Biotechnological Models',
385
+ 'es': 'Informe de Análisis Comparativo - Modelos Biotecnológicos',
386
+ 'fr': 'Rapport d\'Analyse Comparative - Modèles Biotechnologiques',
387
+ 'de': 'Vergleichsanalysebericht - Biotechnologische Modelle',
388
+ 'pt': 'Relatório de Análise Comparativa - Modelos Biotecnológicos'
389
+ }
390
 
391
+ doc.add_heading(title_text.get(language, title_text['en']), 0)
 
392
 
393
+ # Fecha
394
+ date_text = {
395
+ 'en': 'Generated on',
396
+ 'es': 'Generado el',
397
+ 'fr': 'Généré le',
398
+ 'de': 'Erstellt am',
399
+ 'pt': 'Gerado em'
400
+ }
401
  doc.add_paragraph(f"{date_text.get(language, date_text['en'])}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
402
  doc.add_paragraph()
403
 
404
+ # Procesar contenido
405
  lines = content.split('\n')
406
+ current_paragraph = None
407
+
408
  for line in lines:
409
  line = line.strip()
410
+
411
  if line.startswith('###'):
412
+ doc.add_heading(line.replace('###', '').strip(), level=2)
413
  elif line.startswith('##'):
414
+ doc.add_heading(line.replace('##', '').strip(), level=1)
415
  elif line.startswith('#'):
416
+ doc.add_heading(line.replace('#', '').strip(), level=0)
417
  elif line.startswith('**') and line.endswith('**'):
418
+ # Texto en negrita
419
  p = doc.add_paragraph()
420
+ run = p.add_run(line.replace('**', ''))
421
+ run.bold = True
422
  elif line.startswith('- ') or line.startswith('* '):
423
+ # Lista
424
  doc.add_paragraph(line[2:], style='List Bullet')
425
+ elif line.startswith(tuple('0123456789')):
426
+ # Lista numerada
427
+ doc.add_paragraph(line, style='List Number')
428
+ elif line == '---' or line.startswith('==='):
429
+ # Separador
430
+ doc.add_paragraph('_' * 50)
431
  elif line:
432
+ # Párrafo normal
433
  doc.add_paragraph(line)
434
 
435
+ # Guardar documento
436
  doc.save(filename)
437
  return filename
438
+
439
  @staticmethod
440
  def export_to_pdf(content: str, filename: str, language: str = 'en') -> str:
441
+ """Exporta el contenido a un archivo PDF"""
442
+ # Crear documento PDF
443
  doc = SimpleDocTemplate(filename, pagesize=letter)
444
  story = []
445
  styles = getSampleStyleSheet()
446
 
447
+ # Estilos personalizados
448
+ title_style = ParagraphStyle(
449
+ 'CustomTitle',
450
+ parent=styles['Title'],
451
+ fontSize=24,
452
+ textColor=colors.HexColor('#1f4788'),
453
+ spaceAfter=30
454
+ )
455
+
456
+ heading_style = ParagraphStyle(
457
+ 'CustomHeading',
458
+ parent=styles['Heading1'],
459
+ fontSize=16,
460
+ textColor=colors.HexColor('#2e5090'),
461
+ spaceAfter=12
462
+ )
463
+
464
+ # Título
465
+ title_text = {
466
+ 'en': 'Comparative Analysis Report - Biotechnological Models',
467
+ 'es': 'Informe de Análisis Comparativo - Modelos Biotecnológicos',
468
+ 'fr': 'Rapport d\'Analyse Comparative - Modèles Biotechnologiques',
469
+ 'de': 'Vergleichsanalysebericht - Biotechnologische Modelle',
470
+ 'pt': 'Relatório de Análise Comparativa - Modelos Biotecnológicos'
471
+ }
472
 
473
+ story.append(Paragraph(title_text.get(language, title_text['en']), title_style))
 
474
 
475
+ # Fecha
476
+ date_text = {
477
+ 'en': 'Generated on',
478
+ 'es': 'Generado el',
479
+ 'fr': 'Généré le',
480
+ 'de': 'Erstellt am',
481
+ 'pt': 'Gerado em'
482
+ }
483
  story.append(Paragraph(f"{date_text.get(language, date_text['en'])}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", styles['Normal']))
484
+ story.append(Spacer(1, 0.5*inch))
485
 
486
+ # Procesar contenido
487
  lines = content.split('\n')
488
+
489
  for line in lines:
490
  line = line.strip()
491
+
492
  if not line:
493
+ story.append(Spacer(1, 0.2*inch))
494
  elif line.startswith('###'):
495
  story.append(Paragraph(line.replace('###', '').strip(), styles['Heading3']))
496
  elif line.startswith('##'):
 
498
  elif line.startswith('#'):
499
  story.append(Paragraph(line.replace('#', '').strip(), heading_style))
500
  elif line.startswith('**') and line.endswith('**'):
501
+ text = line.replace('**', '')
502
+ story.append(Paragraph(f"<b>{text}</b>", styles['Normal']))
503
  elif line.startswith('- ') or line.startswith('* '):
504
+ story.append(Paragraph(f"• {line[2:]}", styles['Normal']))
505
+ elif line == '---' or line.startswith('==='):
506
+ story.append(Spacer(1, 0.3*inch))
507
+ story.append(Paragraph("_" * 70, styles['Normal']))
508
+ story.append(Spacer(1, 0.3*inch))
509
  else:
510
+ # Limpiar caracteres especiales para PDF
511
+ clean_line = line.replace('📊', '[GRAPH]').replace('🎯', '[TARGET]').replace('🔍', '[SEARCH]').replace('💡', '[TIP]')
512
  story.append(Paragraph(clean_line, styles['Normal']))
 
513
 
514
+ # Construir PDF
515
  doc.build(story)
516
  return filename
517
 
 
 
518
  class AIAnalyzer:
519
+ """Clase para análisis con IA usando Qwen"""
520
+
521
  def __init__(self, client, model_registry):
522
  self.client = client
523
  self.model_registry = model_registry
524
+
525
+ def detect_analysis_type(self, content: Union[str, pd.DataFrame]) -> AnalysisType:
526
+ """Detecta el tipo de análisis necesario"""
527
+ if isinstance(content, pd.DataFrame):
528
+ columns = [col.lower() for col in content.columns]
529
+
530
+ fitting_indicators = [
531
+ 'r2', 'r_squared', 'rmse', 'mse', 'aic', 'bic',
532
+ 'parameter', 'param', 'coefficient', 'fit',
533
+ 'model', 'equation', 'goodness', 'chi_square',
534
+ 'p_value', 'confidence', 'standard_error', 'se'
535
+ ]
536
+
537
+ has_fitting_results = any(indicator in ' '.join(columns) for indicator in fitting_indicators)
538
+
539
+ if has_fitting_results:
540
+ return AnalysisType.FITTING_RESULTS
541
+ else:
542
+ return AnalysisType.DATA_FITTING
543
+
544
+ prompt = """
545
+ Analyze this content and determine if it is:
546
+ 1. A scientific article describing biotechnological mathematical models
547
+ 2. Experimental data for parameter fitting
548
+ 3. Model fitting results (with parameters, R², RMSE, etc.)
549
+
550
+ Reply only with: "MODEL", "DATA" or "RESULTS"
551
+ """
552
+
553
+ try:
554
+ response = self.client.chat.completions.create(
555
+ model="Qwen/Qwen3-14B",
556
+ max_tokens=10,
557
+ temperature=0.0,
558
+ messages=[{"role": "user", "content": f"{prompt}\n\n{content[:1000]}"}]
559
+ )
560
+
561
+ result = response.choices[0].message.content.strip().upper()
562
+ if "MODEL" in result:
563
+ return AnalysisType.MATHEMATICAL_MODEL
564
+ elif "RESULTS" in result:
565
+ return AnalysisType.FITTING_RESULTS
566
+ elif "DATA" in result:
567
+ return AnalysisType.DATA_FITTING
568
+ else:
569
+ return AnalysisType.UNKNOWN
570
+
571
+ except Exception as e:
572
+ print(f"Error en detección de tipo: {str(e)}")
573
+ return AnalysisType.UNKNOWN
574
+
575
  def get_language_prompt_prefix(self, language: str) -> str:
576
+ """Obtiene el prefijo del prompt según el idioma"""
577
+ prefixes = {
578
+ 'en': "Please respond in English. ",
579
+ 'es': "Por favor responde en español. ",
580
+ 'fr': "Veuillez répondre en français. ",
581
+ 'de': "Bitte antworten Sie auf Deutsch. ",
582
+ 'pt': "Por favor responda em português. "
583
+ }
584
+ return prefixes.get(language, prefixes['en'])
585
+
586
+ def analyze_fitting_results(self, data: pd.DataFrame, qwen_model: str, detail_level: str = "detailed",
587
+ language: str = "en", additional_specs: str = "") -> Dict:
588
+ """Analiza resultados de ajuste de modelos usando Qwen"""
589
+
590
+ # Preparar resumen completo de los datos
591
+ data_summary = f"""
592
+ FITTING RESULTS DATA:
593
+
594
+ Data structure:
595
+ - Columns: {list(data.columns)}
596
+ - Number of models evaluated: {len(data)}
597
+
598
+ Complete data:
599
+ {data.to_string()}
600
+
601
+ Descriptive statistics:
602
+ {data.describe().to_string()}
603
+ """
604
+
605
+ # Extraer valores para usar en el código
606
+ data_dict = data.to_dict('records')
607
+
608
+ # Obtener prefijo de idioma
609
  lang_prefix = self.get_language_prompt_prefix(language)
 
610
 
611
+ # Agregar especificaciones adicionales del usuario si existen
612
+ user_specs_section = f"""
613
+
614
+ USER ADDITIONAL SPECIFICATIONS:
615
+ {additional_specs}
616
+
617
+ Please ensure to address these specific requirements in your analysis.
618
+ """ if additional_specs else ""
619
+
620
+ # Prompt mejorado con instrucciones específicas para cada nivel
621
  if detail_level == "detailed":
622
+ prompt = f"""
623
+ {lang_prefix}
624
+
625
+ You are an expert in biotechnology and mathematical modeling. Analyze these kinetic/biotechnological model fitting results.
626
+
627
+ {user_specs_section}
628
+
629
+ DETAIL LEVEL: DETAILED - Provide comprehensive analysis BY EXPERIMENT
630
+
631
+ PERFORM A COMPREHENSIVE COMPARATIVE ANALYSIS PER EXPERIMENT:
632
+
633
+ 1. **EXPERIMENT IDENTIFICATION AND OVERVIEW**
634
+ - List ALL experiments/conditions tested (e.g., pH levels, temperatures, time points)
635
+ - For EACH experiment, identify:
636
+ * Experimental conditions
637
+ * Number of models tested
638
+ * Variables measured (biomass, substrate, product)
639
+
640
+ 2. **MODEL IDENTIFICATION AND CLASSIFICATION BY EXPERIMENT**
641
+ For EACH EXPERIMENT separately:
642
+ - Identify ALL fitted mathematical models BY NAME
643
+ - Classify them: biomass growth, substrate consumption, product formation
644
+ - Show the mathematical equation of each model
645
+ - List parameter values obtained for that specific experiment
646
+
647
+ 3. **COMPARATIVE ANALYSIS PER EXPERIMENT**
648
+ Create a section for EACH EXPERIMENT showing:
649
+
650
+ **EXPERIMENT [Name/Condition]:**
651
+
652
+ a) **BIOMASS MODELS** (if applicable):
653
+ - Best model: [Name] with R²=[value], RMSE=[value]
654
+ - Parameters: μmax=[value], Xmax=[value], etc.
655
+ - Ranking of all biomass models tested
656
+
657
+ b) **SUBSTRATE MODELS** (if applicable):
658
+ - Best model: [Name] with R²=[value], RMSE=[value]
659
+ - Parameters: Ks=[value], Yxs=[value], etc.
660
+ - Ranking of all substrate models tested
661
+
662
+ c) **PRODUCT MODELS** (if applicable):
663
+ - Best model: [Name] with R²=[value], RMSE=[value]
664
+ - Parameters: α=[value], β=[value], etc.
665
+ - Ranking of all product models tested
666
+
667
+ 4. **DETAILED COMPARATIVE TABLES**
668
+
669
+ **Table 1: Summary by Experiment and Variable Type**
670
+ | Experiment | Variable | Best Model | R² | RMSE | Key Parameters | Ranking |
671
+ |------------|----------|------------|-------|------|----------------|---------|
672
+ | Exp1 | Biomass | [Name] | [val] | [val]| μmax=X | 1 |
673
+ | Exp1 | Substrate| [Name] | [val] | [val]| Ks=Y | 1 |
674
+ | Exp1 | Product | [Name] | [val] | [val]| α=Z | 1 |
675
+ | Exp2 | Biomass | [Name] | [val] | [val]| μmax=X2 | 1 |
676
+
677
+ **Table 2: Complete Model Comparison Across All Experiments**
678
+ | Model Name | Type | Exp1_R² | Exp1_RMSE | Exp2_R² | Exp2_RMSE | Avg_R² | Best_For |
679
+
680
+ 5. **PARAMETER ANALYSIS ACROSS EXPERIMENTS**
681
+ - Compare how parameters change between experiments
682
+ - Identify trends (e.g., μmax increases with temperature)
683
+ - Calculate average parameters and variability
684
+ - Suggest optimal conditions based on parameters
685
+
686
+ 6. **BIOLOGICAL INTERPRETATION BY EXPERIMENT**
687
+ For each experiment, explain:
688
+ - What the parameter values mean biologically
689
+ - Whether values are realistic for the conditions
690
+ - Key differences between experiments
691
+ - Critical control parameters identified
692
+
693
+ 7. **OVERALL BEST MODELS DETERMINATION**
694
+ - **BEST BIOMASS MODEL OVERALL**: [Name] - performs best in [X] out of [Y] experiments
695
+ - **BEST SUBSTRATE MODEL OVERALL**: [Name] - average R²=[value]
696
+ - **BEST PRODUCT MODEL OVERALL**: [Name] - most consistent across conditions
697
+
698
+ Justify with numerical evidence from multiple experiments.
699
+
700
+ 8. **CONCLUSIONS AND RECOMMENDATIONS**
701
+ - Which models are most robust across different conditions
702
+ - Specific models to use for each experimental condition
703
+ - Confidence intervals and prediction reliability
704
+ - Scale-up recommendations with specific values
705
+
706
+ Use Markdown format with clear structure. Include ALL numerical values from the data.
707
+ Create clear sections for EACH EXPERIMENT.
708
+ """
709
+ else: # summarized
710
+ prompt = f"""
711
+ {lang_prefix}
712
+
713
+ You are an expert in biotechnology. Provide a CONCISE but COMPLETE analysis BY EXPERIMENT.
714
+
715
+ {user_specs_section}
716
+
717
+ DETAIL LEVEL: SUMMARIZED - Be concise but include all experiments and essential information
718
+
719
+ PROVIDE A FOCUSED COMPARATIVE ANALYSIS:
720
+
721
+ 1. **EXPERIMENTS OVERVIEW**
722
+ - Total experiments analyzed: [number]
723
+ - Conditions tested: [list]
724
+ - Variables measured: biomass/substrate/product
725
+
726
+ 2. **BEST MODELS BY EXPERIMENT - QUICK SUMMARY**
727
+
728
+ 📊 **EXPERIMENT 1 [Name/Condition]:**
729
+ - Biomass: [Model] (R²=[value])
730
+ - Substrate: [Model] (R²=[value])
731
+ - Product: [Model] (R²=[value])
732
+
733
+ 📊 **EXPERIMENT 2 [Name/Condition]:**
734
+ - Biomass: [Model] (R²=[value])
735
+ - Substrate: [Model] (R²=[value])
736
+ - Product: [Model] (R²=[value])
737
+
738
+ [Continue for all experiments...]
739
+
740
+ 3. **OVERALL WINNERS ACROSS ALL EXPERIMENTS**
741
+ 🏆 **Best Models Overall:**
742
+ - **Biomass**: [Model] - Best in [X]/[Y] experiments
743
+ - **Substrate**: [Model] - Average R²=[value]
744
+ - **Product**: [Model] - Most consistent performance
745
+
746
+ 4. **QUICK COMPARISON TABLE**
747
+ | Experiment | Best Biomass | Best Substrate | Best Product | Overall R² |
748
+ |------------|--------------|----------------|--------------|------------|
749
+ | Exp1 | [Model] | [Model] | [Model] | [avg] |
750
+ | Exp2 | [Model] | [Model] | [Model] | [avg] |
751
+
752
+ 5. **KEY FINDINGS**
753
+ - Parameter ranges across experiments: μmax=[min-max], Ks=[min-max]
754
+ - Best conditions identified: [specific values]
755
+ - Most robust models: [list with reasons]
756
+
757
+ 6. **PRACTICAL RECOMMENDATIONS**
758
+ - For biomass prediction: Use [Model]
759
+ - For substrate monitoring: Use [Model]
760
+ - For product estimation: Use [Model]
761
+ - Critical parameters: [list with values]
762
+
763
+ Keep it concise but include ALL experiments and model names with their key metrics.
764
+ """
765
+
766
  try:
767
  # Análisis principal
768
+ response = self.client.chat.completions.create(
769
  model=qwen_model,
 
770
  max_tokens=4000,
771
+ temperature=0.3,
772
+ messages=[{
773
+ "role": "user",
774
+ "content": f"{prompt}\n\n{data_summary}"
775
+ }]
776
  )
777
+
778
+ analysis_result = response.choices[0].message.content
779
+
780
  # Generación de código
781
+ code_prompt = f"""
782
+ {lang_prefix}
783
+
784
+ Based on the analysis and this actual data:
785
+ {data.to_string()}
786
+
787
+ Generate Python code that:
788
+
789
+ 1. Creates a complete analysis system with the ACTUAL NUMERICAL VALUES from the data
790
+ 2. Implements analysis BY EXPERIMENT showing:
791
+ - Best models for each experiment
792
+ - Comparison across experiments
793
+ - Parameter evolution between conditions
794
+ 3. Includes visualization functions that:
795
+ - Show results PER EXPERIMENT
796
+ - Compare models across experiments
797
+ - Display parameter trends
798
+ 4. Shows the best model for biomass, substrate, and product separately
799
+
800
+ The code must include:
801
+ - Data loading with experiment identification
802
+ - Model comparison by experiment and variable type
803
+ - Visualization showing results per experiment
804
+ - Overall best model selection with justification
805
+ - Functions to predict using the best models for each category
806
+
807
+ Make sure to include comments indicating which model won for each variable type and why.
808
+
809
+ Format: Complete, executable Python code with actual data values embedded.
810
+ """
811
 
812
  code_response = self.client.chat.completions.create(
813
  model=qwen_model,
814
+ max_tokens=3000,
815
+ temperature=0.1,
816
+ messages=[{
817
+ "role": "user",
818
+ "content": code_prompt
819
+ }]
820
  )
821
+
822
+ code_result = code_response.choices[0].message.content
823
+
824
  return {
825
+ "tipo": "Comparative Analysis of Mathematical Models",
826
+ "analisis_completo": analysis_result,
827
+ "codigo_implementacion": code_result,
828
+ "resumen_datos": {
829
+ "n_modelos": len(data),
830
+ "columnas": list(data.columns),
831
+ "metricas_disponibles": [col for col in data.columns if any(metric in col.lower()
832
+ for metric in ['r2', 'rmse', 'aic', 'bic', 'mse'])],
833
+ "mejor_r2": data['R2'].max() if 'R2' in data.columns else None,
834
+ "mejor_modelo_r2": data.loc[data['R2'].idxmax()]['Model'] if 'R2' in data.columns and 'Model' in data.columns else None,
835
+ "datos_completos": data_dict # Incluir todos los datos para el código
836
+ }
837
  }
838
+
839
  except Exception as e:
840
+ print(f"Error en análisis: {str(e)}")
841
+ return {"error": str(e)}
 
 
 
 
 
 
 
842
 
843
+ def process_files(files, qwen_model: str, detail_level: str = "detailed",
844
+ language: str = "en", additional_specs: str = "") -> Tuple[str, str]:
845
+ """Procesa múltiples archivos usando Qwen"""
846
+ processor = FileProcessor()
847
  analyzer = AIAnalyzer(client, model_registry)
848
+ results = []
849
+ all_code = []
850
+
851
  for file in files:
852
+ if file is None:
853
+ continue
854
+
855
+ file_name = file.name if hasattr(file, 'name') else "archivo"
856
+ file_ext = Path(file_name).suffix.lower()
857
+
858
+ with open(file.name, 'rb') as f:
859
+ file_content = f.read()
860
+
861
+ if file_ext in ['.csv', '.xlsx', '.xls']:
862
+ if language == 'es':
863
+ results.append(f"## 📊 Análisis de Resultados: {file_name}")
864
+ else:
865
+ results.append(f"## 📊 Results Analysis: {file_name}")
866
 
 
 
 
 
867
  if file_ext == '.csv':
868
+ df = processor.read_csv(file_content)
869
+ else:
870
+ df = processor.read_excel(file_content)
871
 
872
  if df is not None:
873
+ analysis_type = analyzer.detect_analysis_type(df)
 
874
 
875
+ if analysis_type == AnalysisType.FITTING_RESULTS:
876
+ result = analyzer.analyze_fitting_results(
877
+ df, qwen_model, detail_level, language, additional_specs
878
+ )
879
+
880
+ if language == 'es':
881
+ results.append("### 🎯 ANÁLISIS COMPARATIVO DE MODELOS MATEMÁTICOS")
882
+ else:
883
+ results.append("### 🎯 COMPARATIVE ANALYSIS OF MATHEMATICAL MODELS")
884
+
885
+ results.append(result.get("analisis_completo", ""))
886
+ if "codigo_implementacion" in result:
887
+ all_code.append(result["codigo_implementacion"])
888
+
889
+ results.append("\n---\n")
890
 
891
+ analysis_text = "\n".join(results)
892
+ code_text = "\n\n# " + "="*50 + "\n\n".join(all_code) if all_code else generate_implementation_code(analysis_text)
893
+
894
+ return analysis_text, code_text
895
 
896
  def generate_implementation_code(analysis_results: str) -> str:
897
+ """Genera código de implementación con análisis por experimento"""
898
+ code = """
899
+ import numpy as np
900
  import pandas as pd
901
  import matplotlib.pyplot as plt
902
+ from scipy.integrate import odeint
903
+ from scipy.optimize import curve_fit, differential_evolution
904
+ from sklearn.metrics import r2_score, mean_squared_error
905
  import seaborn as sns
906
+ from typing import Dict, List, Tuple, Optional
907
 
908
+ # Visualization configuration
909
+ plt.style.use('seaborn-v0_8-darkgrid')
910
+ sns.set_palette("husl")
911
+
912
+ class ExperimentalModelAnalyzer:
913
  \"\"\"
914
+ Class for comparative analysis of biotechnological models across multiple experiments.
915
+ Analyzes biomass, substrate and product models separately for each experimental condition.
916
  \"\"\"
917
+
918
+ def __init__(self):
919
+ self.results_df = None
920
+ self.experiments = {}
921
+ self.best_models_by_experiment = {}
922
+ self.overall_best_models = {
923
+ 'biomass': None,
924
+ 'substrate': None,
925
+ 'product': None
926
+ }
927
+
928
+ def load_results(self, file_path: str = None, data_dict: dict = None) -> pd.DataFrame:
929
+ \"\"\"Load fitting results from CSV/Excel file or dictionary\"\"\"
930
+ if data_dict:
931
+ self.results_df = pd.DataFrame(data_dict)
932
+ elif file_path:
933
+ if file_path.endswith('.csv'):
934
+ self.results_df = pd.read_csv(file_path)
935
+ else:
936
+ self.results_df = pd.read_excel(file_path)
937
+
938
+ print(f"✅ Data loaded: {len(self.results_df)} models")
939
+ print(f"📊 Available columns: {list(self.results_df.columns)}")
940
+
941
+ # Identify experiments
942
+ if 'Experiment' in self.results_df.columns:
943
+ self.experiments = self.results_df.groupby('Experiment').groups
944
+ print(f"🧪 Experiments found: {list(self.experiments.keys())}")
945
 
946
+ return self.results_df
 
947
 
948
+ def analyze_by_experiment(self,
949
+ experiment_col: str = 'Experiment',
950
+ model_col: str = 'Model',
951
+ type_col: str = 'Type',
952
+ r2_col: str = 'R2',
953
+ rmse_col: str = 'RMSE') -> Dict:
954
+ \"\"\"
955
+ Analyze models by experiment and variable type.
956
+ Identifies best models for biomass, substrate, and product in each experiment.
957
+ \"\"\"
958
+ if self.results_df is None:
959
+ raise ValueError("First load data with load_results()")
960
+
961
+ results_by_exp = {}
962
+
963
+ # Get unique experiments
964
+ if experiment_col in self.results_df.columns:
965
+ experiments = self.results_df[experiment_col].unique()
966
+ else:
967
+ experiments = ['All_Data']
968
+ self.results_df[experiment_col] = 'All_Data'
969
+
970
+ print("\\n" + "="*80)
971
+ print("📊 ANALYSIS BY EXPERIMENT AND VARIABLE TYPE")
972
+ print("="*80)
973
+
974
+ for exp in experiments:
975
+ print(f"\\n🧪 EXPERIMENT: {exp}")
976
+ print("-"*50)
977
+
978
+ exp_data = self.results_df[self.results_df[experiment_col] == exp]
979
+ results_by_exp[exp] = {}
980
+
981
+ # Analyze by variable type if available
982
+ if type_col in exp_data.columns:
983
+ var_types = exp_data[type_col].unique()
984
+
985
+ for var_type in var_types:
986
+ var_data = exp_data[exp_data[type_col] == var_type]
987
+
988
+ if not var_data.empty:
989
+ # Find best model for this variable type
990
+ best_idx = var_data[r2_col].idxmax()
991
+ best_model = var_data.loc[best_idx]
992
+
993
+ results_by_exp[exp][var_type] = {
994
+ 'best_model': best_model[model_col],
995
+ 'r2': best_model[r2_col],
996
+ 'rmse': best_model[rmse_col],
997
+ 'all_models': var_data[[model_col, r2_col, rmse_col]].to_dict('records')
998
+ }
999
+
1000
+ print(f"\\n 📈 {var_type.upper()}:")
1001
+ print(f" Best Model: {best_model[model_col]}")
1002
+ print(f" R² = {best_model[r2_col]:.4f}")
1003
+ print(f" RMSE = {best_model[rmse_col]:.4f}")
1004
+
1005
+ # Show all models for this variable
1006
+ print(f"\\n All {var_type} models tested:")
1007
+ for _, row in var_data.iterrows():
1008
+ print(f" - {row[model_col]}: R²={row[r2_col]:.4f}, RMSE={row[rmse_col]:.4f}")
1009
+ else:
1010
+ # If no type column, analyze all models together
1011
+ best_idx = exp_data[r2_col].idxmax()
1012
+ best_model = exp_data.loc[best_idx]
1013
+
1014
+ results_by_exp[exp]['all'] = {
1015
+ 'best_model': best_model[model_col],
1016
+ 'r2': best_model[r2_col],
1017
+ 'rmse': best_model[rmse_col],
1018
+ 'all_models': exp_data[[model_col, r2_col, rmse_col]].to_dict('records')
1019
+ }
1020
+
1021
+ self.best_models_by_experiment = results_by_exp
1022
+
1023
+ # Determine overall best models
1024
+ self._determine_overall_best_models()
1025
+
1026
+ return results_by_exp
1027
+
1028
+ def _determine_overall_best_models(self):
1029
+ \"\"\"Determine the best models across all experiments\"\"\"
1030
+ print("\\n" + "="*80)
1031
+ print("🏆 OVERALL BEST MODELS ACROSS ALL EXPERIMENTS")
1032
+ print("="*80)
1033
+
1034
+ # Aggregate performance by model and type
1035
+ model_performance = {}
1036
+
1037
+ for exp, exp_results in self.best_models_by_experiment.items():
1038
+ for var_type, var_results in exp_results.items():
1039
+ if var_type not in model_performance:
1040
+ model_performance[var_type] = {}
1041
+
1042
+ for model_data in var_results['all_models']:
1043
+ model_name = model_data['Model']
1044
+ if model_name not in model_performance[var_type]:
1045
+ model_performance[var_type][model_name] = {
1046
+ 'r2_values': [],
1047
+ 'rmse_values': [],
1048
+ 'experiments': []
1049
+ }
1050
+
1051
+ model_performance[var_type][model_name]['r2_values'].append(model_data['R2'])
1052
+ model_performance[var_type][model_name]['rmse_values'].append(model_data['RMSE'])
1053
+ model_performance[var_type][model_name]['experiments'].append(exp)
1054
+
1055
+ # Calculate average performance and select best
1056
+ for var_type, models in model_performance.items():
1057
+ best_avg_r2 = -1
1058
+ best_model = None
1059
+
1060
+ print(f"\\n📊 {var_type.upper()} MODELS:")
1061
+ for model_name, perf_data in models.items():
1062
+ avg_r2 = np.mean(perf_data['r2_values'])
1063
+ avg_rmse = np.mean(perf_data['rmse_values'])
1064
+ n_exp = len(perf_data['experiments'])
1065
+
1066
+ print(f" {model_name}:")
1067
+ print(f" Average R² = {avg_r2:.4f}")
1068
+ print(f" Average RMSE = {avg_rmse:.4f}")
1069
+ print(f" Tested in {n_exp} experiments")
1070
+
1071
+ if avg_r2 > best_avg_r2:
1072
+ best_avg_r2 = avg_r2
1073
+ best_model = {
1074
+ 'name': model_name,
1075
+ 'avg_r2': avg_r2,
1076
+ 'avg_rmse': avg_rmse,
1077
+ 'n_experiments': n_exp
1078
+ }
1079
+
1080
+ if var_type.lower() in ['biomass', 'substrate', 'product']:
1081
+ self.overall_best_models[var_type.lower()] = best_model
1082
+ print(f"\\n 🏆 BEST {var_type.upper()} MODEL: {best_model['name']} (Avg R²={best_model['avg_r2']:.4f})")
1083
+
1084
+ def create_comparison_visualizations(self):
1085
+ \"\"\"Create visualizations comparing models across experiments\"\"\"
1086
+ if not self.best_models_by_experiment:
1087
+ raise ValueError("First run analyze_by_experiment()")
1088
+
1089
+ # Prepare data for visualization
1090
+ experiments = []
1091
+ biomass_r2 = []
1092
+ substrate_r2 = []
1093
+ product_r2 = []
1094
+
1095
+ for exp, results in self.best_models_by_experiment.items():
1096
+ experiments.append(exp)
1097
+ biomass_r2.append(results.get('Biomass', {}).get('r2', 0))
1098
+ substrate_r2.append(results.get('Substrate', {}).get('r2', 0))
1099
+ product_r2.append(results.get('Product', {}).get('r2', 0))
1100
+
1101
+ # Create figure with subplots
1102
+ fig, axes = plt.subplots(2, 2, figsize=(15, 12))
1103
+ fig.suptitle('Model Performance Comparison Across Experiments', fontsize=16)
1104
+
1105
+ # 1. R² comparison by experiment and variable type
1106
+ ax1 = axes[0, 0]
1107
+ x = np.arange(len(experiments))
1108
+ width = 0.25
1109
+
1110
+ ax1.bar(x - width, biomass_r2, width, label='Biomass', color='green', alpha=0.8)
1111
+ ax1.bar(x, substrate_r2, width, label='Substrate', color='blue', alpha=0.8)
1112
+ ax1.bar(x + width, product_r2, width, label='Product', color='red', alpha=0.8)
1113
+
1114
+ ax1.set_xlabel('Experiment')
1115
+ ax1.set_ylabel('R²')
1116
+ ax1.set_title('Best Model R² by Experiment and Variable Type')
1117
+ ax1.set_xticks(x)
1118
+ ax1.set_xticklabels(experiments, rotation=45, ha='right')
1119
+ ax1.legend()
1120
+ ax1.grid(True, alpha=0.3)
1121
+
1122
+ # Add value labels
1123
+ for i, (b, s, p) in enumerate(zip(biomass_r2, substrate_r2, product_r2)):
1124
+ if b > 0: ax1.text(i - width, b + 0.01, f'{b:.3f}', ha='center', va='bottom', fontsize=8)
1125
+ if s > 0: ax1.text(i, s + 0.01, f'{s:.3f}', ha='center', va='bottom', fontsize=8)
1126
+ if p > 0: ax1.text(i + width, p + 0.01, f'{p:.3f}', ha='center', va='bottom', fontsize=8)
1127
+
1128
+ # 2. Model frequency heatmap
1129
+ ax2 = axes[0, 1]
1130
+ # This would show which models appear most frequently as best
1131
+ # Implementation depends on actual data structure
1132
+ ax2.text(0.5, 0.5, 'Model Frequency Analysis\\n(Most Used Models)',
1133
+ ha='center', va='center', transform=ax2.transAxes)
1134
+ ax2.set_title('Most Frequently Selected Models')
1135
+
1136
+ # 3. Parameter evolution across experiments
1137
+ ax3 = axes[1, 0]
1138
+ ax3.text(0.5, 0.5, 'Parameter Evolution\\nAcross Experiments',
1139
+ ha='center', va='center', transform=ax3.transAxes)
1140
+ ax3.set_title('Parameter Trends')
1141
+
1142
+ # 4. Overall best models summary
1143
+ ax4 = axes[1, 1]
1144
+ ax4.axis('off')
1145
+
1146
+ summary_text = "🏆 OVERALL BEST MODELS\\n\\n"
1147
+ for var_type, model_info in self.overall_best_models.items():
1148
+ if model_info:
1149
+ summary_text += f"{var_type.upper()}:\\n"
1150
+ summary_text += f" Model: {model_info['name']}\\n"
1151
+ summary_text += f" Avg R²: {model_info['avg_r2']:.4f}\\n"
1152
+ summary_text += f" Tested in: {model_info['n_experiments']} experiments\\n\\n"
1153
+
1154
+ ax4.text(0.1, 0.9, summary_text, transform=ax4.transAxes,
1155
+ fontsize=12, verticalalignment='top', fontfamily='monospace')
1156
+ ax4.set_title('Overall Best Models Summary')
1157
+
1158
+ plt.tight_layout()
1159
+ plt.show()
1160
+
1161
+ def generate_summary_table(self) -> pd.DataFrame:
1162
+ \"\"\"Generate a summary table of best models by experiment and type\"\"\"
1163
+ summary_data = []
1164
+
1165
+ for exp, results in self.best_models_by_experiment.items():
1166
+ for var_type, var_results in results.items():
1167
+ summary_data.append({
1168
+ 'Experiment': exp,
1169
+ 'Variable_Type': var_type,
1170
+ 'Best_Model': var_results['best_model'],
1171
+ 'R2': var_results['r2'],
1172
+ 'RMSE': var_results['rmse']
1173
+ })
1174
+
1175
+ summary_df = pd.DataFrame(summary_data)
1176
+
1177
+ print("\\n📋 SUMMARY TABLE: BEST MODELS BY EXPERIMENT AND VARIABLE TYPE")
1178
+ print("="*80)
1179
+ print(summary_df.to_string(index=False))
1180
+
1181
+ return summary_df
1182
 
1183
+ # Example usage
1184
+ if __name__ == "__main__":
1185
+ print("🧬 Experimental Model Comparison System")
1186
+ print("="*60)
1187
+
1188
+ # Example data structure with experiments
1189
+ example_data = {
1190
+ 'Experiment': ['pH_7.0', 'pH_7.0', 'pH_7.0', 'pH_7.5', 'pH_7.5', 'pH_7.5',
1191
+ 'pH_7.0', 'pH_7.0', 'pH_7.5', 'pH_7.5',
1192
+ 'pH_7.0', 'pH_7.0', 'pH_7.5', 'pH_7.5'],
1193
+ 'Model': ['Monod', 'Logistic', 'Gompertz', 'Monod', 'Logistic', 'Gompertz',
1194
+ 'First_Order', 'Monod_Substrate', 'First_Order', 'Monod_Substrate',
1195
+ 'Luedeking_Piret', 'Linear', 'Luedeking_Piret', 'Linear'],
1196
+ 'Type': ['Biomass', 'Biomass', 'Biomass', 'Biomass', 'Biomass', 'Biomass',
1197
+ 'Substrate', 'Substrate', 'Substrate', 'Substrate',
1198
+ 'Product', 'Product', 'Product', 'Product'],
1199
+ 'R2': [0.9845, 0.9912, 0.9956, 0.9789, 0.9834, 0.9901,
1200
+ 0.9723, 0.9856, 0.9698, 0.9812,
1201
+ 0.9634, 0.9512, 0.9687, 0.9423],
1202
+ 'RMSE': [0.0234, 0.0189, 0.0145, 0.0267, 0.0223, 0.0178,
1203
+ 0.0312, 0.0245, 0.0334, 0.0289,
1204
+ 0.0412, 0.0523, 0.0389, 0.0567],
1205
+ 'mu_max': [0.45, 0.48, 0.52, 0.42, 0.44, 0.49,
1206
+ None, None, None, None, None, None, None, None],
1207
+ 'Ks': [None, None, None, None, None, None,
1208
+ 2.1, 1.8, 2.3, 1.9, None, None, None, None]
1209
  }
 
1210
 
1211
+ # Create analyzer
1212
+ analyzer = ExperimentalModelAnalyzer()
1213
+
1214
+ # Load data
1215
+ analyzer.load_results(data_dict=example_data)
1216
+
1217
+ # Analyze by experiment
1218
+ results = analyzer.analyze_by_experiment()
1219
+
1220
+ # Create visualizations
1221
+ analyzer.create_comparison_visualizations()
1222
+
1223
+ # Generate summary table
1224
+ summary = analyzer.generate_summary_table()
1225
+
1226
+ print("\\n✨ Analysis complete! Best models identified for each experiment and variable type.")
1227
  """
1228
+
1229
+ return code
1230
 
1231
+ # Estado global para almacenar resultados
1232
  class AppState:
1233
  def __init__(self):
1234
  self.current_analysis = ""
 
1237
 
1238
  app_state = AppState()
1239
 
1240
+ def export_report(export_format: str, language: str) -> Tuple[str, str]:
1241
+ """Exporta el reporte al formato seleccionado"""
1242
  if not app_state.current_analysis:
1243
+ error_msg = {
1244
+ 'en': "No analysis available to export",
1245
+ 'es': "No hay análisis disponible para exportar",
1246
+ 'fr': "Aucune analyse disponible pour exporter",
1247
+ 'de': "Keine Analyse zum Exportieren verfügbar",
1248
+ 'pt': "Nenhuma análise disponível para exportar"
1249
+ }
1250
+ return error_msg.get(language, error_msg['en']), ""
1251
+
1252
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
1253
+
1254
  try:
1255
  if export_format == "DOCX":
1256
  filename = f"biotech_analysis_report_{timestamp}.docx"
 
1262
  success_msg = TRANSLATIONS[language]['report_exported']
1263
  return f"{success_msg} {filename}", filename
1264
  except Exception as e:
1265
+ return f"Error: {str(e)}", ""
 
 
1266
 
1267
+ # Interfaz Gradio con soporte multiidioma y temas
1268
  def create_interface():
1269
+ # Estado inicial
1270
+ current_theme = "light"
1271
  current_language = "en"
1272
+
1273
+ def update_interface_language(language):
1274
+ """Actualiza el idioma de la interfaz"""
1275
  app_state.current_language = language
1276
  t = TRANSLATIONS[language]
1277
+
1278
  return [
1279
+ gr.update(value=f"# {t['title']}"), # title_text
1280
+ gr.update(value=t['subtitle']), # subtitle_text
1281
+ gr.update(label=t['upload_files']), # files_input
1282
+ gr.update(label=t['select_model']), # model_selector
1283
+ gr.update(label=t['select_language']), # language_selector
1284
+ gr.update(label=t['select_theme']), # theme_selector
1285
+ gr.update(label=t['detail_level']), # detail_level
1286
+ gr.update(label=t['additional_specs'], placeholder=t['additional_specs_placeholder']), # additional_specs
1287
+ gr.update(value=t['analyze_button']), # analyze_btn
1288
+ gr.update(label=t['export_format']), # export_format
1289
+ gr.update(value=t['export_button']), # export_btn
1290
+ gr.update(label=t['comparative_analysis']), # analysis_output
1291
+ gr.update(label=t['implementation_code']), # code_output
1292
+ gr.update(label=t['data_format']) # data_format_accordion
 
1293
  ]
1294
+
1295
  def process_and_store(files, model, detail, language, additional_specs):
1296
+ """Procesa archivos y almacena resultados"""
1297
  if not files:
1298
+ error_msg = TRANSLATIONS[language]['error_no_files']
1299
+ return error_msg, ""
1300
 
1301
  analysis, code = process_files(files, model, detail, language, additional_specs)
1302
  app_state.current_analysis = analysis
1303
  app_state.current_code = code
1304
  return analysis, code
1305
+
1306
+ with gr.Blocks(theme=THEMES[current_theme]) as demo:
1307
+ # Componentes de UI
1308
+ with gr.Row():
1309
+ with gr.Column(scale=3):
1310
+ title_text = gr.Markdown(f"# {TRANSLATIONS[current_language]['title']}")
1311
+ subtitle_text = gr.Markdown(TRANSLATIONS[current_language]['subtitle'])
1312
+ with gr.Column(scale=1):
1313
+ with gr.Row():
1314
+ language_selector = gr.Dropdown(
1315
+ choices=[("English", "en"), ("Español", "es"), ("Français", "fr"),
1316
+ ("Deutsch", "de"), ("Português", "pt")],
1317
+ value="en",
1318
+ label=TRANSLATIONS[current_language]['select_language'],
1319
+ interactive=True
1320
+ )
1321
+ theme_selector = gr.Dropdown(
1322
+ choices=[("Light", "light"), ("Dark", "dark")],
1323
+ value="light",
1324
+ label=TRANSLATIONS[current_language]['select_theme'],
1325
+ interactive=True
1326
+ )
1327
+
1328
+ with gr.Row():
1329
+ with gr.Column(scale=1):
1330
+ files_input = gr.File(
1331
+ label=TRANSLATIONS[current_language]['upload_files'],
1332
+ file_count="multiple",
1333
+ file_types=[".csv", ".xlsx", ".xls", ".pdf", ".zip"],
1334
+ type="filepath"
1335
+ )
1336
+
1337
+ model_selector = gr.Dropdown(
1338
+ choices=list(QWEN_MODELS.keys()),
1339
+ value="Qwen/Qwen3-14B",
1340
+ label=TRANSLATIONS[current_language]['select_model'],
1341
+ info=f"{TRANSLATIONS[current_language]['best_for']}: {QWEN_MODELS['Qwen/Qwen3-14B']['best_for']}"
1342
+ )
1343
+
1344
+ detail_level = gr.Radio(
1345
+ choices=[
1346
+ (TRANSLATIONS[current_language]['detailed'], "detailed"),
1347
+ (TRANSLATIONS[current_language]['summarized'], "summarized")
1348
+ ],
1349
+ value="detailed",
1350
+ label=TRANSLATIONS[current_language]['detail_level']
1351
+ )
1352
+
1353
+ # Nueva entrada para especificaciones adicionales
1354
+ additional_specs = gr.Textbox(
1355
+ label=TRANSLATIONS[current_language]['additional_specs'],
1356
+ placeholder=TRANSLATIONS[current_language]['additional_specs_placeholder'],
1357
+ lines=3,
1358
+ max_lines=5,
1359
+ interactive=True
1360
+ )
1361
+
1362
+ analyze_btn = gr.Button(
1363
+ TRANSLATIONS[current_language]['analyze_button'],
1364
+ variant="primary",
1365
+ size="lg"
1366
+ )
1367
+
1368
+ gr.Markdown("---")
1369
+
1370
+ export_format = gr.Radio(
1371
+ choices=["DOCX", "PDF"],
1372
+ value="PDF",
1373
+ label=TRANSLATIONS[current_language]['export_format']
1374
+ )
1375
+
1376
+ export_btn = gr.Button(
1377
+ TRANSLATIONS[current_language]['export_button'],
1378
+ variant="secondary"
1379
+ )
1380
+
1381
+ export_status = gr.Textbox(
1382
+ label="Export Status",
1383
+ interactive=False,
1384
+ visible=False
1385
+ )
1386
+
1387
+ export_file = gr.File(
1388
+ label="Download Report",
1389
+ visible=False
1390
+ )
1391
+
1392
+ with gr.Column(scale=2):
1393
+ analysis_output = gr.Markdown(
1394
+ label=TRANSLATIONS[current_language]['comparative_analysis']
1395
+ )
1396
+
1397
+ code_output = gr.Code(
1398
+ label=TRANSLATIONS[current_language]['implementation_code'],
1399
+ language="python",
1400
+ interactive=True,
1401
+ lines=20
1402
+ )
1403
+
1404
+ data_format_accordion = gr.Accordion(
1405
+ label=TRANSLATIONS[current_language]['data_format'],
1406
+ open=False
1407
+ )
1408
+
1409
+ with data_format_accordion:
1410
+ gr.Markdown("""
1411
+ ### Expected CSV/Excel structure:
1412
+
1413
+ | Experiment | Model | Type | R2 | RMSE | AIC | BIC | mu_max | Ks | Parameters |
1414
+ |------------|-------|------|-----|------|-----|-----|--------|-------|------------|
1415
+ | pH_7.0 | Monod | Biomass | 0.985 | 0.023 | -45.2 | -42.1 | 0.45 | 2.1 | {...} |
1416
+ | pH_7.0 | Logistic | Biomass | 0.976 | 0.031 | -42.1 | -39.5 | 0.42 | - | {...} |
1417
+ | pH_7.0 | First_Order | Substrate | 0.992 | 0.018 | -48.5 | -45.2 | - | 1.8 | {...} |
1418
+ | pH_7.5 | Monod | Biomass | 0.978 | 0.027 | -44.1 | -41.2 | 0.43 | 2.2 | {...} |
1419
+
1420
+ **Important columns:**
1421
+ - **Experiment**: Experimental condition identifier
1422
+ - **Model**: Model name
1423
+ - **Type**: Variable type (Biomass/Substrate/Product)
1424
+ - **R2, RMSE**: Fit quality metrics
1425
+ - **Parameters**: Model-specific parameters
1426
+ """)
1427
+
1428
+ # Definir ejemplos
1429
+ examples = gr.Examples(
1430
+ examples=[
1431
+ [["examples/biomass_models_comparison.csv"], "Qwen/Qwen3-14B", "detailed", ""],
1432
+ [["examples/substrate_kinetics_results.xlsx"], "Qwen/Qwen3-14B", "summarized", "Focus on temperature effects"]
1433
+ ],
1434
+ inputs=[files_input, model_selector, detail_level, additional_specs],
1435
+ label=TRANSLATIONS[current_language]['examples']
1436
+ )
1437
+
1438
+ # Eventos - Actualizado para incluir additional_specs
1439
  language_selector.change(
1440
  update_interface_language,
1441
  inputs=[language_selector],
1442
+ outputs=[
1443
+ title_text, subtitle_text, files_input, model_selector,
1444
+ language_selector, theme_selector, detail_level, additional_specs,
1445
+ analyze_btn, export_format, export_btn, analysis_output,
1446
+ code_output, data_format_accordion
1447
+ ]
1448
+ )
1449
+
1450
+ def change_theme(theme_name):
1451
+ """Cambia el tema de la interfaz"""
1452
+ # Nota: En Gradio actual, cambiar el tema dinámicamente requiere recargar
1453
+ # Esta es una limitación conocida
1454
+ return gr.Info("Theme will be applied on next page load")
1455
+
1456
+ theme_selector.change(
1457
+ change_theme,
1458
+ inputs=[theme_selector],
1459
+ outputs=[]
1460
  )
1461
 
1462
  analyze_btn.click(
1463
  fn=process_and_store,
1464
  inputs=[files_input, model_selector, detail_level, language_selector, additional_specs],
1465
+ outputs=[analysis_output, code_output]
 
1466
  )
1467
 
1468
+ def handle_export(format, language):
1469
+ status, file = export_report(format, language)
1470
+ if file:
1471
+ return gr.update(value=status, visible=True), gr.update(value=file, visible=True)
1472
  else:
1473
  return gr.update(value=status, visible=True), gr.update(visible=False)
1474
+
1475
  export_btn.click(
1476
  fn=handle_export,
1477
  inputs=[export_format, language_selector],
1478
  outputs=[export_status, export_file]
1479
  )
1480
+
1481
  return demo
1482
 
1483
+ # Función principal
 
1484
  def main():
1485
+ if not os.getenv("NEBIUS_API_KEY"):
1486
+ print("⚠️ Configure NEBIUS_API_KEY in HuggingFace Space secrets")
1487
  return gr.Interface(
1488
+ fn=lambda x: TRANSLATIONS['en']['error_no_api'],
1489
+ inputs=gr.Textbox(),
1490
+ outputs=gr.Textbox(),
1491
  title="Configuration Error"
1492
  )
1493
+
1494
  return create_interface()
1495
 
1496
+ # Para ejecución local
1497
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1498
  demo = main()
1499
  if demo:
1500
+ demo.launch(
1501
+ server_name="0.0.0.0",
1502
+ server_port=7860,
1503
+ share=False
1504
+ )