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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +67 -36
app.py CHANGED
@@ -1,5 +1,5 @@
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,7 +18,7 @@ st.set_page_config(
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,9 +73,7 @@ def load_data():
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"],
@@ -93,10 +91,7 @@ CONDITION_SYNONYMS = {
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",
@@ -106,9 +101,17 @@ FOOD_NAME_TO_FOODB_KEY = {
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])
114
  system_prompt = f"""
@@ -133,28 +136,51 @@ def extract_and_infer_with_gemini(query, 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", []))
139
- user_foods = set(f.lower().strip() for f in entities.get("alimentos", []))
140
- inferred_condition_raw = entities.get("condicion_probable", "").lower().strip()
 
 
141
  candidate_terms = set(user_foods)
142
  for food in user_foods:
143
- if food in FOOD_TO_COMPOUND_MAP:
144
- candidate_terms.update(c.lower() for c in FOOD_TO_COMPOUND_MAP[food])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  results = []
146
- for index, entry in enumerate(data):
147
  score_details = {'condition': 0, 'food': 0, 'symptoms': 0, 'total': 0}
148
- entry_condition = entry.get("condicion_asociada", "").lower().strip()
149
- if inferred_condition_raw and inferred_condition_raw == entry_condition:
150
  score_details['condition'] = 100
151
- elif inferred_condition_raw and entry_condition in CONDITION_SYNONYMS:
152
- if inferred_condition_raw in CONDITION_SYNONYMS[entry_condition]:
153
- score_details['condition'] = 100
154
- entry_compounds_text = entry.get("compuesto_alimento", "").lower()
155
  if any(term in entry_compounds_text for term in candidate_terms):
156
  score_details['food'] = 15
157
- entry_symptoms_keys = set(s.lower().strip() for s in entry.get("sintomas_clave", []))
 
158
  symptom_score = 0
159
  for user_symptom in user_symptoms:
160
  for key in entry_symptoms_keys:
@@ -162,13 +188,17 @@ def find_best_matches_hybrid(entities, data):
162
  symptom_score += 10
163
  break
164
  score_details['symptoms'] = symptom_score
 
165
  total_score = sum(score_details.values())
166
  if total_score > 0:
167
- score_details['total'] = total_score
168
  results.append({'entry': entry, 'score': score_details})
 
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 = [
@@ -198,7 +228,7 @@ def generate_detailed_analysis(query, match):
198
  f'* **[Punto 2 de las recomendaciones, enfocado en los exámenes si los hay, basado en "{match.get("recomendaciones_examenes")}"]**',
199
  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")}"]**.',
200
  "\n### **IMPORTANTE: Descargo de Responsabilidad**",
201
- "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."
202
  ])
203
  prompt = "\n".join(prompt_parts)
204
  try:
@@ -229,8 +259,16 @@ def log_feedback(query, result, feedback):
229
  os.makedirs("logs", exist_ok=True)
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:
236
  if os.path.exists("imagen.png"):
@@ -268,6 +306,7 @@ if submitted:
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
@@ -306,26 +345,19 @@ if st.session_state.search_results is not None:
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):
@@ -334,9 +366,8 @@ if st.session_state.search_results is not None:
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
 
 
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
  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
  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
  "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",
 
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
  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 = []
158
+ for entry in data:
159
+ entry_condition = sanitize_text(entry.get("condicion_asociada", ""))
160
+ if entry_condition == inferred_condition_raw:
161
+ filtered_data.append(entry)
162
+ continue
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:
186
  for key in entry_symptoms_keys:
 
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
  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
  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
  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
  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):
 
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