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 2.5 Flash Lite + FooDB.ca Graph + Fast-Path Local Engine
|
| 5 |
# ==============================================================================
|
|
@@ -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
|
| 41 |
def get_gemini_client():
|
| 42 |
api_key = os.getenv("GEMINI_API_KEY")
|
| 43 |
|
|
@@ -66,7 +66,7 @@ def get_gemini_client():
|
|
| 66 |
logger.warning(f"❌ Error instanciando cliente Gemini: {e}")
|
| 67 |
return None
|
| 68 |
|
| 69 |
-
# --- CARGA ULTRA-RÁPIDA DE DATOS Y FOODB
|
| 70 |
@st.cache_resource
|
| 71 |
def load_data():
|
| 72 |
try:
|
|
@@ -95,7 +95,6 @@ def get_foodb_lower_map(_foodb_index):
|
|
| 95 |
|
| 96 |
foodb_lower_map = get_foodb_lower_map(foodb_index)
|
| 97 |
|
| 98 |
-
# --- CACHÉ GLOBAL DE APRENDIZAJE EN RAM ---
|
| 99 |
@st.cache_resource
|
| 100 |
def get_global_learned_cache():
|
| 101 |
return {}
|
|
@@ -446,7 +445,7 @@ COMPOUND_SYNONYM_MAP = {
|
|
| 446 |
"proteínas": ["protein", "amino acid"]
|
| 447 |
}
|
| 448 |
|
| 449 |
-
#
|
| 450 |
MASTER_SYMPTOM_MAP = {
|
| 451 |
"dolor abdominal": {
|
| 452 |
"frases_es": ["dolor de estómago", "dolor de panza", "dolor abdominal", "retortijones", "cólicos", "calambres abdominales", "gastritis", "acidez", "reflujo", "pesadez"],
|
|
@@ -456,6 +455,14 @@ MASTER_SYMPTOM_MAP = {
|
|
| 456 |
"frases_es": ["hinchazón abdominal", "vientre hinchado", "distensión abdominal", "hinchazón", "distensión", "estomago se infla", "inflamado", "inflado", "infla", "hincha", "gases", "flatulencia"],
|
| 457 |
"keywords_en": ["bloating", "distension", "flatulence", "swelling"]
|
| 458 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
"dolor de cabeza": {
|
| 460 |
"frases_es": ["dolor de cabeza", "migraña", "cefalea", "jaqueca", "me duele la cabeza", "duele la cabeza", "dolor de sien", "dolor en la cabeza", "pesadez de cabeza"],
|
| 461 |
"keywords_en": ["headache", "migraine"]
|
|
@@ -473,7 +480,7 @@ MASTER_SYMPTOM_MAP = {
|
|
| 473 |
"keywords_en": ["rash", "itching", "urticaria", "eczema"]
|
| 474 |
},
|
| 475 |
"reactividad respiratoria": {
|
| 476 |
-
"frases_es": ["asma", "tos", "respirar", "pecho", "sibilancia", "ahogo", "falta de aire", "congestión nasal", "
|
| 477 |
"keywords_en": ["asthma", "cough", "wheezing", "rhinitis"]
|
| 478 |
},
|
| 479 |
"respuesta cardiovascular": {
|
|
@@ -623,8 +630,8 @@ def extract_entities_with_gemini(query):
|
|
| 623 |
system_prompt = f"""
|
| 624 |
Rol: Extractor Médico JSON experto en nutrición y síntomas.
|
| 625 |
Tarea: Analiza el texto del usuario y extrae un JSON estricto con dos claves:
|
| 626 |
-
- "alimentos": lista de alimentos o bebidas mencionados en español, forma simple (ej: "jugo de naranja", "chontaduro", "bandeja paisa", "queso", "vino tinto").
|
| 627 |
-
- "sintomas": lista de síntomas mencionados en español, forma clínica simple (ej: "insomnio", "dolor de cabeza", "hinchazón").
|
| 628 |
|
| 629 |
Input: "{query}"
|
| 630 |
Output (JSON estricto):
|
|
@@ -659,7 +666,7 @@ def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_m
|
|
| 659 |
if match_jugo:
|
| 660 |
current_foods.add(match_jugo.group(0))
|
| 661 |
else:
|
| 662 |
-
match_tomo = re.search(r'\b(como|tomo|consumo|ingiero)\s+([a-zA-ZáéíóúñÁÉÍÓÚÑ\s]{3,20})\b', query_sanitized)
|
| 663 |
if match_tomo:
|
| 664 |
food_extracted = match_tomo.group(2).strip()
|
| 665 |
if food_extracted not in STOPWORDS_ES:
|
|
@@ -673,6 +680,18 @@ def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_m
|
|
| 673 |
if phrase in query_sanitized:
|
| 674 |
current_symptoms.add(main_symptom)
|
| 675 |
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 676 |
entities["sintomas"] = list(current_symptoms)
|
| 677 |
return entities
|
| 678 |
|
|
@@ -747,7 +766,7 @@ def build_local_hypothesis(entities, food_compound_map, neuro_map):
|
|
| 747 |
es_neurologico = any(k in symptom_text for k in ["dolor de cabeza", "migraña", "cefalea", "jaqueca", "dolor de sien", "mareo", "vértigo", "vertigo", "neblina mental", "cabeza"])
|
| 748 |
es_psicologico = any(k in symptom_text for k in ["ansiedad", "angustia", "nerviosismo", "depresión", "depresion", "irritabilidad", "desrealización", "desrealizacion", "agitación", "agitacion"])
|
| 749 |
es_insomnio = any(k in symptom_text for k in ["insomnio", "no puedo dormir", "sueño", "desvelado", "desvelo", "despierto", "dormir"])
|
| 750 |
-
es_gastrointestinal = any(k in symptom_text for k in ["
|
| 751 |
es_dermatologico = any(k in symptom_text for k in ["piel", "rasca", "picazón", "picazon", "manchas", "urticaria", "erupción", "erupcion", "alergia", "ronchas", "granitos", "eccema"])
|
| 752 |
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"])
|
| 753 |
es_cardiovascular = any(k in symptom_text for k in ["palpitaciones", "taquicardia", "ritmo cardíaco", "presión", "presion", "rubor", "sofoco", "frío"])
|
|
@@ -841,22 +860,14 @@ def generate_ai_hypothesis_on_the_fly(query, entities):
|
|
| 841 |
if not gemini_client:
|
| 842 |
return None
|
| 843 |
|
| 844 |
-
foods = entities.get('alimentos', [])
|
| 845 |
-
symptoms = entities.get('sintomas', [])
|
| 846 |
-
|
| 847 |
-
foods_text = ", ".join(foods) if foods else "alimentos mencionados en la consulta"
|
| 848 |
-
symptoms_text = ", ".join(symptoms) if symptoms else "síntomas expresados por el paciente"
|
| 849 |
-
|
| 850 |
prompt = (
|
| 851 |
"Rol: Médico Especialista en Nutrición Funcional e Inmunología de Alimentos.\n"
|
| 852 |
-
f'Consulta del paciente: "{query}"\n'
|
| 853 |
-
f"Alimentos clave: {foods_text}\n"
|
| 854 |
-
f"Síntomas clave: {symptoms_text}\n\n"
|
| 855 |
"Analiza la fisiología de este caso y genera un JSON estricto con las siguientes 4 claves:\n"
|
| 856 |
-
'1. "condicion_asociada": Título médico conciso de la hipersensibilidad o intolerancia.\n'
|
| 857 |
-
'2. "mecanismo_fisiologico": Explicación concisa
|
| 858 |
-
'3. "compuesto_alimento": Nombre de los alimentos y las moléculas responsables.\n'
|
| 859 |
-
|
| 860 |
"Devuelve ÚNICAMENTE el código JSON válido, sin bloques de código markdown ni texto adicional."
|
| 861 |
)
|
| 862 |
try:
|
|
@@ -1005,7 +1016,7 @@ def generate_detailed_analysis(query, match, entities=None, food_compound_map=No
|
|
| 1005 |
Tarea: Escribe un análisis en Markdown estructurado:
|
| 1006 |
1. Saludo breve y empático.
|
| 1007 |
2. Frase inicial clara con la hipótesis probable.
|
| 1008 |
-
3. Explicación de las moléculas responsables (ej: histamina, tiramina, sulfitos, fructosa, gluten, salicilatos, fenoles, escopoletina).
|
| 1009 |
4. Mecanismo fisiológico en términos sencillos.
|
| 1010 |
5. Alimentos a evitar o moderar.
|
| 1011 |
6. Alternativas o reemplazos sugeridos.
|
|
@@ -1017,7 +1028,7 @@ def generate_detailed_analysis(query, match, entities=None, food_compound_map=No
|
|
| 1017 |
model='gemini-2.5-flash-lite',
|
| 1018 |
contents=prompt
|
| 1019 |
)
|
| 1020 |
-
ai_report = response.text + TEXTO_BANDERAS_ROJAS
|
| 1021 |
global_learned_cache[cache_lookup_key] = ai_report
|
| 1022 |
return ai_report
|
| 1023 |
except Exception as e:
|
|
@@ -1160,7 +1171,7 @@ with st.form(key="search_form", clear_on_submit=False):
|
|
| 1160 |
query_input = st.text_area(
|
| 1161 |
"Tu Caso:",
|
| 1162 |
height=150,
|
| 1163 |
-
placeholder="Ej:
|
| 1164 |
key="input_query_text"
|
| 1165 |
)
|
| 1166 |
submitted = st.form_submit_button("Analizar Caso", type="primary")
|
|
@@ -1188,6 +1199,7 @@ 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:
|
|
|
|
| 1 |
# ==============================================================================
|
| 2 |
+
# EL DETECTIVE DE ALIMENTOS - EDICIÓN HÍBRIDA DE ALTA DISPONIBILIDAD
|
| 3 |
# Desarrollado por: Jairo Alexander & Diana Milena Soler Martinez
|
| 4 |
# Motor: Gemini 2.5 Flash Lite + FooDB.ca Graph + Fast-Path Local Engine
|
| 5 |
# ==============================================================================
|
|
|
|
| 37 |
**Si tus síntomas son severos, busca ayuda médica urgente.**
|
| 38 |
"""
|
| 39 |
|
| 40 |
+
# --- CONFIGURACIÓN GEMINI CON LA VARIABLE GEMINI_API_KEY ---
|
| 41 |
def get_gemini_client():
|
| 42 |
api_key = os.getenv("GEMINI_API_KEY")
|
| 43 |
|
|
|
|
| 66 |
logger.warning(f"❌ Error instanciando cliente Gemini: {e}")
|
| 67 |
return None
|
| 68 |
|
| 69 |
+
# --- CARGA ULTRA-RÁPIDA DE DATOS Y FOODB ---
|
| 70 |
@st.cache_resource
|
| 71 |
def load_data():
|
| 72 |
try:
|
|
|
|
| 95 |
|
| 96 |
foodb_lower_map = get_foodb_lower_map(foodb_index)
|
| 97 |
|
|
|
|
| 98 |
@st.cache_resource
|
| 99 |
def get_global_learned_cache():
|
| 100 |
return {}
|
|
|
|
| 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"],
|
|
|
|
| 455 |
"frases_es": ["hinchazón abdominal", "vientre hinchado", "distensión abdominal", "hinchazón", "distensión", "estomago se infla", "inflamado", "inflado", "infla", "hincha", "gases", "flatulencia"],
|
| 456 |
"keywords_en": ["bloating", "distension", "flatulence", "swelling"]
|
| 457 |
},
|
| 458 |
+
"diarrea": {
|
| 459 |
+
"frases_es": ["diarrea", "diarreas", "soltura", "estómago suelto", "deposiciones líquidas", "deposiciones liquidas", "diarrea acuosa"],
|
| 460 |
+
"keywords_en": ["diarrhea", "loose stools"]
|
| 461 |
+
},
|
| 462 |
+
"náuseas": {
|
| 463 |
+
"frases_es": ["náuseas", "nauseas", "vómito", "vomito", "ganas de vomitar", "mareo digestivo"],
|
| 464 |
+
"keywords_en": ["nausea", "vomiting"]
|
| 465 |
+
},
|
| 466 |
"dolor de cabeza": {
|
| 467 |
"frases_es": ["dolor de cabeza", "migraña", "cefalea", "jaqueca", "me duele la cabeza", "duele la cabeza", "dolor de sien", "dolor en la cabeza", "pesadez de cabeza"],
|
| 468 |
"keywords_en": ["headache", "migraine"]
|
|
|
|
| 480 |
"keywords_en": ["rash", "itching", "urticaria", "eczema"]
|
| 481 |
},
|
| 482 |
"reactividad respiratoria": {
|
| 483 |
+
"frases_es": ["asma", "tos", "respirar", "pecho", "sibilancia", "ahogo", "falta de aire", "congestión nasal", "estornudos", "rinitis"],
|
| 484 |
"keywords_en": ["asthma", "cough", "wheezing", "rhinitis"]
|
| 485 |
},
|
| 486 |
"respuesta cardiovascular": {
|
|
|
|
| 630 |
system_prompt = f"""
|
| 631 |
Rol: Extractor Médico JSON experto en nutrición y síntomas.
|
| 632 |
Tarea: Analiza el texto del usuario y extrae un JSON estricto con dos claves:
|
| 633 |
+
- "alimentos": lista de alimentos o bebidas mencionados en español, forma simple (ej: "jugo de naranja", "chontaduro", "bandeja paisa", "queso", "leche", "vino tinto").
|
| 634 |
+
- "sintomas": lista de síntomas mencionados en español, forma clínica simple (ej: "insomnio", "dolor de cabeza", "hinchazón", "diarrea").
|
| 635 |
|
| 636 |
Input: "{query}"
|
| 637 |
Output (JSON estricto):
|
|
|
|
| 666 |
if match_jugo:
|
| 667 |
current_foods.add(match_jugo.group(0))
|
| 668 |
else:
|
| 669 |
+
match_tomo = re.search(r'\b(como|tomo|consumo|ingiero|da)\s+([a-zA-ZáéíóúñÁÉÍÓÚÑ\s]{3,20})\b', query_sanitized)
|
| 670 |
if match_tomo:
|
| 671 |
food_extracted = match_tomo.group(2).strip()
|
| 672 |
if food_extracted not in STOPWORDS_ES:
|
|
|
|
| 680 |
if phrase in query_sanitized:
|
| 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")
|
| 690 |
+
elif any(w in query_sanitized for w in ["cabeza", "migraña", "cefalea"]):
|
| 691 |
+
current_symptoms.add("dolor de cabeza")
|
| 692 |
+
elif any(w in query_sanitized for w in ["dormir", "insomnio", "desvelo"]):
|
| 693 |
+
current_symptoms.add("insomnio")
|
| 694 |
+
|
| 695 |
entities["sintomas"] = list(current_symptoms)
|
| 696 |
return entities
|
| 697 |
|
|
|
|
| 766 |
es_neurologico = any(k in symptom_text for k in ["dolor de cabeza", "migraña", "cefalea", "jaqueca", "dolor de sien", "mareo", "vértigo", "vertigo", "neblina mental", "cabeza"])
|
| 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", "eccema"])
|
| 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"])
|
|
|
|
| 860 |
if not gemini_client:
|
| 861 |
return None
|
| 862 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 863 |
prompt = (
|
| 864 |
"Rol: Médico Especialista en Nutrición Funcional e Inmunología de Alimentos.\n"
|
| 865 |
+
f'Consulta directa del paciente: "{query}"\n\n'
|
|
|
|
|
|
|
| 866 |
"Analiza la fisiología de este caso y genera un JSON estricto con las siguientes 4 claves:\n"
|
| 867 |
+
'1. "condicion_asociada": Título médico conciso de la hipersensibilidad o intolerancia (ej: Intolerancia a la Lactosa y Malabsorción Digestiva por Leche).\n'
|
| 868 |
+
'2. "mecanismo_fisiologico": Explicación concisa de la deficiencia enzimática de lactasa e hiperfermentación colónica.\n'
|
| 869 |
+
'3. "compuesto_alimento": Nombre de los alimentos y las moléculas responsables (ej: Leche (Lactosa, Caseína)).\n'
|
| 870 |
+
'4. "sintomas_clave": Lista de los síntomas expresados por el paciente.\n\n'
|
| 871 |
"Devuelve ÚNICAMENTE el código JSON válido, sin bloques de código markdown ni texto adicional."
|
| 872 |
)
|
| 873 |
try:
|
|
|
|
| 1016 |
Tarea: Escribe un análisis en Markdown estructurado:
|
| 1017 |
1. Saludo breve y empático.
|
| 1018 |
2. Frase inicial clara con la hipótesis probable.
|
| 1019 |
+
3. Explicación de las moléculas responsables (ej: histamina, tiramina, sulfitos, fructosa, gluten, salicilatos, fenoles, escopoletina, lactosa, caseína).
|
| 1020 |
4. Mecanismo fisiológico en términos sencillos.
|
| 1021 |
5. Alimentos a evitar o moderar.
|
| 1022 |
6. Alternativas o reemplazos sugeridos.
|
|
|
|
| 1028 |
model='gemini-2.5-flash-lite',
|
| 1029 |
contents=prompt
|
| 1030 |
)
|
| 1031 |
+
ai_report = "🤖 **Análisis Redactado por IA Gemini 2.5 Flash Lite:**\n\n" + response.text + TEXTO_BANDERAS_ROJAS
|
| 1032 |
global_learned_cache[cache_lookup_key] = ai_report
|
| 1033 |
return ai_report
|
| 1034 |
except Exception as e:
|
|
|
|
| 1171 |
query_input = st.text_area(
|
| 1172 |
"Tu Caso:",
|
| 1173 |
height=150,
|
| 1174 |
+
placeholder="Ej: Me da diarrea si tomo leche.",
|
| 1175 |
key="input_query_text"
|
| 1176 |
)
|
| 1177 |
submitted = st.form_submit_button("Analizar Caso", type="primary")
|
|
|
|
| 1199 |
if local_hypothesis:
|
| 1200 |
results = [{'entry': local_hypothesis, 'score': {'total': 50}, 'matched_keywords': []}]
|
| 1201 |
|
| 1202 |
+
# RESCATE UNIVERSAL POR IA CON TEXTO CRUDO
|
| 1203 |
if not results:
|
| 1204 |
ai_hypothesis = generate_ai_hypothesis_on_the_fly(current_query, final_entities)
|
| 1205 |
if ai_hypothesis:
|