C2MV commited on
Commit
da38434
·
verified ·
1 Parent(s): 1d9a7e8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1268 -325
app.py CHANGED
@@ -30,18 +30,13 @@ from openai import OpenAI # Replaced Anthropic with OpenAI for Qwen
30
  # Configuración para HuggingFace
31
  os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
32
 
33
- # --- Inicialización del Cliente Qwen ---
34
- # Asegúrate de que la variable de entorno NEBIUS_API_KEY esté configurada
35
- try:
36
- client = OpenAI(
37
- base_url="https://api.studio.nebius.com/v1/",
38
- api_key=os.environ.get("NEBIUS_API_KEY")
39
- )
40
- except Exception as e:
41
- print(f"Error al inicializar el cliente de OpenAI: {e}")
42
- client = None
43
 
44
- # --- Sistema de Traducción ---
45
  TRANSLATIONS = {
46
  'en': {
47
  'title': '🧬 Comparative Analyzer of Biotechnological Models',
@@ -63,7 +58,7 @@ TRANSLATIONS = {
63
  'light': 'Light',
64
  'dark': 'Dark',
65
  'best_for': 'Best for',
66
- 'loading': 'Analyzing... Please wait.',
67
  'error_no_api': 'Please configure NEBIUS_API_KEY in HuggingFace Space secrets',
68
  'error_no_files': 'Please upload fitting result files to analyze',
69
  'report_exported': 'Report exported successfully as',
@@ -94,7 +89,7 @@ TRANSLATIONS = {
94
  'light': 'Claro',
95
  'dark': 'Oscuro',
96
  'best_for': 'Mejor para',
97
- 'loading': 'Analizando... Por favor, espere.',
98
  'error_no_api': 'Por favor configura NEBIUS_API_KEY en los secretos del Space',
99
  'error_no_files': 'Por favor sube archivos con resultados de ajuste para analizar',
100
  'report_exported': 'Reporte exportado exitosamente como',
@@ -105,10 +100,102 @@ TRANSLATIONS = {
105
  'additional_specs': '📝 Especificaciones adicionales para el análisis',
106
  'additional_specs_placeholder': 'Agregue cualquier requerimiento específico o áreas de enfoque para el análisis...'
107
  },
108
- # Otras traducciones (fr, de, pt) se mantienen igual que en el original
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  }
110
 
111
- # --- Temas de la Interfaz ---
112
  THEMES = {
113
  'light': gr.themes.Soft(),
114
  'dark': gr.themes.Base(
@@ -117,445 +204,1301 @@ THEMES = {
117
  neutral_hue="gray",
118
  font=["Arial", "sans-serif"]
119
  ).set(
 
120
  body_background_fill_dark="*neutral_950",
121
- block_background_fill_dark="*neutral_800",
122
- block_label_text_color_dark="*neutral_200",
123
- body_text_color_dark="*neutral_200",
 
 
 
 
 
 
 
 
 
124
  )
125
  }
126
 
127
- # --- Clases de Estructura de Datos ---
128
  class AnalysisType(Enum):
 
 
129
  FITTING_RESULTS = "fitting_results"
130
  UNKNOWN = "unknown"
131
 
 
132
  @dataclass
133
  class MathematicalModel:
134
  name: str
135
  equation: str
136
  parameters: List[str]
137
  application: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
- # --- Clases de Procesamiento ---
140
  class FileProcessor:
141
- """Clase para leer y procesar diferentes tipos de archivos de datos."""
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  @staticmethod
143
- def read_csv(file_content: bytes) -> Optional[pd.DataFrame]:
 
144
  try:
145
- return pd.read_csv(io.BytesIO(file_content))
146
  except Exception as e:
147
- print(f"Error reading CSV: {e}")
148
  return None
149
-
150
  @staticmethod
151
- def read_excel(file_content: bytes) -> Optional[pd.DataFrame]:
 
152
  try:
153
- return pd.read_excel(io.BytesIO(file_content))
154
  except Exception as e:
155
- print(f"Error reading Excel: {e}")
156
  return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
  class ReportExporter:
159
- """Clase para exportar reportes a diferentes formatos (DOCX, PDF)."""
 
160
  @staticmethod
161
  def export_to_docx(content: str, filename: str, language: str = 'en') -> str:
 
162
  doc = Document()
163
- doc.add_heading('Biotechnology Model Analysis Report', 0)
164
- # Lógica de formato simplificada para brevedad, la original es más compleja y se puede mantener
165
- doc.add_paragraph(content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  doc.save(filename)
167
  return filename
168
-
169
  @staticmethod
170
  def export_to_pdf(content: str, filename: str, language: str = 'en') -> str:
 
 
171
  doc = SimpleDocTemplate(filename, pagesize=letter)
 
172
  styles = getSampleStyleSheet()
173
- story = [Paragraph("Biotechnology Model Analysis Report", styles['h1'])]
174
- # Lógica de formato simplificada, la original es más compleja y se puede mantener
175
- clean_content = re.sub(r'[^\x00-\x7F]+', '', content) # Remover caracteres no-ASCII para PDF básico
176
- story.append(Paragraph(clean_content, styles['Normal']))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  doc.build(story)
178
  return filename
179
 
180
  class AIAnalyzer:
181
- """Clase central para interactuar con el modelo Qwen y realizar el análisis."""
182
-
183
- def __init__(self, client_instance):
184
- self.client = client_instance
185
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  def get_language_prompt_prefix(self, language: str) -> str:
187
- """Obtiene el prefijo del prompt según el idioma para guiar al modelo."""
188
  prefixes = {
189
- 'es': "Por favor, responde en español.",
190
- 'en': "Please respond in English.",
191
- 'fr': "Veuillez répondre en français.",
192
- 'de': "Bitte antworten Sie auf Deutsch.",
193
- 'pt': "Por favor, responda em português."
194
  }
195
  return prefixes.get(language, prefixes['en'])
196
-
197
- def analyze_fitting_results(self, data: pd.DataFrame, qwen_model: str, detail_level: str,
198
- language: str, additional_specs: str) -> Dict:
199
- """
200
- Analiza los resultados de ajuste de modelos.
201
- Convierte el DataFrame en texto y lo envía a la API de Qwen.
202
- """
203
- if self.client is None:
204
- return {"error": TRANSLATIONS[language]['error_no_api']}
205
-
206
- # **Paso Clave: Conversión del DataFrame (CSV/Excel) a Texto para la API**
207
- # El LLM necesita ver todos los datos para hacer una comparación completa.
208
- # to_string() es una forma efectiva de presentar datos tabulares en formato de texto.
209
- dataframe_as_text = f"""
210
- FITTING RESULTS DATASET:
211
-
212
  {data.to_string()}
 
 
 
213
  """
214
-
 
 
 
 
215
  lang_prefix = self.get_language_prompt_prefix(language)
216
- user_specs_section = f"\nUSER ADDITIONAL SPECIFICATIONS:\n{additional_specs}\n" if additional_specs else ""
217
-
218
- # Selección del prompt según el nivel de detalle
 
 
 
 
 
 
 
 
219
  if detail_level == "detailed":
220
- analysis_prompt = f"""
221
  {lang_prefix}
222
- You are an expert in biotechnology and mathematical modeling. Analyze the provided model fitting results in detail.
 
 
223
  {user_specs_section}
224
- TASK: Perform a comprehensive comparative analysis of the models based on the provided data.
225
-
226
- 1. **Overall Summary:** Briefly describe the dataset (number of models, experiments, key metrics).
227
- 2. **Model Performance Ranking:** Create a master table ranking all models based on key metrics (R², RMSE, AIC, BIC). Sort from best to worst.
228
- 3. **Analysis by Experiment/Condition:** If an 'Experiment' column exists, group the analysis by each experiment. For each experiment, identify the best performing model and its parameters.
229
- 4. **Parameter Interpretation:** Discuss the biological meaning of the key parameters (like μmax, Ks) for the best models. Are the values realistic? How do they change across conditions?
230
- 5. **Strengths and Weaknesses:** For the top 3 models, discuss their potential strengths and weaknesses based on the fitting results.
231
- 6. **Final Recommendation:** Conclude with a clear recommendation for the best overall model for the described process, justifying your choice with data. If different models are better for different conditions, state that.
232
-
233
- Format the response using Markdown for clarity.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  """
235
- else: # summarized
236
- analysis_prompt = f"""
237
  {lang_prefix}
238
- You are a biotechnology expert. Provide a concise summary of the provided model fitting results.
 
 
239
  {user_specs_section}
240
- TASK: Summarize the model comparison.
241
-
242
- 1. **Best Model:** Identify the single best model overall based on R² and RMSE.
243
- 2. **Top 3 Ranking:** List the top 3 models with their R² and RMSE values.
244
- 3. **Key Finding:** State the most important conclusion from the data in one or two sentences.
245
- 4. **Recommendation:** Briefly recommend which model to use.
246
-
247
- Keep the response short and to the point.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  """
249
-
250
  try:
251
- # --- 1. Generar el Análisis Comparativo ---
252
- analysis_response = self.client.chat.completions.create(
253
  model=qwen_model,
254
  max_tokens=4000,
255
  temperature=0.3,
256
  messages=[{
257
- "role": "system", "content": analysis_prompt
258
- }, {
259
- "role": "user", "content": dataframe_as_text
260
  }]
261
  )
262
- analysis_result = analysis_response.choices[0].message.content
263
-
264
- # --- 2. Generar el Código de Implementación ---
 
265
  code_prompt = f"""
266
  {lang_prefix}
267
- Based on the provided data, generate a complete, executable Python script.
268
- The script should perform a comparative analysis of the biotechnological models.
269
-
270
- The Python script must include:
271
- 1. Loading the data into a pandas DataFrame (embed the data directly in the script).
272
- 2. A function to identify the best model for each 'Experiment' or condition present in the data.
273
- 3. A function to determine the overall best model across all conditions.
274
- 4. Use of Matplotlib or Seaborn to generate at least two relevant plots:
275
- a) A bar chart comparing the R² values of all models.
276
- b) A plot showing how a key parameter (e.g., 'mu_max') changes across different experiments.
277
- 5. Print statements that clearly announce the results of the analysis (e.g., "The best model for Experiment 'pH 7.0' is 'Gompertz'").
278
- 6. The code should be well-commented and easy to understand.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  """
 
280
  code_response = self.client.chat.completions.create(
281
  model=qwen_model,
282
  max_tokens=3000,
283
  temperature=0.1,
284
  messages=[{
285
- "role": "system", "content": code_prompt
286
- }, {
287
- "role": "user", "content": dataframe_as_text
288
  }]
289
  )
 
290
  code_result = code_response.choices[0].message.content
291
-
292
  return {
 
293
  "analisis_completo": analysis_result,
294
  "codigo_implementacion": code_result,
 
 
 
 
 
 
 
 
 
295
  }
296
-
297
  except Exception as e:
298
- print(f"Error during AI analysis: {e}")
299
  return {"error": str(e)}
300
 
301
- # --- Lógica Principal de la Aplicación ---
302
-
303
- # Estado global para almacenar los últimos resultados
304
- app_state = {
305
- "current_analysis": "",
306
- "current_code": ""
307
- }
308
-
309
- def process_uploaded_files(files: List, qwen_model: str, detail_level: str,
310
- language: str, additional_specs: str, progress=gr.Progress()) -> Tuple[str, str]:
311
- """
312
- Función principal que orquesta el procesamiento de archivos y el análisis.
313
- """
314
- if not files:
315
- error_msg = TRANSLATIONS[language]['error_no_files']
316
- return error_msg, ""
317
-
318
- if client is None:
319
- return TRANSLATIONS[language]['error_no_api'], ""
320
-
321
- progress(0, desc=TRANSLATIONS[language]['loading'])
322
-
323
  processor = FileProcessor()
324
- analyzer = AIAnalyzer(client)
 
 
325
 
326
- all_analyses = []
327
- all_codes = []
328
-
329
- for i, file in enumerate(files):
330
- file_path = Path(file.name)
331
- file_ext = file_path.suffix.lower()
332
 
333
- progress((i + 1) / len(files), desc=f"Processing {file_path.name}...")
334
-
335
- try:
336
- with open(file_path, 'rb') as f:
337
- file_content = f.read()
338
-
339
- df = None
 
 
340
  if file_ext == '.csv':
341
  df = processor.read_csv(file_content)
342
- elif file_ext in ['.xlsx', '.xls']:
343
- df = processor.read_excel(file_content)
344
  else:
345
- all_analyses.append(f"## ⚠️ Unsupported file: {file_path.name}\nOnly CSV and Excel files are supported for analysis.")
346
- continue
347
-
348
  if df is not None:
349
- # El DataFrame se ha leído correctamente, ahora se pasa al analizador
350
- analysis_result = analyzer.analyze_fitting_results(
351
- data=df,
352
- qwen_model=qwen_model,
353
- detail_level=detail_level,
354
- language=language,
355
- additional_specs=additional_specs
356
- )
357
-
358
- if "error" in analysis_result:
359
- all_analyses.append(f"## Error analyzing {file_path.name}\n\n{analysis_result['error']}")
360
- else:
361
- header = TRANSLATIONS[language]['comparative_analysis']
362
- all_analyses.append(f"## {header}: {file_path.name}\n\n{analysis_result.get('analisis_completo', '')}")
363
- all_codes.append(f"# Code generated for: {file_path.name}\n{analysis_result.get('codigo_implementacion', '')}")
364
- else:
365
- all_analyses.append(f"## ❌ Could not read file: {file_path.name}")
 
 
 
 
 
366
 
367
- except Exception as e:
368
- all_analyses.append(f"## Critical error processing {file_path.name}: {e}")
 
 
 
 
 
 
 
 
 
369
 
370
- # Unir todos los resultados
371
- final_analysis = "\n\n---\n\n".join(all_analyses)
372
- final_code = "\n\n# " + "="*70 + "\n\n".join(all_codes)
373
 
374
- # Almacenar en el estado de la app para exportación
375
- app_state["current_analysis"] = final_analysis
376
- app_state["current_code"] = final_code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
 
378
- return final_analysis, final_code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
 
 
 
 
 
 
 
380
 
381
- def export_report_action(export_format: str, language: str) -> Tuple[str, str]:
382
- """Exporta el reporte al formato seleccionado usando el estado guardado."""
383
- if not app_state["current_analysis"]:
384
- error_msg = TRANSLATIONS[language].get('error_no_files', 'No analysis available to export')
385
- return gr.update(value=error_msg, visible=True), gr.update(visible=False)
386
 
 
 
 
 
 
 
 
 
 
 
 
 
387
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
388
 
389
- with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as tmpfile:
390
- filename_base = f"biotech_analysis_report_{timestamp}"
 
 
 
 
 
391
 
392
- try:
393
- if export_format == "DOCX":
394
- report_filename = f"{filename_base}.docx"
395
- ReportExporter.export_to_docx(app_state["current_analysis"], report_filename, language)
396
- file_to_download = report_filename
397
- else: # PDF
398
- report_filename = f"{filename_base}.pdf"
399
- ReportExporter.export_to_pdf(app_state["current_analysis"], report_filename, language)
400
- file_to_download = report_filename
401
-
402
- success_msg = f"{TRANSLATIONS[language]['report_exported']} {file_to_download}"
403
- return gr.update(value=success_msg, visible=True), gr.update(value=file_to_download, visible=True)
404
 
405
- except Exception as e:
406
- return gr.update(value=f"Error exporting report: {e}", visible=True), gr.update(visible=False)
407
-
408
- # --- Interfaz de Gradio ---
409
  def create_interface():
410
- current_lang = "en" # Default language
411
-
412
- def update_ui_language(language):
413
- """Actualiza todos los textos de la UI al cambiar de idioma."""
414
- nonlocal current_lang
415
- current_lang = language
416
- t = TRANSLATIONS.get(language, TRANSLATIONS['en'])
417
-
418
- return (
419
- gr.update(value=f"# {t['title']}"),
420
- gr.update(value=t['subtitle']),
421
- gr.update(label=t['upload_files']),
422
- gr.update(label=t['select_model']),
423
- gr.update(label=t['select_language']),
424
- gr.update(label=t['select_theme']),
425
- gr.update(label=t['detail_level']),
426
- gr.update(label=t['additional_specs'], placeholder=t['additional_specs_placeholder']),
427
- gr.update(value=t['analyze_button']),
428
- gr.update(label=t['export_format']),
429
- gr.update(value=t['export_button']),
430
- gr.update(label=t['comparative_analysis']),
431
- gr.update(label=t['implementation_code']),
432
- gr.update(label=t['data_format']),
433
- gr.update(label=t['examples']),
434
- )
435
-
436
- with gr.Blocks(theme=THEMES['light']) as demo:
437
- # Definición de componentes de la UI
438
- title_text = gr.Markdown(f"# {TRANSLATIONS[current_lang]['title']}")
439
- subtitle_text = gr.Markdown(TRANSLATIONS[current_lang]['subtitle'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
 
441
  with gr.Row():
442
  with gr.Column(scale=1):
443
- # Columna de Controles
444
  files_input = gr.File(
445
- label=TRANSLATIONS[current_lang]['upload_files'],
446
  file_count="multiple",
447
- file_types=[".csv", ".xlsx", ".xls"],
448
  type="filepath"
449
  )
450
 
451
  model_selector = gr.Dropdown(
452
- choices=["Qwen/Qwen3-14B", "Qwen/Qwen3-7B", "Qwen/Qwen1.5-14B"],
453
  value="Qwen/Qwen3-14B",
454
- label=TRANSLATIONS[current_lang]['select_model']
 
455
  )
456
 
457
  detail_level = gr.Radio(
458
- choices=[("Detailed", "detailed"), ("Summarized", "summarized")],
 
 
 
459
  value="detailed",
460
- label=TRANSLATIONS[current_lang]['detail_level']
461
  )
462
-
 
463
  additional_specs = gr.Textbox(
464
- label=TRANSLATIONS[current_lang]['additional_specs'],
465
- placeholder=TRANSLATIONS[current_lang]['additional_specs_placeholder'],
466
- lines=3
 
 
467
  )
468
 
469
- analyze_btn = gr.Button(TRANSLATIONS[current_lang]['analyze_button'], variant="primary")
 
 
 
 
470
 
471
- with gr.Accordion(label=TRANSLATIONS[current_lang]['data_format'], open=False) as data_format_accordion:
472
- gr.Markdown("""
473
- ### Expected CSV/Excel Structure:
474
- The file should contain columns representing different models and their fitting metrics.
475
- | Experiment | Model | Type | R2 | RMSE | AIC | mu_max | Ks |
476
- |------------|-------------|-----------|-------|--------|---------|--------|------|
477
- | pH_7.0 | Monod | Biomass | 0.985 | 0.023 | -45.2 | 0.45 | 2.1 |
478
- | pH_7.0 | Logistic | Biomass | 0.976 | 0.031 | -42.1 | 0.42 | - |
479
- | pH_7.5 | Monod | Biomass | 0.978 | 0.027 | -44.1 | 0.43 | 2.2 |
480
-
481
- **Key Columns:** `Model`, `R2`, `RMSE`. Optional: `Experiment`, `Type`, and parameter columns.
482
- """)
483
 
484
- examples_accordion = gr.Accordion(label=TRANSLATIONS[current_lang]['examples'], open=True)
485
- # La funcionalidad de `gr.Examples` requiere archivos en el servidor.
486
- # Se puede agregar si se alojan archivos de ejemplo.
487
-
488
- with gr.Column(scale=2):
489
- # Columna de Resultados
490
- analysis_output = gr.Markdown(label=TRANSLATIONS[current_lang]['comparative_analysis'])
491
- code_output = gr.Code(label=TRANSLATIONS[current_lang]['implementation_code'], language="python")
492
 
493
- gr.Markdown("---")
 
 
 
494
 
495
- with gr.Row():
496
- export_format = gr.Radio(["PDF", "DOCX"], value="PDF", label=TRANSLATIONS[current_lang]['export_format'])
497
- export_btn = gr.Button(TRANSLATIONS[current_lang]['export_button'])
 
 
498
 
499
- export_status = gr.Textbox(label="Export Status", interactive=False, visible=False)
500
- export_file = gr.File(label="Download Report", visible=False, interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
 
502
- with gr.Row():
503
- language_selector = gr.Dropdown(
504
- choices=[("English", "en"), ("Español", "es"), ("Français", "fr"), ("Deutsch", "de"), ("Português", "pt")],
505
- value="en", label="Language", interactive=True
506
- )
507
- theme_selector = gr.Dropdown(
508
- choices=[("Light", "light"), ("Dark", "dark")],
509
- value="light", label="Theme", interactive=True
510
- )
511
-
512
- # Lógica de Eventos
513
- analyze_btn.click(
514
- fn=process_uploaded_files,
515
- inputs=[files_input, model_selector, detail_level, language_selector, additional_specs],
516
- outputs=[analysis_output, code_output]
517
  )
518
-
519
- export_btn.click(
520
- fn=export_report_action,
521
- inputs=[export_format, language_selector],
522
- outputs=[export_status, export_file]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  )
524
 
 
525
  language_selector.change(
526
- fn=update_ui_language,
527
- inputs=language_selector,
528
  outputs=[
529
- title_text, subtitle_text, files_input, model_selector, language_selector,
530
- theme_selector, detail_level, additional_specs, analyze_btn, export_format,
531
- export_btn, analysis_output, code_output, data_format_accordion, examples_accordion
 
532
  ]
533
  )
534
 
535
- # El cambio de tema en Gradio 4+ se maneja a nivel de Blocks, requiere recarga.
536
- # Esta es una forma de notificar al usuario.
537
- theme_selector.change(None, theme_selector, js="""
538
- (theme) => {
539
- if (theme == 'dark') {
540
- document.body.classList.add('dark');
541
- } else {
542
- document.body.classList.remove('dark');
543
- }
544
- return theme;
545
- }
546
- """)
547
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
548
  return demo
549
 
550
- # --- Punto de Entrada Principal ---
551
- if __name__ == "__main__":
552
  if not os.getenv("NEBIUS_API_KEY"):
553
- print("CRITICAL ERROR: NEBIUS_API_KEY environment variable not set.")
554
- # Interfaz de error si no hay clave API
555
- with gr.Blocks() as error_demo:
556
- gr.Markdown("# Configuration Error")
557
- gr.Markdown(TRANSLATIONS['en']['error_no_api'])
558
- error_demo.launch()
559
- else:
560
- app_interface = create_interface()
561
- app_interface.launch()
 
 
 
 
 
 
 
 
 
 
 
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',
 
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',
 
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',
 
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(
 
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
235
  equation: str
236
  parameters: List[str]
237
  application: str
238
+ sources: List[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))",
268
+ parameters=["μmax (h⁻¹)", "Ks (g/L)"],
269
+ application="Crecimiento limitado por sustrato único",
270
+ sources=["Cambridge", "MIT", "DTU"],
271
+ category="crecimiento_biomasa",
272
+ biological_meaning="Describe cómo la velocidad de crecimiento depende de la concentración de sustrato limitante"
273
+ ))
274
+
275
+ self.register_model(MathematicalModel(
276
+ name="Logístico",
277
+ equation="dX/dt = μmax × X × (1 - X/Xmax)",
278
+ parameters=["μmax (h⁻¹)", "Xmax (g/L)"],
279
+ application="Sistemas cerrados batch",
280
+ sources=["Cranfield", "Swansea", "HAL Theses"],
281
+ category="crecimiento_biomasa",
282
+ biological_meaning="Modela crecimiento limitado por capacidad de carga del sistema"
283
+ ))
284
+
285
+ self.register_model(MathematicalModel(
286
+ name="Gompertz",
287
+ equation="X(t) = Xmax × exp(-exp((μmax × e / Xmax) × (λ - t) + 1))",
288
+ parameters=["λ (h)", "μmax (h⁻¹)", "Xmax (g/L)"],
289
+ application="Crecimiento con fase lag pronunciada",
290
+ sources=["Lund University", "NC State"],
291
+ category="crecimiento_biomasa",
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:
362
+ print(f"Error processing ZIP: {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('##'):
497
+ story.append(Paragraph(line.replace('##', '').strip(), styles['Heading2']))
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 = ""
1235
+ self.current_code = ""
1236
+ self.current_language = "en"
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"
1257
+ ReportExporter.export_to_docx(app_state.current_analysis, filename, language)
1258
+ else: # PDF
1259
+ filename = f"biotech_analysis_report_{timestamp}.pdf"
1260
+ ReportExporter.export_to_pdf(app_state.current_analysis, filename, language)
1261
 
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
+ )