Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
-
# ==================== Buscador de Intolerancias Alimentarias (Versión Híbrida) =====================================
|
| 2 |
-
#
|
| 3 |
|
| 4 |
import streamlit as st
|
| 5 |
import google.generativeai as genai
|
|
@@ -10,7 +10,7 @@ import logging
|
|
| 10 |
import re
|
| 11 |
|
| 12 |
st.set_page_config(
|
| 13 |
-
page_title="
|
| 14 |
page_icon="🍎",
|
| 15 |
layout="wide"
|
| 16 |
)
|
|
@@ -60,14 +60,13 @@ def load_data():
|
|
| 60 |
with open(path_alimentos, 'r', encoding='utf-8') as f:
|
| 61 |
data_alimentos = json.load(f)
|
| 62 |
|
| 63 |
-
# Extraer una lista única de todas las condiciones posibles para guiar a la IA
|
| 64 |
lista_condiciones = sorted(list(set(item['condicion_asociada'] for item in data_alimentos)))
|
| 65 |
|
| 66 |
logger.info(f"✅ Base de datos enriquecida cargada con {len(data_alimentos)} registros.")
|
| 67 |
return data_alimentos, lista_condiciones
|
| 68 |
|
| 69 |
except FileNotFoundError:
|
| 70 |
-
st.error("❌ Error: No se encontró el archivo 'alimentos_enriquecido.json'. Asegúrate de
|
| 71 |
return None, None
|
| 72 |
except json.JSONDecodeError:
|
| 73 |
st.error("❌ Error: El archivo 'alimentos_enriquecido.json' tiene un formato incorrecto.")
|
|
@@ -81,7 +80,8 @@ FOOD_TO_COMPOUND_MAP = {
|
|
| 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"],
|
| 82 |
"manzana": ["salicilatos", "fructosa"], "almendras": ["salicilatos"], "uvas": ["salicilatos"], "vino tinto": ["histamina", "tiramina", "sulfitos"], "vino rojo": ["histamina", "tiramina", "sulfitos"],
|
| 83 |
"azucar": ["azúcar", "fructosa"], "dulces": ["azúcar"], "refrescos": ["azúcar", "fructosa"], "gaseosas": ["azúcar", "fructosa"],
|
| 84 |
-
"embutidos": ["histamina", "tiramina", "nitritos"], "carne": ["alfa-gal", "proteínas"]
|
|
|
|
| 85 |
}
|
| 86 |
|
| 87 |
# --- LÓGICA DE BÚSQUEDA Y ANÁLISIS (ENFOQUE HÍBRIDO) ---
|
|
@@ -104,23 +104,14 @@ def extract_and_infer_with_gemini(query, condiciones):
|
|
| 104 |
Devuelve la respuesta ÚNICAMENTE en formato JSON. Si no puedes inferir una condición, deja el campo como un string vacío "".
|
| 105 |
|
| 106 |
Ejemplo 1:
|
| 107 |
-
Consulta: "Cuando como
|
| 108 |
Respuesta:
|
| 109 |
{{
|
| 110 |
-
"alimentos": ["
|
| 111 |
-
"sintomas": ["
|
| 112 |
-
"condicion_probable": "
|
| 113 |
}}
|
| 114 |
|
| 115 |
-
Ejemplo 2:
|
| 116 |
-
Consulta: "me duele la cabeza cada vez que tomo vino rojo"
|
| 117 |
-
Respuesta:
|
| 118 |
-
{{
|
| 119 |
-
"alimentos": ["vino rojo"],
|
| 120 |
-
"sintomas": ["dolor de cabeza"],
|
| 121 |
-
"condicion_probable": "Intolerancia a la Histamina (Histaminosis)."
|
| 122 |
-
}}
|
| 123 |
-
|
| 124 |
Ahora, analiza la siguiente consulta:
|
| 125 |
Consulta: "{query}"
|
| 126 |
"""
|
|
@@ -138,36 +129,35 @@ def extract_and_infer_with_gemini(query, condiciones):
|
|
| 138 |
return None
|
| 139 |
|
| 140 |
def find_best_matches_hybrid(entities, data):
|
| 141 |
-
"""Motor de búsqueda híbrido con puntuación ponderada."""
|
| 142 |
if not entities or not data: return []
|
| 143 |
|
| 144 |
user_symptoms = set(s.lower().strip() for s in entities.get("sintomas", []))
|
| 145 |
user_foods = set(f.lower().strip() for f in entities.get("alimentos", []))
|
| 146 |
inferred_condition = entities.get("condicion_probable", "").lower().strip()
|
| 147 |
|
| 148 |
-
|
| 149 |
for food in user_foods:
|
| 150 |
if food in FOOD_TO_COMPOUND_MAP:
|
| 151 |
-
|
| 152 |
|
| 153 |
scores = {}
|
| 154 |
for index, entry in enumerate(data):
|
| 155 |
score = 0
|
| 156 |
|
| 157 |
-
# Ponderación 1: Coincidencia de Condición (la más importante)
|
| 158 |
entry_condition = entry.get("condicion_asociada", "").lower().strip()
|
| 159 |
if inferred_condition and inferred_condition == entry_condition:
|
| 160 |
-
score +=
|
| 161 |
|
| 162 |
-
# Ponderación 2: Coincidencia de Alimentos/Compuestos
|
| 163 |
entry_compounds_text = entry.get("compuesto_alimento", "").lower()
|
| 164 |
-
if any(
|
| 165 |
-
score +=
|
| 166 |
|
| 167 |
-
# Ponderación 3: Coincidencia de Síntomas
|
| 168 |
entry_symptoms_keys = set(s.lower().strip() for s in entry.get("sintomas_clave", []))
|
| 169 |
-
|
| 170 |
-
|
|
|
|
|
|
|
| 171 |
|
| 172 |
if score > 0:
|
| 173 |
scores[index] = score
|
|
@@ -178,11 +168,10 @@ def find_best_matches_hybrid(entities, data):
|
|
| 178 |
return [data[i] for i in sorted_matches_indices]
|
| 179 |
|
| 180 |
def generate_detailed_analysis(query, match):
|
| 181 |
-
"""Genera la explicación final para el usuario
|
| 182 |
-
# Esta función ya es robusta y no necesita cambios.
|
| 183 |
if not model: return "Error: El modelo de IA no está disponible."
|
| 184 |
prompt = f"""
|
| 185 |
-
Eres un asistente de IA experto en nutrición y bienestar,
|
| 186 |
Un usuario ha descrito el siguiente caso: "{query}"
|
| 187 |
Basado en esto, nuestro sistema ha encontrado una posible coincidencia con:
|
| 188 |
- **Compuesto Intolerante:** {match.get("compuesto_alimento")}
|
|
@@ -194,7 +183,7 @@ def generate_detailed_analysis(query, match):
|
|
| 194 |
**Tu Tarea:** Redacta una respuesta completa y estructurada para el usuario usando Markdown:
|
| 195 |
1. **Posible Causa:** Resume por qué sus síntomas podrían estar relacionados con esta condición.
|
| 196 |
2. **¿Qué podría estar pasando en tu cuerpo?:** Explica el "Mecanismo Fisiológico" en términos sencillos.
|
| 197 |
-
3. **Pasos a Seguir y Recomendaciones:** Presenta las "Recomendaciones" como sugerencias,
|
| 198 |
4. **Descargo de Responsabilidad Médico:** Incluye OBLIGATORIAMENTE un párrafo final claro y visible que indique que NO es un diagnóstico médico y que debe consultar a un profesional.
|
| 199 |
"""
|
| 200 |
try:
|
|
@@ -204,29 +193,17 @@ def generate_detailed_analysis(query, match):
|
|
| 204 |
logger.error(f"Error generando análisis detallado con Gemini: {e}")
|
| 205 |
return "No se pudo generar el análisis detallado."
|
| 206 |
|
| 207 |
-
|
| 208 |
# --- INTERFAZ DE USUARIO (UI) ---
|
| 209 |
-
# Se crea un layout con columnas para la cabecera
|
| 210 |
col_img, col_text = st.columns([1, 4], gap="medium")
|
| 211 |
-
|
| 212 |
with col_img:
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
with col_text:
|
| 217 |
st.title("El Detective de Alimentos")
|
| 218 |
st.markdown("##### Describe lo que sientes y lo que comiste para descubrir posibles intolerancias.")
|
| 219 |
-
|
| 220 |
-
# Una línea divisoria para separar la cabecera del formulario
|
| 221 |
st.markdown("---")
|
| 222 |
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
if 'search_results' not in st.session_state: st.session_state.search_results = None
|
| 231 |
if 'user_query' not in st.session_state: st.session_state.user_query = ""
|
| 232 |
|
|
@@ -235,14 +212,14 @@ def clear_search_state():
|
|
| 235 |
st.session_state.user_query = ""
|
| 236 |
|
| 237 |
with st.form(key="search_form"):
|
| 238 |
-
query = st.text_area("Describe tu caso aquí:", height=150, placeholder="Ej: Cuando como
|
| 239 |
submitted = st.form_submit_button("Analizar mi caso", type="primary")
|
| 240 |
|
| 241 |
if submitted:
|
| 242 |
if not query:
|
| 243 |
st.warning("Por favor, describe lo que sientes y lo que comiste.")
|
| 244 |
elif alimentos_data is None:
|
| 245 |
-
st.error("La base de datos de alimentos no está disponible.")
|
| 246 |
else:
|
| 247 |
st.session_state.user_query = query
|
| 248 |
with st.spinner("🧠 Interpretando tu caso y buscando pistas con IA..."):
|
|
@@ -260,15 +237,21 @@ if submitted:
|
|
| 260 |
st.session_state.search_results = results
|
| 261 |
else:
|
| 262 |
st.error("No se pudieron identificar alimentos o síntomas claros en tu descripción. Intenta ser un poco más específico.")
|
| 263 |
-
st.session_state.search_results = []
|
| 264 |
|
| 265 |
if st.session_state.search_results is not None:
|
| 266 |
st.button("Realizar nueva consulta", on_click=clear_search_state)
|
| 267 |
st.markdown("---")
|
| 268 |
|
| 269 |
results = st.session_state.search_results
|
|
|
|
|
|
|
| 270 |
if not results:
|
| 271 |
-
st.warning(f"No se encontraron coincidencias claras en nuestra base de datos para tu caso: '{st.session_state.user_query}'."
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
else:
|
| 273 |
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:")
|
| 274 |
|
|
|
|
| 1 |
+
# ==================== Buscador de Intolerancias Alimentarias (Versión Híbrida 2.1) =====================================
|
| 2 |
+
# Incluye manejo de casos donde no se encuentran coincidencias.
|
| 3 |
|
| 4 |
import streamlit as st
|
| 5 |
import google.generativeai as genai
|
|
|
|
| 10 |
import re
|
| 11 |
|
| 12 |
st.set_page_config(
|
| 13 |
+
page_title="El Detective de Alimentos",
|
| 14 |
page_icon="🍎",
|
| 15 |
layout="wide"
|
| 16 |
)
|
|
|
|
| 60 |
with open(path_alimentos, 'r', encoding='utf-8') as f:
|
| 61 |
data_alimentos = json.load(f)
|
| 62 |
|
|
|
|
| 63 |
lista_condiciones = sorted(list(set(item['condicion_asociada'] for item in data_alimentos)))
|
| 64 |
|
| 65 |
logger.info(f"✅ Base de datos enriquecida cargada con {len(data_alimentos)} registros.")
|
| 66 |
return data_alimentos, lista_condiciones
|
| 67 |
|
| 68 |
except FileNotFoundError:
|
| 69 |
+
st.error("❌ Error: No se encontró el archivo 'alimentos_enriquecido.json'. Asegúrate de que existe en la carpeta 'DATOS'.")
|
| 70 |
return None, None
|
| 71 |
except json.JSONDecodeError:
|
| 72 |
st.error("❌ Error: El archivo 'alimentos_enriquecido.json' tiene un formato incorrecto.")
|
|
|
|
| 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"],
|
| 81 |
"manzana": ["salicilatos", "fructosa"], "almendras": ["salicilatos"], "uvas": ["salicilatos"], "vino tinto": ["histamina", "tiramina", "sulfitos"], "vino rojo": ["histamina", "tiramina", "sulfitos"],
|
| 82 |
"azucar": ["azúcar", "fructosa"], "dulces": ["azúcar"], "refrescos": ["azúcar", "fructosa"], "gaseosas": ["azúcar", "fructosa"],
|
| 83 |
+
"embutidos": ["histamina", "tiramina", "nitritos"], "carne": ["alfa-gal", "proteínas", "purinas"], "carnes rojas": ["purinas", "alfa-gal"],
|
| 84 |
+
"mariscos": ["purinas", "sulfitos", "alérgenos"]
|
| 85 |
}
|
| 86 |
|
| 87 |
# --- LÓGICA DE BÚSQUEDA Y ANÁLISIS (ENFOQUE HÍBRIDO) ---
|
|
|
|
| 104 |
Devuelve la respuesta ÚNICAMENTE en formato JSON. Si no puedes inferir una condición, deja el campo como un string vacío "".
|
| 105 |
|
| 106 |
Ejemplo 1:
|
| 107 |
+
Consulta: "Cuando como mucha carne me duele el dedo gordo del pie."
|
| 108 |
Respuesta:
|
| 109 |
{{
|
| 110 |
+
"alimentos": ["carne"],
|
| 111 |
+
"sintomas": ["dolor en el dedo gordo del pie"],
|
| 112 |
+
"condicion_probable": "Gota / Hiperuricemia."
|
| 113 |
}}
|
| 114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
Ahora, analiza la siguiente consulta:
|
| 116 |
Consulta: "{query}"
|
| 117 |
"""
|
|
|
|
| 129 |
return None
|
| 130 |
|
| 131 |
def find_best_matches_hybrid(entities, data):
|
| 132 |
+
"""Motor de búsqueda híbrido (v2.1) con puntuación ponderada y mayor autoridad para la inferencia de la IA."""
|
| 133 |
if not entities or not data: return []
|
| 134 |
|
| 135 |
user_symptoms = set(s.lower().strip() for s in entities.get("sintomas", []))
|
| 136 |
user_foods = set(f.lower().strip() for f in entities.get("alimentos", []))
|
| 137 |
inferred_condition = entities.get("condicion_probable", "").lower().strip()
|
| 138 |
|
| 139 |
+
candidate_terms = set(user_foods)
|
| 140 |
for food in user_foods:
|
| 141 |
if food in FOOD_TO_COMPOUND_MAP:
|
| 142 |
+
candidate_terms.update(c.lower() for c in FOOD_TO_COMPOUND_MAP[food])
|
| 143 |
|
| 144 |
scores = {}
|
| 145 |
for index, entry in enumerate(data):
|
| 146 |
score = 0
|
| 147 |
|
|
|
|
| 148 |
entry_condition = entry.get("condicion_asociada", "").lower().strip()
|
| 149 |
if inferred_condition and inferred_condition == entry_condition:
|
| 150 |
+
score += 100
|
| 151 |
|
|
|
|
| 152 |
entry_compounds_text = entry.get("compuesto_alimento", "").lower()
|
| 153 |
+
if any(term in entry_compounds_text for term in candidate_terms):
|
| 154 |
+
score += 20
|
| 155 |
|
|
|
|
| 156 |
entry_symptoms_keys = set(s.lower().strip() for s in entry.get("sintomas_clave", []))
|
| 157 |
+
# Para hacer la coincidencia más flexible
|
| 158 |
+
for user_symptom in user_symptoms:
|
| 159 |
+
if any(key in user_symptom for key in entry_symptoms_keys) or any(user_symptom in key for key in entry_symptoms_keys):
|
| 160 |
+
score += 5
|
| 161 |
|
| 162 |
if score > 0:
|
| 163 |
scores[index] = score
|
|
|
|
| 168 |
return [data[i] for i in sorted_matches_indices]
|
| 169 |
|
| 170 |
def generate_detailed_analysis(query, match):
|
| 171 |
+
"""Genera la explicación final para el usuario."""
|
|
|
|
| 172 |
if not model: return "Error: El modelo de IA no está disponible."
|
| 173 |
prompt = f"""
|
| 174 |
+
Eres un asistente de IA experto en nutrición y bienestar, con un tono claro, empático y educativo.
|
| 175 |
Un usuario ha descrito el siguiente caso: "{query}"
|
| 176 |
Basado en esto, nuestro sistema ha encontrado una posible coincidencia con:
|
| 177 |
- **Compuesto Intolerante:** {match.get("compuesto_alimento")}
|
|
|
|
| 183 |
**Tu Tarea:** Redacta una respuesta completa y estructurada para el usuario usando Markdown:
|
| 184 |
1. **Posible Causa:** Resume por qué sus síntomas podrían estar relacionados con esta condición.
|
| 185 |
2. **¿Qué podría estar pasando en tu cuerpo?:** Explica el "Mecanismo Fisiológico" en términos sencillos.
|
| 186 |
+
3. **Pasos a Seguir y Recomendaciones:** Presenta las "Recomendaciones" como sugerencias, mencionando otros alimentos a tener en cuenta del campo "compuesto_alimento".
|
| 187 |
4. **Descargo de Responsabilidad Médico:** Incluye OBLIGATORIAMENTE un párrafo final claro y visible que indique que NO es un diagnóstico médico y que debe consultar a un profesional.
|
| 188 |
"""
|
| 189 |
try:
|
|
|
|
| 193 |
logger.error(f"Error generando análisis detallado con Gemini: {e}")
|
| 194 |
return "No se pudo generar el análisis detallado."
|
| 195 |
|
|
|
|
| 196 |
# --- INTERFAZ DE USUARIO (UI) ---
|
|
|
|
| 197 |
col_img, col_text = st.columns([1, 4], gap="medium")
|
|
|
|
| 198 |
with col_img:
|
| 199 |
+
if os.path.exists("imagen.png"):
|
| 200 |
+
st.image("imagen.png", width=150)
|
|
|
|
| 201 |
with col_text:
|
| 202 |
st.title("El Detective de Alimentos")
|
| 203 |
st.markdown("##### Describe lo que sientes y lo que comiste para descubrir posibles intolerancias.")
|
|
|
|
|
|
|
| 204 |
st.markdown("---")
|
| 205 |
|
| 206 |
+
# --- LÓGICA PRINCIPAL DE LA APLICACIÓN ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
if 'search_results' not in st.session_state: st.session_state.search_results = None
|
| 208 |
if 'user_query' not in st.session_state: st.session_state.user_query = ""
|
| 209 |
|
|
|
|
| 212 |
st.session_state.user_query = ""
|
| 213 |
|
| 214 |
with st.form(key="search_form"):
|
| 215 |
+
query = st.text_area("Describe tu caso aquí:", height=150, placeholder="Ej: Cuando como mucha carne me duele el dedo gordo del pie...")
|
| 216 |
submitted = st.form_submit_button("Analizar mi caso", type="primary")
|
| 217 |
|
| 218 |
if submitted:
|
| 219 |
if not query:
|
| 220 |
st.warning("Por favor, describe lo que sientes y lo que comiste.")
|
| 221 |
elif alimentos_data is None:
|
| 222 |
+
st.error("La base de datos de alimentos no está disponible. No se puede continuar.")
|
| 223 |
else:
|
| 224 |
st.session_state.user_query = query
|
| 225 |
with st.spinner("🧠 Interpretando tu caso y buscando pistas con IA..."):
|
|
|
|
| 237 |
st.session_state.search_results = results
|
| 238 |
else:
|
| 239 |
st.error("No se pudieron identificar alimentos o síntomas claros en tu descripción. Intenta ser un poco más específico.")
|
| 240 |
+
st.session_state.search_results = [] # Resetear a lista vacía
|
| 241 |
|
| 242 |
if st.session_state.search_results is not None:
|
| 243 |
st.button("Realizar nueva consulta", on_click=clear_search_state)
|
| 244 |
st.markdown("---")
|
| 245 |
|
| 246 |
results = st.session_state.search_results
|
| 247 |
+
|
| 248 |
+
# --- AQUÍ ESTÁ LA NUEVA LÓGICA DE MANEJO DE "NO ENCONTRADO" ---
|
| 249 |
if not results:
|
| 250 |
+
st.warning(f"No se encontraron coincidencias claras en nuestra base de datos para tu caso: '{st.session_state.user_query}'.\n\n"
|
| 251 |
+
"**Sugerencias:**\n"
|
| 252 |
+
"- Intenta ser más específico con los síntomas.\n"
|
| 253 |
+
"- Asegúrate de mencionar al menos un alimento o bebida.\n"
|
| 254 |
+
"- Reformula tu consulta con otras palabras.")
|
| 255 |
else:
|
| 256 |
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:")
|
| 257 |
|