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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +690 -211
app.py CHANGED
@@ -18,309 +18,788 @@ from docx import Document
18
  from docx.shared import Inches, Pt, RGBColor
19
  from docx.enum.text import WD_ALIGN_PARAGRAPH
20
  from reportlab.lib import colors
21
- from reportlab.lib.pagesizes import letter, A4
22
- from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, PageBreak
23
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
24
  from reportlab.lib.units import inch
25
- from reportlab.pdfbase import pdfmetrics
26
- from reportlab.pdfbase.ttfonts import TTFont
27
  import matplotlib.pyplot as plt
28
  from datetime import datetime
29
 
30
- # Configuración para HuggingFace
 
 
31
  os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
32
 
33
- # Inicializar el cliente de OpenAI para Nebius AI (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 (sin cambios)
40
  TRANSLATIONS = {
41
- 'en': {
42
- 'title': '🧬 Comparative Analyzer of Biotechnological Models',
43
- 'subtitle': 'Specialized in comparative analysis of mathematical model fitting results',
44
- 'upload_files': '📁 Upload fitting results (CSV/Excel)',
45
- 'select_model': '🤖 AI Model',
46
- 'select_language': '🌐 Language',
47
- 'select_theme': '🎨 Theme',
48
- 'detail_level': '📋 Analysis detail level',
49
- 'detailed': 'Detailed',
50
- 'summarized': 'Summarized',
51
- 'analyze_button': '🚀 Analyze and Compare Models',
52
- 'export_format': '📄 Export format',
53
- 'export_button': '💾 Export Report',
54
- 'comparative_analysis': '📊 Comparative Analysis',
55
- 'implementation_code': '💻 Full Implementation Code (AI-Generated)',
56
- 'data_format': '📋 Expected data format',
57
- 'examples': '📚 Analysis examples',
58
- 'light': 'Light',
59
- 'dark': 'Dark',
60
- 'best_for': 'Best for',
61
- 'loading': 'Loading...',
62
- 'error_no_api': 'Please configure NEBIUS_API_KEY in your environment secrets',
63
- 'error_no_files': 'Please upload fitting result files to analyze',
64
- 'report_exported': 'Report exported successfully as',
65
- 'specialized_in': '🎯 Specialized in:',
66
- 'metrics_analyzed': '📊 Analyzed metrics:',
67
- 'what_analyzes': '🔍 What it specifically analyzes:',
68
- 'tips': '💡 Tips for better results:',
69
- 'additional_specs': '📝 Additional specifications for analysis',
70
- 'additional_specs_placeholder': 'Add any specific requirements or focus areas for the analysis...'
71
- },
72
- 'es': {
73
- 'title': '🧬 Analizador Comparativo de Modelos Biotecnológicos',
74
- 'subtitle': 'Especializado en análisis comparativo de resultados de ajuste de modelos matemáticos',
75
- 'upload_files': '📁 Subir resultados de ajuste (CSV/Excel)',
76
- 'select_model': '🤖 Modelo de IA',
77
- 'select_language': '🌐 Idioma',
78
- 'select_theme': '🎨 Tema',
79
- 'detail_level': '📋 Nivel de detalle del análisis',
80
- 'detailed': 'Detallado',
81
- 'summarized': 'Resumido',
82
- 'analyze_button': '🚀 Analizar y Comparar Modelos',
83
- 'export_format': '📄 Formato de exportación',
84
- 'export_button': '💾 Exportar Reporte',
85
- 'comparative_analysis': '📊 Análisis Comparativo',
86
- 'implementation_code': '💻 Código de Implementación Completo (Generado por IA)',
87
- 'data_format': '📋 Formato de datos esperado',
88
- 'examples': '📚 Ejemplos de análisis',
89
- 'light': 'Claro',
90
- 'dark': 'Oscuro',
91
- 'best_for': 'Mejor para',
92
- 'loading': 'Cargando...',
93
- 'error_no_api': 'Por favor configura NEBIUS_API_KEY en los secretos de tu entorno',
94
- 'error_no_files': 'Por favor sube archivos con resultados de ajuste para analizar',
95
- 'report_exported': 'Reporte exportado exitosamente como',
96
- 'specialized_in': '🎯 Especializado en:',
97
- 'metrics_analyzed': '📊 Métricas analizadas:',
98
- 'what_analyzes': '🔍 Qué analiza específicamente:',
99
- 'tips': '💡 Tips para mejores resultados:',
100
- 'additional_specs': '📝 Especificaciones adicionales para el análisis',
101
- 'additional_specs_placeholder': 'Agregue cualquier requerimiento específico o áreas de enfoque para el análisis...'
102
- },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  }
104
 
 
 
 
 
 
 
 
105
 
106
- # Temas y Clases de Estructura (sin cambios)
107
- THEMES = {'light': gr.themes.Soft(), 'dark': gr.themes.Base(primary_hue="blue", secondary_hue="gray", neutral_hue="gray", font=["Arial", "sans-serif"]).set(body_background_fill="dark", body_background_fill_dark="*neutral_950", button_primary_background_fill="*primary_600", button_primary_background_fill_hover="*primary_500", button_primary_text_color="white", block_background_fill="*neutral_800", block_border_color="*neutral_700", block_label_text_color="*neutral_200", block_title_text_color="*neutral_100", checkbox_background_color="*neutral_700", checkbox_background_color_selected="*primary_600", input_background_fill="*neutral_700", input_border_color="*neutral_600", input_placeholder_color="*neutral_400")}
108
- class AnalysisType(Enum): MATHEMATICAL_MODEL = "mathematical_model"; DATA_FITTING = "data_fitting"; FITTING_RESULTS = "fitting_results"; UNKNOWN = "unknown"
109
  @dataclass
110
- class MathematicalModel: name: str; equation: str; parameters: List[str]; application: str; sources: List[str]; category: str; biological_meaning: str
 
 
 
 
 
 
 
 
111
  class ModelRegistry:
112
- def __init__(self): self.models = {}; self._initialize_default_models()
 
 
 
113
  def register_model(self, model: MathematicalModel):
114
- if model.category not in self.models: self.models[model.category] = {}
 
115
  self.models[model.category][model.name] = model
116
- def get_model(self, category: str, name: str) -> MathematicalModel: return self.models.get(category, {}).get(name)
117
- def get_all_models(self) -> Dict: return self.models
 
 
 
 
 
118
  def _initialize_default_models(self):
119
- self.register_model(MathematicalModel(name="Monod", equation="μ = μmax × (S / (Ks + S))", parameters=["μmax (h⁻¹)", "Ks (g/L)"], application="Crecimiento limitado por sustrato único", sources=["Cambridge", "MIT", "DTU"], category="crecimiento_biomasa", biological_meaning="Describe cómo la velocidad de crecimiento depende de la concentración de sustrato limitante"))
120
- self.register_model(MathematicalModel(name="Logístico", equation="dX/dt = μmax × X × (1 - X/Xmax)", parameters=["μmax (h⁻¹)", "Xmax (g/L)"], application="Sistemas cerrados batch", sources=["Cranfield", "Swansea", "HAL Theses"], category="crecimiento_biomasa", biological_meaning="Modela crecimiento limitado por capacidad de carga del sistema"))
121
- self.register_model(MathematicalModel(name="Gompertz", equation="X(t) = Xmax × exp(-exp((μmax × e / Xmax) × (λ - t) + 1))", parameters=["λ (h)", "μmax (h⁻¹)", "Xmax (g/L)"], application="Crecimiento con fase lag pronunciada", sources=["Lund University", "NC State"], category="crecimiento_biomasa", biological_meaning="Incluye fase de adaptación (lag) seguida de crecimiento exponencial y estacionario"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  model_registry = ModelRegistry()
123
- AI_MODELS = {"Qwen/Qwen3-14B": {"name": "Qwen 3 14B (Nebius)", "description": "Modelo potente de la serie Qwen, accedido vía Nebius AI.", "max_tokens": 8000, "best_for": "Análisis complejos y generación de código detallado."}}
 
 
 
 
 
 
 
 
 
 
 
124
  class FileProcessor:
125
  @staticmethod
126
- def read_csv(csv_file) -> pd.DataFrame:
127
- try: return pd.read_csv(io.BytesIO(csv_file))
128
- except Exception: return None
 
 
 
 
 
 
 
 
 
129
  @staticmethod
130
- def read_excel(excel_file) -> pd.DataFrame:
131
- try: return pd.read_excel(io.BytesIO(excel_file))
132
- except Exception: return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  class ReportExporter:
134
  @staticmethod
135
  def export_to_docx(content: str, filename: str, language: str = 'en') -> str:
136
  doc = Document()
137
- doc.add_heading(TRANSLATIONS[language]['title'], 0)
138
- doc.add_paragraph(f"{TRANSLATIONS[language]['generated_on']}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
139
- # ... (lógica de exportación completa)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  doc.save(filename)
141
  return filename
 
142
  @staticmethod
143
  def export_to_pdf(content: str, filename: str, language: str = 'en') -> str:
144
  doc = SimpleDocTemplate(filename, pagesize=letter)
145
- # ... (lógica de exportación completa)
146
- doc.build([])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  return filename
148
 
 
 
149
  class AIAnalyzer:
150
- def __init__(self, client: OpenAI, model_registry: ModelRegistry):
151
  self.client = client
152
  self.model_registry = model_registry
153
 
154
  def get_language_prompt_prefix(self, language: str) -> str:
155
- prefixes = {'en': "Please respond in English. ", 'es': "Por favor responde en español. ", 'fr': "Veuillez répondre en français. ", 'de': "Bitte antworten Sie auf Deutsch. ", 'pt': "Por favor responda em português. "}
156
- return prefixes.get(language, prefixes['en'])
157
-
158
- def analyze_fitting_results(self, data: pd.DataFrame, ai_model: str, detail_level: str = "detailed",
159
- language: str = "en", additional_specs: str = "") -> Dict:
160
- data_summary = f"FITTING RESULTS DATA:\n\n{data.to_string()}"
161
- lang_prefix = self.get_language_prompt_prefix(language)
162
- user_specs_section = f"\n\nUSER ADDITIONAL SPECIFICATIONS:\n{additional_specs}" if additional_specs else ""
163
-
164
- # Prompt para el análisis de texto (sin cambios)
165
- analysis_prompt = f"{lang_prefix}\nYou are an expert in biotechnology and mathematical modeling. Analyze these model fitting results.\n{user_specs_section}\nDETAIL LEVEL: {detail_level.upper()}\n\nProvide a comprehensive comparative analysis based on the provided data. Structure your response clearly using Markdown. Identify the best models for each experimental condition and justify your choices with metrics like R² and RMSE. Conclude with overall recommendations.\n\n{data_summary}"
166
-
167
- # --- CAMBIO 1: Prompt de generación de código mejorado y más exigente ---
168
- code_prompt = f"""
169
- {lang_prefix}
170
 
171
- Based on the following data, generate a SINGLE, COMPLETE, and EXECUTABLE Python script.
 
 
172
 
173
- **Requirements for the script:**
174
- 1. **Self-Contained:** The script must be runnable as-is. It should NOT require any external CSV/Excel files.
175
- 2. **Embed Data:** You MUST embed the provided data directly into the script, for example, by creating a pandas DataFrame from a dictionary or a multiline string.
176
- 3. **Full Analysis:** The script should perform a complete analysis similar to the text report:
177
- - Identify the best model (based on R²) for each 'Experiment' and 'Type' (Biomass, Substrate, Product).
178
- - Print a clear summary table of the findings.
179
- 4. **Visualization:** The script MUST generate at least one publication-quality plot using Matplotlib or Seaborn to visually compare the performance (e.g., values) of the best models across different experiments. The plot must be clearly labeled.
180
- 5. **Code Quality:** Use clear variable names, comments, and functions or a class structure to organize the code logically.
181
- 6. **No Placeholders:** Do not use placeholder comments like '# Add visualization here'. Implement the full functionality.
182
-
183
- **Data to use:**
184
- ```
185
- {data.to_string()}
186
- ```
187
-
188
- Generate only the Python code, starting with `import pandas as pd`.
189
- """
 
 
 
 
 
 
190
 
191
  try:
192
- # Llamada para el análisis de texto
193
  analysis_response = self.client.chat.completions.create(
194
- model=ai_model,
 
195
  max_tokens=4000,
196
  temperature=0.6,
197
- messages=[{"role": "user", "content": analysis_prompt}]
198
  )
199
  analysis_text = analysis_response.choices[0].message.content
200
 
201
- # Llamada para la generación de código
 
 
 
 
 
 
 
 
 
 
 
202
  code_response = self.client.chat.completions.create(
203
- model=ai_model,
204
- max_tokens=4000,
205
- temperature=0.4, # Ligeramente más determinista para el código
206
- messages=[{"role": "user", "content": code_prompt}]
 
207
  )
208
  code_text = code_response.choices[0].message.content
209
-
210
- # Limpiar el código si viene envuelto en ```python ... ```
211
- if code_text.strip().startswith("```python"):
212
- code_text = code_text.strip()[9:]
213
- if code_text.strip().endswith("```"):
214
- code_text = code_text.strip()[:-3]
215
 
216
  return {
217
  "analisis_completo": analysis_text,
218
  "codigo_implementacion": code_text,
219
  }
220
-
221
  except Exception as e:
222
- return {"error": str(e)}
223
 
224
- def process_files(files, ai_model: str, detail_level: str = "detailed",
225
- language: str = "en", additional_specs: str = "") -> Tuple[str, str]:
226
- processor = FileProcessor()
227
- analyzer = AIAnalyzer(client, model_registry)
228
- all_analysis = []
229
- all_code = []
230
 
 
231
  if not files:
232
- return TRANSLATIONS[language]['error_no_files'], ""
 
 
233
 
234
- for file in files:
235
- file_name = file.name if hasattr(file, 'name') else "archivo"
236
- file_ext = Path(file_name).suffix.lower()
237
-
238
- with open(file.name, 'rb') as f:
239
- file_content = f.read()
240
-
241
- if file_ext in ['.csv', '.xlsx', '.xls']:
242
- all_analysis.append(f"## 📊 {TRANSLATIONS[language]['comparative_analysis']}: {file_name}")
243
- df = processor.read_csv(file_content) if file_ext == '.csv' else processor.read_excel(file_content)
244
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  if df is not None:
246
- result = analyzer.analyze_fitting_results(df, ai_model, detail_level, language, additional_specs)
 
247
 
248
  if "error" in result:
249
- all_analysis.append(f"An error occurred: {result['error']}")
250
  else:
251
- all_analysis.append(result.get("analisis_completo", "No analysis generated."))
252
- # --- CAMBIO 2: Usar siempre el código de la IA, sin fallback ---
253
- all_code.append(f"# Code generated for file: {file_name}\n" + result.get("codigo_implementacion", "# No code was generated for this file."))
254
  else:
255
- all_analysis.append("Could not read the file content.")
256
- all_analysis.append("\n---\n")
257
-
258
- final_analysis = "\n".join(all_analysis)
259
- final_code = "\n\n".join(all_code)
260
 
 
 
 
261
  return final_analysis, final_code
262
 
263
- # --- CAMBIO 3: La función `generate_implementation_code` ha sido eliminada por completo. ---
264
 
265
- # Estado de la aplicación y función de exportación (sin cambios)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  class AppState:
267
- def __init__(self): self.current_analysis = ""; self.current_code = ""; self.current_language = "en"
 
 
 
 
268
  app_state = AppState()
269
- def export_report(export_format: str, language: str) -> Tuple[str, str]:
270
- # ... (lógica de exportación sin cambios)
271
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
 
273
  def create_interface():
274
  current_language = "en"
275
-
276
- def update_interface_language(language):
277
  app_state.current_language = language
278
  t = TRANSLATIONS[language]
279
- return [gr.update(value=f"# {t['title']}"), gr.update(value=t['subtitle']), gr.update(label=t['upload_files']), gr.update(label=t['select_model']), gr.update(label=t['select_language']), gr.update(label=t['select_theme']), gr.update(label=t['detail_level']), gr.update(label=t['additional_specs'], placeholder=t['additional_specs_placeholder']), gr.update(value=t['analyze_button']), gr.update(label=t['export_format']), gr.update(value=t['export_button']), gr.update(label=t['comparative_analysis']), gr.update(label=t['implementation_code']), gr.update(label=t['data_format'])]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
 
281
  def process_and_store(files, model, detail, language, additional_specs):
 
 
 
282
  analysis, code = process_files(files, model, detail, language, additional_specs)
283
  app_state.current_analysis = analysis
284
  app_state.current_code = code
285
  return analysis, code
286
 
287
  with gr.Blocks(theme=THEMES['light']) as demo:
288
- # Definición de la UI (sin cambios estructurales, solo etiquetas)
289
- with gr.Row():
290
- with gr.Column(scale=3):
291
- title_text = gr.Markdown(f"# {TRANSLATIONS[current_language]['title']}")
292
- subtitle_text = gr.Markdown(TRANSLATIONS[current_language]['subtitle'])
293
- with gr.Column(scale=1):
294
- language_selector = gr.Dropdown(choices=[("English", "en"), ("Español", "es")], value="en", label=TRANSLATIONS[current_language]['select_language'], interactive=True)
295
- theme_selector = gr.Dropdown(choices=[("Light", "light"), ("Dark", "dark")], value="light", label=TRANSLATIONS[current_language]['select_theme'], interactive=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
 
297
- with gr.Row():
298
- with gr.Column(scale=1):
299
- files_input = gr.File(label=TRANSLATIONS[current_language]['upload_files'], file_count="multiple", file_types=[".csv", ".xlsx", ".xls"], type="filepath")
300
- default_model = "Qwen/Qwen3-14B"
301
- model_selector = gr.Dropdown(choices=list(AI_MODELS.keys()), value=default_model, label=TRANSLATIONS[current_language]['select_model'], info=f"{TRANSLATIONS[current_language]['best_for']}: {AI_MODELS[default_model]['best_for']}")
302
- detail_level = gr.Radio(choices=[(TRANSLATIONS[current_language]['detailed'], "detailed"), (TRANSLATIONS[current_language]['summarized'], "summarized")], value="detailed", label=TRANSLATIONS[current_language]['detail_level'])
303
- additional_specs = gr.Textbox(label=TRANSLATIONS[current_language]['additional_specs'], placeholder=TRANSLATIONS[current_language]['additional_specs_placeholder'], lines=3, interactive=True)
304
- analyze_btn = gr.Button(TRANSLATIONS[current_language]['analyze_button'], variant="primary", size="lg")
305
- # ... (resto de botones de exportación)
306
-
307
- with gr.Column(scale=2):
308
- analysis_output = gr.Markdown(label=TRANSLATIONS[current_language]['comparative_analysis'])
309
- code_output = gr.Code(label=TRANSLATIONS[current_language]['implementation_code'], language="python", interactive=True, lines=25) # Más líneas para el código completo
310
-
311
- # ... (resto de la UI y eventos)
312
- language_selector.change(update_interface_language, inputs=[language_selector], outputs=[title_text, subtitle_text, files_input, model_selector, language_selector, theme_selector, detail_level, additional_specs, analyze_btn, export_format, export_btn, analysis_output, code_output, data_format_accordion])
313
- analyze_btn.click(fn=process_and_store, inputs=[files_input, model_selector, detail_level, language_selector, additional_specs], outputs=[analysis_output, code_output])
 
 
314
 
315
  return demo
316
 
 
 
317
  def main():
318
- if not os.getenv("NEBIUS_API_KEY"):
319
- print("⚠️ Configure NEBIUS_API_KEY in your environment secrets")
320
- return gr.Interface(fn=lambda x: TRANSLATIONS['en']['error_no_api'], inputs=gr.Textbox(), outputs=gr.Textbox(), title="Configuration Error")
 
 
 
 
321
  return create_interface()
322
 
323
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  demo = main()
325
  if demo:
326
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
18
  from docx.shared import Inches, Pt, RGBColor
19
  from docx.enum.text import WD_ALIGN_PARAGRAPH
20
  from reportlab.lib import colors
21
+ from reportlab.lib.pagesizes import letter
22
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
23
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
24
  from reportlab.lib.units import inch
 
 
25
  import matplotlib.pyplot as plt
26
  from datetime import datetime
27
 
28
+ # --- Configuración para la API de Qwen ---
29
+
30
+ # Configuración de Gradio
31
  os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
32
 
33
+ # Inicializar cliente OpenAI para apuntar a la API de Nebius (Qwen)
34
+ try:
35
+ if "NEBIUS_API_KEY" not in os.environ:
36
+ print("⚠️ ADVERTENCIA: La variable de entorno NEBIUS_API_KEY no está configurada. El analizador no funcionará.")
37
+ client = None
38
+ else:
39
+ client = OpenAI(
40
+ base_url="https://api.studio.nebius.com/v1/",
41
+ api_key=os.environ.get("NEBIUS_API_KEY")
42
+ )
43
+ except Exception as e:
44
+ print(f"Error al inicializar el cliente OpenAI para Nebius: {e}")
45
+ client = None
46
+
47
+ # --- Sistema de Traducción Completo ---
48
 
 
49
  TRANSLATIONS = {
50
+ 'en': {
51
+ 'title': '🧬 Comparative Analyzer of Biotechnological Models',
52
+ 'subtitle': 'Specialized in comparative analysis of mathematical model fitting results',
53
+ 'upload_files': '📁 Upload fitting results (CSV/Excel)',
54
+ 'select_model': '🤖 Qwen Model',
55
+ 'select_language': '🌐 Language',
56
+ 'select_theme': '🎨 Theme',
57
+ 'detail_level': '📋 Analysis detail level',
58
+ 'detailed': 'Detailed',
59
+ 'summarized': 'Summarized',
60
+ 'analyze_button': '🚀 Analyze and Compare Models',
61
+ 'export_format': '📄 Export format',
62
+ 'export_button': '💾 Export Report',
63
+ 'comparative_analysis': '📊 Comparative Analysis',
64
+ 'implementation_code': '💻 Implementation Code',
65
+ 'data_format': '📋 Expected data format',
66
+ 'examples': '📚 Analysis examples',
67
+ 'light': 'Light',
68
+ 'dark': 'Dark',
69
+ 'best_for': 'Best for',
70
+ 'loading': 'Loading...',
71
+ 'error_no_api': 'Please configure NEBIUS_API_KEY in the environment variables or Space secrets',
72
+ 'error_no_files': 'Please upload fitting result files to analyze',
73
+ 'report_exported': 'Report exported successfully as',
74
+ 'specialized_in': '🎯 Specialized in:',
75
+ 'metrics_analyzed': '📊 Analyzed metrics:',
76
+ 'what_analyzes': '🔍 What it specifically analyzes:',
77
+ 'tips': '💡 Tips for better results:',
78
+ 'additional_specs': '📝 Additional specifications for analysis',
79
+ 'additional_specs_placeholder': 'Add any specific requirements or focus areas for the analysis...'
80
+ },
81
+ 'es': {
82
+ 'title': '🧬 Analizador Comparativo de Modelos Biotecnológicos',
83
+ 'subtitle': 'Especializado en análisis comparativo de resultados de ajuste de modelos matemáticos',
84
+ 'upload_files': '📁 Subir resultados de ajuste (CSV/Excel)',
85
+ 'select_model': '🤖 Modelo Qwen',
86
+ 'select_language': '🌐 Idioma',
87
+ 'select_theme': '🎨 Tema',
88
+ 'detail_level': '📋 Nivel de detalle del análisis',
89
+ 'detailed': 'Detallado',
90
+ 'summarized': 'Resumido',
91
+ 'analyze_button': '🚀 Analizar y Comparar Modelos',
92
+ 'export_format': '📄 Formato de exportación',
93
+ 'export_button': '💾 Exportar Reporte',
94
+ 'comparative_analysis': '📊 Análisis Comparativo',
95
+ 'implementation_code': '💻 Código de Implementación',
96
+ 'data_format': '📋 Formato de datos esperado',
97
+ 'examples': '📚 Ejemplos de análisis',
98
+ 'light': 'Claro',
99
+ 'dark': 'Oscuro',
100
+ 'best_for': 'Mejor para',
101
+ 'loading': 'Cargando...',
102
+ 'error_no_api': 'Por favor configura NEBIUS_API_KEY en las variables de entorno o secretos del Space',
103
+ 'error_no_files': 'Por favor sube archivos con resultados de ajuste para analizar',
104
+ 'report_exported': 'Reporte exportado exitosamente como',
105
+ 'specialized_in': '🎯 Especializado en:',
106
+ 'metrics_analyzed': '📊 Métricas analizadas:',
107
+ 'what_analyzes': '🔍 Qué analiza específicamente:',
108
+ 'tips': '💡 Tips para mejores resultados:',
109
+ 'additional_specs': '📝 Especificaciones adicionales para el análisis',
110
+ 'additional_specs_placeholder': 'Agregue cualquier requerimiento específico o áreas de enfoque para el análisis...'
111
+ },
112
+ 'fr': {
113
+ 'title': '🧬 Analyseur Comparatif de Modèles Biotechnologiques',
114
+ 'subtitle': 'Spécialisé dans l\'analyse comparative des résultats d\'ajustement',
115
+ 'upload_files': '📁 Télécharger les résultats (CSV/Excel)',
116
+ 'select_model': '🤖 Modèle Qwen',
117
+ 'select_language': '🌐 Langue',
118
+ 'select_theme': '🎨 Thème',
119
+ 'detail_level': '📋 Niveau de détail',
120
+ 'detailed': 'Détaillé',
121
+ 'summarized': 'Résumé',
122
+ 'analyze_button': '🚀 Analyser et Comparer',
123
+ 'export_format': '📄 Format d\'export',
124
+ 'export_button': '💾 Exporter le Rapport',
125
+ 'comparative_analysis': '📊 Analyse Comparative',
126
+ 'implementation_code': '💻 Code d\'Implémentation',
127
+ 'data_format': '📋 Format de données attendu',
128
+ 'examples': '📚 Exemples d\'analyse',
129
+ 'light': 'Clair',
130
+ 'dark': 'Sombre',
131
+ 'best_for': 'Meilleur pour',
132
+ 'loading': 'Chargement...',
133
+ 'error_no_api': 'Veuillez configurer NEBIUS_API_KEY',
134
+ 'error_no_files': 'Veuillez télécharger des fichiers à analyser',
135
+ 'report_exported': 'Rapport exporté avec succès comme',
136
+ 'specialized_in': '🎯 Spécialisé dans:',
137
+ 'metrics_analyzed': '📊 Métriques analysées:',
138
+ 'what_analyzes': '🔍 Ce qu\'il analyse spécifiquement:',
139
+ 'tips': '💡 Conseils pour de meilleurs résultats:',
140
+ 'additional_specs': '📝 Spécifications supplémentaires pour l\'analyse',
141
+ 'additional_specs_placeholder': 'Ajoutez des exigences spécifiques ou des domaines d\'intérêt pour l\'analyse...'
142
+ },
143
+ 'de': {
144
+ 'title': '🧬 Vergleichender Analysator für Biotechnologische Modelle',
145
+ 'subtitle': 'Spezialisiert auf vergleichende Analyse von Modellanpassungsergebnissen',
146
+ 'upload_files': '📁 Ergebnisse hochladen (CSV/Excel)',
147
+ 'select_model': '🤖 Qwen Modell',
148
+ 'select_language': '🌐 Sprache',
149
+ 'select_theme': '🎨 Thema',
150
+ 'detail_level': '📋 Detailgrad der Analyse',
151
+ 'detailed': 'Detailliert',
152
+ 'summarized': 'Zusammengefasst',
153
+ 'analyze_button': '🚀 Analysieren und Vergleichen',
154
+ 'export_format': '📄 Exportformat',
155
+ 'export_button': '💾 Bericht Exportieren',
156
+ 'comparative_analysis': '📊 Vergleichende Analyse',
157
+ 'implementation_code': '💻 Implementierungscode',
158
+ 'data_format': '📋 Erwartetes Datenformat',
159
+ 'examples': '📚 Analysebeispiele',
160
+ 'light': 'Hell',
161
+ 'dark': 'Dunkel',
162
+ 'best_for': 'Am besten für',
163
+ 'loading': 'Laden...',
164
+ 'error_no_api': 'Bitte konfigurieren Sie NEBIUS_API_KEY',
165
+ 'error_no_files': 'Bitte laden Sie Dateien zur Analyse hoch',
166
+ 'report_exported': 'Bericht erfolgreich exportiert als',
167
+ 'specialized_in': '🎯 Spezialisiert auf:',
168
+ 'metrics_analyzed': '📊 Analysierte Metriken:',
169
+ 'what_analyzes': '🔍 Was spezifisch analysiert wird:',
170
+ 'tips': '💡 Tipps für bessere Ergebnisse:',
171
+ 'additional_specs': '📝 Zusätzliche Spezifikationen für die Analyse',
172
+ 'additional_specs_placeholder': 'Fügen Sie spezifische Anforderungen oder Schwerpunktbereiche für die Analyse hinzu...'
173
+ },
174
+ 'pt': {
175
+ 'title': '🧬 Analisador Comparativo de Modelos Biotecnológicos',
176
+ 'subtitle': 'Especializado em análise comparativa de resultados de ajuste',
177
+ 'upload_files': '📁 Carregar resultados (CSV/Excel)',
178
+ 'select_model': '🤖 Modelo Qwen',
179
+ 'select_language': '🌐 Idioma',
180
+ 'select_theme': '🎨 Tema',
181
+ 'detail_level': '📋 Nível de detalhe',
182
+ 'detailed': 'Detalhado',
183
+ 'summarized': 'Resumido',
184
+ 'analyze_button': '🚀 Analisar e Comparar',
185
+ 'export_format': '📄 Formato de exportação',
186
+ 'export_button': '💾 Exportar Relatório',
187
+ 'comparative_analysis': '📊 Análise Comparativa',
188
+ 'implementation_code': '💻 Código de Implementação',
189
+ 'data_format': '📋 Formato de dados esperado',
190
+ 'examples': '📚 Exemplos de análise',
191
+ 'light': 'Claro',
192
+ 'dark': 'Escuro',
193
+ 'best_for': 'Melhor para',
194
+ 'loading': 'Carregando...',
195
+ 'error_no_api': 'Por favor configure NEBIUS_API_KEY',
196
+ 'error_no_files': 'Por favor carregue arquivos para analisar',
197
+ 'report_exported': 'Relatório exportado com sucesso como',
198
+ 'specialized_in': '🎯 Especializado em:',
199
+ 'metrics_analyzed': '📊 Métricas analisadas:',
200
+ 'what_analyzes': '🔍 O que analisa especificamente:',
201
+ 'tips': '💡 Dicas para melhores resultados:',
202
+ 'additional_specs': '📝 Especificações adicionais para a análise',
203
+ 'additional_specs_placeholder': 'Adicione requisitos específicos ou áreas de foco para a análise...'
204
+ }
205
+ }
206
+
207
+ # --- Temas de la Interfaz ---
208
+
209
+ THEMES = {
210
+ 'light': gr.themes.Soft(),
211
+ 'dark': gr.themes.Base(
212
+ primary_hue="blue",
213
+ secondary_hue="gray",
214
+ neutral_hue="gray",
215
+ font=["Arial", "sans-serif"]
216
+ ).set(
217
+ body_background_fill="dark",
218
+ body_background_fill_dark="*neutral_950",
219
+ button_primary_background_fill="*primary_600",
220
+ button_primary_background_fill_hover="*primary_500",
221
+ button_primary_text_color="white",
222
+ block_background_fill="*neutral_800",
223
+ block_border_color="*neutral_700",
224
+ block_label_text_color="*neutral_200",
225
+ block_title_text_color="*neutral_100",
226
+ checkbox_background_color="*neutral_700",
227
+ checkbox_background_color_selected="*primary_600",
228
+ input_background_fill="*neutral_700",
229
+ input_border_color="*neutral_600",
230
+ input_placeholder_color="*neutral_400"
231
+ )
232
  }
233
 
234
+ # --- Clases de Datos y Estructuras ---
235
+
236
+ class AnalysisType(Enum):
237
+ MATHEMATICAL_MODEL = "mathematical_model"
238
+ DATA_FITTING = "data_fitting"
239
+ FITTING_RESULTS = "fitting_results"
240
+ UNKNOWN = "unknown"
241
 
 
 
 
242
  @dataclass
243
+ class MathematicalModel:
244
+ name: str
245
+ equation: str
246
+ parameters: List[str]
247
+ application: str
248
+ sources: List[str]
249
+ category: str
250
+ biological_meaning: str
251
+
252
  class ModelRegistry:
253
+ def __init__(self):
254
+ self.models = {}
255
+ self._initialize_default_models()
256
+
257
  def register_model(self, model: MathematicalModel):
258
+ if model.category not in self.models:
259
+ self.models[model.category] = {}
260
  self.models[model.category][model.name] = model
261
+
262
+ def get_model(self, category: str, name: str) -> Optional[MathematicalModel]:
263
+ return self.models.get(category, {}).get(name)
264
+
265
+ def get_all_models(self) -> Dict:
266
+ return self.models
267
+
268
  def _initialize_default_models(self):
269
+ self.register_model(MathematicalModel(
270
+ name="Monod",
271
+ equation="μ = μmax × (S / (Ks + S))",
272
+ parameters=["μmax (h⁻¹)", "Ks (g/L)"],
273
+ application="Crecimiento limitado por sustrato único",
274
+ sources=["Cambridge", "MIT", "DTU"],
275
+ category="crecimiento_biomasa",
276
+ biological_meaning="Describe cómo la velocidad de crecimiento depende de la concentración de sustrato limitante"
277
+ ))
278
+
279
+ self.register_model(MathematicalModel(
280
+ name="Logístico",
281
+ equation="dX/dt = μmax × X × (1 - X/Xmax)",
282
+ parameters=["μmax (h⁻¹)", "Xmax (g/L)"],
283
+ application="Sistemas cerrados batch",
284
+ sources=["Cranfield", "Swansea", "HAL Theses"],
285
+ category="crecimiento_biomasa",
286
+ biological_meaning="Modela crecimiento limitado por capacidad de carga del sistema"
287
+ ))
288
+
289
+ self.register_model(MathematicalModel(
290
+ name="Gompertz",
291
+ equation="X(t) = Xmax × exp(-exp((μmax × e / Xmax) × (λ - t) + 1))",
292
+ parameters=["λ (h)", "μmax (h⁻¹)", "Xmax (g/L)"],
293
+ application="Crecimiento con fase lag pronunciada",
294
+ sources=["Lund University", "NC State"],
295
+ category="crecimiento_biomasa",
296
+ biological_meaning="Incluye fase de adaptación (lag) seguida de crecimiento exponencial y estacionario"
297
+ ))
298
+
299
  model_registry = ModelRegistry()
300
+
301
+ QWEN_MODELS = {
302
+ "Qwen/Qwen3-14B": {
303
+ "name": "Qwen 3 14B",
304
+ "description": "Modelo potente y versátil de la serie Qwen.",
305
+ "max_tokens": 4096,
306
+ "best_for": "Análisis complejos y generación de código detallado."
307
+ }
308
+ }
309
+
310
+ # --- Clases de Procesamiento y Exportación ---
311
+
312
  class FileProcessor:
313
  @staticmethod
314
+ def extract_text_from_pdf(pdf_file: bytes) -> str:
315
+ try:
316
+ pdf_reader = PyPDF2.PdfReader(io.BytesIO(pdf_file))
317
+ text = ""
318
+ for page in pdf_reader.pages:
319
+ page_text = page.extract_text()
320
+ if page_text:
321
+ text += page_text + "\n"
322
+ return text
323
+ except Exception as e:
324
+ return f"Error reading PDF: {str(e)}"
325
+
326
  @staticmethod
327
+ def read_csv(csv_file: bytes) -> Optional[pd.DataFrame]:
328
+ try:
329
+ return pd.read_csv(io.BytesIO(csv_file))
330
+ except Exception:
331
+ return None
332
+
333
+ @staticmethod
334
+ def read_excel(excel_file: bytes) -> Optional[pd.DataFrame]:
335
+ try:
336
+ return pd.read_excel(io.BytesIO(excel_file))
337
+ except Exception:
338
+ return None
339
+
340
+ @staticmethod
341
+ def extract_from_zip(zip_file: bytes) -> List[Tuple[str, bytes]]:
342
+ files = []
343
+ try:
344
+ with zipfile.ZipFile(io.BytesIO(zip_file), 'r') as zip_ref:
345
+ for file_name in zip_ref.namelist():
346
+ if not file_name.startswith('__MACOSX') and not file_name.endswith('/'):
347
+ file_data = zip_ref.read(file_name)
348
+ files.append((file_name, file_data))
349
+ except Exception as e:
350
+ print(f"Error processing ZIP: {e}")
351
+ return files
352
+
353
  class ReportExporter:
354
  @staticmethod
355
  def export_to_docx(content: str, filename: str, language: str = 'en') -> str:
356
  doc = Document()
357
+ title_style = doc.styles['Title']
358
+ title_style.font.size = Pt(24)
359
+ title_style.font.bold = True
360
+
361
+ heading1_style = doc.styles['Heading 1']
362
+ heading1_style.font.size = Pt(18)
363
+ heading1_style.font.bold = True
364
+
365
+ title_text = TRANSLATIONS[language]['title']
366
+ doc.add_heading(title_text, 0)
367
+
368
+ date_text = {'en': 'Generated on', 'es': 'Generado el'}
369
+ doc.add_paragraph(f"{date_text.get(language, date_text['en'])}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
370
+ doc.add_paragraph()
371
+
372
+ lines = content.split('\n')
373
+ for line in lines:
374
+ line = line.strip()
375
+ if line.startswith('###'):
376
+ doc.add_heading(line.replace('###', '').strip(), level=3)
377
+ elif line.startswith('##'):
378
+ doc.add_heading(line.replace('##', '').strip(), level=2)
379
+ elif line.startswith('#'):
380
+ doc.add_heading(line.replace('#', '').strip(), level=1)
381
+ elif line.startswith('**') and line.endswith('**'):
382
+ p = doc.add_paragraph()
383
+ p.add_run(line.replace('**', '')).bold = True
384
+ elif line.startswith('- ') or line.startswith('* '):
385
+ doc.add_paragraph(line[2:], style='List Bullet')
386
+ elif line:
387
+ doc.add_paragraph(line)
388
+
389
  doc.save(filename)
390
  return filename
391
+
392
  @staticmethod
393
  def export_to_pdf(content: str, filename: str, language: str = 'en') -> str:
394
  doc = SimpleDocTemplate(filename, pagesize=letter)
395
+ story = []
396
+ styles = getSampleStyleSheet()
397
+
398
+ title_style = ParagraphStyle('CustomTitle', parent=styles['Title'], fontSize=24, textColor=colors.HexColor('#1f4788'), spaceAfter=20)
399
+ heading_style = ParagraphStyle('CustomHeading1', parent=styles['Heading1'], fontSize=16, textColor=colors.HexColor('#2e5090'), spaceAfter=12)
400
+
401
+ title_text = TRANSLATIONS[language]['title']
402
+ story.append(Paragraph(title_text, title_style))
403
+
404
+ date_text = {'en': 'Generated on', 'es': 'Generado el'}
405
+ story.append(Paragraph(f"{date_text.get(language, date_text['en'])}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", styles['Normal']))
406
+ story.append(Spacer(1, 0.3 * inch))
407
+
408
+ lines = content.split('\n')
409
+ for line in lines:
410
+ line = line.strip()
411
+ if not line:
412
+ continue
413
+ elif line.startswith('###'):
414
+ story.append(Paragraph(line.replace('###', '').strip(), styles['Heading3']))
415
+ elif line.startswith('##'):
416
+ story.append(Paragraph(line.replace('##', '').strip(), styles['Heading2']))
417
+ elif line.startswith('#'):
418
+ story.append(Paragraph(line.replace('#', '').strip(), heading_style))
419
+ elif line.startswith('**') and line.endswith('**'):
420
+ story.append(Paragraph(f"<b>{line.replace('**', '')}</b>", styles['Normal']))
421
+ elif line.startswith('- ') or line.startswith('* '):
422
+ story.append(Paragraph(f"• {line[2:]}", styles['Normal'], bulletText='•'))
423
+ else:
424
+ # Limpiar caracteres que pueden dar problemas en ReportLab
425
+ clean_line = line.replace('🧬', '[DNA]').replace('🤖', '[BOT]').replace('📁', '[FILE]').replace('🚀', '[ROCKET]').replace('📊', '[GRAPH]').replace('💻', '[CODE]').replace('💾', '[SAVE]').replace('🎯', '[TARGET]').replace('🔍', '[SEARCH]').replace('💡', '[TIP]')
426
+ story.append(Paragraph(clean_line, styles['Normal']))
427
+ story.append(Spacer(1, 0.1 * inch))
428
+
429
+ doc.build(story)
430
  return filename
431
 
432
+ # --- Clase del Analizador de IA con Qwen ---
433
+
434
  class AIAnalyzer:
435
+ def __init__(self, client, model_registry):
436
  self.client = client
437
  self.model_registry = model_registry
438
 
439
  def get_language_prompt_prefix(self, language: str) -> str:
440
+ return TRANSLATIONS.get(language, TRANSLATIONS['en']).get('response_prefix', f"Please respond in {language}. ")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
 
442
+ def analyze_fitting_results(self, data: pd.DataFrame, qwen_model: str, detail_level: str, language: str, additional_specs: str) -> Dict:
443
+ if self.client is None:
444
+ return {"error": "AI client not initialized. Check NEBIUS_API_KEY."}
445
 
446
+ data_summary = f"FITTING RESULTS DATA:\n- Columns: {list(data.columns)}\n- Number of models/entries: {len(data)}\n- Full Data:\n{data.to_string()}"
447
+ lang_prefix = self.get_language_prompt_prefix(language)
448
+ user_specs_section = f"\nADDITIONAL USER SPECIFICATIONS: {additional_specs}\n" if additional_specs else ""
449
+
450
+ if detail_level == "detailed":
451
+ prompt = f"""{lang_prefix}{user_specs_section}
452
+ You are an expert biotechnologist and data scientist. Analyze the provided model fitting results in detail.
453
+ 1. **Overall Summary:** Briefly describe the dataset (number of experiments, models, metrics).
454
+ 2. **Analysis by Experiment/Condition:** For each unique experiment/condition found in the data:
455
+ - Identify the best performing model for each category (e.g., Biomass, Substrate, Product) based on R2 (higher is better) and RMSE (lower is better).
456
+ - List the best model's name, its key metrics (R2, RMSE), and its fitted parameters.
457
+ - Provide a brief biological interpretation of the parameters for the best model in that context.
458
+ 3. **Overall Best Models:** Identify the most robust models across all experiments for each category. Justify your choice (e.g., "Model X was best in 3 out of 4 experiments").
459
+ 4. **Parameter Trends:** If possible, comment on how key parameters (like μmax, Ks) change across different experimental conditions.
460
+ 5. **Conclusion and Recommendations:** Summarize the findings and recommend which models to use for future predictions under specific conditions.
461
+ Format the entire response using clear Markdown headers, lists, and bold text."""
462
+ else: # summarized
463
+ prompt = f"""{lang_prefix}{user_specs_section}
464
+ You are an expert biotechnologist. Provide a concise, summarized analysis of the provided model fitting results.
465
+ 1. **Best Models Summary Table:** Create a table that shows the best model for each Experiment and Type (Biomass, Substrate, etc.) along with its R2 value.
466
+ 2. **Overall Winners:** State the single best model for Biomass, Substrate, and Product across all experiments.
467
+ 3. **Key Insights:** In 2-3 bullet points, what are the most important findings? (e.g., "Gompertz model consistently outperforms others for biomass.", "μmax is highest at pH 7.5.").
468
+ Format as a brief and clear Markdown report."""
469
 
470
  try:
471
+ # Análisis principal
472
  analysis_response = self.client.chat.completions.create(
473
+ model=qwen_model,
474
+ messages=[{"role": "user", "content": f"{prompt}\n\n{data_summary}"}],
475
  max_tokens=4000,
476
  temperature=0.6,
477
+ top_p=0.95
478
  )
479
  analysis_text = analysis_response.choices[0].message.content
480
 
481
+ # Generación de código
482
+ code_prompt = f"""{lang_prefix}
483
+ Based on the provided data, generate a single, complete, and executable Python script for analysis.
484
+ The script must:
485
+ 1. Contain the provided data hardcoded into a pandas DataFrame.
486
+ 2. Define functions to analyze the data to find the best model per experiment and type.
487
+ 3. Include functions to create visualizations using matplotlib or seaborn, such as:
488
+ - A bar chart comparing the R2 values of the best models across experiments.
489
+ - A summary table of the best models.
490
+ 4. Have a `if __name__ == "__main__":` block that runs the analysis and shows the plots.
491
+ The code should be well-commented and self-contained."""
492
+
493
  code_response = self.client.chat.completions.create(
494
+ model=qwen_model,
495
+ messages=[{"role": "user", "content": f"{code_prompt}\n\n{data_summary}"}],
496
+ max_tokens=3500,
497
+ temperature=0.4, # Más determinista para código
498
+ top_p=0.95
499
  )
500
  code_text = code_response.choices[0].message.content
 
 
 
 
 
 
501
 
502
  return {
503
  "analisis_completo": analysis_text,
504
  "codigo_implementacion": code_text,
505
  }
 
506
  except Exception as e:
507
+ return {"error": f"An error occurred with the AI API: {str(e)}"}
508
 
509
+ # --- Lógica de la Aplicación ---
 
 
 
 
 
510
 
511
+ def process_files(files, model_name: str, detail_level: str, language: str, additional_specs: str) -> Tuple[str, str]:
512
  if not files:
513
+ return TRANSLATIONS[language]['error_no_files'], "Please upload files first."
514
+ if client is None:
515
+ return TRANSLATIONS[language]['error_no_api'], "AI client is not configured."
516
 
517
+ analyzer = AIAnalyzer(client, model_registry)
518
+ all_analysis_parts = []
519
+ all_code_parts = []
 
 
 
 
 
 
 
520
 
521
+ for file in files:
522
+ try:
523
+ with open(file.name, 'rb') as f:
524
+ file_content = f.read()
525
+
526
+ file_name = os.path.basename(file.name)
527
+ file_ext = Path(file_name).suffix.lower()
528
+ df = None
529
+
530
+ if file_ext == '.csv':
531
+ df = FileProcessor.read_csv(file_content)
532
+ elif file_ext in ['.xlsx', '.xls']:
533
+ df = FileProcessor.read_excel(file_content)
534
+
535
  if df is not None:
536
+ all_analysis_parts.append(f"# Analysis for: {file_name}")
537
+ result = analyzer.analyze_fitting_results(df, model_name, detail_level, language, additional_specs)
538
 
539
  if "error" in result:
540
+ all_analysis_parts.append(f"An error occurred: {result['error']}")
541
  else:
542
+ all_analysis_parts.append(result.get("analisis_completo", "No analysis generated."))
543
+ all_code_parts.append(f"# Code generated from: {file_name}\n{result.get('codigo_implementacion', '# No code generated.')}")
 
544
  else:
545
+ all_analysis_parts.append(f"# Could not process file: {file_name}")
546
+ except Exception as e:
547
+ all_analysis_parts.append(f"# Error processing {file.name}: {str(e)}")
 
 
548
 
549
+ final_analysis = "\n\n---\n\n".join(all_analysis_parts)
550
+ final_code = "\n\n" + "="*80 + "\n\n".join(all_code_parts) if all_code_parts else generate_implementation_code(final_analysis)
551
+
552
  return final_analysis, final_code
553
 
 
554
 
555
+ def generate_implementation_code(analysis_results: str) -> str:
556
+ # This is a fallback in case the API fails to generate code.
557
+ # It provides a generic template.
558
+ return """
559
+ import pandas as pd
560
+ import matplotlib.pyplot as plt
561
+ import seaborn as sns
562
+
563
+ def analyze_data(df):
564
+ \"\"\"
565
+ Analyzes the dataframe to find the best model per experiment and type.
566
+ This is a template function. You may need to adapt it to your specific column names.
567
+ \"\"\"
568
+ # Assuming columns 'Experiment', 'Type', 'R2', 'Model' exist
569
+ if not all(col in df.columns for col in ['Experiment', 'Type', 'R2', 'Model']):
570
+ print("DataFrame is missing required columns: 'Experiment', 'Type', 'R2', 'Model'")
571
+ return None
572
+
573
+ # Find the index of the max R2 for each group
574
+ best_models_idx = df.loc[df.groupby(['Experiment', 'Type'])['R2'].idxmax()]
575
+
576
+ print("--- Best Models by Experiment and Type ---")
577
+ print(best_models_idx[['Experiment', 'Type', 'Model', 'R2']].to_string(index=False))
578
+ return best_models_idx
579
+
580
+ def visualize_results(best_models_df):
581
+ \"\"\"
582
+ Creates a bar plot to visualize the R2 scores of the best models.
583
+ \"\"\"
584
+ if best_models_df is None:
585
+ print("Cannot visualize results. Analysis failed.")
586
+ return
587
+
588
+ plt.figure(figsize=(12, 7))
589
+ sns.barplot(data=best_models_df, x='Experiment', y='R2', hue='Type', palette='viridis')
590
+
591
+ plt.title('Best Model Performance (R²) by Experiment and Type', fontsize=16)
592
+ plt.xlabel('Experiment', fontsize=12)
593
+ plt.ylabel('R² Score', fontsize=12)
594
+ plt.xticks(rotation=45, ha='right')
595
+ plt.ylim(bottom=max(0, best_models_df['R2'].min() - 0.05), top=1.0)
596
+ plt.legend(title='Variable Type')
597
+ plt.tight_layout()
598
+ plt.show()
599
+
600
+ if __name__ == '__main__':
601
+ # TODO: Replace this with your actual data
602
+ # This is placeholder data.
603
+ data = {
604
+ 'Experiment': ['pH_7.0', 'pH_7.0', 'pH_7.5', 'pH_7.5', 'pH_7.0', 'pH_7.5'],
605
+ 'Model': ['Monod', 'Logistic', 'Monod', 'Logistic', 'FirstOrder', 'FirstOrder'],
606
+ 'Type': ['Biomass', 'Biomass', 'Biomass', 'Biomass', 'Substrate', 'Substrate'],
607
+ 'R2': [0.98, 0.99, 0.97, 0.96, 0.95, 0.99],
608
+ 'RMSE': [0.1, 0.05, 0.12, 0.15, 0.2, 0.08]
609
+ }
610
+ df = pd.DataFrame(data)
611
+
612
+ print("--- Input Data ---")
613
+ print(df)
614
+ print("\\n")
615
+
616
+ best_models = analyze_data(df)
617
+ visualize_results(best_models)
618
+ """
619
+
620
  class AppState:
621
+ def __init__(self):
622
+ self.current_analysis = ""
623
+ self.current_code = ""
624
+ self.current_language = "en"
625
+
626
  app_state = AppState()
627
+
628
+ def export_report(export_format: str, language: str) -> Tuple[str, Optional[str]]:
629
+ if not app_state.current_analysis:
630
+ error_msg = TRANSLATIONS[language]['error_no_files'].replace('analizar', 'exportar')
631
+ return error_msg, None
632
+
633
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
634
+ try:
635
+ if export_format == "DOCX":
636
+ filename = f"biotech_analysis_report_{timestamp}.docx"
637
+ ReportExporter.export_to_docx(app_state.current_analysis, filename, language)
638
+ else: # PDF
639
+ filename = f"biotech_analysis_report_{timestamp}.pdf"
640
+ ReportExporter.export_to_pdf(app_state.current_analysis, filename, language)
641
+
642
+ success_msg = TRANSLATIONS[language]['report_exported']
643
+ return f"{success_msg} {filename}", filename
644
+ except Exception as e:
645
+ return f"Error during export: {str(e)}", None
646
+
647
+ # --- Interfaz de Gradio ---
648
 
649
  def create_interface():
650
  current_language = "en"
651
+
652
+ def update_interface_language(language: str):
653
  app_state.current_language = language
654
  t = TRANSLATIONS[language]
655
+ return [
656
+ gr.update(value=f"# {t['title']}"),
657
+ gr.update(value=t['subtitle']),
658
+ gr.update(label=t['upload_files']),
659
+ gr.update(label=t['select_model']),
660
+ gr.update(label=t['select_language']),
661
+ gr.update(label=t['select_theme']),
662
+ gr.update(label=t['detail_level'], choices=[(t['detailed'], "detailed"), (t['summarized'], "summarized")]),
663
+ gr.update(label=t['additional_specs'], placeholder=t['additional_specs_placeholder']),
664
+ gr.update(value=t['analyze_button']),
665
+ gr.update(label=t['export_format']),
666
+ gr.update(value=t['export_button']),
667
+ gr.update(label=t['comparative_analysis']),
668
+ gr.update(label=t['implementation_code']),
669
+ gr.update(label=t['data_format']),
670
+ gr.update(label=t['examples'])
671
+ ]
672
 
673
  def process_and_store(files, model, detail, language, additional_specs):
674
+ if not files:
675
+ return TRANSLATIONS[language]['error_no_files'], ""
676
+
677
  analysis, code = process_files(files, model, detail, language, additional_specs)
678
  app_state.current_analysis = analysis
679
  app_state.current_code = code
680
  return analysis, code
681
 
682
  with gr.Blocks(theme=THEMES['light']) as demo:
683
+ # Estructura de la UI
684
+ with gr.Column():
685
+ with gr.Row():
686
+ with gr.Column(scale=3):
687
+ title_text = gr.Markdown(f"# {TRANSLATIONS[current_language]['title']}")
688
+ subtitle_text = gr.Markdown(TRANSLATIONS[current_language]['subtitle'])
689
+ with gr.Column(scale=1):
690
+ language_selector = gr.Dropdown(choices=[("English", "en"), ("Español", "es"), ("Français", "fr"), ("Deutsch", "de"), ("Português", "pt")], value=current_language, label="Language", interactive=True)
691
+ theme_selector = gr.Dropdown(choices=["Light", "Dark"], value="Light", label="Theme", interactive=True, visible=False) # Theme switching is complex, hiding for now
692
+
693
+ with gr.Row():
694
+ with gr.Column(scale=1):
695
+ files_input = gr.File(label=TRANSLATIONS[current_language]['upload_files'], file_count="multiple", file_types=[".csv", ".xlsx", ".xls"], type="filepath")
696
+ model_selector = gr.Dropdown(choices=list(QWEN_MODELS.keys()), value="Qwen/Qwen3-14B", label=TRANSLATIONS[current_language]['select_model'], info=QWEN_MODELS["Qwen/Qwen3-14B"]['best_for'])
697
+ detail_level = gr.Radio(choices=[(TRANSLATIONS[current_language]['detailed'], "detailed"), (TRANSLATIONS[current_language]['summarized'], "summarized")], value="detailed", label=TRANSLATIONS[current_language]['detail_level'])
698
+ additional_specs = gr.Textbox(label=TRANSLATIONS[current_language]['additional_specs'], placeholder=TRANSLATIONS[current_language]['additional_specs_placeholder'], lines=3, max_lines=5, interactive=True)
699
+ analyze_btn = gr.Button(TRANSLATIONS[current_language]['analyze_button'], variant="primary", size="lg")
700
+
701
+ gr.Markdown("---")
702
+
703
+ export_format = gr.Radio(choices=["PDF", "DOCX"], value="PDF", label=TRANSLATIONS[current_language]['export_format'])
704
+ export_btn = gr.Button(TRANSLATIONS[current_language]['export_button'], variant="secondary")
705
+ export_status = gr.Textbox(label="Export Status", interactive=False, visible=False)
706
+ export_file = gr.File(label="Download Report", visible=False)
707
+
708
+ with gr.Column(scale=2):
709
+ analysis_output = gr.Markdown(label=TRANSLATIONS[current_language]['comparative_analysis'])
710
+ code_output = gr.Code(label=TRANSLATIONS[current_language]['implementation_code'], language="python", interactive=True, lines=20)
711
+
712
+ with gr.Accordion(label=TRANSLATIONS[current_language]['data_format'], open=False) as data_format_accordion:
713
+ gr.Markdown("""
714
+ ### Expected CSV/Excel Structure:
715
+ The file should contain columns representing your model fitting results. Essential columns are:
716
+ - **Experiment**: An identifier for the experimental condition (e.g., `pH_7.0`, `Temp_30C`).
717
+ - **Model**: The name of the mathematical model (e.g., `Monod`, `Logistic`).
718
+ - **Type**: The type of process being modeled (e.g., `Biomass`, `Substrate`, `Product`). This is crucial for categorical analysis.
719
+ - **R2 / R_squared**: The coefficient of determination. Higher is better.
720
+ - **RMSE**: Root Mean Squared Error. Lower is better.
721
+ - **[Parameter_Columns]**: Additional columns for each model parameter (e.g., `mu_max`, `Ks`, `Xmax`).
722
+
723
+ | Experiment | Model | Type | R2 | RMSE | mu_max |
724
+ |:-----------|:---------|:----------|------:|-------:|--------:|
725
+ | pH_7.0 | Monod | Biomass | 0.985 | 0.023 | 0.45 |
726
+ | pH_7.0 | Logistic | Biomass | 0.991 | 0.018 | 0.48 |
727
+ | pH_7.5 | Monod | Biomass | 0.978 | 0.027 | 0.43 |
728
+ """)
729
+
730
+ examples_ui = gr.Examples(
731
+ examples=[
732
+ [["examples/biomass_models_comparison.csv"], "Qwen/Qwen3-14B", "detailed", ""],
733
+ [["examples/substrate_kinetics_results.xlsx"], "Qwen/Qwen3-14B", "summarized", "Focus on temperature effects and which temp is optimal."]
734
+ ],
735
+ inputs=[files_input, model_selector, detail_level, additional_specs],
736
+ label=TRANSLATIONS[current_language]['examples']
737
+ )
738
+
739
+ # Event Handlers
740
+ language_selector.change(
741
+ update_interface_language,
742
+ inputs=[language_selector],
743
+ outputs=[title_text, subtitle_text, files_input, model_selector, language_selector, theme_selector, detail_level, additional_specs, analyze_btn, export_format, export_btn, analysis_output, code_output, data_format_accordion, examples_ui]
744
+ )
745
 
746
+ analyze_btn.click(
747
+ fn=process_and_store,
748
+ inputs=[files_input, model_selector, detail_level, language_selector, additional_specs],
749
+ outputs=[analysis_output, code_output],
750
+ api_name="analyze"
751
+ )
752
+
753
+ def handle_export(fmt, lang):
754
+ status, file_path = export_report(fmt, lang)
755
+ if file_path:
756
+ return gr.update(value=status, visible=True), gr.update(value=file_path, visible=True)
757
+ else:
758
+ return gr.update(value=status, visible=True), gr.update(visible=False)
759
+
760
+ export_btn.click(
761
+ fn=handle_export,
762
+ inputs=[export_format, language_selector],
763
+ outputs=[export_status, export_file]
764
+ )
765
 
766
  return demo
767
 
768
+ # --- Punto de Entrada Principal ---
769
+
770
  def main():
771
+ if client is None:
772
+ return gr.Interface(
773
+ fn=lambda: TRANSLATIONS['en']['error_no_api'],
774
+ inputs=[],
775
+ outputs=gr.Textbox(label="Error"),
776
+ title="Configuration Error"
777
+ )
778
  return create_interface()
779
 
780
  if __name__ == "__main__":
781
+ # Crear archivos y carpetas de ejemplo si no existen para que la UI no falle
782
+ if not os.path.exists("examples"):
783
+ os.makedirs("examples")
784
+ if not os.path.exists("examples/biomass_models_comparison.csv"):
785
+ pd.DataFrame({
786
+ 'Experiment': ['Exp1_pH7', 'Exp1_pH7', 'Exp2_pH8', 'Exp2_pH8'],
787
+ 'Model': ['Monod', 'Logistic', 'Monod', 'Logistic'],
788
+ 'Type': ['Biomass', 'Biomass', 'Biomass', 'Biomass'],
789
+ 'R2': [0.98, 0.99, 0.97, 0.96],
790
+ 'RMSE': [0.1, 0.05, 0.12, 0.15],
791
+ 'mu_max': [0.5, 0.52, 0.45, 0.46]
792
+ }).to_csv("examples/biomass_models_comparison.csv", index=False)
793
+ if not os.path.exists("examples/substrate_kinetics_results.xlsx"):
794
+ pd.DataFrame({
795
+ 'Experiment': ['T30C', 'T30C', 'T37C', 'T37C'],
796
+ 'Model': ['FirstOrder', 'MichaelisMenten', 'FirstOrder', 'MichaelisMenten'],
797
+ 'Type': ['Substrate', 'Substrate', 'Substrate', 'Substrate'],
798
+ 'R2': [0.95, 0.94, 0.99, 0.98],
799
+ 'RMSE': [0.2, 0.25, 0.08, 0.1],
800
+ 'Ks': [None, 1.5, None, 1.2]
801
+ }).to_excel("examples/substrate_kinetics_results.xlsx", index=False)
802
+
803
  demo = main()
804
  if demo:
805
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=False)