Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# ==============================================================================
|
| 2 |
-
# EL DETECTIVE DE ALIMENTOS - EDICIÓN HÍBRIDA
|
| 3 |
# Desarrollado por: Jairo Alexander & Diana Milena Soler Martinez
|
| 4 |
# Motor: Gemini Flash + FooDB.ca Graph + Fast-Path Local Engine
|
| 5 |
# ==============================================================================
|
|
@@ -16,6 +16,7 @@ import altair as alt
|
|
| 16 |
from io import BytesIO
|
| 17 |
import docx
|
| 18 |
import difflib
|
|
|
|
| 19 |
|
| 20 |
# --- CONFIGURACIÓN DE PÁGINA Y LOGGING ---
|
| 21 |
st.set_page_config(page_title="El Detective de Alimentos", page_icon="🍎", layout="wide")
|
|
@@ -37,7 +38,7 @@ Este análisis es una herramienta informativa de IA y **NO es un diagnóstico m
|
|
| 37 |
**Si tus síntomas son severos, busca ayuda médica urgente.**
|
| 38 |
"""
|
| 39 |
|
| 40 |
-
# --- CONFIGURACIÓN DE CLIENTE GEMINI CACHEADO
|
| 41 |
@st.cache_resource
|
| 42 |
def get_gemini_client():
|
| 43 |
api_key = os.getenv("GEMINI_API_KEY")
|
|
@@ -59,16 +60,15 @@ def get_gemini_client():
|
|
| 59 |
return None
|
| 60 |
|
| 61 |
try:
|
| 62 |
-
# Timeout optimizado a 5000 ms (5s) para evitar congelamientos en la UI
|
| 63 |
return genai.Client(
|
| 64 |
api_key=api_key,
|
| 65 |
-
http_options=types.HttpOptions(timeout=
|
| 66 |
)
|
| 67 |
except Exception as e:
|
| 68 |
logger.warning(f"❌ Error instanciando cliente Gemini: {e}")
|
| 69 |
return None
|
| 70 |
|
| 71 |
-
# ---
|
| 72 |
def generate_content_with_fallback(gemini_client, prompt):
|
| 73 |
models_to_try = [
|
| 74 |
'gemini-2.5-flash',
|
|
@@ -89,7 +89,23 @@ def generate_content_with_fallback(gemini_client, prompt):
|
|
| 89 |
continue
|
| 90 |
raise RuntimeError("Ningún modelo de Gemini respondió adecuadamente.")
|
| 91 |
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
@st.cache_resource
|
| 94 |
def load_data():
|
| 95 |
try:
|
|
@@ -118,7 +134,6 @@ def get_foodb_lower_map(_foodb_index):
|
|
| 118 |
|
| 119 |
foodb_lower_map = get_foodb_lower_map(foodb_index)
|
| 120 |
|
| 121 |
-
# --- CACHÉ GLOBAL DE APRENDIZAJE EN RAM ---
|
| 122 |
@st.cache_resource
|
| 123 |
def get_global_learned_cache():
|
| 124 |
return {}
|
|
@@ -126,7 +141,7 @@ def get_global_learned_cache():
|
|
| 126 |
global_learned_cache = get_global_learned_cache()
|
| 127 |
|
| 128 |
# ==============================================================================
|
| 129 |
-
# DICCIONARIOS MASTER BIOMÉDICOS
|
| 130 |
# ==============================================================================
|
| 131 |
|
| 132 |
FOOD_TO_COMPOUND_MAP = {
|
|
@@ -142,14 +157,12 @@ FOOD_TO_COMPOUND_MAP = {
|
|
| 142 |
"maíz": ["zeína", "lectinas", "fitatos", "fodmaps"],
|
| 143 |
"quinoa": ["saponinas", "oxalatos", "fitatos"],
|
| 144 |
"alforfón": ["oxalatos", "fagopirina"],
|
| 145 |
-
"arroz frito": ["lectinas", "gluten", "soja", "histamina", "fodmaps", "glutamato"],
|
| 146 |
|
| 147 |
# LÁCTEOS
|
| 148 |
"leche": ["caseína", "lactosa", "fodmaps"],
|
| 149 |
"queso": ["tiramina", "histamina", "caseína", "lactosa"],
|
| 150 |
"queso fresco": ["caseína", "lactosa"],
|
| 151 |
"queso maduro": ["tiramina", "histamina", "caseína"],
|
| 152 |
-
"queso azul": ["tiramina", "histamina", "caseína", "lactosa"],
|
| 153 |
"yogur": ["histamina", "caseína", "lactosa"],
|
| 154 |
"mantequilla": ["caseína", "lactosa"],
|
| 155 |
"crema de leche": ["caseína", "lactosa"],
|
|
@@ -164,17 +177,13 @@ FOOD_TO_COMPOUND_MAP = {
|
|
| 164 |
"empanada": ["gluten", "zeína", "solanina", "lectinas"],
|
| 165 |
"arepa": ["zeína", "lectinas", "fitatos"],
|
| 166 |
"lechona": ["histamina", "nitritos", "grasas saturadas", "purinas"],
|
| 167 |
-
"cholado": ["fructosa", "lactosa", "histamina", "azúcar refinado", "salicilatos", "fenoles"],
|
| 168 |
|
| 169 |
-
# FRUTAS
|
| 170 |
"noni": ["escopoletina", "fenoles", "salicilatos", "fructosa", "histamina"],
|
| 171 |
-
"jugo de noni": ["escopoletina", "fenoles", "salicilatos", "fructosa", "histamina"],
|
| 172 |
"chontaduro": ["fenoles", "fructosa", "grasas saturadas", "tiramina", "salicilatos"],
|
| 173 |
-
"jugo de chontaduro": ["fenoles", "fructosa", "grasas saturadas", "tiramina", "salicilatos"],
|
| 174 |
"naranja": ["fructosa", "histamina", "salicilatos", "fenoles"],
|
| 175 |
"jugo de naranja": ["fructosa", "histamina", "salicilatos", "fenoles"],
|
| 176 |
"limón": ["salicilatos", "histamina", "fenoles", "ácido cítrico"],
|
| 177 |
-
"mandarina": ["fructosa", "histamina", "salicilatos", "fenoles"],
|
| 178 |
"manzana": ["fructosa", "sorbitol", "fodmaps", "salicilatos"],
|
| 179 |
"pera": ["fructosa", "sorbitol", "fodmaps", "salicilatos"],
|
| 180 |
"plátano": ["tiramina", "histamina", "salicilatos", "fenoles"],
|
|
@@ -184,93 +193,44 @@ FOOD_TO_COMPOUND_MAP = {
|
|
| 184 |
"mora": ["salicilatos", "oxalatos", "histamina", "taninos"],
|
| 185 |
"uva": ["fructosa", "salicilatos", "histamina", "taninos"],
|
| 186 |
"piña": ["salicilatos", "histamina", "fructosa"],
|
| 187 |
-
"papaya": ["salicilatos", "histamina"],
|
| 188 |
"mango": ["fructosa", "salicilatos", "histamina"],
|
| 189 |
-
"kiwi": ["salicilatos", "oxalatos", "histamina"],
|
| 190 |
-
"durazno": ["fructosa", "sorbitol", "fodmaps", "salicilatos"],
|
| 191 |
-
"ciruela": ["sorbitol", "fodmaps", "salicilatos", "histamina"],
|
| 192 |
-
"higo": ["histamina", "fructosa", "salicilatos"],
|
| 193 |
-
"chirimoya": ["fructosa", "fodmaps", "sorbitol", "taninos"],
|
| 194 |
-
"guanábana": ["fructosa", "fodmaps", "taninos", "salicilatos"],
|
| 195 |
"lulo": ["salicilatos", "fructosa", "ácido cítrico"],
|
| 196 |
-
"naranjilla": ["salicilatos", "fructosa", "ácido cítrico"],
|
| 197 |
"maracuyá": ["fructosa", "histamina", "salicilatos"],
|
| 198 |
"maracuya": ["fructosa", "histamina", "salicilatos"],
|
| 199 |
"feijoa": ["fructosa", "salicilatos", "taninos"],
|
| 200 |
-
"granadilla": ["fructosa", "salicilatos"],
|
| 201 |
-
"curuba": ["fructosa", "salicilatos", "taninos"],
|
| 202 |
"tomate de árbol": ["solanina", "histamina", "salicilatos"],
|
| 203 |
-
"tomate de arbol": ["solanina", "histamina", "salicilatos"],
|
| 204 |
"uchuva": ["solanina", "histamina", "salicilatos", "oxalatos"],
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
"zapote": ["fructosa", "taninos"],
|
| 208 |
-
"mamoncillo": ["fructosa", "taninos", "salicilatos"],
|
| 209 |
-
"borojó": ["fenoles", "fructosa", "tiramina"],
|
| 210 |
-
"borojo": ["fenoles", "fructosa", "tiramina"],
|
| 211 |
-
"corozo": ["fenoles", "taninos", "fructosa"],
|
| 212 |
-
"mangostino": ["fenoles", "taninos", "fructosa"],
|
| 213 |
-
|
| 214 |
-
# VERDURAS, TUBÉRCULOS Y SOLANÁCEAS
|
| 215 |
"tomate": ["histamina", "solanina", "salicilatos", "glutamato"],
|
| 216 |
-
"salsa de tomate": ["histamina", "solanina", "salicilatos", "glutamato"],
|
| 217 |
-
"patata": ["solanina", "oxalatos", "lectinas"],
|
| 218 |
"papa": ["solanina", "oxalatos", "lectinas"],
|
|
|
|
| 219 |
"berenjena": ["solanina", "histamina", "salicilatos"],
|
| 220 |
"pimiento": ["capsaicina", "solanina", "salicilatos"],
|
| 221 |
"espinaca": ["oxalatos", "nitratos", "histamina"],
|
| 222 |
-
"espinacas": ["oxalatos", "nitratos", "histamina"],
|
| 223 |
-
"acelga": ["oxalatos", "nitratos"],
|
| 224 |
-
"remolacha": ["oxalatos", "nitratos", "fructosa"],
|
| 225 |
"cebolla": ["fodmaps", "compuestos azufrados", "salicilatos"],
|
| 226 |
"ajo": ["fodmaps", "compuestos azufrados"],
|
| 227 |
"brócoli": ["fodmaps", "compuestos azufrados", "salicilatos", "goitrógenos"],
|
| 228 |
-
"coliflor": ["fodmaps", "sorbitol", "compuestos azufrados", "goitrógenos"],
|
| 229 |
-
"repollo": ["fodmaps", "compuestos azufrados", "goitrógenos"],
|
| 230 |
-
"pepino": ["salicilatos"],
|
| 231 |
-
"calabaza": ["salicilatos", "fodmaps"],
|
| 232 |
-
"zanahoria": ["salicilatos"],
|
| 233 |
"yuca": ["fodmaps", "fitatos"],
|
| 234 |
"ñame": ["oxalatos", "fitatos", "fodmaps"],
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
"malanga": ["oxalatos", "fitatos"],
|
| 238 |
-
"habichuela": ["fodmaps", "lectinas"],
|
| 239 |
-
"rábano": ["salicilatos", "goitrógenos"],
|
| 240 |
-
"apio": ["fodmaps", "salicilatos"],
|
| 241 |
-
"puerro": ["fodmaps", "compuestos azufrados"],
|
| 242 |
-
"espárragos": ["fodmaps"],
|
| 243 |
-
|
| 244 |
-
# LEGUMBRES (PERFIL BIOQUÍMICO COMPLETO)
|
| 245 |
"frijol": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
|
| 246 |
"frijoles": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
|
| 247 |
"frijol paisa": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
|
| 248 |
"lentejas": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
|
| 249 |
"garbanzos": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos"],
|
| 250 |
"arvejas": ["fodmaps", "lectinas", "fitatos", "inhibidores_proteasas"],
|
| 251 |
-
"arveja": ["fodmaps", "lectinas", "fitatos", "inhibidores_proteasas"],
|
| 252 |
"soja": ["soja", "isoflavonas", "lectinas", "fitatos", "fodmaps", "saponinas", "goitrógenos", "inhibidores_proteasas"],
|
| 253 |
"tofu": ["soja", "isoflavonas", "lectinas", "fitatos"],
|
| 254 |
-
"tempeh": ["soja", "isoflavonas"],
|
| 255 |
"haba": ["vicina_convicina", "fodmaps", "lectinas", "fitatos", "purinas"],
|
| 256 |
"habas": ["vicina_convicina", "fodmaps", "lectinas", "fitatos", "purinas"],
|
| 257 |
|
| 258 |
-
# FRUTOS SECOS Y SEMILLAS
|
| 259 |
-
"almendras": ["oxalatos", "salicilatos", "fitatos", "histamina"],
|
| 260 |
-
"leche de almendras": ["oxalatos", "salicilatos"],
|
| 261 |
-
"nueces": ["histamina", "oxalatos", "salicilatos", "taninos"],
|
| 262 |
-
"nuez": ["histamina", "oxalatos", "salicilatos", "taninos"],
|
| 263 |
-
"maní": ["histamina", "lectinas", "fitatos", "salicilatos", "inhibidores_proteasas"],
|
| 264 |
-
"cacahuate": ["histamina", "lectinas", "fitatos", "salicilatos", "inhibidores_proteasas"],
|
| 265 |
-
"marañón": ["oxalatos", "fitatos", "fodmaps"],
|
| 266 |
-
"frutos secos": ["histamina", "oxalatos", "salicilatos", "fitatos"],
|
| 267 |
-
|
| 268 |
# CARNES, EMBUTIDOS Y BEBIDAS
|
| 269 |
"carne de cerdo": ["histamina", "alfa-gal", "grasas saturadas", "purinas"],
|
| 270 |
"cerdo": ["histamina", "alfa-gal", "grasas saturadas", "purinas"],
|
| 271 |
"chicharrón": ["histamina", "grasas saturadas"],
|
| 272 |
"chicharron": ["histamina", "grasas saturadas"],
|
| 273 |
-
"tocino": ["histamina", "nitritos", "grasas saturadas"],
|
| 274 |
"carne de res": ["histamina", "alfa-gal", "purinas"],
|
| 275 |
"res": ["histamina", "alfa-gal", "purinas"],
|
| 276 |
"café": ["cafeína", "salicilatos", "oxalatos"],
|
|
@@ -279,33 +239,22 @@ FOOD_TO_COMPOUND_MAP = {
|
|
| 279 |
"chocolate": ["teobromina", "feniletilamina", "tiramina", "histamina", "oxalatos", "cafeína"],
|
| 280 |
"vino": ["histamina", "tiramina", "sulfitos", "taninos", "alcohol"],
|
| 281 |
"cerveza": ["gluten", "histamina", "sulfitos", "alcohol", "purinas"],
|
| 282 |
-
"jamón": ["histamina", "tiramina", "nitritos"],
|
| 283 |
-
"salchicha": ["histamina", "tiramina", "nitritos", "glutamato"],
|
| 284 |
"embutidos": ["histamina", "tiramina", "nitritos", "glutamato"],
|
| 285 |
"pescado": ["histamina", "purinas", "albúmina"],
|
| 286 |
-
"
|
| 287 |
-
"huevo": ["albúmina"],
|
| 288 |
-
|
| 289 |
-
# CONDIMENTOS
|
| 290 |
-
"pimienta": ["capsaicina", "salicilatos"],
|
| 291 |
-
"chile": ["capsaicina", "salicilatos", "solanina"],
|
| 292 |
-
"ají": ["capsaicina", "salicilatos", "solanina"],
|
| 293 |
-
"salsa de soja": ["glutamato", "histamina", "tiramina", "gluten", "soja"],
|
| 294 |
-
"vinagre": ["histamina", "sulfitos"]
|
| 295 |
}
|
| 296 |
|
| 297 |
FOOD_NAME_TO_FOODB_KEY = {
|
| 298 |
-
"alforfón": ["buckwheat"], "arroz": ["rice"], "avena": ["oat", "oats", "
|
| 299 |
-
"
|
| 300 |
"vino": ["wine"], "cerveza": ["beer"], "chocolate": ["chocolate", "cocoa"],
|
| 301 |
"tomate": ["tomato"], "aguacate": ["avocado"], "plátano": ["banana", "plantain"], "banano": ["banana"],
|
| 302 |
"naranja": ["orange"], "pan": ["bread", "wheat"], "trigo": ["wheat"], "café": ["coffee"], "cafe": ["coffee"],
|
| 303 |
-
"manzana": ["apple"], "
|
| 304 |
"papa": ["potato"], "patata": ["potato"], "frijol": ["bean", "beans", "kidney bean", "black bean"],
|
| 305 |
"frijoles": ["bean", "beans"], "frijol paisa": ["bean", "pork", "rice"],
|
| 306 |
-
"lentejas": ["lentil"], "garbanzos": ["chickpea", "garbanzo"], "arveja": ["pea"
|
| 307 |
"haba": ["broad bean", "fava bean"], "habas": ["broad bean", "fava bean"],
|
| 308 |
-
"almendras": ["almond"], "nueces": ["walnut"],
|
| 309 |
"bandeja paisa": ["bean", "pork", "rice", "banana", "egg", "avocado"],
|
| 310 |
"ajiaco": ["potato", "chicken", "corn", "milk", "cream"],
|
| 311 |
"sancocho": ["cassava", "plantain", "potato", "pork", "beef", "chicken"],
|
|
@@ -314,52 +263,49 @@ FOOD_NAME_TO_FOODB_KEY = {
|
|
| 314 |
}
|
| 315 |
|
| 316 |
STRICT_COMPOUND_TRIGGERS = {
|
| 317 |
-
"cafeína": ["cafeína", "caffeine", "café", "cafe", "coffee"
|
| 318 |
"teobromina": ["teobromina", "theobromine", "cacao", "chocolate"],
|
| 319 |
-
"solanina": ["solanina", "solanine", "solanáceas", "tomate", "
|
| 320 |
-
"capsaicina": ["capsaicina", "capsaicin", "chile", "ají", "picante"
|
| 321 |
-
"gluten": ["gluten", "trigo", "wheat", "cebada", "
|
| 322 |
-
"caseína": ["caseína", "casein", "
|
| 323 |
-
"soja": ["soja", "soya", "soy", "
|
| 324 |
-
"albúmina": ["albúmina", "albumin", "huevo"
|
| 325 |
"lactosa": ["lactosa", "lactose"],
|
| 326 |
-
"fructosa": ["fructosa", "fructose"
|
| 327 |
-
"fodmaps": ["fodmap", "fodmaps", "fructanos", "galactanos", "
|
| 328 |
"histamina": ["histamina", "histamine", "dao"],
|
| 329 |
"tiramina": ["tiramina", "tyramine"],
|
| 330 |
-
"oxalatos": ["oxalato", "oxalatos", "oxalate"
|
| 331 |
-
"saponinas": ["saponina", "saponinas"
|
| 332 |
-
"lectinas": ["lectina", "lectinas", "
|
| 333 |
-
"fitatos": ["fitato", "fitatos", "
|
| 334 |
-
"salicilatos": ["salicilato", "salicilatos"
|
| 335 |
-
"sulfitos": ["sulfito", "sulfitos"
|
| 336 |
"glutamato": ["glutamato", "glutamate", "msg"],
|
| 337 |
-
"nitritos": ["nitrito", "nitritos", "
|
| 338 |
"alcohol": ["alcohol", "etanol", "cerveza", "vino"],
|
| 339 |
-
"purinas": ["purina", "purinas", "
|
| 340 |
-
"inhibidores_proteasas": ["inhibidor de tripsina", "inhibidores de proteasas", "
|
| 341 |
-
"vicina_convicina": ["vicina", "convicina", "favismo", "g6pd", "haba", "habas"]
|
| 342 |
-
"isoflavonas": ["isoflavona", "isoflavonas", "fitoestrógenos", "fitoestrogenos", "soja"],
|
| 343 |
-
"goitrógenos": ["goitrógeno", "goitrogenos", "goitrogen", "tiroides", "crucíferas"],
|
| 344 |
-
"taninos": ["tanino", "taninos", "tannin", "tannins"]
|
| 345 |
}
|
| 346 |
|
| 347 |
MEDICAL_CONDITION_SAFEGUARDS = {
|
| 348 |
"farmacos_interacciones": {
|
| 349 |
-
"condition_triggers": ["fármaco", "farmaco", "medicamento", "imao", "anticoagulante"
|
| 350 |
-
"required_user_keywords": ["fármaco", "farmaco", "medicamento", "imao", "pastilla", "remedio", "
|
| 351 |
},
|
| 352 |
"respiratorio_asma": {
|
| 353 |
-
"condition_triggers": ["asma", "sibilancia", "broncoespasmo"
|
| 354 |
-
"required_user_keywords": ["asma", "tos", "respirar", "pecho", "sibilancia", "ahogo"
|
| 355 |
},
|
| 356 |
"dermatologico_alergia": {
|
| 357 |
-
"condition_triggers": ["urticaria", "angioedema", "anafilaxia", "
|
| 358 |
-
"required_user_keywords": ["piel", "pica", "picazón", "manchas", "rojo", "alergia", "ronchas"
|
| 359 |
},
|
| 360 |
"articular_gota": {
|
| 361 |
"condition_triggers": ["gota", "artritis", "hiperuricemia"],
|
| 362 |
-
"required_user_keywords": ["articulación", "coyuntura", "dedo gordo", "rodilla", "gota"
|
| 363 |
},
|
| 364 |
"renal_litiasis": {
|
| 365 |
"condition_triggers": ["litiasis", "cálculo renal", "piedras en el riñón"],
|
|
@@ -370,8 +316,8 @@ MEDICAL_CONDITION_SAFEGUARDS = {
|
|
| 370 |
"required_user_keywords": ["embarazada", "embarazo", "bebé", "lactando"]
|
| 371 |
},
|
| 372 |
"hepato_vesicular": {
|
| 373 |
-
"condition_triggers": ["vesícula", "vesicula", "colelitiasis", "
|
| 374 |
-
"required_user_keywords": ["vesícula", "vesicula", "hígado", "higado", "piel amarilla"
|
| 375 |
}
|
| 376 |
}
|
| 377 |
|
|
@@ -381,47 +327,39 @@ INTEGRATED_NEURO_FOOD_MAP = {
|
|
| 381 |
"fuentes_comunes": ["pan", "trigo", "pasta", "cebada", "centeno"]
|
| 382 |
},
|
| 383 |
"caseína": {
|
| 384 |
-
"efecto_neuropsicologico": "Sus péptidos (casomorfinas) pueden actuar sobre receptores opioides en el SNC, afectando la cognición
|
| 385 |
"fuentes_comunes": ["leche", "queso", "yogur"]
|
| 386 |
},
|
| 387 |
"fodmaps": {
|
| 388 |
-
"efecto_neuropsicologico": "Mediante el eje intestino-cerebro, la fermentación intestinal excesiva por oligosacáridos (GOS/rafinosa) genera
|
| 389 |
"fuentes_comunes": ["frijoles", "lentejas", "cebolla", "ajo", "trigo"]
|
| 390 |
},
|
| 391 |
"inhibidores_proteasas": {
|
| 392 |
-
"efecto_neuropsicologico": "Impiden la descomposición enzimática adecuada de
|
| 393 |
-
"fuentes_comunes": ["frijoles", "legumbres
|
| 394 |
},
|
| 395 |
"vicina_convicina": {
|
| 396 |
-
"efecto_neuropsicologico": "En personas
|
| 397 |
"fuentes_comunes": ["habas"]
|
| 398 |
},
|
| 399 |
-
"isoflavonas": {
|
| 400 |
-
"efecto_neuropsicologico": "Fitoestrógenos que pueden interactuar con los receptores de estrógeno en el cerebro, alterando potencialmente el estado de ánimo y la regulación hormonal.",
|
| 401 |
-
"fuentes_comunes": ["soja", "tofu", "tempeh"]
|
| 402 |
-
},
|
| 403 |
-
"goitrógenos": {
|
| 404 |
-
"efecto_neuropsicologico": "Interfieren con la absorción del yodo en la glándula tiroides, pudiendo contribuir a la fatiga crónica, lentitud cognitiva e intolerancia al frío.",
|
| 405 |
-
"fuentes_comunes": ["soja", "brócoli", "coliflor", "repollo"]
|
| 406 |
-
},
|
| 407 |
"tiramina": {
|
| 408 |
-
"efecto_neuropsicologico": "Amina vasoactiva
|
| 409 |
"fuentes_comunes": ["quesos madurados", "vino tinto", "embutidos"]
|
| 410 |
},
|
| 411 |
"histamina": {
|
| 412 |
-
"efecto_neuropsicologico": "Neurotransmisor estimulante central. Su exceso por bajo DAO
|
| 413 |
"fuentes_comunes": ["queso", "vino tinto", "tomate", "pescado azul", "conservas"]
|
| 414 |
},
|
| 415 |
"salicilatos": {
|
| 416 |
-
"efecto_neuropsicologico": "En
|
| 417 |
"fuentes_comunes": ["cítricos", "manzanas", "tomate", "especias", "té"]
|
| 418 |
},
|
| 419 |
"fenoles": {
|
| 420 |
-
"efecto_neuropsicologico": "La sobrecarga de fenoles satura las vías de
|
| 421 |
"fuentes_comunes": ["jugo de naranja", "chontaduro", "noni", "té"]
|
| 422 |
},
|
| 423 |
"cafeína": {
|
| 424 |
-
"efecto_neuropsicologico": "Bloquea los receptores de adenosina en el cerebro.
|
| 425 |
"fuentes_comunes": ["café", "té negro", "energizantes", "chocolate"]
|
| 426 |
},
|
| 427 |
"lactosa": {
|
|
@@ -429,35 +367,25 @@ INTEGRATED_NEURO_FOOD_MAP = {
|
|
| 429 |
"fuentes_comunes": ["leche", "queso fresco", "crema de leche"]
|
| 430 |
},
|
| 431 |
"fructosa": {
|
| 432 |
-
"efecto_neuropsicologico": "
|
| 433 |
"fuentes_comunes": ["jugos concentrados", "miel", "frutas muy dulces"]
|
| 434 |
},
|
| 435 |
-
"oxalatos": {
|
| 436 |
-
"efecto_neuropsicologico": "Pueden interferir en la función mitocondrial de las neuronas, asociados a fatiga crónica y neblina cerebral.",
|
| 437 |
-
"fuentes_comunes": ["espinacas", "quinoa", "frutos secos", "cacao"]
|
| 438 |
-
},
|
| 439 |
"solanina": {
|
| 440 |
-
"efecto_neuropsicologico": "Glicoalcaloide con leve efecto inhibidor de la acetilcolinesterasa;
|
| 441 |
"fuentes_comunes": ["tomate", "patata", "berenjena"]
|
| 442 |
}
|
| 443 |
}
|
| 444 |
|
| 445 |
CONSEJOS_POR_COMPUESTO = {
|
| 446 |
-
"fodmaps": "Para reducir los GOS (rafinosa y estaquiosa) de los frijoles y legumbres, realiza un remojo de
|
| 447 |
-
"inhibidores_proteasas": "La cocción prolongada y el calor de la olla
|
| 448 |
-
"vicina_convicina": "Si padeces favismo (deficiencia de
|
| 449 |
-
"
|
| 450 |
-
"
|
| 451 |
-
"caseína": "La proteína A1 de la leche libera beta-casomorfina-7. Se sugiere evaluar variantes A2 o lácteos fermentados bajo supervisión.",
|
| 452 |
-
"lactosa": "La intolerancia se debe a baja lactasa en el enterocito. Se puede manejar con lactasa exógena o productos deslactosados.",
|
| 453 |
"histamina": "La acumulación de histamina se asocia a bajo DAO. Una dieta baja en histamina y rica en vitamina B6 y C ayuda a restaurar el equilibrio.",
|
| 454 |
-
"tiramina": "Controlar el consumo de alimentos añejados
|
| 455 |
-
"salicilatos": "Moderar el consumo concentrado de jugos cítricos y especias ayuda a estabilizar el
|
| 456 |
-
"
|
| 457 |
-
"cafeína": "Metabolizada por CYP1A2. Moderar su consumo antes de mitad de tarde protege la arquitectura del sueño.",
|
| 458 |
-
"oxalatos": "Consumir alimentos ricos en oxalatos junto con calcio dietético favorece su eliminación intestinal evitando la formación de cristales renales.",
|
| 459 |
-
"saponinas": "El remojo prolongado y el lavado intensivo bajo agua corriente reducen significativamente la espuma de saponinas en quinoa y legumbres.",
|
| 460 |
-
"solanina": "Consumir hortalizas bien maduras y retirar la piel verde o los brotes de las papas minimiza la irritación intestinal."
|
| 461 |
}
|
| 462 |
|
| 463 |
CONSEJO_GENERICO_DEFAULT = "Cada organismo posee una individualidad bioquímica única según su perfil enzimático y digestivo. Monitorear tus respuestas personales te permitirá adaptar la dieta a tu umbral de tolerancia."
|
|
@@ -465,76 +393,52 @@ CONSEJO_GENERICO_DEFAULT = "Cada organismo posee una individualidad bioquímica
|
|
| 465 |
FOODB_EFFECTS_TRANSLATION = {
|
| 466 |
"flatugenic": "genera gases/flatulencia", "antioxidant": "antioxidante",
|
| 467 |
"hypercholesterolemic": "puede elevar colesterol", "triglycerigenic": "puede elevar triglicéridos",
|
| 468 |
-
"uricogenic": "puede elevar ácido úrico", "
|
| 469 |
-
"preservative": "conservante natural", "sweetener": "edulcorante natural",
|
| 470 |
-
"demulcent": "protector de mucosa digestiva", "vasodilator": "vasodilatador (posible migraña)",
|
| 471 |
-
"vasoconstrictor": "vasoconstrictor", "histaminic": "actividad histaminérgica",
|
| 472 |
"laxative": "efecto laxante", "diuretic": "efecto diurético",
|
| 473 |
-
"stimulant": "estimulante del SNC", "
|
| 474 |
-
"antiinflammatory": "antiinflamatorio", "ulcerogenic": "irritante gástrico"
|
| 475 |
}
|
| 476 |
|
| 477 |
COMPOUND_SYNONYM_MAP = {
|
| 478 |
-
"gluten": ["gluten", "gliadin"],
|
| 479 |
-
"caseína": ["casein", "casomorphin"],
|
| 480 |
-
"lactosa": ["lactose"],
|
| 481 |
-
"fructosa": ["fructose"],
|
| 482 |
-
"proteínas": ["protein", "amino acid"]
|
| 483 |
}
|
| 484 |
|
| 485 |
MASTER_SYMPTOM_MAP = {
|
| 486 |
"dolor abdominal": {
|
| 487 |
-
"frases_es": ["dolor de estómago", "dolor de panza", "dolor abdominal", "retortijones", "cólicos", "
|
| 488 |
-
"keywords_en": ["abdominal", "pain", "stomach", "cramp"
|
| 489 |
},
|
| 490 |
"hinchazón": {
|
| 491 |
-
"frases_es": ["hinchazón abdominal", "vientre hinchado", "distensión abdominal", "hinchazón", "distensión", "
|
| 492 |
-
"keywords_en": ["bloating", "distension", "flatulence"
|
| 493 |
},
|
| 494 |
"diarrea": {
|
| 495 |
-
"frases_es": ["diarrea", "diarreas", "soltura", "estómago suelto", "deposiciones líquidas"
|
| 496 |
"keywords_en": ["diarrhea", "loose stools"]
|
| 497 |
},
|
| 498 |
"náuseas": {
|
| 499 |
-
"frases_es": ["náuseas", "nauseas", "vómito", "vomito", "ganas de vomitar"
|
| 500 |
"keywords_en": ["nausea", "vomiting"]
|
| 501 |
},
|
| 502 |
"dolor de cabeza": {
|
| 503 |
-
"frases_es": ["dolor de cabeza", "migraña", "cefalea", "jaqueca", "me duele la cabeza"
|
| 504 |
"keywords_en": ["headache", "migraine"]
|
| 505 |
},
|
| 506 |
"ansiedad": {
|
| 507 |
-
"frases_es": ["ansiedad", "nerviosismo", "
|
| 508 |
-
"keywords_en": ["anxiety"
|
| 509 |
},
|
| 510 |
"insomnio": {
|
| 511 |
-
"frases_es": ["insomnio", "no puedo dormir", "
|
| 512 |
-
"keywords_en": ["insomnia"
|
| 513 |
},
|
| 514 |
"alergia cutánea": {
|
| 515 |
-
"frases_es": ["piel", "rasca", "picazón", "
|
| 516 |
-
"keywords_en": ["rash", "itching"
|
| 517 |
-
},
|
| 518 |
-
"reactividad respiratoria": {
|
| 519 |
-
"frases_es": ["asma", "tos", "respirar", "pecho", "sibilancia", "ahogo", "falta de aire", "congestión nasal", "estornudos", "rinitis"],
|
| 520 |
-
"keywords_en": ["asthma", "cough", "wheezing", "rhinitis"]
|
| 521 |
-
},
|
| 522 |
-
"respuesta cardiovascular": {
|
| 523 |
-
"frases_es": ["palpitaciones", "taquicardia", "ritmo cardíaco", "presión", "presion", "rubor", "sofoco", "picos de presión"],
|
| 524 |
-
"keywords_en": ["palpitations", "tachycardia", "flushing"]
|
| 525 |
-
},
|
| 526 |
-
"dolor articular": {
|
| 527 |
-
"frases_es": ["articulación", "articulacion", "rodilla", "coyuntura", "rigidez", "calambre", "gota", "espalda", "músculo", "musculo", "dolor articular"],
|
| 528 |
-
"keywords_en": ["joint pain", "gout", "stiffness"]
|
| 529 |
-
},
|
| 530 |
-
"molestia renal": {
|
| 531 |
-
"frases_es": ["riñón", "riñon", "orina", "orinar", "cálculo", "calculo", "piedra", "ardor al orinar", "cólico renal"],
|
| 532 |
-
"keywords_en": ["kidney", "urine", "calculus"]
|
| 533 |
}
|
| 534 |
}
|
| 535 |
|
| 536 |
# ==============================================================================
|
| 537 |
-
# FUNCIONES
|
| 538 |
# ==============================================================================
|
| 539 |
|
| 540 |
def sanitize_text(text):
|
|
@@ -549,12 +453,7 @@ STOPWORDS_ES = {
|
|
| 549 |
"en", "con", "por", "para", "que", "se", "su", "sus", "al", "del", "lo",
|
| 550 |
"como", "es", "son", "muy", "mas", "más", "no", "si", "sí", "tu", "mi",
|
| 551 |
"me", "te", "le", "les", "nos", "cuando", "donde", "porque", "pero",
|
| 552 |
-
"
|
| 553 |
-
"eso", "asi", "así", "ya", "ha", "he", "han", "hay", "fue", "ser",
|
| 554 |
-
"tener", "tengo", "tiene", "tienen", "puede", "pueden", "puedo",
|
| 555 |
-
"sobre", "entre", "desde", "hasta", "sin", "e", "ni", "todo", "toda",
|
| 556 |
-
"todos", "todas", "cada", "otro", "otra", "otros", "otras", "tomo",
|
| 557 |
-
"mucho", "poco"
|
| 558 |
}
|
| 559 |
|
| 560 |
def extract_keywords(text):
|
|
@@ -567,24 +466,19 @@ def get_compounds_for_food(food_name, food_map):
|
|
| 567 |
|
| 568 |
food_clean = food_name.lower().strip()
|
| 569 |
|
| 570 |
-
# 1. Coincidencia exacta
|
| 571 |
if food_clean in food_map:
|
| 572 |
return food_map[food_clean]
|
| 573 |
|
| 574 |
-
# 2. Normalización de Plurales
|
| 575 |
for suffix in ["es", "s"]:
|
| 576 |
if food_clean.endswith(suffix):
|
| 577 |
norm = food_clean[:-len(suffix)]
|
| 578 |
if norm in food_map:
|
| 579 |
return food_map[norm]
|
| 580 |
|
| 581 |
-
# 3. Corrección difusa de erratas de tipeo
|
| 582 |
fuzzy_matches = difflib.get_close_matches(food_clean, list(food_map.keys()), n=1, cutoff=0.6)
|
| 583 |
if fuzzy_matches:
|
| 584 |
-
logger.info(f"💡 Errata corregida automáticamente: '{food_clean}' -> '{fuzzy_matches[0]}'")
|
| 585 |
return food_map[fuzzy_matches[0]]
|
| 586 |
|
| 587 |
-
# 4. Búsqueda por subcadena exacta de palabra
|
| 588 |
matched_compounds = set()
|
| 589 |
for key, compounds in food_map.items():
|
| 590 |
if re.search(r'\b' + re.escape(key) + r'\b', food_clean) or food_clean in key:
|
|
@@ -593,33 +487,15 @@ def get_compounds_for_food(food_name, food_map):
|
|
| 593 |
if matched_compounds:
|
| 594 |
return list(matched_compounds)
|
| 595 |
|
| 596 |
-
# 5. Inferencia por palabras individuales
|
| 597 |
inferred = set()
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
inferred.update(food_map[sub_fuzzy[0]])
|
| 604 |
-
|
| 605 |
-
if inferred:
|
| 606 |
-
return list(inferred)
|
| 607 |
-
|
| 608 |
-
# 6. INFERENCIA UNIVERSAL POR FAMILIA BIOLÓGICA
|
| 609 |
-
if any(w in food_clean for w in ["carne", "cerdo", "res", "pollo", "pavo", "cordero", "chicharrón", "tocino"]):
|
| 610 |
-
inferred.update(["histamina", "purinas", "proteínas"])
|
| 611 |
-
elif any(w in food_clean for w in ["pescado", "atún", "marisco", "camarón", "sardina"]):
|
| 612 |
-
inferred.update(["histamina", "purinas", "albúmina"])
|
| 613 |
-
elif any(w in food_clean for w in ["jugo", "zumo", "batido", "fruta", "licuado"]):
|
| 614 |
inferred.update(["fructosa", "salicilatos", "histamina", "fenoles"])
|
| 615 |
-
elif any(w in food_clean for w in ["queso", "leche", "crema", "yogur"
|
| 616 |
inferred.update(["caseína", "lactosa", "histamina", "tiramina"])
|
| 617 |
-
elif any(w in food_clean for w in ["pan", "galleta", "pasta", "harina"]):
|
| 618 |
-
inferred.update(["gluten", "fodmaps"])
|
| 619 |
-
elif any(w in food_clean for w in ["frijol", "lenteja", "garbanzo", "legumbre", "soja", "haba", "arveja"]):
|
| 620 |
-
inferred.update(["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas"])
|
| 621 |
-
elif any(w in food_clean for w in ["salsa", "sazonador", "aderezo", "caldo"]):
|
| 622 |
-
inferred.update(["glutamato", "histamina", "sulfitos"])
|
| 623 |
|
| 624 |
return list(inferred)
|
| 625 |
|
|
@@ -630,15 +506,12 @@ def validar_seguro_logico(entry, user_foods, user_query, food_compound_map):
|
|
| 630 |
user_query_clean = user_query.lower() if user_query else ""
|
| 631 |
texto_condicion = (entry.get("condicion_asociada", "") + " " + entry.get("compuesto_alimento", "")).lower()
|
| 632 |
|
| 633 |
-
# 1. EVALUAR SEGUROS POR CATEGORÍA DE CONDICIONES MÉDICAS
|
| 634 |
for category, rules in MEDICAL_CONDITION_SAFEGUARDS.items():
|
| 635 |
if any(trig in texto_condicion for trig in rules["condition_triggers"]):
|
| 636 |
tiene_contexto = any(req_kw in user_query_clean for req_kw in rules["required_user_keywords"])
|
| 637 |
if not tiene_contexto:
|
| 638 |
-
logger.info(f"🚩 SEGURO LÓGICO ACTIVADO [{category.upper()}]: Se descartó '{entry.get('condicion_asociada')}' por falta de contexto clínico.")
|
| 639 |
return False
|
| 640 |
|
| 641 |
-
# 2. EVALUAR SEGUROS BIOQUÍMICOS POR COMPUESTOS ESTRICTOS
|
| 642 |
user_compounds = set()
|
| 643 |
for food in user_foods:
|
| 644 |
comps = get_compounds_for_food(food, food_compound_map)
|
|
@@ -652,7 +525,6 @@ def validar_seguro_logico(entry, user_foods, user_query, food_compound_map):
|
|
| 652 |
for syn in synonyms
|
| 653 |
)
|
| 654 |
if not tiene_compuesto:
|
| 655 |
-
logger.info(f"🚩 SEGURO LÓGICO ACTIVADO [BIOQUÍMICA]: Se descartó '{entry.get('condicion_asociada')}' porque '{user_foods}' NO contiene {compound_key}.")
|
| 656 |
return False
|
| 657 |
|
| 658 |
return True
|
|
@@ -663,16 +535,12 @@ def extract_entities_with_gemini(query):
|
|
| 663 |
return {"alimentos": [], "sintomas": []}
|
| 664 |
|
| 665 |
system_prompt = f"""
|
| 666 |
-
Rol: Extractor Médico JSON
|
| 667 |
-
|
| 668 |
-
- "alimentos": lista de alimentos o bebidas mencionados en español (ej: "frijoles", "bandeja paisa", "ajiaco", "queso", "leche", "vino tinto").
|
| 669 |
-
- "sintomas": lista de síntomas mencionados en español (ej: "insomnio", "dolor de cabeza", "hinchazón", "diarrea").
|
| 670 |
-
|
| 671 |
Input: "{query}"
|
| 672 |
-
Output (JSON estricto):
|
| 673 |
"""
|
| 674 |
try:
|
| 675 |
-
raw_text, _ =
|
| 676 |
text = raw_text
|
| 677 |
if "```json" in text:
|
| 678 |
text = text.split("```json")[1].split("```")[0]
|
|
@@ -680,7 +548,7 @@ def extract_entities_with_gemini(query):
|
|
| 680 |
text = text.split("```")[1].split("```")[0]
|
| 681 |
return json.loads(text)
|
| 682 |
except Exception as e:
|
| 683 |
-
logger.warning(f"Extracción por IA no disponible ({e}). Se
|
| 684 |
return {"alimentos": [], "sintomas": []}
|
| 685 |
|
| 686 |
def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_map):
|
|
@@ -691,17 +559,6 @@ def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_m
|
|
| 691 |
for food_keyword in food_map.keys():
|
| 692 |
if re.search(r'\b' + re.escape(food_keyword) + r'\b', query_sanitized):
|
| 693 |
current_foods.add(food_keyword)
|
| 694 |
-
|
| 695 |
-
if not current_foods:
|
| 696 |
-
match_jugo = re.search(r'\b(jugo|zumo|batido|licuado)\s+de\s+([a-zA-ZáéíóúñÁÉÍÓÚÑ]+)\b', query_sanitized)
|
| 697 |
-
if match_jugo:
|
| 698 |
-
current_foods.add(match_jugo.group(0))
|
| 699 |
-
else:
|
| 700 |
-
match_tomo = re.search(r'\b(como|tomo|consumo|ingiero|da)\s+([a-zA-ZáéíóúñÁÉÍÓÚÑ\s]{3,20})\b', query_sanitized)
|
| 701 |
-
if match_tomo:
|
| 702 |
-
food_extracted = match_tomo.group(2).strip()
|
| 703 |
-
if food_extracted not in STOPWORDS_ES:
|
| 704 |
-
current_foods.add(food_extracted)
|
| 705 |
|
| 706 |
entities["alimentos"] = list(current_foods)
|
| 707 |
|
|
@@ -711,16 +568,6 @@ def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_m
|
|
| 711 |
if phrase in query_sanitized:
|
| 712 |
current_symptoms.add(main_symptom)
|
| 713 |
break
|
| 714 |
-
|
| 715 |
-
if not current_symptoms:
|
| 716 |
-
if any(w in query_sanitized for w in ["diarrea", "diarreas", "soltura", "líquida", "suelto"]):
|
| 717 |
-
current_symptoms.add("diarrea")
|
| 718 |
-
elif any(w in query_sanitized for w in ["hinchazón", "hinchazon", "gases", "inflamado"]):
|
| 719 |
-
current_symptoms.add("hinchazón")
|
| 720 |
-
elif any(w in query_sanitized for w in ["cabeza", "migraña", "cefalea"]):
|
| 721 |
-
current_symptoms.add("dolor de cabeza")
|
| 722 |
-
elif any(w in query_sanitized for w in ["dormir", "insomnio", "desvelo"]):
|
| 723 |
-
current_symptoms.add("insomnio")
|
| 724 |
|
| 725 |
entities["sintomas"] = list(current_symptoms)
|
| 726 |
return entities
|
|
@@ -789,68 +636,21 @@ def build_local_hypothesis(entities, food_compound_map, neuro_map):
|
|
| 789 |
if not user_foods or not user_symptoms:
|
| 790 |
return None
|
| 791 |
|
| 792 |
-
symptom_text = " ".join(user_symptoms).lower()
|
| 793 |
-
|
| 794 |
-
es_neurologico = any(k in symptom_text for k in ["dolor de cabeza", "migraña", "cefalea", "jaqueca", "mareo", "vértigo", "cabeza"])
|
| 795 |
-
es_psicologico = any(k in symptom_text for k in ["ansiedad", "angustia", "nerviosismo", "depresión", "irritabilidad"])
|
| 796 |
-
es_insomnio = any(k in symptom_text for k in ["insomnio", "no puedo dormir", "sueño", "desvelado", "desvelo", "dormir"])
|
| 797 |
-
es_gastrointestinal = any(k in symptom_text for k in ["diarrea", "diarreas", "hinchazón", "hinchazon", "gases", "dolor abdominal", "retortijón", "cólico", "inflamado", "estómago", "náuseas"])
|
| 798 |
-
es_dermatologico = any(k in symptom_text for k in ["piel", "rasca", "picazón", "manchas", "urticaria", "erupción", "alergia", "ronchas"])
|
| 799 |
-
es_respiratorio = any(k in symptom_text for k in ["asma", "tos", "respirar", "pecho", "sibilancia", "ahogo", "rinitis"])
|
| 800 |
-
es_cardiovascular = any(k in symptom_text for k in ["palpitaciones", "taquicardia", "ritmo cardíaco", "presión", "sofoco"])
|
| 801 |
-
|
| 802 |
candidates = []
|
| 803 |
for food in user_foods:
|
| 804 |
comps = get_compounds_for_food(food, food_compound_map)
|
| 805 |
for compound in comps:
|
| 806 |
info = neuro_map.get(compound, {})
|
| 807 |
-
effect = info.get('efecto_neuropsicologico', f'Sustancia bioactiva ({compound})
|
| 808 |
candidates.append({"alimento": food, "compuesto": compound, "efecto": effect})
|
| 809 |
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
sintomas_str = ", ".join(user_symptoms)
|
| 813 |
-
|
| 814 |
-
if es_neurologico:
|
| 815 |
-
condicion_fallback = f"Cefalea o Migraña Alimentaria relacionada con {alimentos_str}"
|
| 816 |
-
elif es_psicologico:
|
| 817 |
-
condicion_fallback = f"Alteración Neuropsicológica o Estado de Ánimo relacionado con {alimentos_str}"
|
| 818 |
-
elif es_insomnio:
|
| 819 |
-
condicion_fallback = f"Alteración del Sueño o Insomnio relacionado con {alimentos_str}"
|
| 820 |
-
elif es_gastrointestinal:
|
| 821 |
-
condicion_fallback = f"Distensión o Malestar Gastrointestinal relacionado con {alimentos_str}"
|
| 822 |
-
elif es_dermatologico:
|
| 823 |
-
condicion_fallback = f"Reacción Dermatológica o Urticaria relacionada con {alimentos_str}"
|
| 824 |
-
else:
|
| 825 |
-
condicion_fallback = f"Sensibilidad o Intolerancia Alimentaria a {alimentos_str}"
|
| 826 |
-
|
| 827 |
-
return {
|
| 828 |
-
"condicion_asociada": condicion_fallback,
|
| 829 |
-
"mecanismo_fisiologico": f"Respuesta inmunológica, enzimática o metabólica inducida por los componentes bioactivos de {alimentos_str}, manifestándose con {sintomas_str}.",
|
| 830 |
-
"compuesto_alimento": f"{alimentos_str} (Compuestos bioactivos)",
|
| 831 |
-
"sintomas_clave": user_symptoms,
|
| 832 |
-
"es_hipotesis_local": True
|
| 833 |
-
}
|
| 834 |
-
|
| 835 |
-
compuestos_unicos = sorted(set(c['compuesto'] for c in candidates))
|
| 836 |
-
alimentos_unicos = sorted(set(c['alimento'] for c in candidates))
|
| 837 |
compuestos_str = ", ".join(compuestos_unicos)
|
| 838 |
alimentos_str = ", ".join(alimentos_unicos).capitalize()
|
| 839 |
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
elif es_psicologico:
|
| 843 |
-
condicion = f"Alteración Neuropsicológica / Estado de Ánimo asociada a {compuestos_str} en {alimentos_str}"
|
| 844 |
-
elif es_dermatologico:
|
| 845 |
-
condicion = f"Reacción Histaminérgica / Dermatológica por {compuestos_str} en {alimentos_str}"
|
| 846 |
-
elif es_insomnio:
|
| 847 |
-
condicion = f"Alteración del Sueño e Insomnio por respuesta a {compuestos_str} en {alimentos_str}"
|
| 848 |
-
elif es_gastrointestinal:
|
| 849 |
-
condicion = f"Distensión Abdominal e Intolerancia Digestiva a {compuestos_str} en {alimentos_str}"
|
| 850 |
-
else:
|
| 851 |
-
condicion = f"Hipersensibilidad o Intolerancia a {compuestos_str} en {alimentos_str}"
|
| 852 |
-
|
| 853 |
-
mecanismo = " | ".join(sorted(set(c['efecto'] for c in candidates)))
|
| 854 |
|
| 855 |
return {
|
| 856 |
"condicion_asociada": condicion,
|
|
@@ -860,42 +660,6 @@ def build_local_hypothesis(entities, food_compound_map, neuro_map):
|
|
| 860 |
"es_hipotesis_local": True
|
| 861 |
}
|
| 862 |
|
| 863 |
-
def generate_ai_hypothesis_on_the_fly(query, entities):
|
| 864 |
-
gemini_client = get_gemini_client()
|
| 865 |
-
if not gemini_client:
|
| 866 |
-
return None
|
| 867 |
-
|
| 868 |
-
prompt = (
|
| 869 |
-
"Rol: Médico Especialista en Nutrición Funcional e Inmunología de Alimentos.\n"
|
| 870 |
-
f'Consulta directa del paciente: "{query}"\n\n'
|
| 871 |
-
"Analiza la fisiología de este caso y genera un JSON estricto con las siguientes 4 claves:\n"
|
| 872 |
-
'1. "condicion_asociada": Título médico conciso de la hipersensibilidad o intolerancia.\n'
|
| 873 |
-
'2. "mecanismo_fisiologico": Explicación concisa del mecanismo enzimático o inmunológico.\n'
|
| 874 |
-
'3. "compuesto_alimento": Nombre de los alimentos y moléculas responsables.\n'
|
| 875 |
-
'4. "sintomas_clave": Lista de los síntomas expresados por el paciente.\n\n'
|
| 876 |
-
"Devuelve ÚNICAMENTE el código JSON válido, sin bloques de código markdown ni texto adicional."
|
| 877 |
-
)
|
| 878 |
-
try:
|
| 879 |
-
raw_text, _ = generate_content_with_fallback(gemini_client, prompt)
|
| 880 |
-
raw = raw_text.strip()
|
| 881 |
-
|
| 882 |
-
json_match = re.search(r'\{.*\}', raw, re.DOTALL)
|
| 883 |
-
if json_match:
|
| 884 |
-
raw_clean = json_match.group(0)
|
| 885 |
-
else:
|
| 886 |
-
raw_clean = raw
|
| 887 |
-
if "```json" in raw_clean:
|
| 888 |
-
raw_clean = raw_clean.split("```json")[1].split("```")[0]
|
| 889 |
-
elif "```" in raw_clean:
|
| 890 |
-
raw_clean = raw_clean.split("```")[1].split("```")[0]
|
| 891 |
-
|
| 892 |
-
data = json.loads(raw_clean.strip())
|
| 893 |
-
data["es_hipotesis_local"] = True
|
| 894 |
-
return data
|
| 895 |
-
except Exception as e:
|
| 896 |
-
logger.error(f"Error en Agente Rescatador Universal de IA: {e}")
|
| 897 |
-
return None
|
| 898 |
-
|
| 899 |
def find_best_foodb_matches(user_foods_es, _foodb_index, _foodb_lower_map, food_name_map, limit=3):
|
| 900 |
if not user_foods_es or not _foodb_index:
|
| 901 |
return []
|
|
@@ -932,7 +696,7 @@ def find_best_foodb_matches(user_foods_es, _foodb_index, _foodb_lower_map, food_
|
|
| 932 |
return unique[:limit]
|
| 933 |
|
| 934 |
# ==============================================================================
|
| 935 |
-
# GENERADORES DE INFORMES
|
| 936 |
# ==============================================================================
|
| 937 |
|
| 938 |
def generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map):
|
|
@@ -950,7 +714,7 @@ def generate_local_fallback_analysis(query, match, entities, food_compound_map,
|
|
| 950 |
|
| 951 |
compuestos_str = ", ".join(sorted(list(compuestos_detectados))) if compuestos_detectados else match.get('compuesto_alimento', 'compuestos bioactivos')
|
| 952 |
|
| 953 |
-
causa = f"\n### 🧬 Causa e Identificación Molecular\nLos síntomas descritos (**{', '.join(user_symptoms)}**) tras el consumo de **{', '.join(user_foods)}** están asociados a la presencia de
|
| 954 |
|
| 955 |
detalles_compuestos = []
|
| 956 |
for comp in compuestos_detectados:
|
|
@@ -958,13 +722,11 @@ def generate_local_fallback_analysis(query, match, entities, food_compound_map,
|
|
| 958 |
detalles_compuestos.append(f"- **{comp.capitalize()}**: {neuro_map[comp]['efecto_neuropsicologico']}")
|
| 959 |
|
| 960 |
if detalles_compuestos:
|
| 961 |
-
causa += "\n**Mecanismo bioquímico
|
| 962 |
-
elif match.get('mecanismo_fisiologico'):
|
| 963 |
-
causa += f"\n**Mecanismo fisiológico:** {match.get('mecanismo_fisiologico')}\n"
|
| 964 |
|
| 965 |
-
evitar = f"\n### 🛑 Alimentos a Moderar o Evitar\n- **Alimentos evaluados:** {', '.join(user_foods).capitalize()}\n- Productos
|
| 966 |
|
| 967 |
-
alternativas = "\n### 💡 Sugerencias
|
| 968 |
|
| 969 |
consejo_texto = None
|
| 970 |
for comp in compuestos_detectados:
|
|
@@ -981,17 +743,10 @@ def generate_local_fallback_analysis(query, match, entities, food_compound_map,
|
|
| 981 |
return saludo + hipotesis + causa + evitar + alternativas + consejo + TEXTO_BANDERAS_ROJAS
|
| 982 |
|
| 983 |
def generate_detailed_analysis(query, match, entities=None, food_compound_map=None, neuro_map=None):
|
| 984 |
-
cache_lookup_key = f"{hash(query)}_{match.get('condicion_asociada')}"
|
| 985 |
-
if cache_lookup_key in global_learned_cache:
|
| 986 |
-
logger.info("⚡ Respuesta entregada desde la Caché Global de Aprendizaje en RAM (0.001s).")
|
| 987 |
-
return global_learned_cache[cache_lookup_key]
|
| 988 |
-
|
| 989 |
gemini_client = get_gemini_client()
|
| 990 |
|
| 991 |
if not gemini_client:
|
| 992 |
-
|
| 993 |
-
global_learned_cache[cache_lookup_key] = local_report
|
| 994 |
-
return local_report
|
| 995 |
|
| 996 |
compound_context = ""
|
| 997 |
if entities and food_compound_map and neuro_map:
|
|
@@ -1002,36 +757,24 @@ def generate_detailed_analysis(query, match, entities=None, food_compound_map=No
|
|
| 1002 |
if compound in neuro_map:
|
| 1003 |
lines.append(f"- {food.capitalize()} contiene {compound}: {neuro_map[compound]['efecto_neuropsicologico']}")
|
| 1004 |
if lines:
|
| 1005 |
-
compound_context = "Compuestos
|
| 1006 |
|
| 1007 |
prompt = f"""
|
| 1008 |
-
Rol: Nutricionista Funcional
|
| 1009 |
-
Caso
|
| 1010 |
Hipótesis clínica: {match.get('condicion_asociada')}
|
| 1011 |
-
Mecanismo
|
| 1012 |
-
Alimentos/compuestos clave: {match.get('compuesto_alimento')}
|
| 1013 |
{compound_context}
|
| 1014 |
|
| 1015 |
-
|
| 1016 |
-
1. Saludo breve y empático.
|
| 1017 |
-
2. Frase inicial clara con la hipótesis probable.
|
| 1018 |
-
3. Explicación de las moléculas responsables (ej: GOS/FODMAPs, lectinas, inhibidores de proteasas, histamina, tiramina, gluten, lactosa, caseína).
|
| 1019 |
-
4. Mecanismo fisiológico en términos sencillos.
|
| 1020 |
-
5. Alimentos a evitar o moderar.
|
| 1021 |
-
6. Alternativas o reemplazos sugeridos.
|
| 1022 |
-
7. Consejo práctico final.
|
| 1023 |
NO incluyas descargos de responsabilidad.
|
| 1024 |
"""
|
| 1025 |
try:
|
| 1026 |
-
raw_text, used_model =
|
| 1027 |
-
|
| 1028 |
-
global_learned_cache[cache_lookup_key] = ai_report
|
| 1029 |
-
return ai_report
|
| 1030 |
except Exception as e:
|
| 1031 |
-
logger.warning(f"
|
| 1032 |
-
|
| 1033 |
-
global_learned_cache[cache_lookup_key] = local_report
|
| 1034 |
-
return local_report
|
| 1035 |
|
| 1036 |
def generate_neuro_report_text(entities, food_map, neuro_map):
|
| 1037 |
report_lines = ["\n### 🧠 Efectos Neuropsicológicos Posibles"]
|
|
@@ -1043,15 +786,15 @@ def generate_neuro_report_text(entities, food_map, neuro_map):
|
|
| 1043 |
comps = get_compounds_for_food(food, food_map)
|
| 1044 |
relevant_compounds.update(comps)
|
| 1045 |
|
| 1046 |
-
|
| 1047 |
if relevant_compounds:
|
| 1048 |
for compound in sorted(list(relevant_compounds)):
|
| 1049 |
if compound in neuro_map:
|
| 1050 |
-
|
| 1051 |
report_lines.append(f"**{compound.capitalize()}**: _{neuro_map[compound]['efecto_neuropsicologico']}_")
|
| 1052 |
|
| 1053 |
-
if not
|
| 1054 |
-
report_lines.append("No se detectaron efectos neuropsicológicos específicos directos
|
| 1055 |
|
| 1056 |
return "\n".join(report_lines)
|
| 1057 |
|
|
@@ -1059,7 +802,7 @@ def generate_molecular_report_text(best_match, entities, _foodb_index, _foodb_lo
|
|
| 1059 |
report_lines = ["\n### 🔬 Análisis Molecular y Efectos Bioactivos"]
|
| 1060 |
user_foods = entities.get("alimentos", [])
|
| 1061 |
if not user_foods:
|
| 1062 |
-
return "No hay alimentos seleccionados
|
| 1063 |
|
| 1064 |
found_any = False
|
| 1065 |
|
|
@@ -1081,23 +824,16 @@ def generate_molecular_report_text(best_match, entities, _foodb_index, _foodb_lo
|
|
| 1081 |
if not compounds:
|
| 1082 |
continue
|
| 1083 |
found_any = True
|
| 1084 |
-
report_lines.append(f"\n**{food_key.capitalize()}** (
|
| 1085 |
|
| 1086 |
for item in compounds[:5]:
|
| 1087 |
if isinstance(item, dict):
|
| 1088 |
comp_name = item.get('compound', 'N/A')
|
| 1089 |
effects = item.get('effects', [])
|
| 1090 |
|
| 1091 |
-
eff_translated = []
|
| 1092 |
-
|
| 1093 |
-
eff_clean = str(eff).lower().strip()
|
| 1094 |
-
eff_es = FOODB_EFFECTS_TRANSLATION.get(eff_clean, eff_clean)
|
| 1095 |
-
eff_translated.append(eff_es)
|
| 1096 |
-
|
| 1097 |
-
eff_str = f" — *Efectos registrados: {', '.join(eff_translated)}*" if eff_translated else ""
|
| 1098 |
report_lines.append(f"- **{comp_name}**{eff_str}")
|
| 1099 |
-
else:
|
| 1100 |
-
report_lines.append(f"- {str(item)}")
|
| 1101 |
|
| 1102 |
if not found_any:
|
| 1103 |
report_lines.append("No se encontraron registros moleculares adicionales.")
|
|
@@ -1115,7 +851,7 @@ def generate_word_report(report_text):
|
|
| 1115 |
doc_io.seek(0)
|
| 1116 |
return doc_io.getvalue()
|
| 1117 |
except Exception as e:
|
| 1118 |
-
logger.error(f"Error
|
| 1119 |
return None
|
| 1120 |
|
| 1121 |
def create_relevance_chart(results):
|
|
@@ -1129,13 +865,13 @@ def create_relevance_chart(results):
|
|
| 1129 |
).properties(title='Top Coincidencias')
|
| 1130 |
|
| 1131 |
# ==============================================================================
|
| 1132 |
-
#
|
| 1133 |
# ==============================================================================
|
| 1134 |
|
| 1135 |
if 'search_results' not in st.session_state: st.session_state.search_results = None
|
| 1136 |
if 'user_query' not in st.session_state: st.session_state.user_query = ""
|
| 1137 |
if 'entities' not in st.session_state: st.session_state.entities = None
|
| 1138 |
-
if '
|
| 1139 |
if 'neuro_text_cache' not in st.session_state: st.session_state.neuro_text_cache = ""
|
| 1140 |
if 'mol_text_cache' not in st.session_state: st.session_state.mol_text_cache = ""
|
| 1141 |
if 'word_bytes_cache' not in st.session_state: st.session_state.word_bytes_cache = None
|
|
@@ -1152,7 +888,7 @@ with col_img2:
|
|
| 1152 |
|
| 1153 |
gemini_test = get_gemini_client()
|
| 1154 |
if gemini_test:
|
| 1155 |
-
st.caption("🟢 **Motor de IA Gemini Conectado:** Extracción y Redacción
|
| 1156 |
else:
|
| 1157 |
st.caption("🟡 **Modo Local / Alta Disponibilidad:** Activo")
|
| 1158 |
|
|
@@ -1170,11 +906,8 @@ if submitted:
|
|
| 1170 |
current_query = query_input.strip()
|
| 1171 |
st.session_state.user_query = current_query
|
| 1172 |
st.session_state.has_searched = True
|
| 1173 |
-
st.session_state.analysis_cache = {}
|
| 1174 |
-
st.session_state.search_results = None
|
| 1175 |
-
st.session_state.entities = None
|
| 1176 |
|
| 1177 |
-
with st.spinner("🔍 Analizando
|
| 1178 |
raw_entities = extract_entities_with_gemini(current_query)
|
| 1179 |
reinforced = reinforce_entities_with_keywords(raw_entities, current_query, FOOD_TO_COMPOUND_MAP, MASTER_SYMPTOM_MAP)
|
| 1180 |
final_symptoms = translate_symptoms_local(reinforced.get("sintomas", []), MASTER_SYMPTOM_MAP)
|
|
@@ -1188,41 +921,32 @@ if submitted:
|
|
| 1188 |
if local_hypothesis:
|
| 1189 |
results = [{'entry': local_hypothesis, 'score': {'total': 50}, 'matched_keywords': []}]
|
| 1190 |
|
| 1191 |
-
if not results:
|
| 1192 |
-
ai_hypothesis = generate_ai_hypothesis_on_the_fly(current_query, final_entities)
|
| 1193 |
-
if ai_hypothesis:
|
| 1194 |
-
results = [{'entry': ai_hypothesis, 'score': {'total': 50}, 'matched_keywords': []}]
|
| 1195 |
-
|
| 1196 |
st.session_state.search_results = results if results else []
|
| 1197 |
|
| 1198 |
-
# Pre-cálculo de pestañas en sesión para acelerar la navegación y evitar estado "pensando"
|
| 1199 |
if st.session_state.search_results:
|
| 1200 |
best_match = st.session_state.search_results[0]['entry']
|
| 1201 |
-
|
| 1202 |
-
#
|
| 1203 |
st.session_state.main_analysis = generate_detailed_analysis(
|
| 1204 |
st.session_state.user_query, best_match,
|
| 1205 |
st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
|
| 1206 |
)
|
| 1207 |
-
|
| 1208 |
-
# 2. Neuroinforme
|
| 1209 |
st.session_state.neuro_text_cache = generate_neuro_report_text(
|
| 1210 |
st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
|
| 1211 |
)
|
| 1212 |
-
|
| 1213 |
-
# 3. Informe Molecular
|
| 1214 |
st.session_state.mol_text_cache = generate_molecular_report_text(
|
| 1215 |
best_match, st.session_state.entities, foodb_index, foodb_lower_map,
|
| 1216 |
FOOD_NAME_TO_FOODB_KEY, COMPOUND_SYNONYM_MAP, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
|
| 1217 |
)
|
| 1218 |
|
| 1219 |
-
# 4. Informe Word
|
| 1220 |
st.session_state.word_bytes_cache = generate_word_report(f"REPORTE DE SALUD\n\n{st.session_state.main_analysis}")
|
| 1221 |
|
| 1222 |
else:
|
| 1223 |
st.warning("⚠️ Por favor describe tus síntomas o alimentos consumidos antes de analizar.")
|
| 1224 |
|
| 1225 |
-
# ---
|
| 1226 |
if st.session_state.has_searched:
|
| 1227 |
results = st.session_state.search_results
|
| 1228 |
|
|
@@ -1238,10 +962,10 @@ if st.session_state.has_searched:
|
|
| 1238 |
st.altair_chart(create_relevance_chart(results), use_container_width=True)
|
| 1239 |
|
| 1240 |
with tab_neuro:
|
| 1241 |
-
st.markdown(st.session_state.get('neuro_text_cache', 'No hay datos neuropsicológicos
|
| 1242 |
|
| 1243 |
with tab_mol:
|
| 1244 |
-
st.markdown(st.session_state.get('mol_text_cache', 'No hay datos moleculares
|
| 1245 |
|
| 1246 |
word_bytes = st.session_state.get('word_bytes_cache')
|
| 1247 |
if word_bytes:
|
|
@@ -1254,4 +978,4 @@ if st.session_state.has_searched:
|
|
| 1254 |
)
|
| 1255 |
else:
|
| 1256 |
st.warning("⚠️ No pudimos identificar una causa clara para este caso específico.")
|
| 1257 |
-
st.info("Intenta
|
|
|
|
| 1 |
# ==============================================================================
|
| 2 |
+
# EL DETECTIVE DE ALIMENTOS - EDICIÓN HÍBRIDA CON CORTA-FUEGOS Y ALTA DISPONIBILIDAD
|
| 3 |
# Desarrollado por: Jairo Alexander & Diana Milena Soler Martinez
|
| 4 |
# Motor: Gemini Flash + FooDB.ca Graph + Fast-Path Local Engine
|
| 5 |
# ==============================================================================
|
|
|
|
| 16 |
from io import BytesIO
|
| 17 |
import docx
|
| 18 |
import difflib
|
| 19 |
+
import concurrent.futures
|
| 20 |
|
| 21 |
# --- CONFIGURACIÓN DE PÁGINA Y LOGGING ---
|
| 22 |
st.set_page_config(page_title="El Detective de Alimentos", page_icon="🍎", layout="wide")
|
|
|
|
| 38 |
**Si tus síntomas son severos, busca ayuda médica urgente.**
|
| 39 |
"""
|
| 40 |
|
| 41 |
+
# --- CONFIGURACIÓN DE CLIENTE GEMINI CACHEADO ---
|
| 42 |
@st.cache_resource
|
| 43 |
def get_gemini_client():
|
| 44 |
api_key = os.getenv("GEMINI_API_KEY")
|
|
|
|
| 60 |
return None
|
| 61 |
|
| 62 |
try:
|
|
|
|
| 63 |
return genai.Client(
|
| 64 |
api_key=api_key,
|
| 65 |
+
http_options=types.HttpOptions(timeout=3000) # 3000 ms timeout de cliente
|
| 66 |
)
|
| 67 |
except Exception as e:
|
| 68 |
logger.warning(f"❌ Error instanciando cliente Gemini: {e}")
|
| 69 |
return None
|
| 70 |
|
| 71 |
+
# --- CORTA-FUEGOS CON TIEMPO LÍMITE ESTRICTO DE 3.5 SEGUNDOS ---
|
| 72 |
def generate_content_with_fallback(gemini_client, prompt):
|
| 73 |
models_to_try = [
|
| 74 |
'gemini-2.5-flash',
|
|
|
|
| 89 |
continue
|
| 90 |
raise RuntimeError("Ningún modelo de Gemini respondió adecuadamente.")
|
| 91 |
|
| 92 |
+
def generate_content_with_strict_timeout(gemini_client, prompt, timeout_seconds=3.5):
|
| 93 |
+
"""
|
| 94 |
+
Ejecuta la llamada a Gemini dentro de un hilo aislado.
|
| 95 |
+
Si Gemini tarda más de 3.5s, corta la ejecución y activa el motor local.
|
| 96 |
+
"""
|
| 97 |
+
def _call():
|
| 98 |
+
return generate_content_with_fallback(gemini_client, prompt)
|
| 99 |
+
|
| 100 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
| 101 |
+
future = executor.submit(_call)
|
| 102 |
+
try:
|
| 103 |
+
return future.result(timeout=timeout_seconds)
|
| 104 |
+
except concurrent.futures.TimeoutError:
|
| 105 |
+
logger.warning(f"⏳ Gemini tardó más de {timeout_seconds}s. Se corta la espera y se activa el Motor Local.")
|
| 106 |
+
raise TimeoutError("Tiempo límite de IA superado.")
|
| 107 |
+
|
| 108 |
+
# --- CARGA DE DATOS EN MEMORIA ---
|
| 109 |
@st.cache_resource
|
| 110 |
def load_data():
|
| 111 |
try:
|
|
|
|
| 134 |
|
| 135 |
foodb_lower_map = get_foodb_lower_map(foodb_index)
|
| 136 |
|
|
|
|
| 137 |
@st.cache_resource
|
| 138 |
def get_global_learned_cache():
|
| 139 |
return {}
|
|
|
|
| 141 |
global_learned_cache = get_global_learned_cache()
|
| 142 |
|
| 143 |
# ==============================================================================
|
| 144 |
+
# DICCIONARIOS MASTER BIOMÉDICOS
|
| 145 |
# ==============================================================================
|
| 146 |
|
| 147 |
FOOD_TO_COMPOUND_MAP = {
|
|
|
|
| 157 |
"maíz": ["zeína", "lectinas", "fitatos", "fodmaps"],
|
| 158 |
"quinoa": ["saponinas", "oxalatos", "fitatos"],
|
| 159 |
"alforfón": ["oxalatos", "fagopirina"],
|
|
|
|
| 160 |
|
| 161 |
# LÁCTEOS
|
| 162 |
"leche": ["caseína", "lactosa", "fodmaps"],
|
| 163 |
"queso": ["tiramina", "histamina", "caseína", "lactosa"],
|
| 164 |
"queso fresco": ["caseína", "lactosa"],
|
| 165 |
"queso maduro": ["tiramina", "histamina", "caseína"],
|
|
|
|
| 166 |
"yogur": ["histamina", "caseína", "lactosa"],
|
| 167 |
"mantequilla": ["caseína", "lactosa"],
|
| 168 |
"crema de leche": ["caseína", "lactosa"],
|
|
|
|
| 177 |
"empanada": ["gluten", "zeína", "solanina", "lectinas"],
|
| 178 |
"arepa": ["zeína", "lectinas", "fitatos"],
|
| 179 |
"lechona": ["histamina", "nitritos", "grasas saturadas", "purinas"],
|
|
|
|
| 180 |
|
| 181 |
+
# FRUTAS Y TROPICALES
|
| 182 |
"noni": ["escopoletina", "fenoles", "salicilatos", "fructosa", "histamina"],
|
|
|
|
| 183 |
"chontaduro": ["fenoles", "fructosa", "grasas saturadas", "tiramina", "salicilatos"],
|
|
|
|
| 184 |
"naranja": ["fructosa", "histamina", "salicilatos", "fenoles"],
|
| 185 |
"jugo de naranja": ["fructosa", "histamina", "salicilatos", "fenoles"],
|
| 186 |
"limón": ["salicilatos", "histamina", "fenoles", "ácido cítrico"],
|
|
|
|
| 187 |
"manzana": ["fructosa", "sorbitol", "fodmaps", "salicilatos"],
|
| 188 |
"pera": ["fructosa", "sorbitol", "fodmaps", "salicilatos"],
|
| 189 |
"plátano": ["tiramina", "histamina", "salicilatos", "fenoles"],
|
|
|
|
| 193 |
"mora": ["salicilatos", "oxalatos", "histamina", "taninos"],
|
| 194 |
"uva": ["fructosa", "salicilatos", "histamina", "taninos"],
|
| 195 |
"piña": ["salicilatos", "histamina", "fructosa"],
|
|
|
|
| 196 |
"mango": ["fructosa", "salicilatos", "histamina"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
"lulo": ["salicilatos", "fructosa", "ácido cítrico"],
|
|
|
|
| 198 |
"maracuyá": ["fructosa", "histamina", "salicilatos"],
|
| 199 |
"maracuya": ["fructosa", "histamina", "salicilatos"],
|
| 200 |
"feijoa": ["fructosa", "salicilatos", "taninos"],
|
|
|
|
|
|
|
| 201 |
"tomate de árbol": ["solanina", "histamina", "salicilatos"],
|
|
|
|
| 202 |
"uchuva": ["solanina", "histamina", "salicilatos", "oxalatos"],
|
| 203 |
+
|
| 204 |
+
# VERDURAS Y SOLANÁCEAS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
"tomate": ["histamina", "solanina", "salicilatos", "glutamato"],
|
|
|
|
|
|
|
| 206 |
"papa": ["solanina", "oxalatos", "lectinas"],
|
| 207 |
+
"patata": ["solanina", "oxalatos", "lectinas"],
|
| 208 |
"berenjena": ["solanina", "histamina", "salicilatos"],
|
| 209 |
"pimiento": ["capsaicina", "solanina", "salicilatos"],
|
| 210 |
"espinaca": ["oxalatos", "nitratos", "histamina"],
|
|
|
|
|
|
|
|
|
|
| 211 |
"cebolla": ["fodmaps", "compuestos azufrados", "salicilatos"],
|
| 212 |
"ajo": ["fodmaps", "compuestos azufrados"],
|
| 213 |
"brócoli": ["fodmaps", "compuestos azufrados", "salicilatos", "goitrógenos"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
"yuca": ["fodmaps", "fitatos"],
|
| 215 |
"ñame": ["oxalatos", "fitatos", "fodmaps"],
|
| 216 |
+
|
| 217 |
+
# LEGUMBRES (PERFIL COMPLETO)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
"frijol": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
|
| 219 |
"frijoles": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
|
| 220 |
"frijol paisa": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
|
| 221 |
"lentejas": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
|
| 222 |
"garbanzos": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos"],
|
| 223 |
"arvejas": ["fodmaps", "lectinas", "fitatos", "inhibidores_proteasas"],
|
|
|
|
| 224 |
"soja": ["soja", "isoflavonas", "lectinas", "fitatos", "fodmaps", "saponinas", "goitrógenos", "inhibidores_proteasas"],
|
| 225 |
"tofu": ["soja", "isoflavonas", "lectinas", "fitatos"],
|
|
|
|
| 226 |
"haba": ["vicina_convicina", "fodmaps", "lectinas", "fitatos", "purinas"],
|
| 227 |
"habas": ["vicina_convicina", "fodmaps", "lectinas", "fitatos", "purinas"],
|
| 228 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
# CARNES, EMBUTIDOS Y BEBIDAS
|
| 230 |
"carne de cerdo": ["histamina", "alfa-gal", "grasas saturadas", "purinas"],
|
| 231 |
"cerdo": ["histamina", "alfa-gal", "grasas saturadas", "purinas"],
|
| 232 |
"chicharrón": ["histamina", "grasas saturadas"],
|
| 233 |
"chicharron": ["histamina", "grasas saturadas"],
|
|
|
|
| 234 |
"carne de res": ["histamina", "alfa-gal", "purinas"],
|
| 235 |
"res": ["histamina", "alfa-gal", "purinas"],
|
| 236 |
"café": ["cafeína", "salicilatos", "oxalatos"],
|
|
|
|
| 239 |
"chocolate": ["teobromina", "feniletilamina", "tiramina", "histamina", "oxalatos", "cafeína"],
|
| 240 |
"vino": ["histamina", "tiramina", "sulfitos", "taninos", "alcohol"],
|
| 241 |
"cerveza": ["gluten", "histamina", "sulfitos", "alcohol", "purinas"],
|
|
|
|
|
|
|
| 242 |
"embutidos": ["histamina", "tiramina", "nitritos", "glutamato"],
|
| 243 |
"pescado": ["histamina", "purinas", "albúmina"],
|
| 244 |
+
"huevo": ["albúmina"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
}
|
| 246 |
|
| 247 |
FOOD_NAME_TO_FOODB_KEY = {
|
| 248 |
+
"alforfón": ["buckwheat"], "arroz": ["rice"], "avena": ["oat", "oats"], "cebada": ["barley"],
|
| 249 |
+
"queso": ["cheese"], "leche": ["milk"], "yogur": ["yogurt"], "mantequilla": ["butter"],
|
| 250 |
"vino": ["wine"], "cerveza": ["beer"], "chocolate": ["chocolate", "cocoa"],
|
| 251 |
"tomate": ["tomato"], "aguacate": ["avocado"], "plátano": ["banana", "plantain"], "banano": ["banana"],
|
| 252 |
"naranja": ["orange"], "pan": ["bread", "wheat"], "trigo": ["wheat"], "café": ["coffee"], "cafe": ["coffee"],
|
| 253 |
+
"manzana": ["apple"], "fresa": ["strawberry"], "uva": ["grape"], "piña": ["pineapple"],
|
| 254 |
"papa": ["potato"], "patata": ["potato"], "frijol": ["bean", "beans", "kidney bean", "black bean"],
|
| 255 |
"frijoles": ["bean", "beans"], "frijol paisa": ["bean", "pork", "rice"],
|
| 256 |
+
"lentejas": ["lentil"], "garbanzos": ["chickpea", "garbanzo"], "arveja": ["pea"],
|
| 257 |
"haba": ["broad bean", "fava bean"], "habas": ["broad bean", "fava bean"],
|
|
|
|
| 258 |
"bandeja paisa": ["bean", "pork", "rice", "banana", "egg", "avocado"],
|
| 259 |
"ajiaco": ["potato", "chicken", "corn", "milk", "cream"],
|
| 260 |
"sancocho": ["cassava", "plantain", "potato", "pork", "beef", "chicken"],
|
|
|
|
| 263 |
}
|
| 264 |
|
| 265 |
STRICT_COMPOUND_TRIGGERS = {
|
| 266 |
+
"cafeína": ["cafeína", "caffeine", "café", "cafe", "coffee"],
|
| 267 |
"teobromina": ["teobromina", "theobromine", "cacao", "chocolate"],
|
| 268 |
+
"solanina": ["solanina", "solanine", "solanáceas", "tomate", "patata", "papa"],
|
| 269 |
+
"capsaicina": ["capsaicina", "capsaicin", "chile", "ají", "picante"],
|
| 270 |
+
"gluten": ["gluten", "trigo", "wheat", "cebada", "centeno"],
|
| 271 |
+
"caseína": ["caseína", "casein", "queso", "lácteos"],
|
| 272 |
+
"soja": ["soja", "soya", "soy", "tofu", "tempeh"],
|
| 273 |
+
"albúmina": ["albúmina", "albumin", "huevo"],
|
| 274 |
"lactosa": ["lactosa", "lactose"],
|
| 275 |
+
"fructosa": ["fructosa", "fructose"],
|
| 276 |
+
"fodmaps": ["fodmap", "fodmaps", "fructanos", "galactanos", "sorbitol", "rafinosa", "estaquiosa"],
|
| 277 |
"histamina": ["histamina", "histamine", "dao"],
|
| 278 |
"tiramina": ["tiramina", "tyramine"],
|
| 279 |
+
"oxalatos": ["oxalato", "oxalatos", "oxalate"],
|
| 280 |
+
"saponinas": ["saponina", "saponinas"],
|
| 281 |
+
"lectinas": ["lectina", "lectinas", "frijoles", "legumbres"],
|
| 282 |
+
"fitatos": ["fitato", "fitatos", "ácido fítico"],
|
| 283 |
+
"salicilatos": ["salicilato", "salicilatos"],
|
| 284 |
+
"sulfitos": ["sulfito", "sulfitos"],
|
| 285 |
"glutamato": ["glutamato", "glutamate", "msg"],
|
| 286 |
+
"nitritos": ["nitrito", "nitritos", "embutidos"],
|
| 287 |
"alcohol": ["alcohol", "etanol", "cerveza", "vino"],
|
| 288 |
+
"purinas": ["purina", "purinas", "ácido úrico", "gota"],
|
| 289 |
+
"inhibidores_proteasas": ["inhibidor de tripsina", "inhibidores de proteasas", "tripsina"],
|
| 290 |
+
"vicina_convicina": ["vicina", "convicina", "favismo", "g6pd", "haba", "habas"]
|
|
|
|
|
|
|
|
|
|
| 291 |
}
|
| 292 |
|
| 293 |
MEDICAL_CONDITION_SAFEGUARDS = {
|
| 294 |
"farmacos_interacciones": {
|
| 295 |
+
"condition_triggers": ["fármaco", "farmaco", "medicamento", "imao", "anticoagulante"],
|
| 296 |
+
"required_user_keywords": ["fármaco", "farmaco", "medicamento", "imao", "pastilla", "remedio", "receta"]
|
| 297 |
},
|
| 298 |
"respiratorio_asma": {
|
| 299 |
+
"condition_triggers": ["asma", "sibilancia", "broncoespasmo"],
|
| 300 |
+
"required_user_keywords": ["asma", "tos", "respirar", "pecho", "sibilancia", "ahogo"]
|
| 301 |
},
|
| 302 |
"dermatologico_alergia": {
|
| 303 |
+
"condition_triggers": ["urticaria", "angioedema", "anafilaxia", "dermatitis"],
|
| 304 |
+
"required_user_keywords": ["piel", "pica", "picazón", "manchas", "rojo", "alergia", "ronchas"]
|
| 305 |
},
|
| 306 |
"articular_gota": {
|
| 307 |
"condition_triggers": ["gota", "artritis", "hiperuricemia"],
|
| 308 |
+
"required_user_keywords": ["articulación", "coyuntura", "dedo gordo", "rodilla", "gota"]
|
| 309 |
},
|
| 310 |
"renal_litiasis": {
|
| 311 |
"condition_triggers": ["litiasis", "cálculo renal", "piedras en el riñón"],
|
|
|
|
| 316 |
"required_user_keywords": ["embarazada", "embarazo", "bebé", "lactando"]
|
| 317 |
},
|
| 318 |
"hepato_vesicular": {
|
| 319 |
+
"condition_triggers": ["vesícula", "vesicula", "colelitiasis", "hígado"],
|
| 320 |
+
"required_user_keywords": ["vesícula", "vesicula", "hígado", "higado", "piel amarilla"]
|
| 321 |
}
|
| 322 |
}
|
| 323 |
|
|
|
|
| 327 |
"fuentes_comunes": ["pan", "trigo", "pasta", "cebada", "centeno"]
|
| 328 |
},
|
| 329 |
"caseína": {
|
| 330 |
+
"efecto_neuropsicologico": "Sus péptidos (casomorfinas) pueden actuar sobre receptores opioides en el SNC, afectando la cognición y el estado de ánimo.",
|
| 331 |
"fuentes_comunes": ["leche", "queso", "yogur"]
|
| 332 |
},
|
| 333 |
"fodmaps": {
|
| 334 |
+
"efecto_neuropsicologico": "Mediante el eje intestino-cerebro, la fermentación intestinal excesiva por oligosacáridos (GOS/rafinosa) genera distensión visceral, enviando señales de estrés por el nervio vago que aumentan la ansiedad e hipersensibilidad.",
|
| 335 |
"fuentes_comunes": ["frijoles", "lentejas", "cebolla", "ajo", "trigo"]
|
| 336 |
},
|
| 337 |
"inhibidores_proteasas": {
|
| 338 |
+
"efecto_neuropsicologico": "Impiden la descomposición enzimática adecuada de proteínas, produciendo indigestión severa, hipoperfusión gastrointestinal y letargo posprandial.",
|
| 339 |
+
"fuentes_comunes": ["frijoles", "legumbres mal cocidas", "soja"]
|
| 340 |
},
|
| 341 |
"vicina_convicina": {
|
| 342 |
+
"efecto_neuropsicologico": "En personas con deficiencia de la enzima G6PD (favismo), desencadena estrés oxidativo eritrocitario que produce cefalea vascular, astenia y mareos.",
|
| 343 |
"fuentes_comunes": ["habas"]
|
| 344 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
"tiramina": {
|
| 346 |
+
"efecto_neuropsicologico": "Amina vasoactiva. Provoca vasoconstricción inicial seguida de vasodilatación cerebral, desencadenando migrañas.",
|
| 347 |
"fuentes_comunes": ["quesos madurados", "vino tinto", "embutidos"]
|
| 348 |
},
|
| 349 |
"histamina": {
|
| 350 |
+
"efecto_neuropsicologico": "Neurotransmisor estimulante central. Su exceso por bajo DAO provoca migrañas, insomnio, fragmentación del sueño y ansiedad.",
|
| 351 |
"fuentes_comunes": ["queso", "vino tinto", "tomate", "pescado azul", "conservas"]
|
| 352 |
},
|
| 353 |
"salicilatos": {
|
| 354 |
+
"efecto_neuropsicologico": "En personas sensibles provocan estimulación excesiva del SNC, hiperactividad e insomnio.",
|
| 355 |
"fuentes_comunes": ["cítricos", "manzanas", "tomate", "especias", "té"]
|
| 356 |
},
|
| 357 |
"fenoles": {
|
| 358 |
+
"efecto_neuropsicologico": "La sobrecarga de fenoles satura las vías de sulfatación hepática, acumulando catecolaminas que producen agitación nocturna e insomnio.",
|
| 359 |
"fuentes_comunes": ["jugo de naranja", "chontaduro", "noni", "té"]
|
| 360 |
},
|
| 361 |
"cafeína": {
|
| 362 |
+
"efecto_neuropsicologico": "Bloquea los receptores de adenosina en el cerebro. Provoca hiperactivación del SNC, ansiedad e insomnio.",
|
| 363 |
"fuentes_comunes": ["café", "té negro", "energizantes", "chocolate"]
|
| 364 |
},
|
| 365 |
"lactosa": {
|
|
|
|
| 367 |
"fuentes_comunes": ["leche", "queso fresco", "crema de leche"]
|
| 368 |
},
|
| 369 |
"fructosa": {
|
| 370 |
+
"efecto_neuropsicologico": "Disminuye la biodisponibilidad de triptófano en el intestino, interfiriendo en la síntesis de serotonina y melatonina.",
|
| 371 |
"fuentes_comunes": ["jugos concentrados", "miel", "frutas muy dulces"]
|
| 372 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
"solanina": {
|
| 374 |
+
"efecto_neuropsicologico": "Glicoalcaloide con leve efecto inhibidor de la acetilcolinesterasa; puede provocar letargo y rigidez corporal.",
|
| 375 |
"fuentes_comunes": ["tomate", "patata", "berenjena"]
|
| 376 |
}
|
| 377 |
}
|
| 378 |
|
| 379 |
CONSEJOS_POR_COMPUESTO = {
|
| 380 |
+
"fodmaps": "Para reducir los GOS (rafinosa y estaquiosa) de los frijoles y legumbres, realiza un remojo de 12 a 24 horas desechando el agua, y cocina a alta presión adicionando comino o epazote.",
|
| 381 |
+
"inhibidores_proteasas": "La cocción prolongada y el calor de la olla a presión inactivan casi por completo los inhibidores de tripsina en legumbres.",
|
| 382 |
+
"vicina_convicina": "Si padeces favismo (deficiencia de G6PD), evita de forma estricta el consumo de habas.",
|
| 383 |
+
"gluten": "La respuesta al gluten debe diferenciar Enfermedad Celíaca, SGNC y alergia al trigo. Realiza pruebas serológicas antes de retirarlo.",
|
| 384 |
+
"lactosa": "La intolerancia se debe a baja lactasa. Se puede manejar con lactasa exógena o productos deslactosados.",
|
|
|
|
|
|
|
| 385 |
"histamina": "La acumulación de histamina se asocia a bajo DAO. Una dieta baja en histamina y rica en vitamina B6 y C ayuda a restaurar el equilibrio.",
|
| 386 |
+
"tiramina": "Controlar el consumo de alimentos añejados previene picos de presión arterial y cefaleas.",
|
| 387 |
+
"salicilatos": "Moderar el consumo concentrado de jugos cítricos y especias ayuda a estabilizar el sueño.",
|
| 388 |
+
"cafeína": "Moderar su consumo antes de mitad de tarde protege la arquitectura del sueño."
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
}
|
| 390 |
|
| 391 |
CONSEJO_GENERICO_DEFAULT = "Cada organismo posee una individualidad bioquímica única según su perfil enzimático y digestivo. Monitorear tus respuestas personales te permitirá adaptar la dieta a tu umbral de tolerancia."
|
|
|
|
| 393 |
FOODB_EFFECTS_TRANSLATION = {
|
| 394 |
"flatugenic": "genera gases/flatulencia", "antioxidant": "antioxidante",
|
| 395 |
"hypercholesterolemic": "puede elevar colesterol", "triglycerigenic": "puede elevar triglicéridos",
|
| 396 |
+
"uricogenic": "puede elevar ácido úrico", "vasodilator": "vasodilatador (posible migraña)",
|
|
|
|
|
|
|
|
|
|
| 397 |
"laxative": "efecto laxante", "diuretic": "efecto diurético",
|
| 398 |
+
"stimulant": "estimulante del SNC", "antiinflammatory": "antiinflamatorio"
|
|
|
|
| 399 |
}
|
| 400 |
|
| 401 |
COMPOUND_SYNONYM_MAP = {
|
| 402 |
+
"gluten": ["gluten", "gliadin"], "caseína": ["casein"], "lactosa": ["lactose"], "fructosa": ["fructose"]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
}
|
| 404 |
|
| 405 |
MASTER_SYMPTOM_MAP = {
|
| 406 |
"dolor abdominal": {
|
| 407 |
+
"frases_es": ["dolor de estómago", "dolor de panza", "dolor abdominal", "retortijones", "cólicos", "gastritis", "acidez", "reflujo", "pesadez"],
|
| 408 |
+
"keywords_en": ["abdominal", "pain", "stomach", "cramp"]
|
| 409 |
},
|
| 410 |
"hinchazón": {
|
| 411 |
+
"frases_es": ["hinchazón abdominal", "vientre hinchado", "distensión abdominal", "hinchazón", "distensión", "inflamado", "gases", "flatulencia"],
|
| 412 |
+
"keywords_en": ["bloating", "distension", "flatulence"]
|
| 413 |
},
|
| 414 |
"diarrea": {
|
| 415 |
+
"frases_es": ["diarrea", "diarreas", "soltura", "estómago suelto", "deposiciones líquidas"],
|
| 416 |
"keywords_en": ["diarrhea", "loose stools"]
|
| 417 |
},
|
| 418 |
"náuseas": {
|
| 419 |
+
"frases_es": ["náuseas", "nauseas", "vómito", "vomito", "ganas de vomitar"],
|
| 420 |
"keywords_en": ["nausea", "vomiting"]
|
| 421 |
},
|
| 422 |
"dolor de cabeza": {
|
| 423 |
+
"frases_es": ["dolor de cabeza", "migraña", "cefalea", "jaqueca", "me duele la cabeza"],
|
| 424 |
"keywords_en": ["headache", "migraine"]
|
| 425 |
},
|
| 426 |
"ansiedad": {
|
| 427 |
+
"frases_es": ["ansiedad", "nerviosismo", "angustia", "irritabilidad"],
|
| 428 |
+
"keywords_en": ["anxiety"]
|
| 429 |
},
|
| 430 |
"insomnio": {
|
| 431 |
+
"frases_es": ["insomnio", "no puedo dormir", "desvelado", "no duermo", "desvelo"],
|
| 432 |
+
"keywords_en": ["insomnia"]
|
| 433 |
},
|
| 434 |
"alergia cutánea": {
|
| 435 |
+
"frases_es": ["piel", "rasca", "picazón", "urticaria", "erupción", "alergia", "ronchas"],
|
| 436 |
+
"keywords_en": ["rash", "itching"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
}
|
| 438 |
}
|
| 439 |
|
| 440 |
# ==============================================================================
|
| 441 |
+
# FUNCIONES AUXILIARES Y DE BÚSQUEDA
|
| 442 |
# ==============================================================================
|
| 443 |
|
| 444 |
def sanitize_text(text):
|
|
|
|
| 453 |
"en", "con", "por", "para", "que", "se", "su", "sus", "al", "del", "lo",
|
| 454 |
"como", "es", "son", "muy", "mas", "más", "no", "si", "sí", "tu", "mi",
|
| 455 |
"me", "te", "le", "les", "nos", "cuando", "donde", "porque", "pero",
|
| 456 |
+
"tomo", "como", "da"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 457 |
}
|
| 458 |
|
| 459 |
def extract_keywords(text):
|
|
|
|
| 466 |
|
| 467 |
food_clean = food_name.lower().strip()
|
| 468 |
|
|
|
|
| 469 |
if food_clean in food_map:
|
| 470 |
return food_map[food_clean]
|
| 471 |
|
|
|
|
| 472 |
for suffix in ["es", "s"]:
|
| 473 |
if food_clean.endswith(suffix):
|
| 474 |
norm = food_clean[:-len(suffix)]
|
| 475 |
if norm in food_map:
|
| 476 |
return food_map[norm]
|
| 477 |
|
|
|
|
| 478 |
fuzzy_matches = difflib.get_close_matches(food_clean, list(food_map.keys()), n=1, cutoff=0.6)
|
| 479 |
if fuzzy_matches:
|
|
|
|
| 480 |
return food_map[fuzzy_matches[0]]
|
| 481 |
|
|
|
|
| 482 |
matched_compounds = set()
|
| 483 |
for key, compounds in food_map.items():
|
| 484 |
if re.search(r'\b' + re.escape(key) + r'\b', food_clean) or food_clean in key:
|
|
|
|
| 487 |
if matched_compounds:
|
| 488 |
return list(matched_compounds)
|
| 489 |
|
|
|
|
| 490 |
inferred = set()
|
| 491 |
+
if any(w in food_clean for w in ["frijol", "lenteja", "garbanzo", "legumbre", "soja", "haba", "arveja"]):
|
| 492 |
+
inferred.update(["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas"])
|
| 493 |
+
elif any(w in food_clean for w in ["carne", "cerdo", "res", "pollo", "pavo", "chicharrón"]):
|
| 494 |
+
inferred.update(["histamina", "purinas"])
|
| 495 |
+
elif any(w in food_clean for w in ["jugo", "zumo", "batido", "fruta"]):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 496 |
inferred.update(["fructosa", "salicilatos", "histamina", "fenoles"])
|
| 497 |
+
elif any(w in food_clean for w in ["queso", "leche", "crema", "yogur"]):
|
| 498 |
inferred.update(["caseína", "lactosa", "histamina", "tiramina"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
|
| 500 |
return list(inferred)
|
| 501 |
|
|
|
|
| 506 |
user_query_clean = user_query.lower() if user_query else ""
|
| 507 |
texto_condicion = (entry.get("condicion_asociada", "") + " " + entry.get("compuesto_alimento", "")).lower()
|
| 508 |
|
|
|
|
| 509 |
for category, rules in MEDICAL_CONDITION_SAFEGUARDS.items():
|
| 510 |
if any(trig in texto_condicion for trig in rules["condition_triggers"]):
|
| 511 |
tiene_contexto = any(req_kw in user_query_clean for req_kw in rules["required_user_keywords"])
|
| 512 |
if not tiene_contexto:
|
|
|
|
| 513 |
return False
|
| 514 |
|
|
|
|
| 515 |
user_compounds = set()
|
| 516 |
for food in user_foods:
|
| 517 |
comps = get_compounds_for_food(food, food_compound_map)
|
|
|
|
| 525 |
for syn in synonyms
|
| 526 |
)
|
| 527 |
if not tiene_compuesto:
|
|
|
|
| 528 |
return False
|
| 529 |
|
| 530 |
return True
|
|
|
|
| 535 |
return {"alimentos": [], "sintomas": []}
|
| 536 |
|
| 537 |
system_prompt = f"""
|
| 538 |
+
Rol: Extractor Médico JSON de alimentos y síntomas.
|
| 539 |
+
Extrae un JSON estricto con dos claves: "alimentos" (lista) y "sintomas" (lista).
|
|
|
|
|
|
|
|
|
|
| 540 |
Input: "{query}"
|
|
|
|
| 541 |
"""
|
| 542 |
try:
|
| 543 |
+
raw_text, _ = generate_content_with_strict_timeout(gemini_client, system_prompt, timeout_seconds=3.5)
|
| 544 |
text = raw_text
|
| 545 |
if "```json" in text:
|
| 546 |
text = text.split("```json")[1].split("```")[0]
|
|
|
|
| 548 |
text = text.split("```")[1].split("```")[0]
|
| 549 |
return json.loads(text)
|
| 550 |
except Exception as e:
|
| 551 |
+
logger.warning(f"Extracción por IA no disponible o cancelada ({e}). Se usa extracción directa local.")
|
| 552 |
return {"alimentos": [], "sintomas": []}
|
| 553 |
|
| 554 |
def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_map):
|
|
|
|
| 559 |
for food_keyword in food_map.keys():
|
| 560 |
if re.search(r'\b' + re.escape(food_keyword) + r'\b', query_sanitized):
|
| 561 |
current_foods.add(food_keyword)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 562 |
|
| 563 |
entities["alimentos"] = list(current_foods)
|
| 564 |
|
|
|
|
| 568 |
if phrase in query_sanitized:
|
| 569 |
current_symptoms.add(main_symptom)
|
| 570 |
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 571 |
|
| 572 |
entities["sintomas"] = list(current_symptoms)
|
| 573 |
return entities
|
|
|
|
| 636 |
if not user_foods or not user_symptoms:
|
| 637 |
return None
|
| 638 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 639 |
candidates = []
|
| 640 |
for food in user_foods:
|
| 641 |
comps = get_compounds_for_food(food, food_compound_map)
|
| 642 |
for compound in comps:
|
| 643 |
info = neuro_map.get(compound, {})
|
| 644 |
+
effect = info.get('efecto_neuropsicologico', f'Sustancia bioactiva ({compound}) en {food}')
|
| 645 |
candidates.append({"alimento": food, "compuesto": compound, "efecto": effect})
|
| 646 |
|
| 647 |
+
compuestos_unicos = sorted(set(c['compuesto'] for c in candidates)) if candidates else ["Compuestos bioactivos"]
|
| 648 |
+
alimentos_unicos = sorted(set(user_foods))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 649 |
compuestos_str = ", ".join(compuestos_unicos)
|
| 650 |
alimentos_str = ", ".join(alimentos_unicos).capitalize()
|
| 651 |
|
| 652 |
+
condicion = f"Hipersensibilidad o Intolerancia a {compuestos_str} en {alimentos_str}"
|
| 653 |
+
mecanismo = " | ".join(sorted(set(c['efecto'] for c in candidates))) if candidates else "Proceso de malabsorción o reactividad digestiva/metabólica."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 654 |
|
| 655 |
return {
|
| 656 |
"condicion_asociada": condicion,
|
|
|
|
| 660 |
"es_hipotesis_local": True
|
| 661 |
}
|
| 662 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 663 |
def find_best_foodb_matches(user_foods_es, _foodb_index, _foodb_lower_map, food_name_map, limit=3):
|
| 664 |
if not user_foods_es or not _foodb_index:
|
| 665 |
return []
|
|
|
|
| 696 |
return unique[:limit]
|
| 697 |
|
| 698 |
# ==============================================================================
|
| 699 |
+
# GENERADORES DE INFORMES
|
| 700 |
# ==============================================================================
|
| 701 |
|
| 702 |
def generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map):
|
|
|
|
| 714 |
|
| 715 |
compuestos_str = ", ".join(sorted(list(compuestos_detectados))) if compuestos_detectados else match.get('compuesto_alimento', 'compuestos bioactivos')
|
| 716 |
|
| 717 |
+
causa = f"\n### 🧬 Causa e Identificación Molecular\nLos síntomas descritos (**{', '.join(user_symptoms)}**) tras el consumo de **{', '.join(user_foods)}** están asociados a la presencia de **{compuestos_str}**.\n"
|
| 718 |
|
| 719 |
detalles_compuestos = []
|
| 720 |
for comp in compuestos_detectados:
|
|
|
|
| 722 |
detalles_compuestos.append(f"- **{comp.capitalize()}**: {neuro_map[comp]['efecto_neuropsicologico']}")
|
| 723 |
|
| 724 |
if detalles_compuestos:
|
| 725 |
+
causa += "\n**Mecanismo bioquímico:**\n" + "\n".join(detalles_compuestos) + "\n"
|
|
|
|
|
|
|
| 726 |
|
| 727 |
+
evitar = f"\n### 🛑 Alimentos a Moderar o Evitar\n- **Alimentos evaluados:** {', '.join(user_foods).capitalize()}\n- Productos ricos en **{compuestos_str}**.\n"
|
| 728 |
|
| 729 |
+
alternativas = "\n### 💡 Sugerencias\n- Llevar un diario de síntomas registrando alimentos y horas de consumo.\n- Reducir la ingesta del alimento para evaluar remisión de síntomas.\n"
|
| 730 |
|
| 731 |
consejo_texto = None
|
| 732 |
for comp in compuestos_detectados:
|
|
|
|
| 743 |
return saludo + hipotesis + causa + evitar + alternativas + consejo + TEXTO_BANDERAS_ROJAS
|
| 744 |
|
| 745 |
def generate_detailed_analysis(query, match, entities=None, food_compound_map=None, neuro_map=None):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
gemini_client = get_gemini_client()
|
| 747 |
|
| 748 |
if not gemini_client:
|
| 749 |
+
return generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map)
|
|
|
|
|
|
|
| 750 |
|
| 751 |
compound_context = ""
|
| 752 |
if entities and food_compound_map and neuro_map:
|
|
|
|
| 757 |
if compound in neuro_map:
|
| 758 |
lines.append(f"- {food.capitalize()} contiene {compound}: {neuro_map[compound]['efecto_neuropsicologico']}")
|
| 759 |
if lines:
|
| 760 |
+
compound_context = "Compuestos detectados:\n" + "\n".join(lines)
|
| 761 |
|
| 762 |
prompt = f"""
|
| 763 |
+
Rol: Nutricionista Funcional.
|
| 764 |
+
Caso: {query}
|
| 765 |
Hipótesis clínica: {match.get('condicion_asociada')}
|
| 766 |
+
Mecanismo: {match.get('mecanismo_fisiologico')}
|
|
|
|
| 767 |
{compound_context}
|
| 768 |
|
| 769 |
+
Escribe un análisis estructurado en Markdown (saludo, hipótesis, moléculas responsables, alimentos a evitar, alternativas y consejo).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 770 |
NO incluyas descargos de responsabilidad.
|
| 771 |
"""
|
| 772 |
try:
|
| 773 |
+
raw_text, used_model = generate_content_with_strict_timeout(gemini_client, prompt, timeout_seconds=3.5)
|
| 774 |
+
return f"🤖 **Análisis Redactado por IA Gemini ({used_model}):**\n\n" + raw_text + TEXTO_BANDERAS_ROJAS
|
|
|
|
|
|
|
| 775 |
except Exception as e:
|
| 776 |
+
logger.warning(f" Gemini no respondió en < 3.5s ({e}). Se activa la respuesta en modo local.")
|
| 777 |
+
return generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map)
|
|
|
|
|
|
|
| 778 |
|
| 779 |
def generate_neuro_report_text(entities, food_map, neuro_map):
|
| 780 |
report_lines = ["\n### 🧠 Efectos Neuropsicológicos Posibles"]
|
|
|
|
| 786 |
comps = get_compounds_for_food(food, food_map)
|
| 787 |
relevant_compounds.update(comps)
|
| 788 |
|
| 789 |
+
found = False
|
| 790 |
if relevant_compounds:
|
| 791 |
for compound in sorted(list(relevant_compounds)):
|
| 792 |
if compound in neuro_map:
|
| 793 |
+
found = True
|
| 794 |
report_lines.append(f"**{compound.capitalize()}**: _{neuro_map[compound]['efecto_neuropsicologico']}_")
|
| 795 |
|
| 796 |
+
if not found:
|
| 797 |
+
report_lines.append("No se detectaron efectos neuropsicológicos específicos directos.")
|
| 798 |
|
| 799 |
return "\n".join(report_lines)
|
| 800 |
|
|
|
|
| 802 |
report_lines = ["\n### 🔬 Análisis Molecular y Efectos Bioactivos"]
|
| 803 |
user_foods = entities.get("alimentos", [])
|
| 804 |
if not user_foods:
|
| 805 |
+
return "No hay alimentos seleccionados."
|
| 806 |
|
| 807 |
found_any = False
|
| 808 |
|
|
|
|
| 824 |
if not compounds:
|
| 825 |
continue
|
| 826 |
found_any = True
|
| 827 |
+
report_lines.append(f"\n**{food_key.capitalize()}** (Efectos en FooDB):")
|
| 828 |
|
| 829 |
for item in compounds[:5]:
|
| 830 |
if isinstance(item, dict):
|
| 831 |
comp_name = item.get('compound', 'N/A')
|
| 832 |
effects = item.get('effects', [])
|
| 833 |
|
| 834 |
+
eff_translated = [FOODB_EFFECTS_TRANSLATION.get(str(e).lower().strip(), str(e)) for e in effects[:4]]
|
| 835 |
+
eff_str = f" — *Efectos: {', '.join(eff_translated)}*" if eff_translated else ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 836 |
report_lines.append(f"- **{comp_name}**{eff_str}")
|
|
|
|
|
|
|
| 837 |
|
| 838 |
if not found_any:
|
| 839 |
report_lines.append("No se encontraron registros moleculares adicionales.")
|
|
|
|
| 851 |
doc_io.seek(0)
|
| 852 |
return doc_io.getvalue()
|
| 853 |
except Exception as e:
|
| 854 |
+
logger.error(f"Error generando informe Word: {e}")
|
| 855 |
return None
|
| 856 |
|
| 857 |
def create_relevance_chart(results):
|
|
|
|
| 865 |
).properties(title='Top Coincidencias')
|
| 866 |
|
| 867 |
# ==============================================================================
|
| 868 |
+
# INTERFAZ STREAMLIT
|
| 869 |
# ==============================================================================
|
| 870 |
|
| 871 |
if 'search_results' not in st.session_state: st.session_state.search_results = None
|
| 872 |
if 'user_query' not in st.session_state: st.session_state.user_query = ""
|
| 873 |
if 'entities' not in st.session_state: st.session_state.entities = None
|
| 874 |
+
if 'main_analysis' not in st.session_state: st.session_state.main_analysis = ""
|
| 875 |
if 'neuro_text_cache' not in st.session_state: st.session_state.neuro_text_cache = ""
|
| 876 |
if 'mol_text_cache' not in st.session_state: st.session_state.mol_text_cache = ""
|
| 877 |
if 'word_bytes_cache' not in st.session_state: st.session_state.word_bytes_cache = None
|
|
|
|
| 888 |
|
| 889 |
gemini_test = get_gemini_client()
|
| 890 |
if gemini_test:
|
| 891 |
+
st.caption("🟢 **Motor de IA Gemini Conectado:** Extracción y Redacción Activadas (Timeout: 3.5s)")
|
| 892 |
else:
|
| 893 |
st.caption("🟡 **Modo Local / Alta Disponibilidad:** Activo")
|
| 894 |
|
|
|
|
| 906 |
current_query = query_input.strip()
|
| 907 |
st.session_state.user_query = current_query
|
| 908 |
st.session_state.has_searched = True
|
|
|
|
|
|
|
|
|
|
| 909 |
|
| 910 |
+
with st.spinner("🔍 Analizando caso con corta-fuegos activado..."):
|
| 911 |
raw_entities = extract_entities_with_gemini(current_query)
|
| 912 |
reinforced = reinforce_entities_with_keywords(raw_entities, current_query, FOOD_TO_COMPOUND_MAP, MASTER_SYMPTOM_MAP)
|
| 913 |
final_symptoms = translate_symptoms_local(reinforced.get("sintomas", []), MASTER_SYMPTOM_MAP)
|
|
|
|
| 921 |
if local_hypothesis:
|
| 922 |
results = [{'entry': local_hypothesis, 'score': {'total': 50}, 'matched_keywords': []}]
|
| 923 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 924 |
st.session_state.search_results = results if results else []
|
| 925 |
|
|
|
|
| 926 |
if st.session_state.search_results:
|
| 927 |
best_match = st.session_state.search_results[0]['entry']
|
| 928 |
+
|
| 929 |
+
# Pre-generación en segundo plano (imposible de bloquear la UI)
|
| 930 |
st.session_state.main_analysis = generate_detailed_analysis(
|
| 931 |
st.session_state.user_query, best_match,
|
| 932 |
st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
|
| 933 |
)
|
| 934 |
+
|
|
|
|
| 935 |
st.session_state.neuro_text_cache = generate_neuro_report_text(
|
| 936 |
st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
|
| 937 |
)
|
| 938 |
+
|
|
|
|
| 939 |
st.session_state.mol_text_cache = generate_molecular_report_text(
|
| 940 |
best_match, st.session_state.entities, foodb_index, foodb_lower_map,
|
| 941 |
FOOD_NAME_TO_FOODB_KEY, COMPOUND_SYNONYM_MAP, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
|
| 942 |
)
|
| 943 |
|
|
|
|
| 944 |
st.session_state.word_bytes_cache = generate_word_report(f"REPORTE DE SALUD\n\n{st.session_state.main_analysis}")
|
| 945 |
|
| 946 |
else:
|
| 947 |
st.warning("⚠️ Por favor describe tus síntomas o alimentos consumidos antes de analizar.")
|
| 948 |
|
| 949 |
+
# --- MOSTRAR RESULTADOS ---
|
| 950 |
if st.session_state.has_searched:
|
| 951 |
results = st.session_state.search_results
|
| 952 |
|
|
|
|
| 962 |
st.altair_chart(create_relevance_chart(results), use_container_width=True)
|
| 963 |
|
| 964 |
with tab_neuro:
|
| 965 |
+
st.markdown(st.session_state.get('neuro_text_cache', 'No hay datos neuropsicológicos.'))
|
| 966 |
|
| 967 |
with tab_mol:
|
| 968 |
+
st.markdown(st.session_state.get('mol_text_cache', 'No hay datos moleculares.'))
|
| 969 |
|
| 970 |
word_bytes = st.session_state.get('word_bytes_cache')
|
| 971 |
if word_bytes:
|
|
|
|
| 978 |
)
|
| 979 |
else:
|
| 980 |
st.warning("⚠️ No pudimos identificar una causa clara para este caso específico.")
|
| 981 |
+
st.info("Intenta especificar el alimento y el síntoma. Ej: 'Siento [SÍNTOMA] cuando como [ALIMENTO]'.")
|