Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -37,7 +37,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 GEMINI CON
|
| 41 |
def get_gemini_client():
|
| 42 |
api_key = os.getenv("GEMINI_API_KEY")
|
| 43 |
|
|
@@ -60,13 +60,31 @@ def get_gemini_client():
|
|
| 60 |
try:
|
| 61 |
return genai.Client(
|
| 62 |
api_key=api_key,
|
| 63 |
-
http_options=types.HttpOptions(timeout=
|
| 64 |
)
|
| 65 |
except Exception as e:
|
| 66 |
logger.warning(f"❌ Error instanciando cliente Gemini: {e}")
|
| 67 |
return None
|
| 68 |
|
| 69 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
@st.cache_resource
|
| 71 |
def load_data():
|
| 72 |
try:
|
|
@@ -95,6 +113,7 @@ def get_foodb_lower_map(_foodb_index):
|
|
| 95 |
|
| 96 |
foodb_lower_map = get_foodb_lower_map(foodb_index)
|
| 97 |
|
|
|
|
| 98 |
@st.cache_resource
|
| 99 |
def get_global_learned_cache():
|
| 100 |
return {}
|
|
@@ -220,6 +239,7 @@ FOOD_TO_COMPOUND_MAP = {
|
|
| 220 |
"carne de res": ["histamina", "alfa-gal", "purinas"],
|
| 221 |
"res": ["histamina", "alfa-gal", "purinas"],
|
| 222 |
"café": ["cafeína", "salicilatos", "oxalatos"],
|
|
|
|
| 223 |
"té": ["cafeína", "taninos", "oxalatos", "salicilatos"],
|
| 224 |
"té negro": ["cafeína", "taninos", "oxalatos", "salicilatos"],
|
| 225 |
"té verde": ["cafeína", "taninos", "oxalatos", "salicilatos"],
|
|
@@ -261,7 +281,7 @@ FOOD_NAME_TO_FOODB_KEY = {
|
|
| 261 |
"vino": ["wine"], "vino tinto": ["wine", "red wine"], "cerveza": ["beer"], "chocolate": ["chocolate", "cocoa"],
|
| 262 |
"tomate": ["tomato"], "aguacate": ["avocado"], "plátano": ["banana", "plantain"], "banano": ["banana"],
|
| 263 |
"naranja": ["orange"], "jugo de naranja": ["orange juice", "orange"], "pan": ["bread", "wheat"],
|
| 264 |
-
"trigo": ["wheat"], "café": ["coffee"], "té": ["tea"], "manzana": ["apple"], "pera": ["pear"],
|
| 265 |
"fresa": ["strawberry"], "uva": ["grape"], "piña": ["pineapple"], "papaya": ["papaya"],
|
| 266 |
"mango": ["mango"], "limón": ["lemon", "lime"], "espinaca": ["spinach"], "cebolla": ["onion"],
|
| 267 |
"ajo": ["garlic"], "papa": ["potato"], "patata": ["potato"], "frijol": ["bean", "beans"],
|
|
@@ -269,7 +289,7 @@ FOOD_NAME_TO_FOODB_KEY = {
|
|
| 269 |
}
|
| 270 |
|
| 271 |
STRICT_COMPOUND_TRIGGERS = {
|
| 272 |
-
"cafeína": ["cafeína", "caffeine", "café", "coffee", "espresso", "energizante", "guaraná"],
|
| 273 |
"teobromina": ["teobromina", "theobromine", "cacao", "chocolate"],
|
| 274 |
"solanina": ["solanina", "solanine", "solanáceas", "nightshades", "tomate", "berenjena", "patata", "papa"],
|
| 275 |
"capsaicina": ["capsaicina", "capsaicin", "chile", "ají", "picante", "pimiento"],
|
|
@@ -445,7 +465,6 @@ COMPOUND_SYNONYM_MAP = {
|
|
| 445 |
"proteínas": ["protein", "amino acid"]
|
| 446 |
}
|
| 447 |
|
| 448 |
-
# MAPA COMPLETO DE SÍNTOMAS (NUNCA DEJA VACÍA LA EXTRACCIÓN LOCAL)
|
| 449 |
MASTER_SYMPTOM_MAP = {
|
| 450 |
"dolor abdominal": {
|
| 451 |
"frases_es": ["dolor de estómago", "dolor de panza", "dolor abdominal", "retortijones", "cólicos", "calambres abdominales", "gastritis", "acidez", "reflujo", "pesadez"],
|
|
@@ -504,7 +523,6 @@ MASTER_SYMPTOM_MAP = {
|
|
| 504 |
def sanitize_text(text):
|
| 505 |
if not text:
|
| 506 |
return ""
|
| 507 |
-
# Normaliza puntuación y colapsa múltiples espacios a un solo espacio simple
|
| 508 |
text_clean = re.sub(r'[.,;()¿?¡!:"\'\-]', ' ', text).lower()
|
| 509 |
text_clean = re.sub(r'\s+', ' ', text_clean).strip()
|
| 510 |
return text_clean
|
|
@@ -543,7 +561,7 @@ def get_compounds_for_food(food_name, food_map):
|
|
| 543 |
if norm in food_map:
|
| 544 |
return food_map[norm]
|
| 545 |
|
| 546 |
-
# 3. Corrección difusa de erratas de tipeo
|
| 547 |
fuzzy_matches = difflib.get_close_matches(food_clean, list(food_map.keys()), n=1, cutoff=0.6)
|
| 548 |
if fuzzy_matches:
|
| 549 |
logger.info(f"💡 Errata corregida automáticamente: '{food_clean}' -> '{fuzzy_matches[0]}'")
|
|
@@ -558,7 +576,7 @@ def get_compounds_for_food(food_name, food_map):
|
|
| 558 |
if matched_compounds:
|
| 559 |
return list(matched_compounds)
|
| 560 |
|
| 561 |
-
# 5. Inferencia difusa por palabras individuales
|
| 562 |
inferred = set()
|
| 563 |
words = food_clean.split()
|
| 564 |
for word in words:
|
|
@@ -570,7 +588,7 @@ def get_compounds_for_food(food_name, food_map):
|
|
| 570 |
if inferred:
|
| 571 |
return list(inferred)
|
| 572 |
|
| 573 |
-
# 6. INFERENCIA UNIVERSAL POR FAMILIA BIOLÓGICA
|
| 574 |
if any(w in food_clean for w in ["carne", "cerdo", "res", "pollo", "pavo", "cordero", "pato", "chicharrón", "tocino"]):
|
| 575 |
inferred.update(["histamina", "purinas", "proteínas"])
|
| 576 |
elif any(w in food_clean for w in ["pescado", "atún", "marisco", "camarón", "sardina", "langostino", "pulpo"]):
|
|
@@ -681,9 +699,9 @@ def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_m
|
|
| 681 |
current_symptoms.add(main_symptom)
|
| 682 |
break
|
| 683 |
|
| 684 |
-
# EXTRAER SÍNTOMAS ADICIONALES POR PALABRAS CLAVE DIRECTAS
|
| 685 |
if not current_symptoms:
|
| 686 |
-
if any(w in query_sanitized for w in ["diarrea", "diarreas", "soltura", "líquida"]):
|
| 687 |
current_symptoms.add("diarrea")
|
| 688 |
elif any(w in query_sanitized for w in ["hinchazón", "hinchazon", "gases", "inflamado"]):
|
| 689 |
current_symptoms.add("hinchazón")
|
|
@@ -767,7 +785,7 @@ def build_local_hypothesis(entities, food_compound_map, neuro_map):
|
|
| 767 |
es_psicologico = any(k in symptom_text for k in ["ansiedad", "angustia", "nerviosismo", "depresión", "depresion", "irritabilidad", "desrealización", "desrealizacion", "agitación", "agitacion"])
|
| 768 |
es_insomnio = any(k in symptom_text for k in ["insomnio", "no puedo dormir", "sueño", "desvelado", "desvelo", "despierto", "dormir"])
|
| 769 |
es_gastrointestinal = any(k in symptom_text for k in ["diarrea", "diarreas", "hinchazón", "hinchazon", "gases", "dolor abdominal", "retortijón", "retortijon", "cólico", "colico", "inflamado", "inflama", "pesadez", "estómago", "estomago", "náuseas", "nausea", "acidez", "reflujo"])
|
| 770 |
-
es_dermatologico = any(k in symptom_text for k in ["piel", "rasca", "picazón", "picazon", "manchas", "urticaria", "erupción", "erupcion", "alergia", "ronchas", "granitos"
|
| 771 |
es_respiratorio = any(k in symptom_text for k in ["asma", "tos", "respirar", "pecho", "sibilancia", "ahogo", "falta de aire", "congestión nasal", "congestion nasal", "estornudo", "rinitis"])
|
| 772 |
es_cardiovascular = any(k in symptom_text for k in ["palpitaciones", "taquicardia", "ritmo cardíaco", "presión", "presion", "rubor", "sofoco", "frío"])
|
| 773 |
es_articular = any(k in symptom_text for k in ["articulación", "articulacion", "rodilla", "coyuntura", "rigidez", "calambre", "gota", "espalda", "músculo", "musculo"])
|
|
|
|
| 37 |
**Si tus síntomas son severos, busca ayuda médica urgente.**
|
| 38 |
"""
|
| 39 |
|
| 40 |
+
# --- CONFIGURACIÓN DE CLIENTE GEMINI CON GEMINI_API_KEY ---
|
| 41 |
def get_gemini_client():
|
| 42 |
api_key = os.getenv("GEMINI_API_KEY")
|
| 43 |
|
|
|
|
| 60 |
try:
|
| 61 |
return genai.Client(
|
| 62 |
api_key=api_key,
|
| 63 |
+
http_options=types.HttpOptions(timeout=15.0) # 15 segundos
|
| 64 |
)
|
| 65 |
except Exception as e:
|
| 66 |
logger.warning(f"❌ Error instanciando cliente Gemini: {e}")
|
| 67 |
return None
|
| 68 |
|
| 69 |
+
# --- CASCADA DE MODELOS DE GEMINI CON FALLBACK ---
|
| 70 |
+
def generate_content_with_fallback(gemini_client, prompt):
|
| 71 |
+
"""Prueba los modelos disponibles en la cuenta de Google AI Studio en cascada."""
|
| 72 |
+
models_to_try = ['gemini-2.5-flash-lite', 'gemini-2.5-flash', 'gemini-1.5-flash']
|
| 73 |
+
for model_name in models_to_try:
|
| 74 |
+
try:
|
| 75 |
+
response = gemini_client.models.generate_content(
|
| 76 |
+
model=model_name,
|
| 77 |
+
contents=prompt
|
| 78 |
+
)
|
| 79 |
+
if response and response.text:
|
| 80 |
+
logger.info(f"✅ Respuesta exitosa generada por el modelo: {model_name}")
|
| 81 |
+
return response.text
|
| 82 |
+
except Exception as e:
|
| 83 |
+
logger.warning(f"⚠️ Modelo {model_name} no disponible ({e}), probando siguiente modelo...")
|
| 84 |
+
continue
|
| 85 |
+
raise RuntimeError("Todos los modelos de Gemini fallaron o no están disponibles.")
|
| 86 |
+
|
| 87 |
+
# --- CARGA ULTRA-RÁPIDA DE DATOS Y FOODB (CERO COPIA EN MEMORIA) ---
|
| 88 |
@st.cache_resource
|
| 89 |
def load_data():
|
| 90 |
try:
|
|
|
|
| 113 |
|
| 114 |
foodb_lower_map = get_foodb_lower_map(foodb_index)
|
| 115 |
|
| 116 |
+
# --- CACHÉ GLOBAL DE APRENDIZAJE EN RAM ---
|
| 117 |
@st.cache_resource
|
| 118 |
def get_global_learned_cache():
|
| 119 |
return {}
|
|
|
|
| 239 |
"carne de res": ["histamina", "alfa-gal", "purinas"],
|
| 240 |
"res": ["histamina", "alfa-gal", "purinas"],
|
| 241 |
"café": ["cafeína", "salicilatos", "oxalatos"],
|
| 242 |
+
"cafe": ["cafeína", "salicilatos", "oxalatos"],
|
| 243 |
"té": ["cafeína", "taninos", "oxalatos", "salicilatos"],
|
| 244 |
"té negro": ["cafeína", "taninos", "oxalatos", "salicilatos"],
|
| 245 |
"té verde": ["cafeína", "taninos", "oxalatos", "salicilatos"],
|
|
|
|
| 281 |
"vino": ["wine"], "vino tinto": ["wine", "red wine"], "cerveza": ["beer"], "chocolate": ["chocolate", "cocoa"],
|
| 282 |
"tomate": ["tomato"], "aguacate": ["avocado"], "plátano": ["banana", "plantain"], "banano": ["banana"],
|
| 283 |
"naranja": ["orange"], "jugo de naranja": ["orange juice", "orange"], "pan": ["bread", "wheat"],
|
| 284 |
+
"trigo": ["wheat"], "café": ["coffee"], "cafe": ["coffee"], "té": ["tea"], "manzana": ["apple"], "pera": ["pear"],
|
| 285 |
"fresa": ["strawberry"], "uva": ["grape"], "piña": ["pineapple"], "papaya": ["papaya"],
|
| 286 |
"mango": ["mango"], "limón": ["lemon", "lime"], "espinaca": ["spinach"], "cebolla": ["onion"],
|
| 287 |
"ajo": ["garlic"], "papa": ["potato"], "patata": ["potato"], "frijol": ["bean", "beans"],
|
|
|
|
| 289 |
}
|
| 290 |
|
| 291 |
STRICT_COMPOUND_TRIGGERS = {
|
| 292 |
+
"cafeína": ["cafeína", "caffeine", "café", "cafe", "coffee", "espresso", "energizante", "guaraná"],
|
| 293 |
"teobromina": ["teobromina", "theobromine", "cacao", "chocolate"],
|
| 294 |
"solanina": ["solanina", "solanine", "solanáceas", "nightshades", "tomate", "berenjena", "patata", "papa"],
|
| 295 |
"capsaicina": ["capsaicina", "capsaicin", "chile", "ají", "picante", "pimiento"],
|
|
|
|
| 465 |
"proteínas": ["protein", "amino acid"]
|
| 466 |
}
|
| 467 |
|
|
|
|
| 468 |
MASTER_SYMPTOM_MAP = {
|
| 469 |
"dolor abdominal": {
|
| 470 |
"frases_es": ["dolor de estómago", "dolor de panza", "dolor abdominal", "retortijones", "cólicos", "calambres abdominales", "gastritis", "acidez", "reflujo", "pesadez"],
|
|
|
|
| 523 |
def sanitize_text(text):
|
| 524 |
if not text:
|
| 525 |
return ""
|
|
|
|
| 526 |
text_clean = re.sub(r'[.,;()¿?¡!:"\'\-]', ' ', text).lower()
|
| 527 |
text_clean = re.sub(r'\s+', ' ', text_clean).strip()
|
| 528 |
return text_clean
|
|
|
|
| 561 |
if norm in food_map:
|
| 562 |
return food_map[norm]
|
| 563 |
|
| 564 |
+
# 3. Corrección difusa de erratas de tipeo
|
| 565 |
fuzzy_matches = difflib.get_close_matches(food_clean, list(food_map.keys()), n=1, cutoff=0.6)
|
| 566 |
if fuzzy_matches:
|
| 567 |
logger.info(f"💡 Errata corregida automáticamente: '{food_clean}' -> '{fuzzy_matches[0]}'")
|
|
|
|
| 576 |
if matched_compounds:
|
| 577 |
return list(matched_compounds)
|
| 578 |
|
| 579 |
+
# 5. Inferencia difusa por palabras individuales
|
| 580 |
inferred = set()
|
| 581 |
words = food_clean.split()
|
| 582 |
for word in words:
|
|
|
|
| 588 |
if inferred:
|
| 589 |
return list(inferred)
|
| 590 |
|
| 591 |
+
# 6. INFERENCIA UNIVERSAL POR FAMILIA BIOLÓGICA
|
| 592 |
if any(w in food_clean for w in ["carne", "cerdo", "res", "pollo", "pavo", "cordero", "pato", "chicharrón", "tocino"]):
|
| 593 |
inferred.update(["histamina", "purinas", "proteínas"])
|
| 594 |
elif any(w in food_clean for w in ["pescado", "atún", "marisco", "camarón", "sardina", "langostino", "pulpo"]):
|
|
|
|
| 699 |
current_symptoms.add(main_symptom)
|
| 700 |
break
|
| 701 |
|
| 702 |
+
# EXTRAER SÍNTOMAS ADICIONALES POR PALABRAS CLAVE DIRECTAS EN CASO DE FALLO
|
| 703 |
if not current_symptoms:
|
| 704 |
+
if any(w in query_sanitized for w in ["diarrea", "diarreas", "soltura", "líquida", "suelto"]):
|
| 705 |
current_symptoms.add("diarrea")
|
| 706 |
elif any(w in query_sanitized for w in ["hinchazón", "hinchazon", "gases", "inflamado"]):
|
| 707 |
current_symptoms.add("hinchazón")
|
|
|
|
| 785 |
es_psicologico = any(k in symptom_text for k in ["ansiedad", "angustia", "nerviosismo", "depresión", "depresion", "irritabilidad", "desrealización", "desrealizacion", "agitación", "agitacion"])
|
| 786 |
es_insomnio = any(k in symptom_text for k in ["insomnio", "no puedo dormir", "sueño", "desvelado", "desvelo", "despierto", "dormir"])
|
| 787 |
es_gastrointestinal = any(k in symptom_text for k in ["diarrea", "diarreas", "hinchazón", "hinchazon", "gases", "dolor abdominal", "retortijón", "retortijon", "cólico", "colico", "inflamado", "inflama", "pesadez", "estómago", "estomago", "náuseas", "nausea", "acidez", "reflujo"])
|
| 788 |
+
es_dermatologico = any(k in symptom_text for k in ["piel", "rasca", "picazón", "picazon", "manchas", "urticaria", "erupción", "erupcion", "alergia", "ronchas", "granitos"])
|
| 789 |
es_respiratorio = any(k in symptom_text for k in ["asma", "tos", "respirar", "pecho", "sibilancia", "ahogo", "falta de aire", "congestión nasal", "congestion nasal", "estornudo", "rinitis"])
|
| 790 |
es_cardiovascular = any(k in symptom_text for k in ["palpitaciones", "taquicardia", "ritmo cardíaco", "presión", "presion", "rubor", "sofoco", "frío"])
|
| 791 |
es_articular = any(k in symptom_text for k in ["articulación", "articulacion", "rodilla", "coyuntura", "rigidez", "calambre", "gota", "espalda", "músculo", "musculo"])
|