Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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
|
22 |
-
from reportlab.platypus import SimpleDocTemplate,
|
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
|
|
|
|
|
31 |
os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
|
32 |
|
33 |
-
# Inicializar
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
|
39 |
-
# Sistema de traducción (sin cambios)
|
40 |
TRANSLATIONS = {
|
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 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
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:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
class ModelRegistry:
|
112 |
-
def __init__(self):
|
|
|
|
|
|
|
113 |
def register_model(self, model: MathematicalModel):
|
114 |
-
if model.category not in self.models:
|
|
|
115 |
self.models[model.category][model.name] = model
|
116 |
-
|
117 |
-
def
|
|
|
|
|
|
|
|
|
|
|
118 |
def _initialize_default_models(self):
|
119 |
-
self.register_model(MathematicalModel(
|
120 |
-
|
121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
model_registry = ModelRegistry()
|
123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
class FileProcessor:
|
125 |
@staticmethod
|
126 |
-
def
|
127 |
-
try:
|
128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
@staticmethod
|
130 |
-
def
|
131 |
-
try:
|
132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
133 |
class ReportExporter:
|
134 |
@staticmethod
|
135 |
def export_to_docx(content: str, filename: str, language: str = 'en') -> str:
|
136 |
doc = Document()
|
137 |
-
doc.
|
138 |
-
|
139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
147 |
return filename
|
148 |
|
|
|
|
|
149 |
class AIAnalyzer:
|
150 |
-
def __init__(self, client
|
151 |
self.client = client
|
152 |
self.model_registry = model_registry
|
153 |
|
154 |
def get_language_prompt_prefix(self, language: str) -> str:
|
155 |
-
|
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 |
-
|
|
|
|
|
172 |
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
190 |
|
191 |
try:
|
192 |
-
#
|
193 |
analysis_response = self.client.chat.completions.create(
|
194 |
-
model=
|
|
|
195 |
max_tokens=4000,
|
196 |
temperature=0.6,
|
197 |
-
|
198 |
)
|
199 |
analysis_text = analysis_response.choices[0].message.content
|
200 |
|
201 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
code_response = self.client.chat.completions.create(
|
203 |
-
model=
|
204 |
-
|
205 |
-
|
206 |
-
|
|
|
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 |
-
|
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 |
-
|
235 |
-
|
236 |
-
|
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 |
-
|
|
|
247 |
|
248 |
if "error" in result:
|
249 |
-
|
250 |
else:
|
251 |
-
|
252 |
-
#
|
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 |
-
|
256 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
266 |
class AppState:
|
267 |
-
def __init__(self):
|
|
|
|
|
|
|
|
|
268 |
app_state = AppState()
|
269 |
-
|
270 |
-
|
271 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
#
|
289 |
-
with gr.
|
290 |
-
with gr.
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
296 |
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
|
|
|
|
314 |
|
315 |
return demo
|
316 |
|
|
|
|
|
317 |
def main():
|
318 |
-
if
|
319 |
-
|
320 |
-
|
|
|
|
|
|
|
|
|
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)
|