JairoCesar commited on
Commit
7fbce98
·
verified ·
1 Parent(s): f47cf51

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -40
app.py CHANGED
@@ -1,5 +1,5 @@
1
- # ==================== El Detective de Alimentos (Versión 3.0 - Refinada) =====================================
2
- # Mejoras: Base de conocimiento de alimentos expandida, búsqueda de síntomas flexible y generación de respuestas mejorada.
3
 
4
  import streamlit as st
5
  import google.generativeai as genai
@@ -8,6 +8,8 @@ import os
8
  import json
9
  import logging
10
  import re
 
 
11
 
12
  st.set_page_config(
13
  page_title="El Detective de Alimentos",
@@ -15,6 +17,7 @@ st.set_page_config(
15
  layout="wide"
16
  )
17
 
 
18
  # Configurar logging
19
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
20
  logger = logging.getLogger("food_detective_app")
@@ -74,32 +77,27 @@ def load_data():
74
 
75
  alimentos_data, lista_condiciones = load_data()
76
 
77
- # DICCIONARIO DE TRADUCCIÓN AMPLIADO
78
  FOOD_TO_COMPOUND_MAP = {
79
- # Gluten
80
  "pan": ["gluten"], "trigo": ["gluten"], "harina de trigo": ["gluten"], "cebada": ["gluten"],
81
  "centeno": ["gluten"], "pasta": ["gluten"], "galletas": ["gluten"], "avena": ["gluten"], "pizza": ["gluten"], "torta": ["gluten"],
82
- # Lácteos
83
  "leche": ["lácteos", "caseína", "lactosa"], "queso": ["lácteos", "caseína", "lactosa", "histamina", "tiramina"],
84
  "yogur": ["lácteos", "caseína", "lactosa"], "mantequilla": ["lácteos", "caseína", "lactosa"], "crema": ["lácteos"], "helado": ["lácteos"],
85
- # Fenoles y Salicilatos
86
  "manzana": ["salicilatos", "fructosa"], "almendras": ["salicilatos"], "uvas": ["salicilatos"], "pasas": ["salicilatos"],
87
  "naranja": ["salicilatos"], "brócoli": ["salicilatos", "goitrógenos"], "cúrcuma": ["salicilatos"],
88
- # Azúcares y Fructosa
89
  "azucar": ["azúcar", "fructosa"], "dulces": ["azúcar"], "refrescos": ["azúcar", "fructosa"], "gaseosas": ["azúcar", "fructosa"],
90
  "miel": ["fructosa"], "jarabe de maiz": ["fructosa"],
91
- # Aminas (Histamina, Tiramina)
92
  "vino tinto": ["histamina", "tiramina", "sulfitos"], "vino rojo": ["histamina", "tiramina", "sulfitos"],
93
  "cerveza": ["histamina", "tiramina", "purinas"], "chocolate": ["cafeína", "tiramina", "níquel"],
94
  "embutidos": ["histamina", "tiramina", "nitritos"], "pescado enlatado": ["histamina"], "tomate": ["histamina", "solaninas"],
95
- # Otros
96
  "carne": ["alfa-gal", "proteínas", "purinas", "hierro"], "carnes rojas": ["purinas", "alfa-gal", "hierro"],
97
  "mariscos": ["purinas", "sulfitos", "alérgenos", "yodo"], "huevo": ["alérgenos"], "soya": ["alérgenos"],
98
  "café": ["cafeína", "ácidos"]
99
  }
100
 
101
- # --- LÓGICA DE BÚSQUEDA Y ANÁLISIS (ENFOQUE HÍBRIDO REFINADO) ---
102
 
 
103
  def extract_and_infer_with_gemini(query, condiciones):
104
  """Extrae entidades e infiere una condición probable."""
105
  if not model: return None
@@ -125,8 +123,11 @@ def extract_and_infer_with_gemini(query, condiciones):
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
- """Motor de búsqueda híbrido (v3.0) con puntuación ponderada y búsqueda de síntomas flexible."""
 
 
130
  if not entities or not data: return []
131
 
132
  user_symptoms = set(s.lower().strip() for s in entities.get("sintomas", []))
@@ -138,36 +139,42 @@ def find_best_matches_hybrid(entities, data):
138
  if food in FOOD_TO_COMPOUND_MAP:
139
  candidate_terms.update(c.lower() for c in FOOD_TO_COMPOUND_MAP[food])
140
 
141
- scores = {}
142
  for index, entry in enumerate(data):
143
- score = 0
144
 
145
  # Ponderación 1: Coincidencia de Condición
146
  entry_condition = entry.get("condicion_asociada", "").lower().strip()
147
  if inferred_condition and inferred_condition == entry_condition:
148
- score += 100
149
 
150
  # Ponderación 2: Coincidencia de Alimentos/Compuestos
151
  entry_compounds_text = entry.get("compuesto_alimento", "").lower()
152
  if any(term in entry_compounds_text for term in candidate_terms):
153
- score += 20
154
 
155
- # Ponderación 3: Coincidencia de Síntomas (BÚSQUEDA FLEXIBLE)
156
  entry_symptoms_keys = set(s.lower().strip() for s in entry.get("sintomas_clave", []))
 
157
  for user_symptom in user_symptoms:
158
  for key in entry_symptoms_keys:
159
  if key in user_symptom or user_symptom in key:
160
- score += 5
161
- break # Evita sumar puntos múltiples veces por el mismo síntoma de usuario
 
162
 
163
- if score > 0:
164
- scores[index] = score
 
 
165
 
166
- if not scores: return []
167
 
168
- sorted_matches_indices = sorted(scores.keys(), key=scores.get, reverse=True)
169
- return [data[i] for i in sorted_matches_indices]
 
170
 
 
171
  def generate_detailed_analysis(query, match):
172
  """Genera la explicación final para el usuario (PROMPT MEJORADO)."""
173
  if not model: return "Error: El modelo de IA no está disponible."
@@ -211,6 +218,32 @@ def generate_detailed_analysis(query, match):
211
  except Exception as e:
212
  logger.error(f"Error generando análisis detallado con Gemini: {e}")
213
  return "No se pudo generar el análisis detallado."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
 
215
  # --- INTERFAZ DE USUARIO (UI) ---
216
  col_img, col_text = st.columns([1, 4], gap="medium")
@@ -238,7 +271,7 @@ if submitted:
238
  if not query:
239
  st.warning("Por favor, describe lo que sientes y lo que comiste.")
240
  elif alimentos_data is None:
241
- st.error("La base de datos de alimentos no está disponible. No se puede continuar.")
242
  else:
243
  st.session_state.user_query = query
244
  with st.spinner("🧠 Interpretando tu caso y buscando pistas con IA..."):
@@ -251,11 +284,11 @@ if submitted:
251
  info_str += f", Condición Probable: {entities.get('condicion_probable')}"
252
  st.info(info_str)
253
 
254
- with st.spinner("🔬 Cruzando información en la base de conocimiento..."):
255
  results = find_best_matches_hybrid(entities, alimentos_data)
256
  st.session_state.search_results = results
257
  else:
258
- st.error("No se pudieron identificar alimentos o síntomas claros en tu descripción. Intenta ser un poco más específico.")
259
  st.session_state.search_results = []
260
 
261
  if st.session_state.search_results is not None:
@@ -265,25 +298,36 @@ if st.session_state.search_results is not None:
265
  results = st.session_state.search_results
266
 
267
  if not results:
268
- st.warning(f"No se encontraron coincidencias claras en nuestra base de datos para tu caso: '{st.session_state.user_query}'.\n\n"
269
- "**Sugerencias:**\n"
270
- "- Intenta ser más específico con los síntomas.\n"
271
- "- Asegúrate de mencionar al menos un alimento o bebida.\n"
272
- "- Reformula tu consulta con otras palabras.")
273
  else:
274
- st.success(f"Hemos encontrado {len(results)} posible(s) causa(s) relacionada(s) con tu caso. Aquí está el análisis de la más probable:")
275
 
276
- best_match = results[0]
277
-
278
- with st.expander(f"**Principal Coincidencia: {best_match.get('condicion_asociada')}**", expanded=True):
279
- with st.spinner("✍️ Generando un análisis detallado y personalizado con IA..."):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  detailed_analysis = generate_detailed_analysis(st.session_state.user_query, best_match)
281
  st.markdown(detailed_analysis)
282
 
283
  if len(results) > 1:
284
- with st.expander("Otras posibles coincidencias (menos probables)"):
285
  for result in results[1:]:
286
- st.subheader(f"{result.get('condicion_asociada')}")
287
- st.write(f"**Compuestos/Alimentos:** {result.get('compuesto_alimento')}")
288
- st.write(f"**Síntomas Clínicos:** {result.get('sintomas_clinicos')}")
289
  st.markdown("---")
 
1
+ # ==================== El Detective de Alimentos (Versión 4.0 - Transparencia con Gráficos) =====================================
2
+ # Mejoras: Gráfico de relevancia de resultados, desglose de puntuación y motor de búsqueda modificado.
3
 
4
  import streamlit as st
5
  import google.generativeai as genai
 
8
  import json
9
  import logging
10
  import re
11
+ import pandas as pd
12
+ import altair as alt # <-- LIBRERÍA NUEVA
13
 
14
  st.set_page_config(
15
  page_title="El Detective de Alimentos",
 
17
  layout="wide"
18
  )
19
 
20
+ # ... (El código de configuración de Gemini y carga de datos no cambia) ...
21
  # Configurar logging
22
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
23
  logger = logging.getLogger("food_detective_app")
 
77
 
78
  alimentos_data, lista_condiciones = load_data()
79
 
 
80
  FOOD_TO_COMPOUND_MAP = {
81
+ # ... (el diccionario expandido no cambia) ...
82
  "pan": ["gluten"], "trigo": ["gluten"], "harina de trigo": ["gluten"], "cebada": ["gluten"],
83
  "centeno": ["gluten"], "pasta": ["gluten"], "galletas": ["gluten"], "avena": ["gluten"], "pizza": ["gluten"], "torta": ["gluten"],
 
84
  "leche": ["lácteos", "caseína", "lactosa"], "queso": ["lácteos", "caseína", "lactosa", "histamina", "tiramina"],
85
  "yogur": ["lácteos", "caseína", "lactosa"], "mantequilla": ["lácteos", "caseína", "lactosa"], "crema": ["lácteos"], "helado": ["lácteos"],
 
86
  "manzana": ["salicilatos", "fructosa"], "almendras": ["salicilatos"], "uvas": ["salicilatos"], "pasas": ["salicilatos"],
87
  "naranja": ["salicilatos"], "brócoli": ["salicilatos", "goitrógenos"], "cúrcuma": ["salicilatos"],
 
88
  "azucar": ["azúcar", "fructosa"], "dulces": ["azúcar"], "refrescos": ["azúcar", "fructosa"], "gaseosas": ["azúcar", "fructosa"],
89
  "miel": ["fructosa"], "jarabe de maiz": ["fructosa"],
 
90
  "vino tinto": ["histamina", "tiramina", "sulfitos"], "vino rojo": ["histamina", "tiramina", "sulfitos"],
91
  "cerveza": ["histamina", "tiramina", "purinas"], "chocolate": ["cafeína", "tiramina", "níquel"],
92
  "embutidos": ["histamina", "tiramina", "nitritos"], "pescado enlatado": ["histamina"], "tomate": ["histamina", "solaninas"],
 
93
  "carne": ["alfa-gal", "proteínas", "purinas", "hierro"], "carnes rojas": ["purinas", "alfa-gal", "hierro"],
94
  "mariscos": ["purinas", "sulfitos", "alérgenos", "yodo"], "huevo": ["alérgenos"], "soya": ["alérgenos"],
95
  "café": ["cafeína", "ácidos"]
96
  }
97
 
98
+ # --- LÓGICA DE BÚSQUEDA Y ANÁLISIS ---
99
 
100
+ # ... (La función extract_and_infer_with_gemini no cambia) ...
101
  def extract_and_infer_with_gemini(query, condiciones):
102
  """Extrae entidades e infiere una condición probable."""
103
  if not model: return None
 
123
  st.error(f"Hubo un problema al interpretar tu consulta con la IA.")
124
  return None
125
 
126
+ # --- FUNCIÓN DE BÚSQUEDA MODIFICADA ---
127
  def find_best_matches_hybrid(entities, data):
128
+ """
129
+ Motor de búsqueda híbrido (v4.0) que devuelve un desglose detallado de la puntuación.
130
+ """
131
  if not entities or not data: return []
132
 
133
  user_symptoms = set(s.lower().strip() for s in entities.get("sintomas", []))
 
139
  if food in FOOD_TO_COMPOUND_MAP:
140
  candidate_terms.update(c.lower() for c in FOOD_TO_COMPOUND_MAP[food])
141
 
142
+ results = []
143
  for index, entry in enumerate(data):
144
+ score_details = {'condition': 0, 'food': 0, 'symptoms': 0, 'total': 0}
145
 
146
  # Ponderación 1: Coincidencia de Condición
147
  entry_condition = entry.get("condicion_asociada", "").lower().strip()
148
  if inferred_condition and inferred_condition == entry_condition:
149
+ score_details['condition'] = 100
150
 
151
  # Ponderación 2: Coincidencia de Alimentos/Compuestos
152
  entry_compounds_text = entry.get("compuesto_alimento", "").lower()
153
  if any(term in entry_compounds_text for term in candidate_terms):
154
+ score_details['food'] = 20
155
 
156
+ # Ponderación 3: Coincidencia de Síntomas
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:
161
  if key in user_symptom or user_symptom in key:
162
+ symptom_score += 5
163
+ break
164
+ score_details['symptoms'] = symptom_score
165
 
166
+ total_score = sum(score_details.values())
167
+ if total_score > 0:
168
+ score_details['total'] = total_score
169
+ results.append({'entry': entry, 'score': score_details})
170
 
171
+ if not results: return []
172
 
173
+ # Ordenar por puntuación total descendente
174
+ sorted_results = sorted(results, key=lambda x: x['score']['total'], reverse=True)
175
+ return sorted_results
176
 
177
+ # ... (La función generate_detailed_analysis no cambia) ...
178
  def generate_detailed_analysis(query, match):
179
  """Genera la explicación final para el usuario (PROMPT MEJORADO)."""
180
  if not model: return "Error: El modelo de IA no está disponible."
 
218
  except Exception as e:
219
  logger.error(f"Error generando análisis detallado con Gemini: {e}")
220
  return "No se pudo generar el análisis detallado."
221
+
222
+ # --- NUEVA FUNCIÓN PARA CREAR EL GRÁFICO ---
223
+ def create_relevance_chart(results):
224
+ """Crea un gráfico de barras de Altair para visualizar la relevancia de los resultados."""
225
+ # Preparar los datos para el gráfico (tomamos los 5 mejores)
226
+ top_results = results[:5]
227
+ chart_data = {
228
+ "Condición": [res['entry']['condicion_asociada'] for res in top_results],
229
+ "Relevancia": [res['score']['total'] for res in top_results]
230
+ }
231
+ source = pd.DataFrame(chart_data)
232
+
233
+ # Crear el gráfico
234
+ chart = alt.Chart(source).mark_bar().encode(
235
+ x=alt.X('Relevancia:Q', title='Puntuación de Relevancia'),
236
+ y=alt.Y('Condición:N', sort='-x', title='Posible Condición'),
237
+ tooltip=['Condición', 'Relevancia']
238
+ ).properties(
239
+ title='Principales Coincidencias según tu Caso'
240
+ ).configure_axis(
241
+ labelFontSize=12,
242
+ titleFontSize=14
243
+ ).configure_title(
244
+ fontSize=16
245
+ )
246
+ return chart
247
 
248
  # --- INTERFAZ DE USUARIO (UI) ---
249
  col_img, col_text = st.columns([1, 4], gap="medium")
 
271
  if not query:
272
  st.warning("Por favor, describe lo que sientes y lo que comiste.")
273
  elif alimentos_data is None:
274
+ st.error("La base de datos de alimentos no está disponible.")
275
  else:
276
  st.session_state.user_query = query
277
  with st.spinner("🧠 Interpretando tu caso y buscando pistas con IA..."):
 
284
  info_str += f", Condición Probable: {entities.get('condicion_probable')}"
285
  st.info(info_str)
286
 
287
+ with st.spinner("🔬 Cruzando información y calculando relevancia..."):
288
  results = find_best_matches_hybrid(entities, alimentos_data)
289
  st.session_state.search_results = results
290
  else:
291
+ st.error("No se pudieron identificar alimentos o síntomas claros en tu descripción.")
292
  st.session_state.search_results = []
293
 
294
  if st.session_state.search_results is not None:
 
298
  results = st.session_state.search_results
299
 
300
  if not results:
301
+ st.warning(f"No se encontraron coincidencias claras para tu caso: '{st.session_state.user_query}'.")
 
 
 
 
302
  else:
303
+ st.success(f"Hemos encontrado {len(results)} posible(s) causa(s) relacionada(s) con tu caso.")
304
 
305
+ # --- SECCIÓN DEL GRÁFICO NUEVA ---
306
+ st.subheader("Análisis de Relevancia de las Coincidencias")
307
+ chart = create_relevance_chart(results)
308
+ st.altair_chart(chart, use_container_width=True)
309
+
310
+ best_match = results[0]['entry']
311
+ best_score = results[0]['score']
312
+
313
+ with st.expander(f"**Análisis Detallado de la Principal Coincidencia: {best_match.get('condicion_asociada')}**", expanded=True):
314
+ # Mostramos el desglose de la puntuación
315
+ st.markdown("##### Desglose de la Puntuación de Relevancia:")
316
+ col1, col2, col3, col4 = st.columns(4)
317
+ col1.metric("Puntos por Condición", f"{best_score['condition']}")
318
+ col2.metric("Puntos por Alimento", f"{best_score['food']}")
319
+ col3.metric("Puntos por Síntomas", f"{best_score['symptoms']}")
320
+ col4.metric("PUNTUACIÓN TOTAL", f"{best_score['total']}", delta="Máxima coincidencia")
321
+ st.markdown("---")
322
+
323
+ with st.spinner("✍️ Generando un análisis personalizado con IA..."):
324
  detailed_analysis = generate_detailed_analysis(st.session_state.user_query, best_match)
325
  st.markdown(detailed_analysis)
326
 
327
  if len(results) > 1:
328
+ with st.expander("Otras posibles coincidencias (ordenadas por relevancia)"):
329
  for result in results[1:]:
330
+ entry = result['entry']
331
+ score = result['score']
332
+ st.write(f"**{entry.get('condicion_asociada')}** - Puntuación Total: {score['total']}")
333
  st.markdown("---")