JairoCesar commited on
Commit
942954c
·
verified ·
1 Parent(s): 298a6f9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +36 -35
app.py CHANGED
@@ -1,5 +1,5 @@
1
- # ==================== El Detective de Alimentos (Versión 7.1 - Búsqueda FoodB Mejorada) =====================================
2
- # Mejoras: Lógica del popover de FoodB corregida y centrada en el input del usuario.
3
 
4
  import streamlit as st
5
  import google.generativeai as genai
@@ -18,7 +18,7 @@ st.set_page_config(
18
  layout="wide"
19
  )
20
 
21
- # ... (Todo el código hasta la UI no cambia) ...
22
  # Configurar logging
23
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
24
  logger = logging.getLogger("food_detective_app")
@@ -72,12 +72,16 @@ def load_data():
72
  st.error(f"❌ Error: Un archivo JSON tiene un formato incorrecto: {e}")
73
  return None, None, None
74
  alimentos_data, lista_condiciones, foodb_index = load_data()
 
 
 
 
75
  FOOD_TO_COMPOUND_MAP = {
76
  "pan": ["gluten"], "trigo": ["gluten"], "harina de trigo": ["gluten"], "cebada": ["gluten"], "centeno": ["gluten"], "pasta": ["gluten"], "galletas": ["gluten"], "avena": ["gluten"], "pizza": ["gluten"], "torta": ["gluten"],
77
  "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"],
78
  "manzana": ["salicilatos", "fructosa"], "almendras": ["salicilatos", "arginina"], "uvas": ["salicilatos"], "pasas": ["salicilatos"], "naranja": ["salicilatos"], "brócoli": ["salicilatos", "goitrógenos", "fodmaps"], "cúrcuma": ["salicilatos"],
79
  "azucar": ["azúcar", "fructosa"], "dulces": ["azúcar"], "refrescos": ["azúcar", "fructosa"], "gaseosas": ["azúcar", "fructosa"], "miel": ["fructosa", "fodmaps"], "jarabe de maiz": ["fructosa"],
80
- "vino tinto": ["histamina", "tiramina", "sulfitos"], "vino rojo": ["histamina", "tiramina", "sulfitos"], "cerveza": ["histamina", "tiramina", "purinas", "gluten"], "chocolate": ["cafeína", "tiramina", "níquel", "arginina"],
81
  "embutidos": ["histamina", "tiramina", "nitritos"], "pescado enlatado": ["histamina"], "tomate": ["histamina", "solaninas", "ácidos"],
82
  "aguacate": ["fodmaps", "polioles"], "cebolla": ["fodmaps"], "ajo": ["fodmaps"], "legumbres": ["fodmaps", "gos"],
83
  "carne": ["alfa-gal", "purinas", "hierro"], "carnes rojas": ["purinas", "alfa-gal", "hierro"],
@@ -89,16 +93,21 @@ CONDITION_SYNONYMS = {
89
  "gota / hiperuricemia.": ["acido urico aumentado"], "intolerancia a la lactosa.": ["déficit de lactasa"],
90
  "enfermedad celíaca (clásica).": ["dermatitis herpetiforme"], "migraña.": ["dolor de cabeza", "cefalea"]
91
  }
 
 
92
  FOOD_NAME_TO_FOODB_KEY = {
93
- "pan": "bread", "pasta": "pasta", "galletas": "cookie", "pizza": "pizza", "cebada": "barley", "centeno": "rye",
 
94
  "leche": "milk", "queso": "cheese", "huevo": "egg", "carne": "meat", "ternera": "beef", "cerdo": "pork", "cordero": "lamb",
95
  "manzana": "apple", "naranja": "orange", "uva": "grape", "plátano": "banana", "aguacate": "avocado", "limón": "lemon",
96
- "tomate": "tomato", "patata": "potato", "cebolla": "onion", "ajo": "garlic", "espinacas": "spinach",
97
- "vino tinto": "red wine", "cerveza": "beer", "café": "coffee", "chocolate": "chocolate", "miel": "honey",
98
- "almendras": "almond", "nueces": "walnut", "cacahuetes": "peanut", "arroz": "rice", "maíz": "corn",
99
- "pescado": "fish", "atún": "tuna", "salmón": "salmon", "mariscos": "shellfish", "camarones": "shrimp"
100
  }
101
 
 
 
102
  def extract_and_infer_with_gemini(query, condiciones):
103
  if not model: return None
104
  condiciones_str = "\n".join([f"- {c}" for c in condiciones])
@@ -124,7 +133,6 @@ def extract_and_infer_with_gemini(query, condiciones):
124
  logger.error(f"Error en la extracción/inferencia con Gemini: {e}")
125
  st.error(f"Hubo un problema al interpretar tu consulta con la IA.")
126
  return None
127
-
128
  def find_best_matches_hybrid(entities, data):
129
  if not entities or not data: return []
130
  user_symptoms = set(s.lower().strip() for s in entities.get("sintomas", []))
@@ -161,9 +169,7 @@ def find_best_matches_hybrid(entities, data):
161
  if not results: return []
162
  sorted_results = sorted(results, key=lambda x: x['score']['total'], reverse=True)
163
  return sorted_results
164
-
165
  def generate_detailed_analysis(query, match):
166
- # ... (Sin cambios)
167
  if not model: return "Error: El modelo de IA no está disponible."
168
  prompt_parts = [
169
  "Eres un asistente de IA experto en nutrición personalizada. Tu tono es empático, claro y muy educativo. NO actúas como un médico, sino como un guía informativo.",
@@ -201,10 +207,7 @@ def generate_detailed_analysis(query, match):
201
  except Exception as e:
202
  logger.error(f"Error generando análisis detallado con Gemini: {e}")
203
  return "No se pudo generar el análisis detallado."
204
-
205
-
206
  def create_relevance_chart(results):
207
- # ... (Sin cambios)
208
  top_results = results[:5]
209
  condition_names = [re.sub(r'\(.*\)', '', res['entry']['condicion_asociada']).strip() for res in top_results]
210
  chart_data = {"Condición": condition_names, "Relevancia": [res['score']['total'] for res in top_results]}
@@ -215,9 +218,7 @@ def create_relevance_chart(results):
215
  tooltip=[alt.Tooltip('Condición:N', title='Condición'), alt.Tooltip('Relevancia:Q', title='Puntuación')]
216
  ).properties(title='Principales Coincidencias según tu Caso').configure_axis(labelFontSize=12, titleFontSize=14).configure_title(fontSize=16, anchor='start')
217
  return chart
218
-
219
  def log_feedback(query, result, feedback):
220
- # ... (Sin cambios)
221
  log_entry = {
222
  "timestamp": datetime.now().isoformat(),
223
  "query": query,
@@ -229,7 +230,6 @@ def log_feedback(query, result, feedback):
229
  with open(os.path.join("logs", "feedback_log.txt"), "a", encoding="utf-8") as f:
230
  f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
231
 
232
-
233
  # --- INTERFAZ DE USUARIO Y LÓGICA PRINCIPAL (CON CAMBIOS) ---
234
  col_img, col_text = st.columns([1, 4], gap="medium")
235
  with col_img:
@@ -242,7 +242,7 @@ st.markdown("---")
242
 
243
  if 'search_results' not in st.session_state: st.session_state.search_results = None
244
  if 'user_query' not in st.session_state: st.session_state.user_query = ""
245
- if 'entities' not in st.session_state: st.session_state.entities = None # Para guardar las entidades extraídas
246
 
247
  def clear_search_state():
248
  st.session_state.search_results = None
@@ -262,13 +262,12 @@ if submitted:
262
  st.session_state.user_query = query
263
  with st.spinner("🧠 Interpretando tu caso y buscando pistas con IA..."):
264
  entities = extract_and_infer_with_gemini(query, lista_condiciones)
265
- st.session_state.entities = entities # Guardamos las entidades
266
 
267
  if entities and (entities.get("alimentos") or entities.get("sintomas")):
268
  info_str = f"IA identificó - Alimentos: {', '.join(entities.get('alimentos',[])) or 'Ninguno'}, Síntomas: {', '.join(entities.get('sintomas',[])) or 'Ninguno'}"
269
  if entities.get("condicion_probable"): info_str += f", Condición Probable: {entities.get('condicion_probable')}"
270
  st.info(info_str)
271
-
272
  with st.spinner("🔬 Cruzando información y calculando relevancia..."):
273
  results = find_best_matches_hybrid(entities, alimentos_data)
274
  st.session_state.search_results = results
@@ -298,7 +297,6 @@ if st.session_state.search_results is not None:
298
  col1, col2 = st.columns([3, 1])
299
  with col1:
300
  st.markdown("##### Desglose de la Puntuación de Relevancia:")
301
- # ... (código de las métricas no cambia)
302
  score_col1, score_col2, score_col3, score_col4 = st.columns(4)
303
  score_col1.metric("Puntos por Condición", f"{best_score['condition']}")
304
  score_col2.metric("Puntos por Alimento", f"{best_score['food']}")
@@ -308,33 +306,37 @@ if st.session_state.search_results is not None:
308
  with col2:
309
  st.write("")
310
  if foodb_index:
311
- # --- LÓGICA DEL POPOVER COMPLETAMENTE RECONSTRUIDA ---
312
- with st.popover("🔬 Principales componentes moleculares del alimento"):
313
- # Obtenemos los alimentos que el usuario mencionó
314
  user_foods_mentioned = st.session_state.entities.get("alimentos", [])
315
 
316
  if not user_foods_mentioned:
317
- st.info("El análisis se basa en los alimentos de ejemplo de esta condición.")
318
- # Si el usuario no mencionó comida, usamos los de ejemplo del JSON
319
  food_string_to_check = best_match.get("compuesto_alimento", "")
320
- user_foods_mentioned = re.findall(r'\b\w+\b', re.sub(r'\(.*\)', '', food_string_to_check))
321
-
322
 
323
  found_data = False
 
324
  for alimento_es in user_foods_mentioned:
325
- alimento_en = FOOD_NAME_TO_FOODB_KEY.get(alimento_es.lower())
326
- if alimento_en and alimento_en in foodb_index:
 
 
 
 
 
 
327
  found_data = True
328
  with st.container(border=True):
329
  st.subheader(f"Análisis de: {alimento_es.capitalize()}")
330
- for item in foodb_index[alimento_en][:5]:
331
  st.write(f"**Compuesto:** {item['compound']}")
332
  st.write(f"**Efectos reportados:** {', '.join(item['effects'])}")
333
  st.markdown("---")
334
 
335
  if not found_data:
336
- # Mensaje corregido y más simple
337
- st.write("Sin datos moleculares para este alimento.")
338
 
339
  st.markdown("---")
340
 
@@ -354,7 +356,6 @@ if st.session_state.search_results is not None:
354
 
355
  if len(results) > 1:
356
  with st.expander("Otras posibles coincidencias (ordenadas por relevancia)"):
357
- # ... (código para mostrar otras coincidencias no cambia) ...
358
  for result in results[1:]:
359
  entry = result['entry']
360
  score = result['score']
 
1
+ # ==================== El Detective de Alimentos (Versión 7.2 - Traducción Inteligente) =====================================
2
+ # Mejoras: Lógica de "traducción inteligente" para la búsqueda en FoodB.
3
 
4
  import streamlit as st
5
  import google.generativeai as genai
 
18
  layout="wide"
19
  )
20
 
21
+ # ... (Configuración de Gemini y carga de datos no cambia) ...
22
  # Configurar logging
23
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
24
  logger = logging.getLogger("food_detective_app")
 
72
  st.error(f"❌ Error: Un archivo JSON tiene un formato incorrecto: {e}")
73
  return None, None, None
74
  alimentos_data, lista_condiciones, foodb_index = load_data()
75
+
76
+
77
+ # --- DICCIONARIOS DE MAPEADO ---
78
+ # ... (FOOD_TO_COMPOUND_MAP y CONDITION_SYNONYMS 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"],
82
  "manzana": ["salicilatos", "fructosa"], "almendras": ["salicilatos", "arginina"], "uvas": ["salicilatos"], "pasas": ["salicilatos"], "naranja": ["salicilatos"], "brócoli": ["salicilatos", "goitrógenos", "fodmaps"], "cúrcuma": ["salicilatos"],
83
  "azucar": ["azúcar", "fructosa"], "dulces": ["azúcar"], "refrescos": ["azúcar", "fructosa"], "gaseosas": ["azúcar", "fructosa"], "miel": ["fructosa", "fodmaps"], "jarabe de maiz": ["fructosa"],
84
+ "vino tinto": ["histamina", "tiramina", "sulfitos"], "vino rojo": ["histamina", "tiramina", "sulfitos"], "vino": ["histamina", "tiramina", "sulfitos"], "cerveza": ["histamina", "tiramina", "purinas", "gluten"], "chocolate": ["cafeína", "tiramina", "níquel", "arginina"],
85
  "embutidos": ["histamina", "tiramina", "nitritos"], "pescado enlatado": ["histamina"], "tomate": ["histamina", "solaninas", "ácidos"],
86
  "aguacate": ["fodmaps", "polioles"], "cebolla": ["fodmaps"], "ajo": ["fodmaps"], "legumbres": ["fodmaps", "gos"],
87
  "carne": ["alfa-gal", "purinas", "hierro"], "carnes rojas": ["purinas", "alfa-gal", "hierro"],
 
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
+
97
+ # --- DICCIONARIO TRADUCTOR REFINADO ---
98
  FOOD_NAME_TO_FOODB_KEY = {
99
+ # La clave ahora es más genérica para facilitar la búsqueda por contención
100
+ "pan": "bread", "pasta": "pasta", "galleta": "cookie", "pizza": "pizza", "cebada": "barley", "centeno": "rye",
101
  "leche": "milk", "queso": "cheese", "huevo": "egg", "carne": "meat", "ternera": "beef", "cerdo": "pork", "cordero": "lamb",
102
  "manzana": "apple", "naranja": "orange", "uva": "grape", "plátano": "banana", "aguacate": "avocado", "limón": "lemon",
103
+ "tomate": "tomato", "patata": "potato", "cebolla": "onion", "ajo": "garlic", "espinaca": "spinach",
104
+ "vino": "red wine", "cerveza": "beer", "café": "coffee", "chocolate": "chocolate", "miel": "honey",
105
+ "almendra": "almond", "nuez": "walnut", "cacahuete": "peanut", "arroz": "rice", "maíz": "corn",
106
+ "pescado": "fish", "atún": "tuna", "salmón": "salmon", "marisco": "shellfish", "camarón": "shrimp"
107
  }
108
 
109
+ # --- LÓGICA DE BÚSQUEDA Y ANÁLISIS (SIN CAMBIOS) ---
110
+ # (Todas las funciones hasta la UI se mantienen igual que en la versión anterior)
111
  def extract_and_infer_with_gemini(query, condiciones):
112
  if not model: return None
113
  condiciones_str = "\n".join([f"- {c}" for c in condiciones])
 
133
  logger.error(f"Error en la extracción/inferencia con Gemini: {e}")
134
  st.error(f"Hubo un problema al interpretar tu consulta con la IA.")
135
  return None
 
136
  def find_best_matches_hybrid(entities, data):
137
  if not entities or not data: return []
138
  user_symptoms = set(s.lower().strip() for s in entities.get("sintomas", []))
 
169
  if not results: return []
170
  sorted_results = sorted(results, key=lambda x: x['score']['total'], reverse=True)
171
  return sorted_results
 
172
  def generate_detailed_analysis(query, match):
 
173
  if not model: return "Error: El modelo de IA no está disponible."
174
  prompt_parts = [
175
  "Eres un asistente de IA experto en nutrición personalizada. Tu tono es empático, claro y muy educativo. NO actúas como un médico, sino como un guía informativo.",
 
207
  except Exception as e:
208
  logger.error(f"Error generando análisis detallado con Gemini: {e}")
209
  return "No se pudo generar el análisis detallado."
 
 
210
  def create_relevance_chart(results):
 
211
  top_results = results[:5]
212
  condition_names = [re.sub(r'\(.*\)', '', res['entry']['condicion_asociada']).strip() for res in top_results]
213
  chart_data = {"Condición": condition_names, "Relevancia": [res['score']['total'] for res in top_results]}
 
218
  tooltip=[alt.Tooltip('Condición:N', title='Condición'), alt.Tooltip('Relevancia:Q', title='Puntuación')]
219
  ).properties(title='Principales Coincidencias según tu Caso').configure_axis(labelFontSize=12, titleFontSize=14).configure_title(fontSize=16, anchor='start')
220
  return chart
 
221
  def log_feedback(query, result, feedback):
 
222
  log_entry = {
223
  "timestamp": datetime.now().isoformat(),
224
  "query": query,
 
230
  with open(os.path.join("logs", "feedback_log.txt"), "a", encoding="utf-8") as f:
231
  f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
232
 
 
233
  # --- INTERFAZ DE USUARIO Y LÓGICA PRINCIPAL (CON CAMBIOS) ---
234
  col_img, col_text = st.columns([1, 4], gap="medium")
235
  with col_img:
 
242
 
243
  if 'search_results' not in st.session_state: st.session_state.search_results = None
244
  if 'user_query' not in st.session_state: st.session_state.user_query = ""
245
+ if 'entities' not in st.session_state: st.session_state.entities = None
246
 
247
  def clear_search_state():
248
  st.session_state.search_results = None
 
262
  st.session_state.user_query = query
263
  with st.spinner("🧠 Interpretando tu caso y buscando pistas con IA..."):
264
  entities = extract_and_infer_with_gemini(query, lista_condiciones)
265
+ st.session_state.entities = entities
266
 
267
  if entities and (entities.get("alimentos") or entities.get("sintomas")):
268
  info_str = f"IA identificó - Alimentos: {', '.join(entities.get('alimentos',[])) or 'Ninguno'}, Síntomas: {', '.join(entities.get('sintomas',[])) or 'Ninguno'}"
269
  if entities.get("condicion_probable"): info_str += f", Condición Probable: {entities.get('condicion_probable')}"
270
  st.info(info_str)
 
271
  with st.spinner("🔬 Cruzando información y calculando relevancia..."):
272
  results = find_best_matches_hybrid(entities, alimentos_data)
273
  st.session_state.search_results = results
 
297
  col1, col2 = st.columns([3, 1])
298
  with col1:
299
  st.markdown("##### Desglose de la Puntuación de Relevancia:")
 
300
  score_col1, score_col2, score_col3, score_col4 = st.columns(4)
301
  score_col1.metric("Puntos por Condición", f"{best_score['condition']}")
302
  score_col2.metric("Puntos por Alimento", f"{best_score['food']}")
 
306
  with col2:
307
  st.write("")
308
  if foodb_index:
309
+ # --- LÓGICA DEL POPOVER COMPLETAMENTE RECONSTRUIDA Y CORREGIDA ---
310
+ with st.popover("🔬 Principales componentes moleculares"):
 
311
  user_foods_mentioned = st.session_state.entities.get("alimentos", [])
312
 
313
  if not user_foods_mentioned:
314
+ st.info("Mostrando componentes de los alimentos de ejemplo de esta condición.")
 
315
  food_string_to_check = best_match.get("compuesto_alimento", "")
316
+ # Usamos una regex más simple para encontrar palabras en mayúsculas o minúsculas
317
+ user_foods_mentioned = [food.strip() for food in re.split(r'[,()]', food_string_to_check) if food.strip() and not food.islower()]
318
 
319
  found_data = False
320
+ # Iteramos sobre los alimentos que el usuario mencionó
321
  for alimento_es in user_foods_mentioned:
322
+ foodb_key = None
323
+ # Buscamos la traducción con la lógica de contención
324
+ for key_es, key_en in FOOD_NAME_TO_FOODB_KEY.items():
325
+ if key_es in alimento_es.lower():
326
+ foodb_key = key_en
327
+ break
328
+
329
+ if foodb_key and foodb_key in foodb_index:
330
  found_data = True
331
  with st.container(border=True):
332
  st.subheader(f"Análisis de: {alimento_es.capitalize()}")
333
+ for item in foodb_index[foodb_key][:5]:
334
  st.write(f"**Compuesto:** {item['compound']}")
335
  st.write(f"**Efectos reportados:** {', '.join(item['effects'])}")
336
  st.markdown("---")
337
 
338
  if not found_data:
339
+ st.warning("Sin datos moleculares para este alimento.") # Mensaje corregido
 
340
 
341
  st.markdown("---")
342
 
 
356
 
357
  if len(results) > 1:
358
  with st.expander("Otras posibles coincidencias (ordenadas por relevancia)"):
 
359
  for result in results[1:]:
360
  entry = result['entry']
361
  score = result['score']