JairoCesar commited on
Commit
8d8dc0f
·
verified ·
1 Parent(s): 3d689e2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +40 -189
app.py CHANGED
@@ -136,171 +136,42 @@ CONDITION_SYNONYMS = {
136
 
137
  FOOD_NAME_TO_FOODB_KEY = {
138
  # --- CEREALES Y GRANOS ---
139
- "alforfón": ["buckwheat"],
140
- "arroz": ["rice"],
141
- "avena": ["oat", "oats"],
142
- "cebada": ["barley"],
143
- "centeno": ["rye"],
144
- "galleta": ["cookie", "biscuit"],
145
- "maíz": ["corn", "maize"],
146
- "pan": ["bread"],
147
- "pasta": ["pasta"],
148
- "pizza": ["pizza"],
149
- "quinoa": ["quinoa"],
150
- "trigo": ["wheat"],
151
- "trigo sarraceno": ["buckwheat"],
152
-
153
  # --- LÁCTEOS Y DERIVADOS ---
154
- "crema": ["cream"],
155
- "helado": ["ice cream"],
156
- "leche": ["milk"],
157
- "mantequilla": ["butter"],
158
- "queso": ["cheese"],
159
- "yogur": ["yogurt", "yoghurt"],
160
-
161
  # --- VEGETALES ---
162
- "acelga": ["chard", "swiss chard"],
163
- "ajo": ["garlic"],
164
- "alcachofa": ["artichoke"],
165
- "apio": ["celery"],
166
- "berenjena": ["eggplant", "aubergine"],
167
- "brócoli": ["broccoli"],
168
- "calabacín": ["zucchini", "courgette"],
169
- "calabaza": ["pumpkin", "squash"],
170
- "cebolla": ["onion"],
171
- "champiñón": ["mushroom"],
172
- "col": ["cabbage"],
173
- "coliflor": ["cauliflower"],
174
- "edamame": ["edamame"],
175
- "espárrago": ["asparagus"],
176
- "espinaca": ["spinach"],
177
- "garbanzo": ["chickpea"],
178
- "guisante": ["pea", "peas"],
179
- "frijol": ["bean", "beans"],
180
- "lenteja": ["lentil"],
181
- "patata": ["potato"],
182
- "pepino": ["cucumber"],
183
- "pimiento": ["bell pepper", "pepper"],
184
- "remolacha": ["beet", "beetroot"],
185
- "repollo": ["cabbage"],
186
- "seta": ["mushroom"],
187
- "soja": ["soy", "soybean"],
188
- "tofu": ["tofu"],
189
- "tomate": ["tomato"],
190
- "zanahoria": ["carrot"],
191
-
192
  # --- FRUTAS ---
193
- "aguacate": ["avocado"],
194
- "albaricoque": ["apricot"],
195
- "arándano": ["blueberry"],
196
- "cereza": ["cherry"],
197
- "ciruela": ["plum"],
198
- "dátil": ["date"],
199
- "frambuesa": ["raspberry"],
200
- "fresa": ["strawberry"],
201
- "higo": ["fig"],
202
- "kiwi": ["kiwi", "kiwifruit"],
203
- "limón": ["lemon"],
204
- "mandarina": ["tangerine", "mandarin"],
205
- "mango": ["mango"],
206
- "manzana": ["apple"],
207
- "melocotón": ["peach"],
208
- "melón": ["melon", "cantaloupe"],
209
- "mora": ["blackberry"],
210
- "naranja": ["orange"],
211
- "nectarina": ["nectarine"],
212
- "papaya": ["papaya"],
213
- "pera": ["pear"],
214
- "piña": ["pineapple"],
215
- "plátano": ["banana"],
216
- "pomelo": ["grapefruit"],
217
- "sandía": ["watermelon"],
218
- "uva": ["grape"],
219
-
220
  # --- PROTEÍNAS (CARNES, PESCADOS, HUEVOS) ---
221
- "anchoa": ["anchovy", "anchovies"],
222
- "atún": ["tuna"],
223
- "camarón": ["shrimp", "prawn"],
224
- "carne": ["meat", "beef", "pork", "lamb"],
225
- "cerdo": ["pork"],
226
- "cordero": ["lamb"],
227
- "gamba": ["shrimp", "prawn"],
228
- "huevo": ["egg"],
229
- "marisco": ["shellfish", "seafood"],
230
- "pavo": ["turkey"],
231
- "pescado": ["fish"],
232
- "pollo": ["chicken"],
233
- "salchicha": ["sausage"],
234
- "salmón": ["salmon"],
235
- "sardina": ["sardine"],
236
- "ternera": ["beef", "veal"],
237
-
238
  # --- FRUTOS SECOS Y SEMILLAS ---
239
- "almendra": ["almond"],
240
- "anacardo": ["cashew"],
241
- "avellana": ["hazelnut"],
242
- "cacahuete": ["peanut"],
243
- "chía": ["chia", "chia seed"],
244
- "lino": ["flax", "flaxseed", "linseed"],
245
- "nuez": ["walnut"],
246
- "pistacho": ["pistachio"],
247
- "sésamo": ["sesame", "sesame seed"],
248
-
249
  # --- BEBIDAS, DULCES Y CONDIMENTOS ---
250
- "aceituna": ["olive"],
251
- "café": ["coffee"],
252
- "caldo": ["broth", "stock"],
253
- "cerveza": ["beer"],
254
- "chocolate": ["chocolate"],
255
- "jengibre": ["ginger"],
256
- "cúrcuma": ["turmeric"],
257
- "miel": ["honey"],
258
- "mostaza": ["mustard"],
259
- "té": ["tea"],
260
- "vinagre": ["vinegar"],
261
- "vino": ["wine", "red wine", "white wine"]
262
  }
263
 
264
- # --- AÑADE ESTA FUNCIÓN NUEVA EN TU CÓDIGO ---
265
-
266
  def reinforce_entities_with_keywords(entities, query, food_map):
267
- """
268
- Revisa la salida de la IA y añade alimentos clave si fueron omitidos.
269
- Esta es una red de seguridad contra las fallas de extracción de la IA.
270
- """
271
  if not entities:
272
  entities = {"alimentos": [], "sintomas": []}
273
-
274
  query_sanitized = sanitize_text(query)
275
  current_foods = entities.get("alimentos", [])
276
-
277
- # Si la IA ya encontró alimentos, los sanitizamos para la comparación
278
  current_foods_sanitized = {sanitize_text(f) for f in current_foods}
279
-
280
- # Iteramos por nuestra lista de alimentos conocidos
281
  for food_keyword in food_map.keys():
282
- # Si una palabra clave de alimento está en la consulta...
283
  if food_keyword in query_sanitized:
284
- # ...y la IA NO la incluyó en su lista...
285
  if food_keyword not in current_foods_sanitized:
286
- # ...la añadimos nosotros.
287
  logger.info(f"Red de seguridad: La IA omitió '{food_keyword}', añadiéndolo manualmente.")
288
  current_foods.append(food_keyword)
289
-
290
- entities["alimentos"] = list(set(current_foods)) # Eliminar duplicados
291
  return entities
292
 
293
-
294
- # --- LÓGICA DE BÚSQUEDA Y ANÁLISIS ---
295
- # (Todas las funciones auxiliares se mantienen igual que en la versión anterior)
296
  def sanitize_text(text):
297
  if not text: return ""
298
  return re.sub(r'[.,;()]', '', text).lower().strip()
 
299
  def extract_and_infer_with_gemini(query, condiciones):
300
  if not model: return None
301
  condiciones_str = "\n".join([f"- {c}" for c in condiciones])
302
-
303
- # Prompt mejorado para ser más estricto
304
  system_prompt = f"""
305
  Eres un asistente de triaje clínico experto. Tu tarea es analizar la consulta de un usuario y extraer tres tipos de información:
306
  1. `alimentos`: Lista de alimentos consumidos.
@@ -331,31 +202,23 @@ def extract_and_infer_with_gemini(query, condiciones):
331
 
332
  def find_best_matches_hybrid(entities, data):
333
  if not entities or not data: return []
334
-
335
  user_symptoms = set(sanitize_text(s) for s in entities.get("sintomas", []))
336
  user_foods = set(sanitize_text(f) for f in entities.get("alimentos", []))
337
  inferred_condition_raw = sanitize_text(entities.get("condicion_probable", ""))
338
-
339
  candidate_terms = set(user_foods)
340
  for food in user_foods:
341
  food_sanitized = sanitize_text(food)
342
  if food_sanitized in FOOD_TO_COMPOUND_MAP:
343
  candidate_terms.update(c.lower() for c in FOOD_TO_COMPOUND_MAP[food_sanitized])
344
- # Añadir también los componentes del vino directamente para ser más robusto
345
  if food_sanitized in ["vino", "vino tinto", "vino rojo"]:
346
  candidate_terms.update(["histamina", "tiramina", "sulfitos"])
347
-
348
  results = []
349
  for entry in data:
350
  entry_compounds_text = sanitize_text(entry.get("compuesto_alimento", ""))
351
  food_match = any(term in entry_compounds_text for term in candidate_terms)
352
-
353
  if not food_match:
354
  continue
355
-
356
- # Inicializamos el diccionario SIN el total
357
  score_details = {'condition': 0, 'food': 15, 'symptoms': 0}
358
-
359
  if inferred_condition_raw:
360
  entry_condition_sanitized = sanitize_text(entry.get("condicion_asociada", ""))
361
  is_match = (entry_condition_sanitized == inferred_condition_raw)
@@ -364,7 +227,6 @@ def find_best_matches_hybrid(entities, data):
364
  is_match = True
365
  if is_match:
366
  score_details['condition'] = 100
367
-
368
  entry_symptoms_keys = set(sanitize_text(s) for s in entry.get("sintomas_clave", []))
369
  symptom_score = 0
370
  matched_symptoms = []
@@ -375,24 +237,13 @@ def find_best_matches_hybrid(entities, data):
375
  matched_symptoms.append(key)
376
  break
377
  score_details['symptoms'] = symptom_score
378
-
379
- # --- LÍNEA CORREGIDA Y CRÍTICA ---
380
- # Calculamos la puntuación total y la añadimos al diccionario
381
  score_details['total'] = score_details['condition'] + score_details['food'] + score_details['symptoms']
382
-
383
  results.append({
384
  'entry': entry,
385
  'score': score_details,
386
  'matched_symptoms': list(set(matched_symptoms))
387
  })
388
-
389
  if not results: return []
390
-
391
- # Ahora el ordenamiento funcionará correctamente porque 'total' tiene el valor correcto
392
- sorted_results = sorted(results, key=lambda x: x['score']['total'], reverse=True)
393
- return sorted_results
394
-
395
- # 4. Ordenar y devolver (sin cambios)
396
  sorted_results = sorted(results, key=lambda x: x['score']['total'], reverse=True)
397
  return sorted_results
398
 
@@ -435,7 +286,6 @@ def generate_detailed_analysis(query, match):
435
  logger.error(f"Error generando análisis detallado con Gemini: {e}")
436
  return "No se pudo generar el análisis detallado."
437
  def create_relevance_chart(results):
438
- # ... (Sin cambios)
439
  top_results = results[:5]
440
  condition_names = [re.sub(r'\(.*\)', '', res['entry']['condicion_asociada']).strip() for res in top_results]
441
  chart_data = {"Condición": condition_names, "Relevancia": [res['score']['total'] for res in top_results]}
@@ -447,7 +297,6 @@ def create_relevance_chart(results):
447
  ).properties(title='Principales Coincidencias según tu Caso').configure_axis(labelFontSize=12, titleFontSize=14).configure_title(fontSize=16, anchor='start')
448
  return chart
449
  def generate_report_text(query, results):
450
- # ... (Sin cambios)
451
  report_lines = []
452
  report_lines.append("="*50)
453
  report_lines.append("INFORME DEL DETECTIVE DE ALIMENTOS")
@@ -472,7 +321,6 @@ def generate_report_text(query, results):
472
  return "\n".join(report_lines)
473
 
474
  # --- INTERFAZ DE USUARIO Y LÓGICA PRINCIPAL ---
475
- # --- BLOQUE DE ENCABEZADO MODIFICADO ---
476
  col_img1, col_text, col_img2 = st.columns([1, 4, 1], gap="medium")
477
  with col_img1:
478
  if os.path.exists("imagen.png"):
@@ -491,6 +339,8 @@ if 'user_query' not in st.session_state: st.session_state.user_query = ""
491
  if 'entities' not in st.session_state: st.session_state.entities = None
492
  if 'analysis_cache' not in st.session_state: st.session_state.analysis_cache = {}
493
  if 'query' not in st.session_state: st.session_state.query = ""
 
 
494
 
495
  def clear_search_state():
496
  st.session_state.search_results = None
@@ -498,8 +348,10 @@ def clear_search_state():
498
  st.session_state.entities = None
499
  st.session_state.analysis_cache = {}
500
 
501
- def set_query_from_example(example_text):
 
502
  st.session_state.query = example_text
 
503
 
504
  # SECCIÓN: EJEMPLOS DE CONSULTA
505
  st.write("**¿No sabes por dónde empezar? Prueba con un ejemplo:**")
@@ -509,30 +361,42 @@ example_queries = [
509
  "Después de tomar leche, tengo muchos gases e hinchazón.",
510
  "El vino tinto siempre me da dolor de cabeza."
511
  ]
 
512
  if example_cols[0].button(example_queries[0]):
513
- set_query_from_example(example_queries[0])
514
  if example_cols[1].button(example_queries[1]):
515
- set_query_from_example(example_queries[1])
516
  if example_cols[2].button(example_queries[2]):
517
- set_query_from_example(example_queries[2])
518
 
 
 
519
  with st.form(key="search_form"):
520
- query = st.text_area("Describe tu caso aquí:", height=150, key="query")
521
  submitted = st.form_submit_button("Analizar mi caso", type="primary")
 
 
522
 
523
- if submitted:
 
 
 
 
 
 
524
  clear_search_state()
525
- st.session_state.user_query = query
526
- if not query:
 
527
  st.warning("Por favor, describe lo que sientes y lo que comiste.")
528
  elif alimentos_data is None:
529
  st.error("La base de datos de alimentos no está disponible.")
530
  else:
531
  with st.spinner("🧠 Interpretando tu caso y buscando pistas con IA..."):
532
- entities = extract_and_infer_with_gemini(query, lista_condiciones)
533
- # --- AÑADE ESTA LÍNEA PARA ACTIVAR LA RED DE SEGURIDAD ---
534
- entities = reinforce_entities_with_keywords(entities, query, FOOD_TO_COMPOUND_MAP)
535
  st.session_state.entities = entities
 
536
  if entities and (entities.get("alimentos") or entities.get("sintomas")):
537
  info_str = f"IA identificó - Alimentos: {', '.join(entities.get('alimentos',[])) or 'Ninguno'}, Síntomas: {', '.join(entities.get('sintomas',[])) or 'Ninguno'}"
538
  if entities.get("condicion_probable"): info_str += f", Condición Probable: {entities.get('condicion_probable')}"
@@ -544,13 +408,13 @@ if submitted:
544
  st.error("No se pudieron identificar alimentos o síntomas claros en tu descripción.")
545
  st.session_state.search_results = []
546
 
 
547
  if st.session_state.search_results is not None:
548
  results = st.session_state.search_results
549
 
550
  if not results:
551
  st.warning(f"No se encontraron coincidencias claras para tu caso: '{st.session_state.user_query}'.")
552
  else:
553
- # --- Cabecera de resultados y botón de descarga (sin cambios) ---
554
  col1, col2 = st.columns([3,1])
555
  with col1:
556
  st.success(f"Hemos encontrado {len(results)} posible(s) causa(s) relacionada(s) con tu caso.")
@@ -563,12 +427,10 @@ if st.session_state.search_results is not None:
563
  mime="text/plain"
564
  )
565
 
566
- # --- Gráfico de relevancia (sin cambios) ---
567
- st.subheader("Análisis de Relevancia de las Coinciden cias")
568
  chart = create_relevance_chart(results)
569
  st.altair_chart(chart, use_container_width=True)
570
 
571
- # --- Análisis detallado del MEJOR resultado (sin cambios) ---
572
  best_match_data = results[0]
573
  best_match = best_match_data['entry']
574
  with st.expander(f"**Análisis Detallado de la Principal Coincidencia: {best_match.get('condicion_asociada')}**", expanded=True):
@@ -605,7 +467,7 @@ if st.session_state.search_results is not None:
605
  for item in foodb_data[:3]:
606
  st.write(f"**Compuesto:** {item['compound']}")
607
  st.write(f"**Efectos reportados:** {', '.join(item['effects'])}")
608
- st.markdown("---")
609
  if not found_data:
610
  st.warning("Sin datos moleculares para este alimento.")
611
  st.markdown("---")
@@ -614,36 +476,25 @@ if st.session_state.search_results is not None:
614
  st.session_state.analysis_cache['best_match_analysis'] = generate_detailed_analysis(st.session_state.user_query, best_match)
615
  st.markdown(st.session_state.analysis_cache['best_match_analysis'])
616
 
617
- # --- NUEVA SECCIÓN MEJORADA: HASTA 4 OTRAS POSIBILIDADES ---
618
  if len(results) > 1:
619
  with st.expander("🔍 **Explora otras posibilidades relevantes (Diagnóstico Diferencial)**"):
620
- # Iteramos sobre los resultados del 2 al 5 (hasta 4 alternativas)
621
  for i, result in enumerate(results[1:5]):
622
  with st.container(border=True):
623
  entry = result['entry']
624
  score = result['score']
625
-
626
  st.subheader(f"{i+2}. {entry.get('condicion_asociada')}")
627
-
628
  col_info, col_action = st.columns([3, 1])
629
-
630
  with col_info:
631
  if result.get('matched_symptoms'):
632
  st.markdown(f"**Pistas Clave (Síntomas Coincidentes):** {', '.join(result['matched_symptoms']).capitalize()}")
633
  st.markdown(f"**Alimentos Típicos Asociados:** {entry.get('compuesto_alimento')}")
634
-
635
  with col_action:
636
  st.metric("Relevancia", score['total'])
637
  analysis_key = f"analysis_{i+2}"
638
  if st.button("Generar análisis", key=analysis_key, help=f"Generar análisis de IA para {entry.get('condicion_asociada')}"):
639
  with st.spinner(f"Generando análisis para {entry.get('condicion_asociada')}..."):
640
- # Guardamos el análisis en el cache para no regenerarlo
641
  st.session_state.analysis_cache[analysis_key] = generate_detailed_analysis(st.session_state.user_query, entry)
642
-
643
- # Muestra el análisis si ya fue generado y guardado en el cache
644
  if analysis_key in st.session_state.analysis_cache:
645
  st.info(st.session_state.analysis_cache[analysis_key])
646
-
647
- # Añade un separador visual, excepto para el último elemento
648
  if i < len(results[1:5]) - 1:
649
- st.markdown("---")
 
136
 
137
  FOOD_NAME_TO_FOODB_KEY = {
138
  # --- CEREALES Y GRANOS ---
139
+ "alforfón": ["buckwheat"], "arroz": ["rice"], "avena": ["oat", "oats"], "cebada": ["barley"], "centeno": ["rye"], "galleta": ["cookie", "biscuit"], "maíz": ["corn", "maize"], "pan": ["bread"], "pasta": ["pasta"], "pizza": ["pizza"], "quinoa": ["quinoa"], "trigo": ["wheat"], "trigo sarraceno": ["buckwheat"],
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  # --- LÁCTEOS Y DERIVADOS ---
141
+ "crema": ["cream"], "helado": ["ice cream"], "leche": ["milk"], "mantequilla": ["butter"], "queso": ["cheese"], "yogur": ["yogurt", "yoghurt"],
 
 
 
 
 
 
142
  # --- VEGETALES ---
143
+ "acelga": ["chard", "swiss chard"], "ajo": ["garlic"], "alcachofa": ["artichoke"], "apio": ["celery"], "berenjena": ["eggplant", "aubergine"], "brócoli": ["broccoli"], "calabacín": ["zucchini", "courgette"], "calabaza": ["pumpkin", "squash"], "cebolla": ["onion"], "champiñón": ["mushroom"], "col": ["cabbage"], "coliflor": ["cauliflower"], "edamame": ["edamame"], "espárrago": ["asparagus"], "espinaca": ["spinach"], "garbanzo": ["chickpea"], "guisante": ["pea", "peas"], "frijol": ["bean", "beans"], "lenteja": ["lentil"], "patata": ["potato"], "pepino": ["cucumber"], "pimiento": ["bell pepper", "pepper"], "remolacha": ["beet", "beetroot"], "repollo": ["cabbage"], "seta": ["mushroom"], "soja": ["soy", "soybean"], "tofu": ["tofu"], "tomate": ["tomato"], "zanahoria": ["carrot"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  # --- FRUTAS ---
145
+ "aguacate": ["avocado"], "albaricoque": ["apricot"], "arándano": ["blueberry"], "cereza": ["cherry"], "ciruela": ["plum"], "dátil": ["date"], "frambuesa": ["raspberry"], "fresa": ["strawberry"], "higo": ["fig"], "kiwi": ["kiwi", "kiwifruit"], "limón": ["lemon"], "mandarina": ["tangerine", "mandarin"], "mango": ["mango"], "manzana": ["apple"], "melocotón": ["peach"], "melón": ["melon", "cantaloupe"], "mora": ["blackberry"], "naranja": ["orange"], "nectarina": ["nectarine"], "papaya": ["papaya"], "pera": ["pear"], "piña": ["pineapple"], "plátano": ["banana"], "pomelo": ["grapefruit"], "sandía": ["watermelon"], "uva": ["grape"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  # --- PROTEÍNAS (CARNES, PESCADOS, HUEVOS) ---
147
+ "anchoa": ["anchovy", "anchovies"], "atún": ["tuna"], "camarón": ["shrimp", "prawn"], "carne": ["meat", "beef", "pork", "lamb"], "cerdo": ["pork"], "cordero": ["lamb"], "gamba": ["shrimp", "prawn"], "huevo": ["egg"], "marisco": ["shellfish", "seafood"], "pavo": ["turkey"], "pescado": ["fish"], "pollo": ["chicken"], "salchicha": ["sausage"], "salmón": ["salmon"], "sardina": ["sardine"], "ternera": ["beef", "veal"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  # --- FRUTOS SECOS Y SEMILLAS ---
149
+ "almendra": ["almond"], "anacardo": ["cashew"], "avellana": ["hazelnut"], "cacahuete": ["peanut"], "chía": ["chia", "chia seed"], "lino": ["flax", "flaxseed", "linseed"], "nuez": ["walnut"], "pistacho": ["pistachio"], "sésamo": ["sesame", "sesame seed"],
 
 
 
 
 
 
 
 
 
150
  # --- BEBIDAS, DULCES Y CONDIMENTOS ---
151
+ "aceituna": ["olive"], "café": ["coffee"], "caldo": ["broth", "stock"], "cerveza": ["beer"], "chocolate": ["chocolate"], "jengibre": ["ginger"], "cúrcuma": ["turmeric"], "miel": ["honey"], "mostaza": ["mustard"], "té": ["tea"], "vinagre": ["vinegar"], "vino": ["wine", "red wine", "white wine"]
 
 
 
 
 
 
 
 
 
 
 
152
  }
153
 
 
 
154
  def reinforce_entities_with_keywords(entities, query, food_map):
 
 
 
 
155
  if not entities:
156
  entities = {"alimentos": [], "sintomas": []}
 
157
  query_sanitized = sanitize_text(query)
158
  current_foods = entities.get("alimentos", [])
 
 
159
  current_foods_sanitized = {sanitize_text(f) for f in current_foods}
 
 
160
  for food_keyword in food_map.keys():
 
161
  if food_keyword in query_sanitized:
 
162
  if food_keyword not in current_foods_sanitized:
 
163
  logger.info(f"Red de seguridad: La IA omitió '{food_keyword}', añadiéndolo manualmente.")
164
  current_foods.append(food_keyword)
165
+ entities["alimentos"] = list(set(current_foods))
 
166
  return entities
167
 
 
 
 
168
  def sanitize_text(text):
169
  if not text: return ""
170
  return re.sub(r'[.,;()]', '', text).lower().strip()
171
+
172
  def extract_and_infer_with_gemini(query, condiciones):
173
  if not model: return None
174
  condiciones_str = "\n".join([f"- {c}" for c in condiciones])
 
 
175
  system_prompt = f"""
176
  Eres un asistente de triaje clínico experto. Tu tarea es analizar la consulta de un usuario y extraer tres tipos de información:
177
  1. `alimentos`: Lista de alimentos consumidos.
 
202
 
203
  def find_best_matches_hybrid(entities, data):
204
  if not entities or not data: return []
 
205
  user_symptoms = set(sanitize_text(s) for s in entities.get("sintomas", []))
206
  user_foods = set(sanitize_text(f) for f in entities.get("alimentos", []))
207
  inferred_condition_raw = sanitize_text(entities.get("condicion_probable", ""))
 
208
  candidate_terms = set(user_foods)
209
  for food in user_foods:
210
  food_sanitized = sanitize_text(food)
211
  if food_sanitized in FOOD_TO_COMPOUND_MAP:
212
  candidate_terms.update(c.lower() for c in FOOD_TO_COMPOUND_MAP[food_sanitized])
 
213
  if food_sanitized in ["vino", "vino tinto", "vino rojo"]:
214
  candidate_terms.update(["histamina", "tiramina", "sulfitos"])
 
215
  results = []
216
  for entry in data:
217
  entry_compounds_text = sanitize_text(entry.get("compuesto_alimento", ""))
218
  food_match = any(term in entry_compounds_text for term in candidate_terms)
 
219
  if not food_match:
220
  continue
 
 
221
  score_details = {'condition': 0, 'food': 15, 'symptoms': 0}
 
222
  if inferred_condition_raw:
223
  entry_condition_sanitized = sanitize_text(entry.get("condicion_asociada", ""))
224
  is_match = (entry_condition_sanitized == inferred_condition_raw)
 
227
  is_match = True
228
  if is_match:
229
  score_details['condition'] = 100
 
230
  entry_symptoms_keys = set(sanitize_text(s) for s in entry.get("sintomas_clave", []))
231
  symptom_score = 0
232
  matched_symptoms = []
 
237
  matched_symptoms.append(key)
238
  break
239
  score_details['symptoms'] = symptom_score
 
 
 
240
  score_details['total'] = score_details['condition'] + score_details['food'] + score_details['symptoms']
 
241
  results.append({
242
  'entry': entry,
243
  'score': score_details,
244
  'matched_symptoms': list(set(matched_symptoms))
245
  })
 
246
  if not results: return []
 
 
 
 
 
 
247
  sorted_results = sorted(results, key=lambda x: x['score']['total'], reverse=True)
248
  return sorted_results
249
 
 
286
  logger.error(f"Error generando análisis detallado con Gemini: {e}")
287
  return "No se pudo generar el análisis detallado."
288
  def create_relevance_chart(results):
 
289
  top_results = results[:5]
290
  condition_names = [re.sub(r'\(.*\)', '', res['entry']['condicion_asociada']).strip() for res in top_results]
291
  chart_data = {"Condición": condition_names, "Relevancia": [res['score']['total'] for res in top_results]}
 
297
  ).properties(title='Principales Coincidencias según tu Caso').configure_axis(labelFontSize=12, titleFontSize=14).configure_title(fontSize=16, anchor='start')
298
  return chart
299
  def generate_report_text(query, results):
 
300
  report_lines = []
301
  report_lines.append("="*50)
302
  report_lines.append("INFORME DEL DETECTIVE DE ALIMENTOS")
 
321
  return "\n".join(report_lines)
322
 
323
  # --- INTERFAZ DE USUARIO Y LÓGICA PRINCIPAL ---
 
324
  col_img1, col_text, col_img2 = st.columns([1, 4, 1], gap="medium")
325
  with col_img1:
326
  if os.path.exists("imagen.png"):
 
339
  if 'entities' not in st.session_state: st.session_state.entities = None
340
  if 'analysis_cache' not in st.session_state: st.session_state.analysis_cache = {}
341
  if 'query' not in st.session_state: st.session_state.query = ""
342
+ # --- CAMBIO 1: AÑADIMOS LA BANDERA DE CONTROL ---
343
+ if 'start_analysis' not in st.session_state: st.session_state.start_analysis = False
344
 
345
  def clear_search_state():
346
  st.session_state.search_results = None
 
348
  st.session_state.entities = None
349
  st.session_state.analysis_cache = {}
350
 
351
+ # --- CAMBIO 2: NUEVA FUNCIÓN PARA LOS BOTONES DE EJEMPLO ---
352
+ def set_query_and_trigger_analysis(example_text):
353
  st.session_state.query = example_text
354
+ st.session_state.start_analysis = True
355
 
356
  # SECCIÓN: EJEMPLOS DE CONSULTA
357
  st.write("**¿No sabes por dónde empezar? Prueba con un ejemplo:**")
 
361
  "Después de tomar leche, tengo muchos gases e hinchazón.",
362
  "El vino tinto siempre me da dolor de cabeza."
363
  ]
364
+ # --- CAMBIO 3: LOS BOTONES AHORA LLAMAN A LA NUEVA FUNCIÓN ---
365
  if example_cols[0].button(example_queries[0]):
366
+ set_query_and_trigger_analysis(example_queries[0])
367
  if example_cols[1].button(example_queries[1]):
368
+ set_query_and_trigger_analysis(example_queries[1])
369
  if example_cols[2].button(example_queries[2]):
370
+ set_query_and_trigger_analysis(example_queries[2])
371
 
372
+ # --- CAMBIO 4: ESTRUCTURA PRINCIPAL MODIFICADA ---
373
+ # El formulario solo define la UI y activa la bandera al ser enviado.
374
  with st.form(key="search_form"):
375
+ st.text_area("Describe tu caso aquí:", height=150, key="query")
376
  submitted = st.form_submit_button("Analizar mi caso", type="primary")
377
+ if submitted:
378
+ st.session_state.start_analysis = True
379
 
380
+ # La lógica de análisis se ejecuta si CUALQUIER botón activó la bandera.
381
+ if st.session_state.start_analysis:
382
+ # Inmediatamente bajamos la bandera para evitar ejecuciones repetidas.
383
+ st.session_state.start_analysis = False
384
+
385
+ query_to_analyze = st.session_state.query
386
+
387
  clear_search_state()
388
+ st.session_state.user_query = query_to_analyze
389
+
390
+ if not query_to_analyze:
391
  st.warning("Por favor, describe lo que sientes y lo que comiste.")
392
  elif alimentos_data is None:
393
  st.error("La base de datos de alimentos no está disponible.")
394
  else:
395
  with st.spinner("🧠 Interpretando tu caso y buscando pistas con IA..."):
396
+ entities = extract_and_infer_with_gemini(query_to_analyze, lista_condiciones)
397
+ entities = reinforce_entities_with_keywords(entities, query_to_analyze, FOOD_TO_COMPOUND_MAP)
 
398
  st.session_state.entities = entities
399
+
400
  if entities and (entities.get("alimentos") or entities.get("sintomas")):
401
  info_str = f"IA identificó - Alimentos: {', '.join(entities.get('alimentos',[])) or 'Ninguno'}, Síntomas: {', '.join(entities.get('sintomas',[])) or 'Ninguno'}"
402
  if entities.get("condicion_probable"): info_str += f", Condición Probable: {entities.get('condicion_probable')}"
 
408
  st.error("No se pudieron identificar alimentos o síntomas claros en tu descripción.")
409
  st.session_state.search_results = []
410
 
411
+ # La sección de mostrar resultados permanece igual, se ejecuta si hay resultados en el estado.
412
  if st.session_state.search_results is not None:
413
  results = st.session_state.search_results
414
 
415
  if not results:
416
  st.warning(f"No se encontraron coincidencias claras para tu caso: '{st.session_state.user_query}'.")
417
  else:
 
418
  col1, col2 = st.columns([3,1])
419
  with col1:
420
  st.success(f"Hemos encontrado {len(results)} posible(s) causa(s) relacionada(s) con tu caso.")
 
427
  mime="text/plain"
428
  )
429
 
430
+ st.subheader("Análisis de Relevancia de las Coincidencias")
 
431
  chart = create_relevance_chart(results)
432
  st.altair_chart(chart, use_container_width=True)
433
 
 
434
  best_match_data = results[0]
435
  best_match = best_match_data['entry']
436
  with st.expander(f"**Análisis Detallado de la Principal Coincidencia: {best_match.get('condicion_asociada')}**", expanded=True):
 
467
  for item in foodb_data[:3]:
468
  st.write(f"**Compuesto:** {item['compound']}")
469
  st.write(f"**Efectos reportados:** {', '.join(item['effects'])}")
470
+ st.markdown("---")
471
  if not found_data:
472
  st.warning("Sin datos moleculares para este alimento.")
473
  st.markdown("---")
 
476
  st.session_state.analysis_cache['best_match_analysis'] = generate_detailed_analysis(st.session_state.user_query, best_match)
477
  st.markdown(st.session_state.analysis_cache['best_match_analysis'])
478
 
 
479
  if len(results) > 1:
480
  with st.expander("🔍 **Explora otras posibilidades relevantes (Diagnóstico Diferencial)**"):
 
481
  for i, result in enumerate(results[1:5]):
482
  with st.container(border=True):
483
  entry = result['entry']
484
  score = result['score']
 
485
  st.subheader(f"{i+2}. {entry.get('condicion_asociada')}")
 
486
  col_info, col_action = st.columns([3, 1])
 
487
  with col_info:
488
  if result.get('matched_symptoms'):
489
  st.markdown(f"**Pistas Clave (Síntomas Coincidentes):** {', '.join(result['matched_symptoms']).capitalize()}")
490
  st.markdown(f"**Alimentos Típicos Asociados:** {entry.get('compuesto_alimento')}")
 
491
  with col_action:
492
  st.metric("Relevancia", score['total'])
493
  analysis_key = f"analysis_{i+2}"
494
  if st.button("Generar análisis", key=analysis_key, help=f"Generar análisis de IA para {entry.get('condicion_asociada')}"):
495
  with st.spinner(f"Generando análisis para {entry.get('condicion_asociada')}..."):
 
496
  st.session_state.analysis_cache[analysis_key] = generate_detailed_analysis(st.session_state.user_query, entry)
 
 
497
  if analysis_key in st.session_state.analysis_cache:
498
  st.info(st.session_state.analysis_cache[analysis_key])
 
 
499
  if i < len(results[1:5]) - 1:
500
+ st.markdown("---")