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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -68
app.py CHANGED
@@ -1,5 +1,5 @@
1
- # ==================== El Detective de Alimentos (Versión 8.0 - Lógica Gatekeeper) =====================================
2
- # Mejoras: Implementación de la lógica de búsqueda "Gatekeeper" para una precisión superior.
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
- # --- CONFIGURACIÓN Y CARGA DE DATOS (SIN CAMBIOS) ---
22
  # Configurar logging
23
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
24
  logger = logging.getLogger("food_detective_app")
@@ -73,7 +73,8 @@ def load_data():
73
  return None, None, None
74
  alimentos_data, lista_condiciones, foodb_index = load_data()
75
 
76
- # --- DICCIONARIOS DE MAPEADO (SIN CAMBIOS) ---
 
77
  FOOD_TO_COMPOUND_MAP = {
78
  "pan": ["gluten"], "trigo": ["gluten"], "harina de trigo": ["gluten"], "cebada": ["gluten"], "centeno": ["gluten"], "pasta": ["gluten"], "galletas": ["gluten"], "avena": ["gluten"], "pizza": ["gluten"], "torta": ["gluten"],
79
  "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"],
@@ -91,27 +92,21 @@ CONDITION_SYNONYMS = {
91
  "gota / hiperuricemia.": ["acido urico aumentado"], "intolerancia a la lactosa.": ["déficit de lactasa"],
92
  "enfermedad celíaca (clásica).": ["dermatitis herpetiforme"], "migraña.": ["dolor de cabeza", "cefalea"]
93
  }
 
 
94
  FOOD_NAME_TO_FOODB_KEY = {
95
- "pan": "bread", "pasta": "pasta", "galleta": "cookie", "pizza": "pizza", "cebada": "barley", "centeno": "rye",
96
- "leche": "milk", "queso": "cheese", "huevo": "egg", "carne": "meat", "ternera": "beef", "cerdo": "pork", "cordero": "lamb",
97
- "manzana": "apple", "naranja": "orange", "uva": "grape", "plátano": "banana", "aguacate": "avocado", "limón": "lemon",
98
- "tomate": "tomato", "patata": "potato", "cebolla": "onion", "ajo": "garlic", "espinaca": "spinach",
99
- "vino": "red wine", "cerveza": "beer", "café": "coffee", "chocolate": "chocolate", "miel": "honey",
100
- "almendra": "almond", "nuez": "walnut", "cacahuete": "peanut", "arroz": "rice", "maíz": "corn",
101
- "pescado": "fish", "atún": "tuna", "salmón": "salmon", "marisco": "shellfish", "camarón": "shrimp"
102
  }
103
 
104
- # --- LÓGICA DE BÚSQUEDA Y ANÁLISIS ---
105
-
106
- def sanitize_text(text):
107
- """Convierte texto a minúsculas, elimina puntuación común y espacios extra."""
108
- if not text:
109
- return ""
110
- text = re.sub(r'[.,;()]', '', text)
111
- return text.lower().strip()
112
-
113
  def extract_and_infer_with_gemini(query, condiciones):
114
- # ... (Sin cambios)
115
  if not model: return None
116
  condiciones_str = "\n".join([f"- {c}" for c in condiciones])
117
  system_prompt = f"""
@@ -136,22 +131,20 @@ def extract_and_infer_with_gemini(query, condiciones):
136
  logger.error(f"Error en la extracción/inferencia con Gemini: {e}")
137
  st.error(f"Hubo un problema al interpretar tu consulta con la IA.")
138
  return None
139
-
 
 
 
140
  def find_best_matches_hybrid(entities, data):
141
- """Motor de búsqueda híbrido (v8.0) con lógica "Gatekeeper" de alta confianza."""
142
  if not entities or not data: return []
143
-
144
  user_symptoms = set(sanitize_text(s) for s in entities.get("sintomas", []))
145
  user_foods = set(sanitize_text(f) for f in entities.get("alimentos", []))
146
  inferred_condition_raw = sanitize_text(entities.get("condicion_probable", ""))
147
-
148
  candidate_terms = set(user_foods)
149
  for food in user_foods:
150
  food_sanitized = sanitize_text(food)
151
  if food_sanitized in FOOD_TO_COMPOUND_MAP:
152
  candidate_terms.update(c.lower() for c in FOOD_TO_COMPOUND_MAP[food_sanitized])
153
-
154
- # --- LÓGICA GATEKEEPER ---
155
  target_data = data
156
  if inferred_condition_raw:
157
  filtered_data = []
@@ -163,23 +156,17 @@ def find_best_matches_hybrid(entities, data):
163
  if entry_condition in CONDITION_SYNONYMS:
164
  if inferred_condition_raw in [sanitize_text(s) for s in CONDITION_SYNONYMS[entry_condition]]:
165
  filtered_data.append(entry)
166
-
167
  if filtered_data:
168
  target_data = filtered_data
169
  logger.info(f"Modo Gatekeeper activado. Buscando solo entre {len(target_data)} condiciones.")
170
-
171
- # --- Puntuación (Ahora sobre los datos correctos) ---
172
  results = []
173
  for entry in target_data:
174
  score_details = {'condition': 0, 'food': 0, 'symptoms': 0, 'total': 0}
175
-
176
  if inferred_condition_raw and sanitize_text(entry.get("condicion_asociada", "")) == inferred_condition_raw:
177
  score_details['condition'] = 100
178
-
179
  entry_compounds_text = sanitize_text(entry.get("compuesto_alimento", ""))
180
  if any(term in entry_compounds_text for term in candidate_terms):
181
  score_details['food'] = 15
182
-
183
  entry_symptoms_keys = set(sanitize_text(s) for s in entry.get("sintomas_clave", []))
184
  symptom_score = 0
185
  for user_symptom in user_symptoms:
@@ -188,17 +175,12 @@ def find_best_matches_hybrid(entities, data):
188
  symptom_score += 10
189
  break
190
  score_details['symptoms'] = symptom_score
191
-
192
  total_score = sum(score_details.values())
193
  if total_score > 0:
194
  results.append({'entry': entry, 'score': score_details})
195
-
196
  if not results: return []
197
-
198
  sorted_results = sorted(results, key=lambda x: x['score']['total'], reverse=True)
199
  return sorted_results
200
-
201
- # ... (El resto de funciones (generate_detailed_analysis, create_relevance_chart, log_feedback, etc.) no cambian) ...
202
  def generate_detailed_analysis(query, match):
203
  if not model: return "Error: El modelo de IA no está disponible."
204
  prompt_parts = [
@@ -228,7 +210,7 @@ def generate_detailed_analysis(query, match):
228
  f'* **[Punto 2 de las recomendaciones, enfocado en los exámenes si los hay, basado en "{match.get("recomendaciones_examenes")}"]**',
229
  f'* **Atención a otros alimentos:** Ten en cuenta que, además de lo que mencionaste, otros alimentos implicados en esta condición son: **[menciona 2-3 ejemplos del campo "{match.get("compuesto_alimento")}"]**.',
230
  "\n### **IMPORTANTE: Descargo de Responsabilidad**",
231
- "Este análisis es una herramienta informativa y de orientación basada en inteligencia artificial. **NO es un diagnóstico médico.** La información proporcionada no debe sustituir la consulta, el diagnóstico o el tratamiento de un médico o profesional de la salud qualificado. Consulta siempre a un experto para evaluar tu caso particular."
232
  ])
233
  prompt = "\n".join(prompt_parts)
234
  try:
@@ -259,16 +241,9 @@ def log_feedback(query, result, feedback):
259
  os.makedirs("logs", exist_ok=True)
260
  with open(os.path.join("logs", "feedback_log.txt"), "a", encoding="utf-8") as f:
261
  f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
262
- def extract_foods_from_string(food_string):
263
- match = re.search(r'\(([^)]+)\)$', food_string)
264
- if match:
265
- foods_part = match.group(1)
266
- return [food.strip().lower() for food in foods_part.split(',') if food.strip()]
267
- else:
268
- main_part = re.sub(r'^\w+\s*\(.*?\)\s*', '', food_string).strip()
269
- return [food.strip().lower() for food in main_part.split(',') if food.strip()]
270
 
271
- # --- INTERFAZ DE USUARIO Y LÓGICA PRINCIPAL ---
 
272
  col_img, col_text = st.columns([1, 4], gap="medium")
273
  with col_img:
274
  if os.path.exists("imagen.png"):
@@ -306,7 +281,6 @@ if submitted:
306
  info_str = f"IA identificó - Alimentos: {', '.join(entities.get('alimentos',[])) or 'Ninguno'}, Síntomas: {', '.join(entities.get('sintomas',[])) or 'Ninguno'}"
307
  if entities.get("condicion_probable"): info_str += f", Condición Probable: {entities.get('condicion_probable')}"
308
  st.info(info_str)
309
-
310
  with st.spinner("🔬 Cruzando información y calculando relevancia..."):
311
  results = find_best_matches_hybrid(entities, alimentos_data)
312
  st.session_state.search_results = results
@@ -345,29 +319,38 @@ if st.session_state.search_results is not None:
345
  with col2:
346
  st.write("")
347
  if foodb_index:
 
348
  with st.popover("🔬 Principales componentes moleculares"):
349
  user_foods_mentioned = st.session_state.entities.get("alimentos", [])
 
350
  if not user_foods_mentioned:
351
- st.info("Mostrando componentes de los alimentos de ejemplo de esta condición.")
352
- food_string_to_check = best_match.get("compuesto_alimento", "")
353
- user_foods_mentioned = [food.strip() for food in re.split(r'[,()]', food_string_to_check) if food.strip() and not food.islower()]
354
- found_data = False
355
- for alimento_es in user_foods_mentioned:
356
- foodb_key = None
357
- for key_es, key_en in FOOD_NAME_TO_FOODB_KEY.items():
358
- if key_es in alimento_es.lower():
359
- foodb_key = key_en
360
- break
361
- if foodb_key and foodb_key in foodb_index:
362
- found_data = True
363
- with st.container(border=True):
364
- st.subheader(f"Análisis de: {alimento_es.capitalize()}")
365
- for item in foodb_index[foodb_key][:5]:
366
- st.write(f"**Compuesto:** {item['compound']}")
367
- st.write(f"**Efectos reportados:** {', '.join(item['effects'])}")
368
- st.markdown("---")
 
 
 
 
 
 
 
369
  if not found_data:
370
- st.warning("Sin datos moleculares para este alimento.")
371
 
372
  st.markdown("---")
373
 
 
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
 
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")
 
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 = {
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
  "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"],
100
+ "manzana": ["apple"], "naranja": ["orange"], "uva": ["grape"], "plátano": ["banana"], "aguacate": ["avocado"], "limón": ["lemon"],
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
  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", []))
141
  user_foods = set(sanitize_text(f) for f in entities.get("alimentos", []))
142
  inferred_condition_raw = sanitize_text(entities.get("condicion_probable", ""))
 
143
  candidate_terms = set(user_foods)
144
  for food in user_foods:
145
  food_sanitized = sanitize_text(food)
146
  if food_sanitized in FOOD_TO_COMPOUND_MAP:
147
  candidate_terms.update(c.lower() for c in FOOD_TO_COMPOUND_MAP[food_sanitized])
 
 
148
  target_data = data
149
  if inferred_condition_raw:
150
  filtered_data = []
 
156
  if entry_condition in CONDITION_SYNONYMS:
157
  if inferred_condition_raw in [sanitize_text(s) for s in CONDITION_SYNONYMS[entry_condition]]:
158
  filtered_data.append(entry)
 
159
  if filtered_data:
160
  target_data = filtered_data
161
  logger.info(f"Modo Gatekeeper activado. Buscando solo entre {len(target_data)} condiciones.")
 
 
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", ""))
168
  if any(term in entry_compounds_text for term in candidate_terms):
169
  score_details['food'] = 15
 
170
  entry_symptoms_keys = set(sanitize_text(s) for s in entry.get("sintomas_clave", []))
171
  symptom_score = 0
172
  for user_symptom in user_symptoms:
 
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 = [
 
210
  f'* **[Punto 2 de las recomendaciones, enfocado en los exámenes si los hay, basado en "{match.get("recomendaciones_examenes")}"]**',
211
  f'* **Atención a otros alimentos:** Ten en cuenta que, además de lo que mencionaste, otros alimentos implicados en esta condición son: **[menciona 2-3 ejemplos del campo "{match.get("compuesto_alimento")}"]**.',
212
  "\n### **IMPORTANTE: Descargo de Responsabilidad**",
213
+ "Este análisis es una herramienta informativa y de orientación basada en inteligencia artificial. **NO es un diagnóstico médico.** La información proporcionada no debe sustituir la consulta, el diagnóstico o el tratamiento de un médico o profesional de la salud cualificado. Consulta siempre a un experto para evaluar tu caso particular."
214
  ])
215
  prompt = "\n".join(prompt_parts)
216
  try:
 
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")
248
  with col_img:
249
  if os.path.exists("imagen.png"):
 
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')}"
283
  st.info(info_str)
 
284
  with st.spinner("🔬 Cruzando información y calculando relevancia..."):
285
  results = find_best_matches_hybrid(entities, alimentos_data)
286
  st.session_state.search_results = results
 
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