Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
-
# ==================== Buscador de Intolerancias Alimentarias
|
| 2 |
-
#
|
| 3 |
-
# Idea original y estructura de JAIRO ALEXANDER ERASO MD y DIANA MILENA SOLER MARTINEZ Psc.
|
| 4 |
|
| 5 |
import streamlit as st
|
| 6 |
import google.generativeai as genai
|
|
@@ -12,7 +11,7 @@ import re
|
|
| 12 |
|
| 13 |
st.set_page_config(
|
| 14 |
page_title="Asistente de Bienestar Digestivo",
|
| 15 |
-
page_icon="🍎",
|
| 16 |
layout="wide"
|
| 17 |
)
|
| 18 |
|
|
@@ -22,11 +21,9 @@ logger = logging.getLogger("food_intolerance_app")
|
|
| 22 |
|
| 23 |
# --- CONFIGURACIÓN DE GEMINI ---
|
| 24 |
try:
|
| 25 |
-
# Para Streamlit Community Cloud, usa st.secrets
|
| 26 |
if 'GEMINI_API_KEY' in st.secrets:
|
| 27 |
GEMINI_API_KEY = st.secrets['GEMINI_API_KEY']
|
| 28 |
else:
|
| 29 |
-
# Para desarrollo local, usa variables de entorno
|
| 30 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 31 |
|
| 32 |
if not GEMINI_API_KEY:
|
|
@@ -56,59 +53,72 @@ model = get_gemini_model()
|
|
| 56 |
|
| 57 |
@st.cache_data
|
| 58 |
def load_data():
|
| 59 |
-
"""Carga la base de datos
|
| 60 |
try:
|
| 61 |
-
|
| 62 |
-
path_alimentos = os.path.join('DATOS', 'alimentos.json')
|
| 63 |
|
| 64 |
with open(path_alimentos, 'r', encoding='utf-8') as f:
|
| 65 |
data_alimentos = json.load(f)
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
logger.info(f"✅ Base de datos
|
| 68 |
-
return data_alimentos
|
| 69 |
|
| 70 |
except FileNotFoundError:
|
| 71 |
-
st.error("❌ Error: No se encontró el archivo '
|
| 72 |
-
return None
|
| 73 |
except json.JSONDecodeError:
|
| 74 |
-
st.error("❌ Error: El archivo '
|
| 75 |
-
return None
|
| 76 |
|
| 77 |
-
alimentos_data = load_data()
|
| 78 |
|
| 79 |
-
# Mapeo de alimentos
|
| 80 |
-
# Este diccionario es crucial y debe expandirse.
|
| 81 |
FOOD_TO_COMPOUND_MAP = {
|
| 82 |
-
"pan": ["
|
| 83 |
-
"leche": ["
|
| 84 |
-
"manzana": ["
|
| 85 |
-
"azucar": ["
|
|
|
|
| 86 |
}
|
| 87 |
|
| 88 |
-
# --- LÓGICA DE BÚSQUEDA Y ANÁLISIS ---
|
| 89 |
|
| 90 |
-
def
|
| 91 |
-
"""Usa Gemini para extraer alimentos
|
| 92 |
if not model: return None
|
| 93 |
-
system_prompt = f"""
|
| 94 |
-
Eres un experto en procesar textos clínicos. Tu tarea es analizar la consulta de un usuario y extraer dos tipos de entidades: alimentos consumidos y síntomas experimentados.
|
| 95 |
-
Debes devolver la respuesta ÚNICAMENTE en formato JSON, con las claves "alimentos" y "sintomas".
|
| 96 |
-
Normaliza los síntomas a términos médicos o comunes. Por ejemplo, "se me hincha el estómago" debería ser "hinchazón abdominal". "Mente nublada" debería ser "niebla mental".
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
Ejemplo 1:
|
| 99 |
-
Consulta: "Cuando como mucho pan se me hincha el estomago
|
| 100 |
Respuesta:
|
| 101 |
{{
|
| 102 |
-
"alimentos": ["pan"],
|
| 103 |
-
"sintomas": ["hinchazón abdominal", "
|
|
|
|
| 104 |
}}
|
| 105 |
|
| 106 |
Ejemplo 2:
|
| 107 |
-
Consulta: "
|
| 108 |
Respuesta:
|
| 109 |
{{
|
| 110 |
-
"alimentos": ["
|
| 111 |
-
"sintomas": ["
|
|
|
|
| 112 |
}}
|
| 113 |
|
| 114 |
Ahora, analiza la siguiente consulta:
|
|
@@ -116,96 +126,88 @@ def extract_entities_with_gemini(query):
|
|
| 116 |
"""
|
| 117 |
try:
|
| 118 |
response = model.generate_content(system_prompt)
|
| 119 |
-
# Limpiar y parsear la respuesta JSON del texto que puede venir con Markdown
|
| 120 |
json_text_match = re.search(r'```json\s*(\{.*?\})\s*```', response.text, re.DOTALL)
|
| 121 |
if json_text_match:
|
| 122 |
json_text = json_text_match.group(1)
|
| 123 |
-
else:
|
| 124 |
json_text = re.search(r'\{.*\}', response.text, re.DOTALL).group(0)
|
| 125 |
-
|
| 126 |
return json.loads(json_text)
|
| 127 |
except Exception as e:
|
| 128 |
-
logger.error(f"Error
|
| 129 |
st.error(f"Hubo un problema al interpretar tu consulta con la IA. Error: {e}")
|
| 130 |
return None
|
| 131 |
|
| 132 |
-
def
|
| 133 |
-
"""
|
| 134 |
if not entities or not data: return []
|
| 135 |
|
| 136 |
-
user_symptoms = set(
|
| 137 |
-
user_foods =
|
| 138 |
-
|
| 139 |
-
|
| 140 |
candidate_compounds = set()
|
| 141 |
for food in user_foods:
|
| 142 |
if food in FOOD_TO_COMPOUND_MAP:
|
| 143 |
-
candidate_compounds.update(FOOD_TO_COMPOUND_MAP[food])
|
| 144 |
|
| 145 |
scores = {}
|
| 146 |
for index, entry in enumerate(data):
|
| 147 |
score = 0
|
| 148 |
-
entry_compound = entry.get("Compuesto / Alimento Intolerante", "").split('(')[0].strip().lower()
|
| 149 |
-
entry_symptoms = entry.get("Posibles Síntomas (Presentación Clínica Representativa)", "").lower()
|
| 150 |
-
|
| 151 |
-
# Puntuar por coincidencia de compuesto
|
| 152 |
-
if entry_compound in candidate_compounds:
|
| 153 |
-
score += 10 # Puntuación alta por coincidencia directa
|
| 154 |
|
| 155 |
-
#
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
|
| 160 |
if score > 0:
|
| 161 |
scores[index] = score
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
# Devolver los objetos completos de las mejores coincidencias
|
| 167 |
return [data[i] for i in sorted_matches_indices]
|
| 168 |
|
| 169 |
def generate_detailed_analysis(query, match):
|
| 170 |
-
"""
|
|
|
|
| 171 |
if not model: return "Error: El modelo de IA no está disponible."
|
| 172 |
-
|
| 173 |
prompt = f"""
|
| 174 |
Eres un asistente de IA experto en nutrición y bienestar, diseñado para ayudar a las personas a entender posibles intolerancias alimentarias. Tu tono debe ser claro, empático y educativo.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
- **Síntomas Comunes:** {match.get("Posibles Síntomas (Presentación Clínica Representativa)")}
|
| 182 |
-
- **Enfermedades Asociadas:** {match.get("Enfermedades Asociadas")}
|
| 183 |
-
- **Mecanismo Fisiológico:** {match.get("Mecanismo de Acción / Fisiología Alterada")}
|
| 184 |
-
- **Recomendaciones Generales:** {match.get("Recomendaciones Médicas / Exámenes Confirmatorios")}
|
| 185 |
-
|
| 186 |
-
**Tu Tarea:**
|
| 187 |
-
Redacta una respuesta completa para el usuario. La respuesta debe estar estructurada en las siguientes secciones (usa los títulos en negrita y formato Markdown):
|
| 188 |
-
|
| 189 |
-
1. **Posible Causa:** Comienza resumiendo por qué sus síntomas podrían estar relacionados con este compuesto, vinculando directamente su caso con la información proporcionada.
|
| 190 |
-
2. **¿Qué podría estar pasando en tu cuerpo?:** Explica el "Mecanismo Fisiológico" en términos sencillos que cualquiera pueda entender.
|
| 191 |
-
3. **Condiciones Relacionadas:** Menciona las "Enfermedades Asociadas", aclarando que son asociaciones y no un diagnóstico.
|
| 192 |
-
4. **Pasos a Seguir y Recomendaciones:** Presenta las "Recomendaciones" como sugerencias generales, enfatizando la importancia de la supervisión profesional.
|
| 193 |
-
5. **Descargo de Responsabilidad Médico:** Incluye OBLIGATORIAMENTE un párrafo final claro y visible que indique que esta es una herramienta informativa y NO un diagnóstico médico, y que debe consultar a un profesional de la salud.
|
| 194 |
-
|
| 195 |
-
Mantén un lenguaje accesible y evita el alarmismo. El objetivo es informar y guiar, no diagnosticar.
|
| 196 |
"""
|
| 197 |
try:
|
| 198 |
response = model.generate_content(prompt)
|
| 199 |
return response.text
|
| 200 |
except Exception as e:
|
| 201 |
logger.error(f"Error generando análisis detallado con Gemini: {e}")
|
| 202 |
-
return "No se pudo generar el análisis detallado.
|
| 203 |
|
| 204 |
# --- INTERFAZ DE USUARIO (UI) ---
|
| 205 |
st.title("🍎 Asistente de Bienestar Digestivo")
|
| 206 |
st.markdown("Explora la posible relación entre lo que comes y cómo te sientes.")
|
| 207 |
|
| 208 |
-
# Manejo de estado
|
| 209 |
if 'search_results' not in st.session_state: st.session_state.search_results = None
|
| 210 |
if 'user_query' not in st.session_state: st.session_state.user_query = ""
|
| 211 |
|
|
@@ -221,20 +223,25 @@ if submitted:
|
|
| 221 |
if not query:
|
| 222 |
st.warning("Por favor, describe lo que sientes y lo que comiste.")
|
| 223 |
elif alimentos_data is None:
|
| 224 |
-
st.error("La base de datos de alimentos no está disponible.
|
| 225 |
else:
|
| 226 |
st.session_state.user_query = query
|
| 227 |
-
with st.spinner("🧠 Interpretando tu caso con IA..."):
|
| 228 |
-
entities =
|
| 229 |
|
| 230 |
if entities and (entities.get("alimentos") or entities.get("sintomas")):
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
st.session_state.search_results = results
|
| 235 |
else:
|
| 236 |
st.error("No se pudieron identificar alimentos o síntomas claros en tu descripción. Intenta ser un poco más específico.")
|
| 237 |
-
st.session_state.search_results = []
|
| 238 |
|
| 239 |
if st.session_state.search_results is not None:
|
| 240 |
st.button("Realizar nueva consulta", on_click=clear_search_state)
|
|
@@ -244,21 +251,19 @@ if st.session_state.search_results is not None:
|
|
| 244 |
if not results:
|
| 245 |
st.warning(f"No se encontraron coincidencias claras en nuestra base de datos para tu caso: '{st.session_state.user_query}'.")
|
| 246 |
else:
|
| 247 |
-
st.success(f"Hemos encontrado {len(results)} posible(s) causa(s) relacionada(s) con tu caso.")
|
| 248 |
|
| 249 |
-
# Tomar solo el mejor resultado para el análisis detallado
|
| 250 |
best_match = results[0]
|
| 251 |
|
| 252 |
-
with st.expander(f"**Principal Coincidencia: {best_match.get('
|
| 253 |
with st.spinner("✍️ Generando un análisis detallado con IA..."):
|
| 254 |
detailed_analysis = generate_detailed_analysis(st.session_state.user_query, best_match)
|
| 255 |
st.markdown(detailed_analysis)
|
| 256 |
|
| 257 |
-
# Opcional: Mostrar otros resultados menos probables
|
| 258 |
if len(results) > 1:
|
| 259 |
with st.expander("Otras posibles coincidencias (menos probables)"):
|
| 260 |
for result in results[1:]:
|
| 261 |
-
st.subheader(f"{result.get('
|
| 262 |
-
st.write(f"**
|
| 263 |
-
st.write(f"**
|
| 264 |
st.markdown("---")
|
|
|
|
| 1 |
+
# ==================== Buscador de Intolerancias Alimentarias (Versión Híbrida) =====================================
|
| 2 |
+
# Implementa un motor de búsqueda híbrido que combina la extracción de entidades y la inferencia de la IA.
|
|
|
|
| 3 |
|
| 4 |
import streamlit as st
|
| 5 |
import google.generativeai as genai
|
|
|
|
| 11 |
|
| 12 |
st.set_page_config(
|
| 13 |
page_title="Asistente de Bienestar Digestivo",
|
| 14 |
+
page_icon="🍎",
|
| 15 |
layout="wide"
|
| 16 |
)
|
| 17 |
|
|
|
|
| 21 |
|
| 22 |
# --- CONFIGURACIÓN DE GEMINI ---
|
| 23 |
try:
|
|
|
|
| 24 |
if 'GEMINI_API_KEY' in st.secrets:
|
| 25 |
GEMINI_API_KEY = st.secrets['GEMINI_API_KEY']
|
| 26 |
else:
|
|
|
|
| 27 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 28 |
|
| 29 |
if not GEMINI_API_KEY:
|
|
|
|
| 53 |
|
| 54 |
@st.cache_data
|
| 55 |
def load_data():
|
| 56 |
+
"""Carga la base de datos enriquecida y extrae la lista de condiciones."""
|
| 57 |
try:
|
| 58 |
+
path_alimentos = os.path.join('DATOS', 'alimentos_enriquecido.json')
|
|
|
|
| 59 |
|
| 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 haber ejecutado el script de enriquecimiento.")
|
| 71 |
+
return None, None
|
| 72 |
except json.JSONDecodeError:
|
| 73 |
+
st.error("❌ Error: El archivo 'alimentos_enriquecido.json' tiene un formato incorrecto.")
|
| 74 |
+
return None, None
|
| 75 |
|
| 76 |
+
alimentos_data, lista_condiciones = load_data()
|
| 77 |
|
| 78 |
+
# Mapeo expandido de alimentos a compuestos.
|
|
|
|
| 79 |
FOOD_TO_COMPOUND_MAP = {
|
| 80 |
+
"pan": ["gluten"], "trigo": ["gluten"], "harina de trigo": ["gluten"], "cebada": ["gluten"], "centeno": ["gluten"], "pasta": ["gluten"], "galletas": ["gluten"], "avena": ["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"],
|
| 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) ---
|
| 88 |
|
| 89 |
+
def extract_and_infer_with_gemini(query, condiciones):
|
| 90 |
+
"""Usa Gemini para extraer alimentos, síntomas E INFERIR una condición probable."""
|
| 91 |
if not model: return None
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
+
condiciones_str = "\n".join([f"- {c}" for c in condiciones])
|
| 94 |
+
|
| 95 |
+
system_prompt = f"""
|
| 96 |
+
Eres un asistente de triaje clínico experto. Tu tarea es analizar la consulta de un usuario y extraer tres tipos de información:
|
| 97 |
+
1. `alimentos`: Una lista de los alimentos que el usuario menciona haber consumido.
|
| 98 |
+
2. `sintomas`: Una lista de los síntomas que el usuario describe, normalizados a un término común (ej: "hinchazón abdominal", "dolor de cabeza").
|
| 99 |
+
3. `condicion_probable`: Tu mejor inferencia sobre cuál de las siguientes condiciones podría explicar los síntomas del usuario. Elige solo una de la lista.
|
| 100 |
+
|
| 101 |
+
LISTA DE CONDICIONES POSIBLES:
|
| 102 |
+
{condiciones_str}
|
| 103 |
+
|
| 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 mucho pan con harina de trigo se me hincha el estomago y siento fatiga."
|
| 108 |
Respuesta:
|
| 109 |
{{
|
| 110 |
+
"alimentos": ["pan", "harina de trigo"],
|
| 111 |
+
"sintomas": ["hinchazón abdominal", "fatiga"],
|
| 112 |
+
"condicion_probable": "Enfermedad Celíaca (Clásica)."
|
| 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:
|
|
|
|
| 126 |
"""
|
| 127 |
try:
|
| 128 |
response = model.generate_content(system_prompt)
|
|
|
|
| 129 |
json_text_match = re.search(r'```json\s*(\{.*?\})\s*```', response.text, re.DOTALL)
|
| 130 |
if json_text_match:
|
| 131 |
json_text = json_text_match.group(1)
|
| 132 |
+
else:
|
| 133 |
json_text = re.search(r'\{.*\}', response.text, re.DOTALL).group(0)
|
|
|
|
| 134 |
return json.loads(json_text)
|
| 135 |
except Exception as e:
|
| 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. Error: {e}")
|
| 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 |
candidate_compounds = set()
|
| 149 |
for food in user_foods:
|
| 150 |
if food in FOOD_TO_COMPOUND_MAP:
|
| 151 |
+
candidate_compounds.update(c.lower() for c in FOOD_TO_COMPOUND_MAP[food])
|
| 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 += 15
|
| 161 |
+
|
| 162 |
+
# Ponderación 2: Coincidencia de Alimentos/Compuestos
|
| 163 |
+
entry_compounds_text = entry.get("compuesto_alimento", "").lower()
|
| 164 |
+
if any(food in entry_compounds_text for food in user_foods) or any(comp in entry_compounds_text for comp in candidate_compounds):
|
| 165 |
+
score += 8
|
| 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 |
+
matching_symptoms = user_symptoms.intersection(entry_symptoms_keys)
|
| 170 |
+
score += len(matching_symptoms) * 4 # Cada síntoma coincidente suma
|
| 171 |
|
| 172 |
if score > 0:
|
| 173 |
scores[index] = score
|
| 174 |
|
| 175 |
+
if not scores: return []
|
| 176 |
+
|
| 177 |
+
sorted_matches_indices = sorted(scores.keys(), key=scores.get, reverse=True)
|
|
|
|
| 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 (sin cambios necesarios aquí)."""
|
| 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, diseñado para ayudar a las personas a entender posibles intolerancias alimentarias. Tu tono debe ser claro, empático y educativo.
|
| 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")}
|
| 189 |
+
- **Síntomas Comunes:** {match.get("sintomas_clinicos")}
|
| 190 |
+
- **Condición Asociada:** {match.get("condicion_asociada")}
|
| 191 |
+
- **Mecanismo Fisiológico:** {match.get("mecanismo_fisiologico")}
|
| 192 |
+
- **Recomendaciones Generales:** {match.get("recomendaciones_examenes")}
|
| 193 |
|
| 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, enfatizando la supervisión profesional. Menciona otros alimentos a tener en cuenta del campo "compuesto_alimento".
|
| 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:
|
| 201 |
response = model.generate_content(prompt)
|
| 202 |
return response.text
|
| 203 |
except Exception as e:
|
| 204 |
logger.error(f"Error generando análisis detallado con Gemini: {e}")
|
| 205 |
+
return "No se pudo generar el análisis detallado."
|
| 206 |
|
| 207 |
# --- INTERFAZ DE USUARIO (UI) ---
|
| 208 |
st.title("🍎 Asistente de Bienestar Digestivo")
|
| 209 |
st.markdown("Explora la posible relación entre lo que comes y cómo te sientes.")
|
| 210 |
|
|
|
|
| 211 |
if 'search_results' not in st.session_state: st.session_state.search_results = None
|
| 212 |
if 'user_query' not in st.session_state: st.session_state.user_query = ""
|
| 213 |
|
|
|
|
| 223 |
if not query:
|
| 224 |
st.warning("Por favor, describe lo que sientes y lo que comiste.")
|
| 225 |
elif alimentos_data is None:
|
| 226 |
+
st.error("La base de datos de alimentos no está disponible.")
|
| 227 |
else:
|
| 228 |
st.session_state.user_query = query
|
| 229 |
+
with st.spinner("🧠 Interpretando tu caso y buscando pistas con IA..."):
|
| 230 |
+
entities = extract_and_infer_with_gemini(query, lista_condiciones)
|
| 231 |
|
| 232 |
if entities and (entities.get("alimentos") or entities.get("sintomas")):
|
| 233 |
+
info_str = f"IA identificó - Alimentos: {', '.join(entities.get('alimentos',[])) or 'Ninguno'}"
|
| 234 |
+
info_str += f", Síntomas: {', '.join(entities.get('sintomas',[])) or 'Ninguno'}"
|
| 235 |
+
if entities.get("condicion_probable"):
|
| 236 |
+
info_str += f", Condición Probable: {entities.get('condicion_probable')}"
|
| 237 |
+
st.info(info_str)
|
| 238 |
+
|
| 239 |
+
with st.spinner("🔬 Cruzando información en la base de conocimiento..."):
|
| 240 |
+
results = find_best_matches_hybrid(entities, alimentos_data)
|
| 241 |
st.session_state.search_results = results
|
| 242 |
else:
|
| 243 |
st.error("No se pudieron identificar alimentos o síntomas claros en tu descripción. Intenta ser un poco más específico.")
|
| 244 |
+
st.session_state.search_results = []
|
| 245 |
|
| 246 |
if st.session_state.search_results is not None:
|
| 247 |
st.button("Realizar nueva consulta", on_click=clear_search_state)
|
|
|
|
| 251 |
if not results:
|
| 252 |
st.warning(f"No se encontraron coincidencias claras en nuestra base de datos para tu caso: '{st.session_state.user_query}'.")
|
| 253 |
else:
|
| 254 |
+
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:")
|
| 255 |
|
|
|
|
| 256 |
best_match = results[0]
|
| 257 |
|
| 258 |
+
with st.expander(f"**Principal Coincidencia: {best_match.get('condicion_asociada')}**", expanded=True):
|
| 259 |
with st.spinner("✍️ Generando un análisis detallado con IA..."):
|
| 260 |
detailed_analysis = generate_detailed_analysis(st.session_state.user_query, best_match)
|
| 261 |
st.markdown(detailed_analysis)
|
| 262 |
|
|
|
|
| 263 |
if len(results) > 1:
|
| 264 |
with st.expander("Otras posibles coincidencias (menos probables)"):
|
| 265 |
for result in results[1:]:
|
| 266 |
+
st.subheader(f"{result.get('condicion_asociada')}")
|
| 267 |
+
st.write(f"**Compuestos/Alimentos:** {result.get('compuesto_alimento')}")
|
| 268 |
+
st.write(f"**Síntomas Clínicos:** {result.get('sintomas_clinicos')}")
|
| 269 |
st.markdown("---")
|