Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,837 +1,423 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
-
import
|
| 3 |
import pandas as pd
|
| 4 |
-
import numpy as np
|
| 5 |
-
import io
|
| 6 |
-
import os
|
| 7 |
import json
|
| 8 |
-
import zipfile
|
| 9 |
import tempfile
|
| 10 |
-
|
| 11 |
-
import re
|
| 12 |
-
from pathlib import Path
|
| 13 |
-
import openpyxl
|
| 14 |
-
from dataclasses import dataclass, asdict
|
| 15 |
-
from enum import Enum
|
| 16 |
-
from docx import Document
|
| 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 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
)
|
| 37 |
-
|
| 38 |
-
#
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
},
|
| 75 |
-
'es': {
|
| 76 |
-
'title': '๐งฌ Analizador Escalable de Modelos Biotecnolรณgicos',
|
| 77 |
-
'subtitle': 'Analiza grandes conjuntos de datos de ajuste de modelos usando una estrategia por partes',
|
| 78 |
-
'upload_files': '๐ Subir resultados de ajuste (CSV/Excel)',
|
| 79 |
-
'chunk_column_label': '๐ฌ Seleccionar Columna para Agrupar Experimentos',
|
| 80 |
-
'chunk_column_info': 'Elige la columna que identifica cada experimento รบnico. Se usarรก para dividir el anรกlisis.',
|
| 81 |
-
'select_model': '๐ค Modelo IA (editable)',
|
| 82 |
-
'select_language': '๐ Idioma',
|
| 83 |
-
'select_theme': '๐จ Tema',
|
| 84 |
-
'detail_level': '๐ Nivel de detalle del anรกlisis',
|
| 85 |
-
'detailed': 'Detallado',
|
| 86 |
-
'summarized': 'Resumido',
|
| 87 |
-
'analyze_button': '๐ Analizar y Comparar Modelos',
|
| 88 |
-
'export_format': '๐ Formato de exportaciรณn',
|
| 89 |
-
'export_button': '๐พ Exportar Reporte',
|
| 90 |
-
'comparative_analysis': '๐ Anรกlisis Comparativo',
|
| 91 |
-
'implementation_code': '๐ป Cรณdigo de Implementaciรณn',
|
| 92 |
-
'data_format': '๐ Formato de datos esperado',
|
| 93 |
-
'loading': 'Cargando...',
|
| 94 |
-
'error_no_api': 'Por favor configura NEBIUS_API_KEY en los secretos del Space',
|
| 95 |
-
'error_no_files': 'Por favor sube archivos con resultados de ajuste para analizar',
|
| 96 |
-
'report_exported': 'Reporte exportado exitosamente como',
|
| 97 |
-
'additional_specs': '๐ Especificaciones adicionales para el anรกlisis',
|
| 98 |
-
'additional_specs_placeholder': 'Agregue cualquier requerimiento especรญfico o รกreas de enfoque para el anรกlisis...',
|
| 99 |
-
'output_tokens_per_chunk': '๐ข Max tokens de salida por pieza (1k-32k)',
|
| 100 |
-
'token_info': 'โน๏ธ Informaciรณn de uso de tokens',
|
| 101 |
-
'input_token_count': 'Tokens de entrada usados',
|
| 102 |
-
'output_token_count': 'Tokens de salida usados',
|
| 103 |
-
'total_token_count': 'Total de tokens usados',
|
| 104 |
-
'token_cost': 'Costo estimado',
|
| 105 |
-
'thinking_process': '๐ง Proceso de Pensamiento',
|
| 106 |
-
'analysis_report': '๐ Reporte de Anรกlisis',
|
| 107 |
-
'code_output': '๐ป Cรณdigo de Implementaciรณn',
|
| 108 |
-
'token_usage': '๐ฐ Uso de Tokens'
|
| 109 |
-
}
|
| 110 |
-
}
|
| 111 |
-
THEMES = { 'light': gr.themes.Soft(), 'dark': gr.themes.Base() }
|
| 112 |
-
|
| 113 |
-
QWEN_MODELS = {
|
| 114 |
-
"Qwen/Qwen3-14B": {"max_context_tokens": 40960, "input_cost": 0.0000007, "output_cost": 0.0000021},
|
| 115 |
-
"Qwen/Qwen3-7B": {"max_context_tokens": 40960, "input_cost": 0.00000035, "output_cost": 0.00000105},
|
| 116 |
-
"Qwen/Qwen1.5-14B": {"max_context_tokens": 40960, "input_cost": 0.0000007, "output_cost": 0.0000021}
|
| 117 |
-
}
|
| 118 |
-
|
| 119 |
-
# --- CLASES DE UTILIDAD (Se asume que existen, omitidas por brevedad) ---
|
| 120 |
-
class FileProcessor:
|
| 121 |
-
"""Clase para procesar diferentes tipos de archivos"""
|
| 122 |
-
|
| 123 |
-
@staticmethod
|
| 124 |
-
def extract_text_from_pdf(pdf_file) -> str:
|
| 125 |
-
"""Extrae texto de un archivo PDF"""
|
| 126 |
-
try:
|
| 127 |
-
pdf_reader = PyPDF2.PdfReader(io.BytesIO(pdf_file))
|
| 128 |
-
text = ""
|
| 129 |
-
for page in pdf_reader.pages:
|
| 130 |
-
text += page.extract_text() + "\n"
|
| 131 |
-
return text
|
| 132 |
-
except Exception as e:
|
| 133 |
-
return f"Error reading PDF: {str(e)}"
|
| 134 |
-
|
| 135 |
-
@staticmethod
|
| 136 |
-
def read_csv(csv_file) -> pd.DataFrame:
|
| 137 |
-
"""Lee archivo CSV"""
|
| 138 |
-
try:
|
| 139 |
-
return pd.read_csv(io.BytesIO(csv_file))
|
| 140 |
-
except Exception as e:
|
| 141 |
-
return None
|
| 142 |
-
|
| 143 |
-
@staticmethod
|
| 144 |
-
def read_excel(excel_file) -> pd.DataFrame:
|
| 145 |
-
"""Lee archivo Excel"""
|
| 146 |
-
try:
|
| 147 |
-
return pd.read_excel(io.BytesIO(excel_file))
|
| 148 |
-
except Exception as e:
|
| 149 |
-
return None
|
| 150 |
-
|
| 151 |
-
@staticmethod
|
| 152 |
-
def extract_from_zip(zip_file) -> List[Tuple[str, bytes]]:
|
| 153 |
-
"""Extrae archivos de un ZIP"""
|
| 154 |
-
files = []
|
| 155 |
try:
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
class ReportExporter:
|
| 166 |
-
"""Clase para exportar reportes a diferentes formatos"""
|
| 167 |
-
|
| 168 |
-
@staticmethod
|
| 169 |
-
def export_to_docx(content: str, filename: str, language: str = 'en') -> str:
|
| 170 |
-
"""Exporta el contenido a un archivo DOCX"""
|
| 171 |
-
doc = Document()
|
| 172 |
-
|
| 173 |
-
# Configurar estilos
|
| 174 |
-
title_style = doc.styles['Title']
|
| 175 |
-
title_style.font.size = Pt(24)
|
| 176 |
-
title_style.font.bold = True
|
| 177 |
-
|
| 178 |
-
heading_style = doc.styles['Heading 1']
|
| 179 |
-
heading_style.font.size = Pt(18)
|
| 180 |
-
heading_style.font.bold = True
|
| 181 |
-
|
| 182 |
-
# Tรญtulo
|
| 183 |
-
title_text = {
|
| 184 |
-
'en': 'Comparative Analysis Report - Biotechnological Models',
|
| 185 |
-
'es': 'Informe de Anรกlisis Comparativo - Modelos Biotecnolรณgicos',
|
| 186 |
-
}
|
| 187 |
-
|
| 188 |
-
doc.add_heading(title_text.get(language, title_text['en']), 0)
|
| 189 |
-
|
| 190 |
-
# Fecha
|
| 191 |
-
date_text = {
|
| 192 |
-
'en': 'Generated on',
|
| 193 |
-
'es': 'Generado el',
|
| 194 |
-
}
|
| 195 |
-
doc.add_paragraph(f"{date_text.get(language, date_text['en'])}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
| 196 |
-
doc.add_paragraph()
|
| 197 |
-
|
| 198 |
-
# Procesar contenido
|
| 199 |
-
lines = content.split('\n')
|
| 200 |
-
current_paragraph = None
|
| 201 |
-
|
| 202 |
-
for line in lines:
|
| 203 |
-
line = line.strip()
|
| 204 |
-
|
| 205 |
-
if line.startswith('###'):
|
| 206 |
-
doc.add_heading(line.replace('###', '').strip(), level=2)
|
| 207 |
-
elif line.startswith('##'):
|
| 208 |
-
doc.add_heading(line.replace('##', '').strip(), level=1)
|
| 209 |
-
elif line.startswith('#'):
|
| 210 |
-
doc.add_heading(line.replace('#', '').strip(), level=0)
|
| 211 |
-
elif line.startswith('**') and line.endswith('**'):
|
| 212 |
-
# Texto en negrita
|
| 213 |
-
p = doc.add_paragraph()
|
| 214 |
-
run = p.add_run(line.replace('**', ''))
|
| 215 |
-
run.bold = True
|
| 216 |
-
elif line.startswith('- ') or line.startswith('* '):
|
| 217 |
-
# Lista
|
| 218 |
-
doc.add_paragraph(line[2:], style='List Bullet')
|
| 219 |
-
elif line.startswith(tuple('0123456789')):
|
| 220 |
-
# Lista numerada
|
| 221 |
-
doc.add_paragraph(line, style='List Number')
|
| 222 |
-
elif line == '---' or line.startswith('==='):
|
| 223 |
-
# Separador
|
| 224 |
-
doc.add_paragraph('_' * 50)
|
| 225 |
-
elif line:
|
| 226 |
-
# Pรกrrafo normal
|
| 227 |
-
doc.add_paragraph(line)
|
| 228 |
-
|
| 229 |
-
# Guardar documento
|
| 230 |
-
doc.save(filename)
|
| 231 |
-
return filename
|
| 232 |
-
|
| 233 |
-
@staticmethod
|
| 234 |
-
def export_to_pdf(content: str, filename: str, language: str = 'en') -> str:
|
| 235 |
-
"""Exporta el contenido a un archivo PDF"""
|
| 236 |
-
# Crear documento PDF
|
| 237 |
-
doc = SimpleDocTemplate(filename, pagesize=letter)
|
| 238 |
-
story = []
|
| 239 |
-
styles = getSampleStyleSheet()
|
| 240 |
-
|
| 241 |
-
# Estilos personalizados
|
| 242 |
-
title_style = ParagraphStyle(
|
| 243 |
-
'CustomTitle',
|
| 244 |
-
parent=styles['Title'],
|
| 245 |
-
fontSize=24,
|
| 246 |
-
textColor=colors.HexColor('#1f4788'),
|
| 247 |
-
spaceAfter=30
|
| 248 |
-
)
|
| 249 |
-
|
| 250 |
-
heading_style = ParagraphStyle(
|
| 251 |
-
'CustomHeading',
|
| 252 |
-
parent=styles['Heading1'],
|
| 253 |
-
fontSize=16,
|
| 254 |
-
textColor=colors.HexColor('#2e5090'),
|
| 255 |
-
spaceAfter=12
|
| 256 |
-
)
|
| 257 |
-
|
| 258 |
-
# Tรญtulo
|
| 259 |
-
title_text = {
|
| 260 |
-
'en': 'Comparative Analysis Report - Biotechnological Models',
|
| 261 |
-
'es': 'Informe de Anรกlisis Comparativo - Modelos Biotecnolรณgicos',
|
| 262 |
-
}
|
| 263 |
-
|
| 264 |
-
story.append(Paragraph(title_text.get(language, title_text['en']), title_style))
|
| 265 |
-
|
| 266 |
-
# Fecha
|
| 267 |
-
date_text = {
|
| 268 |
-
'en': 'Generated on',
|
| 269 |
-
'es': 'Generado el',
|
| 270 |
-
}
|
| 271 |
-
story.append(Paragraph(f"{date_text.get(language, date_text['en'])}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", styles['Normal']))
|
| 272 |
-
story.append(Spacer(1, 0.5*inch))
|
| 273 |
-
|
| 274 |
-
# Procesar contenido
|
| 275 |
-
lines = content.split('\n')
|
| 276 |
-
|
| 277 |
-
for line in lines:
|
| 278 |
-
line = line.strip()
|
| 279 |
-
|
| 280 |
-
if not line:
|
| 281 |
-
story.append(Spacer(1, 0.2*inch))
|
| 282 |
-
elif line.startswith('###'):
|
| 283 |
-
story.append(Paragraph(line.replace('###', '').strip(), styles['Heading3']))
|
| 284 |
-
elif line.startswith('##'):
|
| 285 |
-
story.append(Paragraph(line.replace('##', '').strip(), styles['Heading2']))
|
| 286 |
-
elif line.startswith('#'):
|
| 287 |
-
story.append(Paragraph(line.replace('#', '').strip(), heading_style))
|
| 288 |
-
elif line.startswith('**') and line.endswith('**'):
|
| 289 |
-
text = line.replace('**', '')
|
| 290 |
-
story.append(Paragraph(f"<b>{text}</b>", styles['Normal']))
|
| 291 |
-
elif line.startswith('- ') or line.startswith('* '):
|
| 292 |
-
story.append(Paragraph(f"โข {line[2:]}", styles['Normal']))
|
| 293 |
-
elif line == '---' or line.startswith('==='):
|
| 294 |
-
story.append(Spacer(1, 0.3*inch))
|
| 295 |
-
story.append(Paragraph("_" * 70, styles['Normal']))
|
| 296 |
-
story.append(Spacer(1, 0.3*inch))
|
| 297 |
else:
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
return filename
|
| 305 |
-
|
| 306 |
-
# --- CLASE AIAnalyzer (MODIFICADA PARA ACEPTAR chunk_column) ---
|
| 307 |
-
class AIAnalyzer:
|
| 308 |
-
"""Clase para anรกlisis con IA que implementa una estrategia 'chunk-and-stitch'."""
|
| 309 |
-
def __init__(self, client):
|
| 310 |
-
self.client = client
|
| 311 |
-
self.token_usage = {}
|
| 312 |
-
self.reset_token_usage()
|
| 313 |
-
|
| 314 |
-
def reset_token_usage(self):
|
| 315 |
-
self.token_usage = {'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0, 'estimated_cost': 0.0}
|
| 316 |
-
|
| 317 |
-
def _update_token_usage(self, model_name: str, usage):
|
| 318 |
-
if not usage: return
|
| 319 |
-
self.token_usage['input_tokens'] += usage.prompt_tokens
|
| 320 |
-
self.token_usage['output_tokens'] += usage.completion_tokens
|
| 321 |
-
self.token_usage['total_tokens'] += usage.total_tokens
|
| 322 |
-
model_info = QWEN_MODELS.get(model_name, {})
|
| 323 |
-
input_cost = model_info.get('input_cost', 0.0)
|
| 324 |
-
output_cost = model_info.get('output_cost', 0.0)
|
| 325 |
-
self.token_usage['estimated_cost'] += (usage.prompt_tokens * input_cost) + (usage.completion_tokens * output_cost)
|
| 326 |
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
context_limit = model_info['max_context_tokens']
|
| 330 |
-
PROMPT_SAFETY_MARGIN = 8192
|
| 331 |
-
max_allowable_output = context_limit - PROMPT_SAFETY_MARGIN
|
| 332 |
-
return max(100, min(user_requested_tokens, max_allowable_output))
|
| 333 |
-
####
|
| 334 |
-
def _analyze_single_experiment(self, experiment_df: pd.DataFrame, experiment_id: str, qwen_model: str, lang_prefix: str, max_output_tokens: int) -> Optional[Dict]:
|
| 335 |
"""
|
| 336 |
-
|
| 337 |
-
|
|
|
|
|
|
|
|
|
|
| 338 |
"""
|
| 339 |
-
|
| 340 |
-
# El prompt es la parte mรกs importante. Estรก diseรฑado para ser muy especรญfico y dar un ejemplo claro.
|
| 341 |
-
prompt = f"""
|
| 342 |
-
{lang_prefix}
|
| 343 |
-
You are an expert biotechnological data analyst. Your task is to analyze the provided model fitting results for a single experiment identified as: '{experiment_id}'.
|
| 344 |
-
The data contains different mathematical models that were fitted to experimental data for variables like Biomass, Substrate, or Product.
|
| 345 |
-
|
| 346 |
-
DATA FOR THIS SPECIFIC EXPERIMENT ('{experiment_id}'):
|
| 347 |
-
```
|
| 348 |
-
{experiment_df.to_string()}
|
| 349 |
-
```
|
| 350 |
-
|
| 351 |
-
YOUR INSTRUCTIONS:
|
| 352 |
-
1. **Identify Best Models**: For EACH variable type present in the data (e.g., 'Biomass', 'Substrate'), determine the single best-performing model. The best model is the one with the highest Rยฒ value. If Rยฒ values are equal, use the lowest RMSE as a tie-breaker.
|
| 353 |
-
2. **Extract Key Information**: For each of these best models, you must extract:
|
| 354 |
-
- The model's name.
|
| 355 |
-
- The specific metrics (Rยฒ, RMSE, AIC, etc.) as key-value pairs.
|
| 356 |
-
- All kinetic parameters and their fitted values (e.g., mu_max, Ks) as key-value pairs.
|
| 357 |
-
3. **Summarize All Tested Models**: Create a simple list of the names of ALL models that were tested in this experiment, regardless of their performance.
|
| 358 |
-
4. **Provide Biological Interpretation**: Write a brief, concise interpretation (2-3 sentences) of what the results for this specific experiment imply. For example, "The selection of the Monod model for biomass with a ยต_max of 0.45 suggests rapid growth under these conditions, while the high Rยฒ indicates a strong fit."
|
| 359 |
-
|
| 360 |
-
**CRITICAL OUTPUT FORMAT**: You MUST respond ONLY with a single, valid JSON object. Do not add any explanatory text, markdown formatting, or anything else before or after the JSON structure.
|
| 361 |
-
|
| 362 |
-
Follow this EXACT JSON structure:
|
| 363 |
-
{{
|
| 364 |
-
"experiment_id": "{experiment_id}",
|
| 365 |
-
"best_models_by_variable": [
|
| 366 |
-
{{
|
| 367 |
-
"variable_type": "Biomass",
|
| 368 |
-
"model_name": "Name of the best model for Biomass",
|
| 369 |
-
"metrics": {{
|
| 370 |
-
"R2": 0.99,
|
| 371 |
-
"RMSE": 0.01,
|
| 372 |
-
"AIC": -50.2
|
| 373 |
-
}},
|
| 374 |
-
"parameters": {{
|
| 375 |
-
"mu_max": 0.5,
|
| 376 |
-
"Ks": 10.2
|
| 377 |
-
}}
|
| 378 |
-
}},
|
| 379 |
-
{{
|
| 380 |
-
"variable_type": "Substrate",
|
| 381 |
-
"model_name": "Name of the best model for Substrate",
|
| 382 |
-
"metrics": {{
|
| 383 |
-
"R2": 0.98,
|
| 384 |
-
"RMSE": 0.05
|
| 385 |
-
}},
|
| 386 |
-
"parameters": {{
|
| 387 |
-
"k_consumption": 1.5
|
| 388 |
-
}}
|
| 389 |
-
}}
|
| 390 |
-
],
|
| 391 |
-
"all_tested_models": ["Monod", "Logistic", "Gompertz", "First_Order"],
|
| 392 |
-
"interpretation": "A brief, data-driven interpretation of the kinetic behavior observed in this specific experiment."
|
| 393 |
-
}}
|
| 394 |
-
"""
|
| 395 |
-
|
| 396 |
try:
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
{"role": "user", "content": prompt}
|
| 409 |
-
]
|
| 410 |
-
)
|
| 411 |
-
|
| 412 |
-
# Actualizar el contador de tokens y el costo estimado.
|
| 413 |
-
self._update_token_usage(qwen_model, response.usage)
|
| 414 |
-
|
| 415 |
-
# Extraer el contenido de la respuesta.
|
| 416 |
-
content = response.choices[0].message.content
|
| 417 |
-
|
| 418 |
-
# Parsear la cadena de texto JSON a un diccionario de Python.
|
| 419 |
-
# Este paso es propenso a errores si el LLM no sigue las instrucciones perfectamente.
|
| 420 |
-
parsed_json = json.loads(content)
|
| 421 |
-
return parsed_json
|
| 422 |
-
|
| 423 |
-
except json.JSONDecodeError as e:
|
| 424 |
-
# Capturar errores si la respuesta del modelo no es un JSON vรกlido.
|
| 425 |
-
print(f"CRITICAL ERROR: Failed to decode JSON for experiment '{experiment_id}'.")
|
| 426 |
-
print(f"JSONDecodeError: {e}")
|
| 427 |
-
print(f"LLM Raw Output that caused the error:\n---\n{content}\n---")
|
| 428 |
-
return None # Devolver None para indicar que el anรกlisis de este chunk fallรณ.
|
| 429 |
-
|
| 430 |
except Exception as e:
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
return None # Devolver None para que el proceso principal pueda saltar este chunk.
|
| 434 |
|
| 435 |
-
|
| 436 |
-
def
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
"""
|
| 441 |
-
|
| 442 |
-
# 1. Preparar los datos de entrada para el modelo.
|
| 443 |
-
# Convertimos la lista de diccionarios de Python a una cadena de texto JSON bien formateada.
|
| 444 |
-
# Esto es lo que el LLM verรก como su "base de conocimiento".
|
| 445 |
-
analyses_summary = json.dumps(individual_analyses, indent=2)
|
| 446 |
-
|
| 447 |
-
# 2. Construir el prompt de sรญntesis.
|
| 448 |
-
# Este prompt es mรกs conceptual que el anterior. Le pide al modelo que actรบe como un cientรญfico senior.
|
| 449 |
-
|
| 450 |
-
# Secciรณn para las especificaciones adicionales del usuario.
|
| 451 |
-
user_specs_section = f"""
|
| 452 |
-
## User's Additional Specifications
|
| 453 |
-
Please pay special attention to the following user-provided requirements during your analysis:
|
| 454 |
-
- {additional_specs}
|
| 455 |
-
""" if additional_specs else ""
|
| 456 |
-
|
| 457 |
-
# Instrucciรณn de nivel de detalle basada en la selecciรณn del usuario.
|
| 458 |
-
detail_instruction = (
|
| 459 |
-
"Your report must be highly detailed and exhaustive. Include multiple tables, in-depth parameter comparisons, and nuanced biological interpretations."
|
| 460 |
-
if detail_level == "detailed" else
|
| 461 |
-
"Your report should be a high-level summary. Focus on the main conclusions and key takeaways, using concise tables and bullet points."
|
| 462 |
-
)
|
| 463 |
-
|
| 464 |
-
prompt = f"""
|
| 465 |
-
{lang_prefix}
|
| 466 |
-
You are a Principal Scientist tasked with creating a final, consolidated report from a series of individual experimental analyses.
|
| 467 |
-
You have been provided with a JSON array, where each object represents the detailed analysis of one specific experiment.
|
| 468 |
-
|
| 469 |
-
{user_specs_section}
|
| 470 |
-
|
| 471 |
-
YOUR PRIMARY OBJECTIVE:
|
| 472 |
-
Synthesize all the provided information into a single, cohesive, and comparative analysis report. The report must be written in rich Markdown format.
|
| 473 |
-
{detail_instruction}
|
| 474 |
-
|
| 475 |
-
Your final report MUST contain the following sections:
|
| 476 |
-
|
| 477 |
-
### 1. Executive Summary & Experimental Inventory
|
| 478 |
-
- Start with a brief paragraph summarizing the scope of the experiments analyzed.
|
| 479 |
-
- Create a Markdown table that serves as an inventory of all experiments. The table should list each `experiment_id`, the `variable_type` (e.g., Biomass), and the `model_name` of the best-performing model for that variable.
|
| 480 |
-
|
| 481 |
-
### 2. In-Depth Comparative Analysis
|
| 482 |
-
- **Model Performance Matrix:** This is the most critical part. Create a Markdown table that compares the performance of all major models across all experiments. Use Rยฒ as the primary metric. Rows should be model names, and columns should be experiment IDs. This allows for a direct visual comparison of which models are robust across different conditions.
|
| 483 |
-
- **Parameter Trend Analysis:** Analyze how key kinetic parameters (e.g., `mu_max`, `Ks`, etc.) change across the different experimental conditions. Discuss any observable trends, correlations, or significant differences. For example: "We observed that `mu_max` consistently increased as temperature rose from Exp_A to Exp_C, suggesting a direct correlation in this range."
|
| 484 |
-
- **Model Selection Justification:** Discuss why certain models performed better under specific conditions, referencing the biological interpretations from the input data.
|
| 485 |
-
|
| 486 |
-
### 3. Overall Recommendations & Conclusions
|
| 487 |
-
- **Globally Recommended Models:** Based on the entire dataset, declare the best overall model for each primary variable type (Biomass, Substrate, etc.). Justify your choice based on consistent high performance and robustness across experiments.
|
| 488 |
-
- **Condition-Specific Guidelines:** Provide actionable recommendations. For example, "For experiments conducted under high pH conditions (similar to 'Exp_C'), the 'Gompertz' model is strongly recommended due to its superior fit."
|
| 489 |
-
- **Suggestions for Future Research:** Briefly suggest a few next steps or potential experiments to validate the findings or explore new hypotheses.
|
| 490 |
-
|
| 491 |
-
---
|
| 492 |
-
**INPUT DATA: JSON ARRAY OF INDIVIDUAL ANALYSES**
|
| 493 |
-
```json
|
| 494 |
-
{analyses_summary}
|
| 495 |
-
```
|
| 496 |
-
---
|
| 497 |
-
|
| 498 |
-
Now, generate the complete, final Markdown report based on these instructions.
|
| 499 |
-
"""
|
| 500 |
-
|
| 501 |
try:
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
return response.choices[0].message.content
|
| 521 |
-
|
| 522 |
except Exception as e:
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
print(error_message)
|
| 526 |
-
return error_message
|
| 527 |
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
""
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
return
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
if
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
)
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
yield "Generating implementation code..."
|
| 569 |
-
code_result = "# Code generation is a placeholder in this version."
|
| 570 |
-
yield "โ
Code generated."
|
| 571 |
-
|
| 572 |
-
# Al final, produce el diccionario de resultados completo.
|
| 573 |
-
yield {
|
| 574 |
-
"analisis_completo": final_analysis,
|
| 575 |
-
"codigo_implementacion": code_result,
|
| 576 |
-
}
|
| 577 |
|
| 578 |
-
#
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
"""
|
| 583 |
-
Procesa archivos subidos y orquesta el anรกlisis, actualizando la UI con 'yield'.
|
| 584 |
-
"""
|
| 585 |
-
if not files:
|
| 586 |
-
yield "Please upload a file first.", "", "", ""
|
| 587 |
-
return
|
| 588 |
-
if not chunk_column:
|
| 589 |
-
yield "Please upload a file and select a column for grouping before analyzing.", "", "", ""
|
| 590 |
-
return
|
| 591 |
|
| 592 |
-
# Inicializa las variables que se irรกn actualizando.
|
| 593 |
-
thinking_log = ["### ๐ Starting Analysis\n"]
|
| 594 |
-
analysis_result, code_result, token_report = "", "", ""
|
| 595 |
-
|
| 596 |
-
# Funciรณn auxiliar para actualizar el log y hacer yield a la UI
|
| 597 |
-
def update_log_and_yield(message):
|
| 598 |
-
nonlocal thinking_log
|
| 599 |
-
thinking_log.append(f"- {datetime.now().strftime('%H:%M:%S')}: {message}\n")
|
| 600 |
-
return "\n".join(thinking_log), gr.update(), gr.update(), gr.update()
|
| 601 |
-
|
| 602 |
-
yield update_log_and_yield("Processing uploaded file...")
|
| 603 |
-
|
| 604 |
-
file = files[0]
|
| 605 |
try:
|
| 606 |
-
|
| 607 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 608 |
except Exception as e:
|
| 609 |
-
|
| 610 |
-
return
|
| 611 |
-
|
| 612 |
-
# Inicia el analizador
|
| 613 |
-
analyzer = AIAnalyzer(client)
|
| 614 |
-
|
| 615 |
-
# Itera sobre el generador `analyze_data`
|
| 616 |
-
# Cada 'item' serรก una actualizaciรณn de estado (string) o el resultado final (dict)
|
| 617 |
-
for item in analyzer.analyze_data(df, chunk_column, qwen_model, detail_level, language, additional_specs, max_output_tokens):
|
| 618 |
-
if isinstance(item, str):
|
| 619 |
-
# Es una actualizaciรณn de estado, actualizamos el log de "thinking"
|
| 620 |
-
yield update_log_and_yield(item)
|
| 621 |
-
elif isinstance(item, dict) and "error" in item:
|
| 622 |
-
# Es un diccionario de error, terminamos el proceso.
|
| 623 |
-
yield update_log_and_yield(f"ANALYSIS FAILED: {item['error']}")
|
| 624 |
-
return
|
| 625 |
-
elif isinstance(item, dict):
|
| 626 |
-
# Es el diccionario de resultados final.
|
| 627 |
-
analysis_result = item["analisis_completo"]
|
| 628 |
-
code_result = item["codigo_implementacion"]
|
| 629 |
-
|
| 630 |
-
# Almacenar en el estado global para la exportaciรณn
|
| 631 |
-
app_state.current_analysis = analysis_result
|
| 632 |
-
app_state.current_code = code_result
|
| 633 |
-
|
| 634 |
-
# Formatear el reporte de tokens final
|
| 635 |
-
t = TRANSLATIONS[language]
|
| 636 |
-
token_info = analyzer.token_usage
|
| 637 |
-
token_report = f"""
|
| 638 |
-
### {t['token_info']}
|
| 639 |
-
- **{t['input_token_count']}:** {token_info['input_tokens']}
|
| 640 |
-
- **{t['output_token_count']}:** {token_info['output_tokens']}
|
| 641 |
-
- **{t['total_token_count']}:** {token_info['total_tokens']}
|
| 642 |
-
- **{t['token_cost']}:** ${token_info['estimated_cost']:.6f}
|
| 643 |
-
"""
|
| 644 |
-
|
| 645 |
-
# Hacemos un รบltimo yield con todos los resultados finales.
|
| 646 |
-
yield "\n".join(thinking_log), analysis_result, code_result, token_report
|
| 647 |
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
def __init__(self):
|
| 651 |
-
self.current_analysis = ""
|
| 652 |
-
self.current_code = ""
|
| 653 |
-
self.current_language = "en"
|
| 654 |
|
| 655 |
-
|
| 656 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 657 |
|
| 658 |
-
|
| 659 |
-
""
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
# 1. Verificar si hay contenido para exportar en el estado global.
|
| 664 |
-
if not app_state.current_analysis:
|
| 665 |
-
error_msg = TRANSLATIONS[language].get('error_no_files', 'No analysis available to export.')
|
| 666 |
-
# Devuelve el mensaje de error y None para la ruta del archivo.
|
| 667 |
-
return error_msg, None
|
| 668 |
-
|
| 669 |
-
# 2. Generar un nombre de archivo รบnico con marca de tiempo.
|
| 670 |
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 671 |
|
| 672 |
-
# 3. Crear un directorio temporal para almacenar el reporte.
|
| 673 |
-
# Esto es una buena prรกctica para no llenar el directorio raรญz de la aplicaciรณn.
|
| 674 |
try:
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
|
| 679 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 680 |
try:
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
content=app_state.current_analysis,
|
| 689 |
-
filename=filename,
|
| 690 |
-
language=language
|
| 691 |
-
)
|
| 692 |
-
|
| 693 |
-
elif export_format == "PDF":
|
| 694 |
-
# Construye la ruta para el archivo .pdf
|
| 695 |
-
filename = os.path.join(temp_dir, f"biotech_analysis_report_{timestamp}.pdf")
|
| 696 |
-
|
| 697 |
-
# Llama al mรฉtodo estรกtico de la clase ReportExporter para crear el PDF.
|
| 698 |
-
# Se asume que ReportExporter estรก definido en otra parte del cรณdigo.
|
| 699 |
-
ReportExporter.export_to_pdf(
|
| 700 |
-
content=app_state.current_analysis,
|
| 701 |
-
filename=filename,
|
| 702 |
-
language=language
|
| 703 |
-
)
|
| 704 |
else:
|
| 705 |
-
|
| 706 |
-
return f"Unsupported export format: {export_format}", None
|
| 707 |
-
|
| 708 |
-
# 5. Si la creaciรณn del archivo fue exitosa, devolver un mensaje de รฉxito y la ruta al archivo.
|
| 709 |
-
success_msg_template = TRANSLATIONS[language].get('report_exported', 'Report exported successfully as')
|
| 710 |
-
success_msg = f"{success_msg_template} {os.path.basename(filename)}"
|
| 711 |
-
|
| 712 |
-
return success_msg, filename
|
| 713 |
-
|
| 714 |
except Exception as e:
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
error_message = f"Error during report export to {export_format}: {str(e)}"
|
| 718 |
-
print(f"EXPORT ERROR: {error_message}") # Loguear el error en la consola para depuraciรณn.
|
| 719 |
-
return error_message, None
|
| 720 |
|
| 721 |
-
|
| 722 |
-
def create_interface():
|
| 723 |
-
global app
|
| 724 |
|
| 725 |
-
|
| 726 |
-
app_state.current_language = language
|
| 727 |
-
t = TRANSLATIONS[language]
|
| 728 |
-
return [
|
| 729 |
-
gr.update(value=f"# {t['title']}"), gr.update(value=t['subtitle']),
|
| 730 |
-
gr.update(label=t['upload_files']), gr.update(label=t['chunk_column_label'], info=t['chunk_column_info']),
|
| 731 |
-
gr.update(label=t['select_model']), gr.update(label=t['select_language']), gr.update(label=t['select_theme']),
|
| 732 |
-
gr.update(label=t['detail_level']), gr.update(choices=[(t['detailed'], "detailed"), (t['summarized'], "summarized")]),
|
| 733 |
-
gr.update(label=t['additional_specs'], placeholder=t['additional_specs_placeholder']),
|
| 734 |
-
gr.update(label=t['output_tokens_per_chunk']), gr.update(value=t['analyze_button']),
|
| 735 |
-
gr.update(label=t['export_format']), gr.update(value=t['export_button']),
|
| 736 |
-
gr.update(label=t['thinking_process']), gr.update(label=t['analysis_report']),
|
| 737 |
-
gr.update(label=t['code_output']), gr.update(label=t['token_usage']), gr.update(label=t['data_format'])
|
| 738 |
-
]
|
| 739 |
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
files_input = gr.File(label=TRANSLATIONS['en']['upload_files'], file_count="multiple", type="filepath")
|
| 752 |
-
|
| 753 |
-
# NUEVO COMPONENTE: Selector de columna de agrupaciรณn
|
| 754 |
-
chunk_column_selector = gr.Dropdown(
|
| 755 |
-
label=TRANSLATIONS['en']['chunk_column_label'],
|
| 756 |
-
info=TRANSLATIONS['en']['chunk_column_info'],
|
| 757 |
-
interactive=False # Se activa al subir archivo
|
| 758 |
-
)
|
| 759 |
-
|
| 760 |
-
model_selector = gr.Textbox(label=TRANSLATIONS['en']['select_model'], value="deepseek-ai/DeepSeek-V3-0324")
|
| 761 |
-
detail_level_radio = gr.Radio(choices=[("Detailed", "detailed"), ("Summarized", "summarized")], value="detailed", label=TRANSLATIONS['en']['detail_level'])
|
| 762 |
-
additional_specs = gr.Textbox(label=TRANSLATIONS['en']['additional_specs'], placeholder=TRANSLATIONS['en']['additional_specs_placeholder'], lines=3)
|
| 763 |
-
output_tokens_slider = gr.Slider(minimum=1000, maximum=32000, value=4000, step=500, label=TRANSLATIONS['en']['output_tokens_per_chunk'])
|
| 764 |
-
|
| 765 |
-
analyze_btn = gr.Button(TRANSLATIONS['en']['analyze_button'], variant="primary", interactive=False) # Desactivado por defecto
|
| 766 |
-
|
| 767 |
-
gr.Markdown("---")
|
| 768 |
-
export_format_radio = gr.Radio(choices=["DOCX", "PDF"], value="PDF", label=TRANSLATIONS['en']['export_format'])
|
| 769 |
-
export_btn = gr.Button(TRANSLATIONS['en']['export_button'])
|
| 770 |
-
export_status = gr.Textbox(label="Export Status", visible=False)
|
| 771 |
-
export_file = gr.File(label="Download Report", visible=False)
|
| 772 |
-
|
| 773 |
-
with gr.Column(scale=2):
|
| 774 |
-
thinking_output = gr.Markdown(label=TRANSLATIONS['en']['thinking_process'])
|
| 775 |
-
analysis_output = gr.Markdown(label=TRANSLATIONS['en']['analysis_report'])
|
| 776 |
-
code_output = gr.Code(label=TRANSLATIONS['en']['code_output'], language="python")
|
| 777 |
-
token_usage_output = gr.Markdown(label=TRANSLATIONS['en']['token_usage'])
|
| 778 |
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
gr.Markdown("""...""") # Contenido del acordeรณn sin cambios
|
| 782 |
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
# NUEVO EVENTO: Se activa al subir un archivo para poblar el selector de columna
|
| 786 |
-
def update_chunk_column_selector(files):
|
| 787 |
-
if not files:
|
| 788 |
-
return gr.update(choices=[], value=None, interactive=False), gr.update(interactive=False)
|
| 789 |
-
|
| 790 |
-
try:
|
| 791 |
-
file_path = files[0].name
|
| 792 |
-
df = pd.read_csv(file_path, nrows=0) if file_path.endswith('.csv') else pd.read_excel(file_path, nrows=0)
|
| 793 |
-
columns = df.columns.tolist()
|
| 794 |
-
|
| 795 |
-
# Intenta encontrar una columna por defecto
|
| 796 |
-
default_candidates = ['Experiment', 'Experimento', 'Condition', 'Run', 'Batch', 'ID']
|
| 797 |
-
default_selection = next((col for col in default_candidates if col in columns), None)
|
| 798 |
-
|
| 799 |
-
return gr.update(choices=columns, value=default_selection, interactive=True), gr.update(interactive=True)
|
| 800 |
-
except Exception as e:
|
| 801 |
-
gr.Warning(f"Could not read columns from file: {e}")
|
| 802 |
-
return gr.update(choices=[], value=None, interactive=False), gr.update(interactive=False)
|
| 803 |
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 821 |
)
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 832 |
|
| 833 |
if __name__ == "__main__":
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
print("
|
| 837 |
-
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
from gradio_client import Client, handle_file
|
| 3 |
import pandas as pd
|
|
|
|
|
|
|
|
|
|
| 4 |
import json
|
|
|
|
| 5 |
import tempfile
|
| 6 |
+
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
from datetime import datetime
|
| 8 |
+
import plotly.graph_objects as go
|
| 9 |
+
import plotly.express as px
|
| 10 |
+
import numpy as np
|
| 11 |
+
from smolagents import CodeAgent, tool, InferenceClientModel
|
| 12 |
+
import logging
|
| 13 |
+
|
| 14 |
+
# Configuraciรณn de logging
|
| 15 |
+
logging.basicConfig(level=logging.INFO)
|
| 16 |
+
logger = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
# --- INICIO DE CONFIGURACIรN DE CLIENTES ---
|
| 19 |
+
# Se inicializan ambos clientes para el pipeline de dos etapas
|
| 20 |
+
try:
|
| 21 |
+
biotech_client = Client("C2MV/BiotechU4")
|
| 22 |
+
logger.info("โ
Cliente BiotechU4 inicializado correctamente.")
|
| 23 |
+
except Exception as e:
|
| 24 |
+
logger.error(f"โ No se pudo inicializar el cliente de BiotechU4: {e}")
|
| 25 |
+
biotech_client = None
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
analysis_client = Client("C2MV/Project-HF-2025-2")
|
| 29 |
+
logger.info("โ
Cliente Project-HF-2025-2 inicializado correctamente.")
|
| 30 |
+
except Exception as e:
|
| 31 |
+
logger.error(f"โ No se pudo inicializar el cliente de Project-HF-2025-2: {e}")
|
| 32 |
+
analysis_client = None
|
| 33 |
+
|
| 34 |
+
# Configuraciรณn del motor de Hugging Face (opcional)
|
| 35 |
+
try:
|
| 36 |
+
hf_engine = InferenceClientModel(model_id="mistralai/Mistral-7B-Instruct-v0.2")
|
| 37 |
+
except Exception:
|
| 38 |
+
logger.warning("No se pudo inicializar el modelo de HF. Los agentes usarรกn lรณgica simple.")
|
| 39 |
+
hf_engine = None
|
| 40 |
+
|
| 41 |
+
# ============================================================================
|
| 42 |
+
# ๐ค SISTEMA DE AGENTES (Conservado y adaptado)
|
| 43 |
+
# ============================================================================
|
| 44 |
+
class BiotechAgentTools:
|
| 45 |
+
@tool
|
| 46 |
+
def analyze_data_characteristics(data_info: str) -> dict:
|
| 47 |
+
"""
|
| 48 |
+
Analiza las caracterรญsticas de los datos biotecnolรณgicos subidos.
|
| 49 |
+
Args:
|
| 50 |
+
data_info (str): Informaciรณn sobre el archivo de datos incluyendo nombre, tipo y contenido
|
| 51 |
+
Returns:
|
| 52 |
+
dict: Diccionario con tipo de experimento, modelos recomendados, parรกmetros sugeridos y calidad de datos
|
| 53 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
try:
|
| 55 |
+
characteristics = {"experiment_type": "unknown", "recommended_models": [], "suggested_params": {}, "data_quality": "good"}
|
| 56 |
+
data_lower = data_info.lower()
|
| 57 |
+
models_from_docs = ['logistic', 'gompertz', 'moser', 'baranyi', 'monod', 'contois', 'andrews', 'tessier', 'richards', 'stannard', 'huang']
|
| 58 |
+
growth_models = [m for m in ['logistic', 'gompertz', 'baranyi', 'richards'] if m in models_from_docs]
|
| 59 |
+
fermentation_models = [m for m in ['monod', 'contois', 'andrews', 'moser'] if m in models_from_docs]
|
| 60 |
+
if "biomass" in data_lower or "growth" in data_lower:
|
| 61 |
+
characteristics.update({"experiment_type": "growth_kinetics", "recommended_models": growth_models, "suggested_params": {"component": "biomass", "use_de": True, "maxfev": 75000}})
|
| 62 |
+
elif "ferment" in data_lower or "substrate" in data_lower:
|
| 63 |
+
characteristics.update({"experiment_type": "fermentation", "recommended_models": fermentation_models,"suggested_params": {"component": "all", "use_de": False, "maxfev": 50000}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
else:
|
| 65 |
+
characteristics.update({"experiment_type": "general_biotech", "recommended_models": growth_models, "suggested_params": {"component": "all", "use_de": False, "maxfev": 50000}})
|
| 66 |
+
logger.info(f"Anรกlisis completado: {characteristics['experiment_type']}")
|
| 67 |
+
return characteristics
|
| 68 |
+
except Exception as e:
|
| 69 |
+
logger.error(f"Error en anรกlisis de datos: {str(e)}")
|
| 70 |
+
return {"experiment_type": "error", "recommended_models": ['logistic', 'gompertz'], "suggested_params": {"component": "all", "use_de": False, "maxfev": 50000}, "data_quality": "unknown"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
+
@tool
|
| 73 |
+
def prepare_ia_context(data_summary: str) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
"""
|
| 75 |
+
Prepara el contexto especรญfico para el anรกlisis de IA.
|
| 76 |
+
Args:
|
| 77 |
+
data_summary (str): Resumen de los datos analizados incluyendo tipo de experimento y resultados
|
| 78 |
+
Returns:
|
| 79 |
+
str: Contexto enriquecido y estructurado para el anรกlisis de IA
|
| 80 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
try:
|
| 82 |
+
enhanced_context = f"""CONTEXTO BIOTECNOLรGICO ESPECรFICO:
|
| 83 |
+
Resultados del modelado: {data_summary}
|
| 84 |
+
Por favor, enfรณcate en:
|
| 85 |
+
1. Interpretaciรณn biolรณgica de los parรกmetros ajustados (ej. ฮผmax, Ks, Yx/s).
|
| 86 |
+
2. Comparaciรณn de la bondad de ajuste entre modelos (Rยฒ, RMSE).
|
| 87 |
+
3. Implicaciones prรกcticas para el proceso biotecnolรณgico.
|
| 88 |
+
4. Recomendaciones para la optimizaciรณn del proceso basadas en los modelos.
|
| 89 |
+
5. Identificaciรณn de posibles limitaciones o artefactos en los datos o modelos.
|
| 90 |
+
Incluye un anรกlisis estadรญstico riguroso y recomendaciones prรกcticas y accionables."""
|
| 91 |
+
logger.info("Contexto preparado para la IA")
|
| 92 |
+
return enhanced_context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
except Exception as e:
|
| 94 |
+
logger.error(f"Error preparando contexto: {str(e)}")
|
| 95 |
+
return data_summary
|
|
|
|
| 96 |
|
| 97 |
+
class CoordinatorAgent:
|
| 98 |
+
def __init__(self):
|
| 99 |
+
self.agent = CodeAgent(tools=[BiotechAgentTools.analyze_data_characteristics, BiotechAgentTools.prepare_ia_context], model=hf_engine) if hf_engine else None
|
| 100 |
+
self.tools = BiotechAgentTools()
|
| 101 |
+
def analyze_and_optimize(self, file_info: str, current_config: dict) -> dict:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
try:
|
| 103 |
+
logger.info("๐ค Agente Coordinador iniciando anรกlisis...")
|
| 104 |
+
characteristics = self.tools.analyze_data_characteristics(file_info)
|
| 105 |
+
optimized_config = current_config.copy()
|
| 106 |
+
if characteristics["experiment_type"] != "error":
|
| 107 |
+
# Optimiza parรกmetros para BiotechU4
|
| 108 |
+
optimized_config.update({
|
| 109 |
+
"models": characteristics["recommended_models"],
|
| 110 |
+
"component": characteristics["suggested_params"]["component"],
|
| 111 |
+
"use_de": characteristics["suggested_params"]["use_de"],
|
| 112 |
+
"maxfev": characteristics["suggested_params"]["maxfev"]
|
| 113 |
+
})
|
| 114 |
+
# Optimiza 'additional_specs' para Project-HF-2025-2
|
| 115 |
+
if characteristics["experiment_type"] == "growth_kinetics":
|
| 116 |
+
optimized_config["additional_specs"] = self.tools.prepare_ia_context("Anรกlisis de cinรฉtica de crecimiento.")
|
| 117 |
+
elif characteristics["experiment_type"] == "fermentation":
|
| 118 |
+
optimized_config["additional_specs"] = self.tools.prepare_ia_context("Anรกlisis de datos de fermentaciรณn.")
|
| 119 |
+
logger.info(f"โ
Configuraciรณn optimizada para: {characteristics['experiment_type']}")
|
| 120 |
+
return {"config": optimized_config, "analysis": characteristics, "recommendations": f"Configuraciรณn optimizada para {characteristics['experiment_type']}"}
|
|
|
|
|
|
|
| 121 |
except Exception as e:
|
| 122 |
+
logger.error(f"โ Error en Agente Coordinador: {str(e)}")
|
| 123 |
+
return {"config": current_config, "analysis": {"experiment_type": "error"}, "recommendations": f"Error en optimizaciรณn: {str(e)}"}
|
|
|
|
|
|
|
| 124 |
|
| 125 |
+
class BiotechAgentSystem:
|
| 126 |
+
def __init__(self):
|
| 127 |
+
self.coordinator = CoordinatorAgent()
|
| 128 |
+
logger.info("๐ Sistema de agentes inicializado")
|
| 129 |
+
def process_with_agents(self, file_info: str, user_config: dict) -> dict:
|
| 130 |
+
try:
|
| 131 |
+
coordination_result = self.coordinator.analyze_and_optimize(file_info, user_config)
|
| 132 |
+
return {"success": True, **coordination_result}
|
| 133 |
+
except Exception as e:
|
| 134 |
+
logger.error(f"โ Error en sistema de agentes: {str(e)}")
|
| 135 |
+
return {"success": False, "config": user_config, "analysis": {"experiment_type": "error"}, "recommendations": f"Error: {str(e)}"}
|
| 136 |
+
|
| 137 |
+
# ============================================================================
|
| 138 |
+
# โ๏ธ FUNCIONES DEL PIPELINE
|
| 139 |
+
# ============================================================================
|
| 140 |
+
agent_system = BiotechAgentSystem()
|
| 141 |
+
|
| 142 |
+
def create_dummy_plot():
|
| 143 |
+
fig = go.Figure(go.Scatter(x=[], y=[]))
|
| 144 |
+
fig.update_layout(title="Esperando resultados...", template="plotly_white", height=500, annotations=[dict(text="Sube un archivo y ejecuta el pipeline para ver los resultados", showarrow=False)])
|
| 145 |
+
return fig
|
| 146 |
+
|
| 147 |
+
def parse_plot_data(plot_info):
|
| 148 |
+
"""Parsea la informaciรณn de la grรกfica recibida de la API BiotechU4."""
|
| 149 |
+
if not plot_info:
|
| 150 |
+
return create_dummy_plot()
|
| 151 |
+
try:
|
| 152 |
+
if isinstance(plot_info, dict) and 'plot' in plot_info:
|
| 153 |
+
plot_json_string = plot_info['plot']
|
| 154 |
+
return go.Figure(json.loads(plot_json_string))
|
| 155 |
+
if isinstance(plot_info, str): return go.Figure(json.loads(plot_info))
|
| 156 |
+
if isinstance(plot_info, dict): return go.Figure(plot_info)
|
| 157 |
+
except Exception as e:
|
| 158 |
+
logger.error(f"Error parsing plot: {e}")
|
| 159 |
+
return create_dummy_plot()
|
| 160 |
+
|
| 161 |
+
# --- FUNCIรN PRINCIPAL DEL PIPELINE COMBINADO ---
|
| 162 |
+
def process_complete_pipeline_with_agents(
|
| 163 |
+
# Entradas de la UI
|
| 164 |
+
file, models, component, use_de, maxfev, exp_names,
|
| 165 |
+
chunk_column, ia_model, detail_level, language, additional_specs, max_output_tokens,
|
| 166 |
+
export_format, use_personal_key, personal_api_key,
|
| 167 |
+
# Progreso
|
| 168 |
+
progress=gr.Progress()):
|
| 169 |
+
|
| 170 |
+
progress(0, desc="๐ Iniciando Pipeline...")
|
| 171 |
+
if not file:
|
| 172 |
+
return create_dummy_plot(), None, None, None, None, "โ Por favor, sube un archivo."
|
| 173 |
+
if not models:
|
| 174 |
+
return create_dummy_plot(), None, None, None, None, "โ Por favor, selecciona al menos un modelo para el anรกlisis."
|
| 175 |
+
|
| 176 |
+
progress_updates = []
|
| 177 |
+
|
| 178 |
+
# 1. Sistema de Agentes para optimizar la configuraciรณn
|
| 179 |
+
progress(0.1, desc="๐ค Activando sistema de agentes...")
|
| 180 |
+
file_info = f"Archivo: {os.path.basename(file.name)}, Modelos: {models}"
|
| 181 |
+
user_config = {
|
| 182 |
+
"models": models, "component": component, "use_de": use_de, "maxfev": maxfev,
|
| 183 |
+
"additional_specs": additional_specs
|
| 184 |
+
}
|
| 185 |
+
agent_result = agent_system.process_with_agents(file_info, user_config)
|
| 186 |
+
|
| 187 |
+
if agent_result["success"]:
|
| 188 |
+
optimized_config = agent_result["config"]
|
| 189 |
+
progress_updates.append(f"โ
Agentes detectaron: {agent_result['analysis']['experiment_type']}")
|
| 190 |
+
progress_updates.append(f"๐ฏ {agent_result['recommendations']}")
|
| 191 |
+
# Sobrescribir parรกmetros con las optimizaciones del agente
|
| 192 |
+
models, component, use_de, maxfev, additional_specs = (
|
| 193 |
+
optimized_config.get("models", models),
|
| 194 |
+
optimized_config.get("component", component),
|
| 195 |
+
optimized_config.get("use_de", use_de),
|
| 196 |
+
optimized_config.get("maxfev", maxfev),
|
| 197 |
+
optimized_config.get("additional_specs", additional_specs)
|
| 198 |
)
|
| 199 |
+
else:
|
| 200 |
+
progress_updates.append(f"โ ๏ธ Agentes no pudieron optimizar: {agent_result['recommendations']}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
|
| 202 |
+
# 2. Ejecutar anรกlisis biotecnolรณgico con BiotechU4
|
| 203 |
+
progress(0.2, desc="๐ฌ Ejecutando anรกlisis biotecnolรณgico...")
|
| 204 |
+
if not biotech_client:
|
| 205 |
+
return create_dummy_plot(), None, None, None, None, "\n".join(progress_updates) + "\nโ Error: El cliente de BiotechU4 no estรก disponible."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
try:
|
| 208 |
+
plot_info, df_data, status = biotech_client.predict(
|
| 209 |
+
file=handle_file(file.name), models=models, component=component,
|
| 210 |
+
use_de=use_de, maxfev=maxfev, exp_names=exp_names,
|
| 211 |
+
api_name="/run_analysis_wrapper"
|
| 212 |
+
)
|
| 213 |
+
progress_updates.append(f"โ
Anรกlisis BiotechU4 completado: {status}")
|
| 214 |
except Exception as e:
|
| 215 |
+
error_msg = f"Error en anรกlisis biotecnolรณgico: {e}"
|
| 216 |
+
return create_dummy_plot(), None, None, None, None, "\n".join(progress_updates) + f"\nโ {error_msg}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
+
if "Error" in status or not df_data:
|
| 219 |
+
return parse_plot_data(plot_info), None, None, None, None, "\n".join(progress_updates) + f"\nโ {status}"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
+
# 3. Crear archivo CSV temporal para hacer de puente a la siguiente API
|
| 222 |
+
progress(0.4, desc="๐ Creando puente de datos (CSV)...")
|
| 223 |
+
temp_csv_file = None
|
| 224 |
+
try:
|
| 225 |
+
df = pd.DataFrame(df_data['data'], columns=df_data['headers'])
|
| 226 |
+
with tempfile.NamedTemporaryFile(mode='w+', suffix='.csv', delete=False, encoding='utf-8') as temp_f:
|
| 227 |
+
df.to_csv(temp_f.name, index=False)
|
| 228 |
+
temp_csv_file = temp_f.name
|
| 229 |
+
progress_updates.append("โ
Puente de datos creado exitosamente.")
|
| 230 |
+
except Exception as e:
|
| 231 |
+
error_msg = f"Error al crear el archivo intermedio: {e}"
|
| 232 |
+
return parse_plot_data(plot_info), df_data, None, None, None, "\n".join(progress_updates) + f"\nโ {error_msg}"
|
| 233 |
|
| 234 |
+
# 4. Generar informe con Project-HF-2025-2
|
| 235 |
+
progress(0.5, desc=f"๐ค Generando informe con {ia_model}...")
|
| 236 |
+
if not analysis_client:
|
| 237 |
+
os.remove(temp_csv_file)
|
| 238 |
+
return parse_plot_data(plot_info), df_data, None, None, None, "\n".join(progress_updates) + "\nโ Error: El cliente de anรกlisis no estรก disponible."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
|
|
|
|
|
|
|
| 240 |
try:
|
| 241 |
+
# Usar clave personal si se proporciona
|
| 242 |
+
current_analysis_client = analysis_client
|
| 243 |
+
if use_personal_key and personal_api_key:
|
| 244 |
+
current_analysis_client = Client("C2MV/Project-HF-2025-2", hf_token=personal_api_key)
|
| 245 |
+
progress_updates.append("๐ Usando clave API personal para el informe.")
|
| 246 |
+
|
| 247 |
+
# Actualizar dinรกmicamente el selector de columna antes de la llamada principal
|
| 248 |
+
if not chunk_column:
|
| 249 |
+
chunk_choices = current_analysis_client.predict(files=[handle_file(temp_csv_file)], api_name="/update_chunk_column_selector")
|
| 250 |
+
chunk_column = chunk_choices[0] if chunk_choices else 'Experiment' # Fallback
|
| 251 |
+
progress_updates.append(f"โ
Columna de agrupaciรณn seleccionada automรกticamente: {chunk_column}")
|
| 252 |
+
|
| 253 |
+
# Llamada principal a la API de anรกlisis
|
| 254 |
+
thinking_process, analysis_report, implementation_code, token_usage = current_analysis_client.predict(
|
| 255 |
+
files=[handle_file(temp_csv_file)],
|
| 256 |
+
chunk_column=chunk_column,
|
| 257 |
+
qwen_model=ia_model,
|
| 258 |
+
detail_level=detail_level,
|
| 259 |
+
language=language,
|
| 260 |
+
additional_specs=additional_specs,
|
| 261 |
+
max_output_tokens=max_output_tokens,
|
| 262 |
+
api_name="/process_files_and_analyze"
|
| 263 |
+
)
|
| 264 |
+
progress_updates.append(f"โ
Informe de IA generado. {token_usage}")
|
| 265 |
+
progress_updates.append(f"๐ง Proceso de Pensamiento: {thinking_process}")
|
| 266 |
|
| 267 |
+
except Exception as e:
|
| 268 |
+
error_msg = f"Error generando el informe de IA: {e}"
|
| 269 |
+
return parse_plot_data(plot_info), df_data, error_msg, None, None, "\n".join(progress_updates) + f"\nโ {error_msg}"
|
| 270 |
+
finally:
|
| 271 |
+
if temp_csv_file and os.path.exists(temp_csv_file):
|
| 272 |
+
os.remove(temp_csv_file) # Limpiar el archivo temporal
|
| 273 |
+
|
| 274 |
+
# 5. Exportar el informe final
|
| 275 |
+
progress(0.9, desc=f"๐ Exportando informe en {export_format}...")
|
| 276 |
try:
|
| 277 |
+
export_status, report_file = analysis_client.predict(
|
| 278 |
+
export_format=export_format,
|
| 279 |
+
language=language,
|
| 280 |
+
api_name="/export_report"
|
| 281 |
+
)
|
| 282 |
+
if report_file:
|
| 283 |
+
progress_updates.append(f"โ
Informe exportado: {export_status}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
else:
|
| 285 |
+
progress_updates.append(f"โ Error al exportar: {export_status}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
except Exception as e:
|
| 287 |
+
report_file = None
|
| 288 |
+
progress_updates.append(f"โ Error excepcional durante la exportaciรณn: {e}")
|
|
|
|
|
|
|
|
|
|
| 289 |
|
| 290 |
+
progress(1, desc="๐ Pipeline Completado")
|
|
|
|
|
|
|
| 291 |
|
| 292 |
+
return parse_plot_data(plot_info), df_data, analysis_report, implementation_code, report_file, "\n".join(progress_updates)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
|
| 294 |
+
def create_example_videos():
|
| 295 |
+
# ... (cรณdigo sin cambios)
|
| 296 |
+
pass
|
| 297 |
+
|
| 298 |
+
# ============================================================================
|
| 299 |
+
# ๐ผ๏ธ INTERFAZ DE USUARIO CON GRADIO (Combinada y Organizada)
|
| 300 |
+
# ============================================================================
|
| 301 |
+
BIOTECH_MODELS = ['logistic', 'gompertz', 'moser', 'baranyi', 'monod', 'contois', 'andrews', 'tessier', 'richards', 'stannard', 'huang']
|
| 302 |
+
DEFAULT_BIOTECH_SELECTION = ['logistic', 'gompertz', 'moser', 'baranyi']
|
| 303 |
+
IA_MODELS = ["deepseek-ai/DeepSeek-V3-0324"]
|
| 304 |
+
DEFAULT_IA_MODEL = "deepseek-ai/DeepSeek-V3-0324"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
|
| 306 |
+
theme = gr.themes.Soft(primary_hue="blue", secondary_hue="indigo", neutral_hue="slate")
|
| 307 |
+
custom_css = ".file-upload { border: 2px dashed #3b82f6; } button.primary { background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%); }"
|
|
|
|
| 308 |
|
| 309 |
+
create_example_videos()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
|
| 311 |
+
with gr.Blocks(theme=theme, title="BioTech Analysis & Report Generator", css=custom_css) as demo:
|
| 312 |
+
gr.Markdown(
|
| 313 |
+
"""
|
| 314 |
+
# ๐งฌ BioTech Analysis & Report Generator
|
| 315 |
+
## **Full Pipeline: Biotech Modeling โ AI Reporting**
|
| 316 |
+
*An intelligent pipeline that automates the analysis of bioprocess data, from kinetic modeling with `BiotechU4` to generating detailed reports with `Project-HF-2025-2`.*
|
| 317 |
+
"""
|
| 318 |
+
)
|
| 319 |
+
# ... (El resto del Markdown y Videos se mantiene igual)
|
| 320 |
+
with gr.Accordion("๐ค How the AI Agents Work (Click to Expand)", open=True):
|
| 321 |
+
gr.Markdown(
|
| 322 |
+
"""
|
| 323 |
+
```text
|
| 324 |
+
[ ๐ค USER INPUT: Data File & Initial Settings ]
|
| 325 |
+
โ
|
| 326 |
+
โผ
|
| 327 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 328 |
+
โ ๐ค Coordinator Agent โ
|
| 329 |
+
โ โข Analyzes experiment type (e.g., kinetics, ferment.). โ
|
| 330 |
+
โ โข Recommends optimal models and parameters for BiotechU4.โ
|
| 331 |
+
โ โข Prepares a rich context for the AI Report Generator. โ
|
| 332 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 333 |
+
โ
|
| 334 |
+
โผ
|
| 335 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 336 |
+
โ โ๏ธ BiotechU4 API โ
|
| 337 |
+
โ โข Performs kinetic modeling and statistical fitting. โ
|
| 338 |
+
โ โข Returns: Plot, Results Table, Status. โ
|
| 339 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 340 |
+
โ
|
| 341 |
+
โผ
|
| 342 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 343 |
+
โ ๐ Data Bridge โ
|
| 344 |
+
โ โข Converts the results table into a temporary CSV file. โ
|
| 345 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 346 |
+
โ
|
| 347 |
+
โผ
|
| 348 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 349 |
+
โ ๐ง Project-HF-2025-2 API โ
|
| 350 |
+
โ โข Analyzes the modeling results from the CSV file. โ
|
| 351 |
+
โ โข Generates: Detailed Report, Python Code, Token Usage. โ
|
| 352 |
+
โโโโโโโโโโโโโโ๏ฟฝ๏ฟฝ๏ฟฝโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 353 |
+
โ
|
| 354 |
+
โผ
|
| 355 |
+
[ ๐ FINAL OUTPUT: Plot, Table, Report, Code & Download ]
|
| 356 |
+
```
|
| 357 |
+
"""
|
| 358 |
)
|
| 359 |
+
with gr.Row():
|
| 360 |
+
with gr.Column(scale=1):
|
| 361 |
+
gr.Markdown("## ๐ Configuration")
|
| 362 |
+
file_input = gr.File(label="๐ Data File (CSV/Excel)", file_types=[".csv", ".xlsx", ".xls"], elem_classes=["file-upload"])
|
| 363 |
+
gr.Examples(examples=[os.path.join("examples", "archivo.xlsx")], inputs=[file_input], label="Click an example to run")
|
| 364 |
+
|
| 365 |
+
with gr.Accordion("๐ฌ Step 1: Biotech Analysis Parameters (AI Optimized)", open=True):
|
| 366 |
+
models_input = gr.CheckboxGroup(choices=BIOTECH_MODELS, value=DEFAULT_BIOTECH_SELECTION, label="๐ Models to Test")
|
| 367 |
+
component_input = gr.Dropdown(['all', 'biomass', 'substrate', 'product'], value='all', label="๐ Component to Visualize")
|
| 368 |
+
exp_names_input = gr.Textbox(label="๐ท๏ธ Experiment Names", value="Biotech Analysis")
|
| 369 |
+
use_de_input = gr.Checkbox(label="๐งฎ Use Differential Evolution", value=False)
|
| 370 |
+
maxfev_input = gr.Slider(label="๐ Max Iterations", minimum=10000, maximum=100000, value=50000, step=1000)
|
| 371 |
+
|
| 372 |
+
with gr.Accordion("๐ค Step 2: AI Report Generation Parameters", open=True):
|
| 373 |
+
chunk_column_input = gr.Dropdown(label="๐ฌ Select Column for Grouping", info="This is based on the results from Step 1. Default is usually fine.", choices=["Experiment"], value="Experiment", interactive=True)
|
| 374 |
+
ia_model_input = gr.Dropdown(choices=IA_MODELS, value=DEFAULT_IA_MODEL, label="๐ค IA Model")
|
| 375 |
+
detail_level_input = gr.Radio(['detailed', 'summarized'], value='detailed', label="๐ Detail Level")
|
| 376 |
+
max_output_tokens_input = gr.Slider(minimum=1000, maximum=32000, value=4000, step=100, label="๐ข Max Output Tokens")
|
| 377 |
+
additional_specs_input = gr.Textbox(label="๐ Additional Specifications", placeholder="AI Agents will customize this...", lines=3, value="Provide a detailed analysis of the models, metrics, and practical recommendations.")
|
| 378 |
+
|
| 379 |
+
with gr.Accordion("โ๏ธ Global & Export Settings", open=True):
|
| 380 |
+
language_input = gr.Dropdown(['en', 'es'], value='en', label="๐ Language")
|
| 381 |
+
export_format_input = gr.Radio(['PDF', 'DOCX'], value='PDF', label="๐ Export Format")
|
| 382 |
+
with gr.Accordion("๐ Personal API Key (Optional)", open=False):
|
| 383 |
+
use_personal_key_input = gr.Checkbox(label="Use Personal HF Token for AI Report", value=False)
|
| 384 |
+
personal_api_key_input = gr.Textbox(label="Personal HF Token", type="password", placeholder="Enter your token (hf_...)", visible=False)
|
| 385 |
+
|
| 386 |
+
process_btn = gr.Button("๐ Run Full Pipeline with AI Agents", variant="primary", size="lg")
|
| 387 |
+
|
| 388 |
+
with gr.Column(scale=2):
|
| 389 |
+
gr.Markdown("## ๐ Results")
|
| 390 |
+
status_output = gr.Textbox(label="๐ Process Status Log", lines=8, interactive=False)
|
| 391 |
+
with gr.Tabs():
|
| 392 |
+
with gr.TabItem("๐ Visualization"):
|
| 393 |
+
plot_output = gr.Plot()
|
| 394 |
+
with gr.TabItem("๐ Modeling Results Table"):
|
| 395 |
+
table_output = gr.Dataframe()
|
| 396 |
+
with gr.TabItem("๐ AI Analysis Report"):
|
| 397 |
+
analysis_output = gr.Markdown()
|
| 398 |
+
with gr.TabItem("๐ป Implementation Code"):
|
| 399 |
+
code_output = gr.Code(language="python")
|
| 400 |
+
report_output = gr.File(label="๐ฅ Download Final Report", interactive=False)
|
| 401 |
+
|
| 402 |
+
def toggle_api_key_visibility(checked):
|
| 403 |
+
return gr.Textbox(visible=checked)
|
| 404 |
+
|
| 405 |
+
use_personal_key_input.change(fn=toggle_api_key_visibility, inputs=use_personal_key_input, outputs=personal_api_key_input)
|
| 406 |
+
|
| 407 |
+
process_btn.click(
|
| 408 |
+
fn=process_complete_pipeline_with_agents,
|
| 409 |
+
inputs=[
|
| 410 |
+
file_input, models_input, component_input, use_de_input, maxfev_input, exp_names_input,
|
| 411 |
+
chunk_column_input, ia_model_input, detail_level_input, language_input, additional_specs_input,
|
| 412 |
+
max_output_tokens_input, export_format_input, use_personal_key_input, personal_api_key_input
|
| 413 |
+
],
|
| 414 |
+
outputs=[
|
| 415 |
+
plot_output, table_output, analysis_output, code_output, report_output, status_output
|
| 416 |
+
]
|
| 417 |
+
)
|
| 418 |
|
| 419 |
if __name__ == "__main__":
|
| 420 |
+
if not os.path.exists("examples"):
|
| 421 |
+
os.makedirs("examples")
|
| 422 |
+
print("Carpeta 'examples' creada. Por favor, aรฑade 'video1.mp4', 'video2.mp4', y 'archivo.xlsx' dentro.")
|
| 423 |
+
demo.launch(show_error=True, debug=True)
|