JairoCesar commited on
Commit
591cd17
·
verified ·
1 Parent(s): 603dab7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -72
app.py CHANGED
@@ -1,5 +1,5 @@
1
- # ==================== El Detective de Alimentos (Versión 7.2 - Búsqueda Molecular Inteligente) =====================================
2
- # Mejoras: Lógica de búsqueda en FoodB por contención y diccionario de traducción mejorado.
3
 
4
  import streamlit as st
5
  import google.generativeai as genai
@@ -74,7 +74,8 @@ def load_data():
74
  alimentos_data, lista_condiciones, foodb_index = load_data()
75
 
76
 
77
- # --- DICCIONARIOS DE MAPEADO ---
 
78
  FOOD_TO_COMPOUND_MAP = {
79
  "pan": ["gluten"], "trigo": ["gluten"], "harina de trigo": ["gluten"], "cebada": ["gluten"], "centeno": ["gluten"], "pasta": ["gluten"], "galletas": ["gluten"], "avena": ["gluten"], "pizza": ["gluten"], "torta": ["gluten"],
80
  "leche": ["lácteos", "caseína", "lactosa"], "queso": ["lácteos", "caseína", "lactosa", "histamina", "tiramina"], "yogur": ["lácteos", "caseína", "lactosa"], "mantequilla": ["lácteos", "caseína", "lactosa"], "crema": ["lácteos"], "helado": ["lácteos"],
@@ -92,8 +93,6 @@ CONDITION_SYNONYMS = {
92
  "gota / hiperuricemia.": ["acido urico aumentado"], "intolerancia a la lactosa.": ["déficit de lactasa"],
93
  "enfermedad celíaca (clásica).": ["dermatitis herpetiforme"], "migraña.": ["dolor de cabeza", "cefalea"]
94
  }
95
-
96
- # --- DICCIONARIO TRADUCTOR REFINADO ---
97
  FOOD_NAME_TO_FOODB_KEY = {
98
  "pan": ["bread"], "pasta": ["pasta"], "galleta": ["cookie"], "pizza": ["pizza"], "cebada": ["barley"], "centeno": ["rye"],
99
  "leche": ["milk"], "queso": ["cheese"], "huevo": ["egg"], "carne": ["beef", "pork", "lamb", "meat"], "ternera": ["beef"], "cerdo": ["pork"], "cordero": ["lamb"], "pollo": ["chicken"],
@@ -101,12 +100,13 @@ FOOD_NAME_TO_FOODB_KEY = {
101
  "tomate": ["tomato"], "patata": ["potato"], "cebolla": ["onion"], "ajo": ["garlic"], "espinaca": ["spinach"],
102
  "vino": ["wine"], "cerveza": ["beer"], "café": ["coffee"], "chocolate": ["chocolate"], "miel": ["honey"],
103
  "almendra": ["almond"], "nuez": ["walnut"], "cacahuete": ["peanut"], "arroz": ["rice"], "maíz": ["corn"],
104
- "pescado": ["fish"], "atún": ["tuna"], "salmón": ["salmon"], "marisco": ["shellfish"], "camarón": ["shrimp"]
105
  }
106
-
107
- # --- LÓGICA DE BÚSQUEDA Y ANÁLISIS (SIN CAMBIOS) ---
108
- # (Todas las funciones hasta la UI se mantienen igual que en la versión anterior)
109
  def extract_and_infer_with_gemini(query, condiciones):
 
110
  if not model: return None
111
  condiciones_str = "\n".join([f"- {c}" for c in condiciones])
112
  system_prompt = f"""
@@ -131,10 +131,8 @@ def extract_and_infer_with_gemini(query, condiciones):
131
  logger.error(f"Error en la extracción/inferencia con Gemini: {e}")
132
  st.error(f"Hubo un problema al interpretar tu consulta con la IA.")
133
  return None
134
- def sanitize_text(text):
135
- if not text: return ""
136
- text = re.sub(r'[.,;()]', '', text)
137
- return text.lower().strip()
138
  def find_best_matches_hybrid(entities, data):
139
  if not entities or not data: return []
140
  user_symptoms = set(sanitize_text(s) for s in entities.get("sintomas", []))
@@ -162,6 +160,7 @@ def find_best_matches_hybrid(entities, data):
162
  results = []
163
  for entry in target_data:
164
  score_details = {'condition': 0, 'food': 0, 'symptoms': 0, 'total': 0}
 
165
  if inferred_condition_raw and sanitize_text(entry.get("condicion_asociada", "")) == inferred_condition_raw:
166
  score_details['condition'] = 100
167
  entry_compounds_text = sanitize_text(entry.get("compuesto_alimento", ""))
@@ -173,14 +172,16 @@ def find_best_matches_hybrid(entities, data):
173
  for key in entry_symptoms_keys:
174
  if key in user_symptom or user_symptom in key:
175
  symptom_score += 10
 
176
  break
177
  score_details['symptoms'] = symptom_score
178
  total_score = sum(score_details.values())
179
  if total_score > 0:
180
- results.append({'entry': entry, 'score': score_details})
181
  if not results: return []
182
  sorted_results = sorted(results, key=lambda x: x['score']['total'], reverse=True)
183
  return sorted_results
 
184
  def generate_detailed_analysis(query, match):
185
  if not model: return "Error: El modelo de IA no está disponible."
186
  prompt_parts = [
@@ -241,7 +242,14 @@ def log_feedback(query, result, feedback):
241
  os.makedirs("logs", exist_ok=True)
242
  with open(os.path.join("logs", "feedback_log.txt"), "a", encoding="utf-8") as f:
243
  f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
244
-
 
 
 
 
 
 
 
245
 
246
  # --- INTERFAZ DE USUARIO Y LÓGICA PRINCIPAL (CON CAMBIOS) ---
247
  col_img, col_text = st.columns([1, 4], gap="medium")
@@ -256,17 +264,20 @@ st.markdown("---")
256
  if 'search_results' not in st.session_state: st.session_state.search_results = None
257
  if 'user_query' not in st.session_state: st.session_state.user_query = ""
258
  if 'entities' not in st.session_state: st.session_state.entities = None
 
259
 
260
  def clear_search_state():
261
  st.session_state.search_results = None
262
  st.session_state.user_query = ""
263
  st.session_state.entities = None
 
264
 
265
  with st.form(key="search_form"):
266
  query = st.text_area("Describe tu caso aquí:", height=150, placeholder="Ej: Cuando como mucha carne me duele el dedo gordo del pie...")
267
  submitted = st.form_submit_button("Analizar mi caso", type="primary")
268
 
269
  if submitted:
 
270
  if not query:
271
  st.warning("Por favor, describe lo que sientes y lo que comiste.")
272
  elif alimentos_data is None:
@@ -276,7 +287,6 @@ if submitted:
276
  with st.spinner("🧠 Interpretando tu caso y buscando pistas con IA..."):
277
  entities = extract_and_infer_with_gemini(query, lista_condiciones)
278
  st.session_state.entities = entities
279
-
280
  if entities and (entities.get("alimentos") or entities.get("sintomas")):
281
  info_str = f"IA identificó - Alimentos: {', '.join(entities.get('alimentos',[])) or 'Ninguno'}, Síntomas: {', '.join(entities.get('sintomas',[])) or 'Ninguno'}"
282
  if entities.get("condicion_probable"): info_str += f", Condición Probable: {entities.get('condicion_probable')}"
@@ -289,7 +299,7 @@ if submitted:
289
  st.session_state.search_results = []
290
 
291
  if st.session_state.search_results is not None:
292
- st.button("Realizar nueva consulta", on_click=clear_search_state)
293
  st.markdown("---")
294
 
295
  results = st.session_state.search_results
@@ -302,62 +312,17 @@ if st.session_state.search_results is not None:
302
  chart = create_relevance_chart(results)
303
  st.altair_chart(chart, use_container_width=True)
304
 
 
305
  best_match_data = results[0]
306
  best_match = best_match_data['entry']
307
- best_score = best_match_data['score']
308
-
309
  with st.expander(f"**Análisis Detallado de la Principal Coincidencia: {best_match.get('condicion_asociada')}**", expanded=True):
310
- col1, col2 = st.columns([3, 1])
311
- with col1:
312
- st.markdown("##### Desglose de la Puntuación de Relevancia:")
313
- score_col1, score_col2, score_col3, score_col4 = st.columns(4)
314
- score_col1.metric("Puntos por Condición", f"{best_score['condition']}")
315
- score_col2.metric("Puntos por Alimento", f"{best_score['food']}")
316
- score_col3.metric("Puntos por Síntomas", f"{best_score['symptoms']}")
317
- score_col4.metric("PUNTUACIÓN TOTAL", f"{best_score['total']}", delta="Máxima coincidencia")
318
-
319
- with col2:
320
- st.write("")
321
- if foodb_index:
322
- # --- LÓGICA DEL POPOVER COMPLETAMENTE RECONSTRUIDA ---
323
- with st.popover("🔬 Principales componentes moleculares"):
324
- user_foods_mentioned = st.session_state.entities.get("alimentos", [])
325
-
326
- if not user_foods_mentioned:
327
- st.info("El usuario no especificó un alimento, no se puede realizar la búsqueda molecular.")
328
- else:
329
- found_data = False
330
- displayed_foodb_keys = set() # Para no mostrar resultados duplicados
331
-
332
- for alimento_es in user_foods_mentioned:
333
- # TRADUCCIÓN INTELIGENTE
334
- search_terms_en = []
335
- for key_es, value_en_list in FOOD_NAME_TO_FOODB_KEY.items():
336
- if key_es in alimento_es.lower():
337
- search_terms_en.extend(value_en_list)
338
-
339
- # BÚSQUEDA POR CONTENCIÓN EN FOODB
340
- for term in set(search_terms_en): # Usamos set para evitar buscar "beef" dos veces
341
- for foodb_key, foodb_data in foodb_index.items():
342
- if term in foodb_key and foodb_key not in displayed_foodb_keys:
343
- found_data = True
344
- displayed_foodb_keys.add(foodb_key)
345
- with st.container(border=True):
346
- st.subheader(f"Análisis de: {foodb_key.capitalize()}")
347
- for item in foodb_data[:3]: # Limitamos a 3 compuestos por alimento encontrado
348
- st.write(f"**Compuesto:** {item['compound']}")
349
- st.write(f"**Efectos reportados:** {', '.join(item['effects'])}")
350
- st.markdown("---")
351
-
352
- if not found_data:
353
- st.warning("Sin datos moleculares para este alimento.") # Mensaje corregido
354
-
355
- st.markdown("---")
356
-
357
  with st.spinner("✍️ Generando un análisis personalizado con IA..."):
358
- detailed_analysis = generate_detailed_analysis(st.session_state.user_query, best_match)
359
- st.markdown(detailed_analysis)
360
-
 
361
  st.markdown("---")
362
  st.write("**¿Te fue útil este análisis?**")
363
  feedback_cols = st.columns(8)
@@ -368,10 +333,27 @@ if st.session_state.search_results is not None:
368
  log_feedback(st.session_state.user_query, best_match_data, "no_util")
369
  st.warning("Gracias. Usaremos tu feedback para mejorar.")
370
 
 
371
  if len(results) > 1:
372
- with st.expander("Otras posibles coincidencias (ordenadas por relevancia)"):
373
- for result in results[1:]:
 
374
  entry = result['entry']
375
  score = result['score']
376
- st.write(f"**{entry.get('condicion_asociada')}** - Puntuación Total: {score['total']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
  st.markdown("---")
 
1
+ # ==================== El Detective de Alimentos (Versión 8.1 - Diagnóstico Diferencial) =====================================
2
+ # Mejoras: Presentación de un "Top 3" de resultados con justificación de síntomas.
3
 
4
  import streamlit as st
5
  import google.generativeai as genai
 
74
  alimentos_data, lista_condiciones, foodb_index = load_data()
75
 
76
 
77
+ # --- DICCIONARIOS Y FUNCIONES AUXILIARES (CON CAMBIOS) ---
78
+ # ... (Los diccionarios no cambian) ...
79
  FOOD_TO_COMPOUND_MAP = {
80
  "pan": ["gluten"], "trigo": ["gluten"], "harina de trigo": ["gluten"], "cebada": ["gluten"], "centeno": ["gluten"], "pasta": ["gluten"], "galletas": ["gluten"], "avena": ["gluten"], "pizza": ["gluten"], "torta": ["gluten"],
81
  "leche": ["lácteos", "caseína", "lactosa"], "queso": ["lácteos", "caseína", "lactosa", "histamina", "tiramina"], "yogur": ["lácteos", "caseína", "lactosa"], "mantequilla": ["lácteos", "caseína", "lactosa"], "crema": ["lácteos"], "helado": ["lácteos"],
 
93
  "gota / hiperuricemia.": ["acido urico aumentado"], "intolerancia a la lactosa.": ["déficit de lactasa"],
94
  "enfermedad celíaca (clásica).": ["dermatitis herpetiforme"], "migraña.": ["dolor de cabeza", "cefalea"]
95
  }
 
 
96
  FOOD_NAME_TO_FOODB_KEY = {
97
  "pan": ["bread"], "pasta": ["pasta"], "galleta": ["cookie"], "pizza": ["pizza"], "cebada": ["barley"], "centeno": ["rye"],
98
  "leche": ["milk"], "queso": ["cheese"], "huevo": ["egg"], "carne": ["beef", "pork", "lamb", "meat"], "ternera": ["beef"], "cerdo": ["pork"], "cordero": ["lamb"], "pollo": ["chicken"],
 
100
  "tomate": ["tomato"], "patata": ["potato"], "cebolla": ["onion"], "ajo": ["garlic"], "espinaca": ["spinach"],
101
  "vino": ["wine"], "cerveza": ["beer"], "café": ["coffee"], "chocolate": ["chocolate"], "miel": ["honey"],
102
  "almendra": ["almond"], "nuez": ["walnut"], "cacahuete": ["peanut"], "arroz": ["rice"], "maíz": ["corn"],
103
+ "pescado": ["fish"], "atún": ["tuna", "salmón": "salmon", "marisco": "shellfish", "camarón": "shrimp"]
104
  }
105
+ def sanitize_text(text):
106
+ if not text: return ""
107
+ return re.sub(r'[.,;()]', '', text).lower().strip()
108
  def extract_and_infer_with_gemini(query, condiciones):
109
+ # ... (Sin cambios)
110
  if not model: return None
111
  condiciones_str = "\n".join([f"- {c}" for c in condiciones])
112
  system_prompt = f"""
 
131
  logger.error(f"Error en la extracción/inferencia con Gemini: {e}")
132
  st.error(f"Hubo un problema al interpretar tu consulta con la IA.")
133
  return None
134
+
135
+ # --- FUNCIÓN DE BÚSQUEDA MODIFICADA PARA DEVOLVER MÁS INFO ---
 
 
136
  def find_best_matches_hybrid(entities, data):
137
  if not entities or not data: return []
138
  user_symptoms = set(sanitize_text(s) for s in entities.get("sintomas", []))
 
160
  results = []
161
  for entry in target_data:
162
  score_details = {'condition': 0, 'food': 0, 'symptoms': 0, 'total': 0}
163
+ matched_symptoms = [] # <-- NUEVO: Guardamos las pistas
164
  if inferred_condition_raw and sanitize_text(entry.get("condicion_asociada", "")) == inferred_condition_raw:
165
  score_details['condition'] = 100
166
  entry_compounds_text = sanitize_text(entry.get("compuesto_alimento", ""))
 
172
  for key in entry_symptoms_keys:
173
  if key in user_symptom or user_symptom in key:
174
  symptom_score += 10
175
+ matched_symptoms.append(key) # <-- NUEVO: Añadimos la pista
176
  break
177
  score_details['symptoms'] = symptom_score
178
  total_score = sum(score_details.values())
179
  if total_score > 0:
180
+ results.append({'entry': entry, 'score': score_details, 'matched_symptoms': list(set(matched_symptoms))})
181
  if not results: return []
182
  sorted_results = sorted(results, key=lambda x: x['score']['total'], reverse=True)
183
  return sorted_results
184
+ # ... (El resto de funciones auxiliares no cambia) ...
185
  def generate_detailed_analysis(query, match):
186
  if not model: return "Error: El modelo de IA no está disponible."
187
  prompt_parts = [
 
242
  os.makedirs("logs", exist_ok=True)
243
  with open(os.path.join("logs", "feedback_log.txt"), "a", encoding="utf-8") as f:
244
  f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
245
+ def extract_foods_from_string(food_string):
246
+ match = re.search(r'\(([^)]+)\)$', food_string)
247
+ if match:
248
+ foods_part = match.group(1)
249
+ return [food.strip().lower() for food in foods_part.split(',') if food.strip()]
250
+ else:
251
+ main_part = re.sub(r'^\w+\s*\(.*?\)\s*', '', food_string).strip()
252
+ return [food.strip().lower() for food in main_part.split(',') if food.strip()]
253
 
254
  # --- INTERFAZ DE USUARIO Y LÓGICA PRINCIPAL (CON CAMBIOS) ---
255
  col_img, col_text = st.columns([1, 4], gap="medium")
 
264
  if 'search_results' not in st.session_state: st.session_state.search_results = None
265
  if 'user_query' not in st.session_state: st.session_state.user_query = ""
266
  if 'entities' not in st.session_state: st.session_state.entities = None
267
+ if 'analysis_cache' not in st.session_state: st.session_state.analysis_cache = {}
268
 
269
  def clear_search_state():
270
  st.session_state.search_results = None
271
  st.session_state.user_query = ""
272
  st.session_state.entities = None
273
+ st.session_state.analysis_cache = {}
274
 
275
  with st.form(key="search_form"):
276
  query = st.text_area("Describe tu caso aquí:", height=150, placeholder="Ej: Cuando como mucha carne me duele el dedo gordo del pie...")
277
  submitted = st.form_submit_button("Analizar mi caso", type="primary")
278
 
279
  if submitted:
280
+ clear_search_state() # Limpiamos el estado en cada nueva búsqueda
281
  if not query:
282
  st.warning("Por favor, describe lo que sientes y lo que comiste.")
283
  elif alimentos_data is None:
 
287
  with st.spinner("🧠 Interpretando tu caso y buscando pistas con IA..."):
288
  entities = extract_and_infer_with_gemini(query, lista_condiciones)
289
  st.session_state.entities = entities
 
290
  if entities and (entities.get("alimentos") or entities.get("sintomas")):
291
  info_str = f"IA identificó - Alimentos: {', '.join(entities.get('alimentos',[])) or 'Ninguno'}, Síntomas: {', '.join(entities.get('sintomas',[])) or 'Ninguno'}"
292
  if entities.get("condicion_probable"): info_str += f", Condición Probable: {entities.get('condicion_probable')}"
 
299
  st.session_state.search_results = []
300
 
301
  if st.session_state.search_results is not None:
302
+ st.button("Realizar nueva consulta", on_click=clear_search_state, type="primary")
303
  st.markdown("---")
304
 
305
  results = st.session_state.search_results
 
312
  chart = create_relevance_chart(results)
313
  st.altair_chart(chart, use_container_width=True)
314
 
315
+ # --- ANÁLISIS DEL RESULTADO PRINCIPAL ---
316
  best_match_data = results[0]
317
  best_match = best_match_data['entry']
 
 
318
  with st.expander(f"**Análisis Detallado de la Principal Coincidencia: {best_match.get('condicion_asociada')}**", expanded=True):
319
+ # ... (código del desglose de puntuación y popover de FoodB no cambia)
320
+ # (El código para mostrar el análisis detallado del #1 es el mismo que antes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  with st.spinner("✍️ Generando un análisis personalizado con IA..."):
322
+ # Usamos un caché para no regenerar el análisis si ya existe
323
+ if 'best_match_analysis' not in st.session_state.analysis_cache:
324
+ st.session_state.analysis_cache['best_match_analysis'] = generate_detailed_analysis(st.session_state.user_query, best_match)
325
+ st.markdown(st.session_state.analysis_cache['best_match_analysis'])
326
  st.markdown("---")
327
  st.write("**¿Te fue útil este análisis?**")
328
  feedback_cols = st.columns(8)
 
333
  log_feedback(st.session_state.user_query, best_match_data, "no_util")
334
  st.warning("Gracias. Usaremos tu feedback para mejorar.")
335
 
336
+ # --- NUEVA SECCIÓN PARA "DIAGNÓSTICO DIFERENCIAL" ---
337
  if len(results) > 1:
338
+ with st.expander("**Otras Posibilidades Relevantes (Diagnóstico Diferencial)**"):
339
+ # Mostramos las siguientes 2 o 3 posibilidades
340
+ for i, result in enumerate(results[1:4]):
341
  entry = result['entry']
342
  score = result['score']
343
+ st.subheader(f"{i+2}. {entry.get('condicion_asociada')}")
344
+ col1, col2 = st.columns([2,1])
345
+ with col1:
346
+ st.write(f"**Puntuación Total de Relevancia:** {score['total']}")
347
+ if result.get('matched_symptoms'):
348
+ st.write(f"**Pistas Clave (Síntomas Coincidentes):** {', '.join(result['matched_symptoms']).capitalize()}")
349
+ st.write(f"**Alimentos Típicos Asociados:** {entry.get('compuesto_alimento')}")
350
+
351
+ with col2:
352
+ analysis_key = f"analysis_{i+2}"
353
+ if st.button(f"Generar análisis para esta opción", key=analysis_key):
354
+ with st.spinner("Generando análisis..."):
355
+ st.session_state.analysis_cache[analysis_key] = generate_detailed_analysis(st.session_state.user_query, entry)
356
+
357
+ if analysis_key in st.session_state.analysis_cache:
358
+ st.info(st.session_state.analysis_cache[analysis_key])
359
  st.markdown("---")