Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -664,39 +664,162 @@ def build_local_hypothesis(entities, food_compound_map, neuro_map):
|
|
| 664 |
"sintomas_clave": user_symptoms,
|
| 665 |
"es_hipotesis_local": True
|
| 666 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 667 |
|
| 668 |
def generate_ai_hypothesis_on_the_fly(query, entities):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 669 |
gemini_client = get_gemini_client()
|
| 670 |
if not gemini_client: return None
|
| 671 |
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
"
|
| 682 |
-
"
|
| 683 |
-
"
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
|
|
|
|
|
|
|
|
|
| 687 |
try:
|
| 688 |
response = gemini_client.models.generate_content(
|
| 689 |
model='gemini-2.5-flash-lite',
|
| 690 |
contents=prompt
|
| 691 |
)
|
| 692 |
-
|
| 693 |
-
if "```json" in
|
| 694 |
-
elif "```" in
|
| 695 |
-
data = json.loads(
|
| 696 |
data["es_hipotesis_local"] = True
|
| 697 |
return data
|
| 698 |
except Exception as e:
|
| 699 |
-
logger.error(f"Error
|
| 700 |
return None
|
| 701 |
|
| 702 |
def find_best_foodb_matches(user_foods_es, _foodb_index, _foodb_lower_map, food_name_map, limit=3):
|
|
|
|
| 664 |
"sintomas_clave": user_symptoms,
|
| 665 |
"es_hipotesis_local": True
|
| 666 |
}
|
| 667 |
+
def get_compounds_for_food(food_name, food_map):
|
| 668 |
+
"""
|
| 669 |
+
Buscador e Inferidor Universal de Compuestos:
|
| 670 |
+
1. Coincidencia exacta.
|
| 671 |
+
2. Limpieza de plurales.
|
| 672 |
+
3. Búsqueda por subcadena.
|
| 673 |
+
4. INFERENCIA UNIVERSAL DE CLASES: Si el alimento es totalmente nuevo,
|
| 674 |
+
deduce sus compuestos por su familia biológica (carnes, mariscos, frutas, lácteos, etc.).
|
| 675 |
+
"""
|
| 676 |
+
if not food_name: return []
|
| 677 |
+
food_clean = food_name.lower().strip()
|
| 678 |
+
|
| 679 |
+
# 1. Coincidencia exacta
|
| 680 |
+
if food_clean in food_map:
|
| 681 |
+
return food_map[food_clean]
|
| 682 |
+
|
| 683 |
+
# 2. Limpieza de Plurales
|
| 684 |
+
for suffix in ["es", "s"]:
|
| 685 |
+
if food_clean.endswith(suffix):
|
| 686 |
+
norm = food_clean[:-len(suffix)]
|
| 687 |
+
if norm in food_map:
|
| 688 |
+
return food_map[norm]
|
| 689 |
+
|
| 690 |
+
# 3. Búsqueda por subcadena en el diccionario
|
| 691 |
+
matched_compounds = set()
|
| 692 |
+
for key, compounds in food_map.items():
|
| 693 |
+
if key in food_clean or food_clean in key:
|
| 694 |
+
matched_compounds.update(compounds)
|
| 695 |
+
if matched_compounds:
|
| 696 |
+
return list(matched_compounds)
|
| 697 |
+
|
| 698 |
+
# 4. INFERENCIA UNIVERSAL DE CLASES PARA ALIMENTOS NO REGISTRADOS
|
| 699 |
+
inferred = set()
|
| 700 |
+
if any(w in food_clean for w in ["carne", "cerdo", "res", "pollo", "pavo", "cordero", "pato", "chicharrón", "tocino"]):
|
| 701 |
+
inferred.update(["histamina", "purinas", "proteínas"])
|
| 702 |
+
elif any(w in food_clean for w in ["pescado", "atún", "marisco", "camarón", "sardina", "langostino", "pulpo"]):
|
| 703 |
+
inferred.update(["histamina", "purinas", "albúmina"])
|
| 704 |
+
elif any(w in food_clean for w in ["jugo", "zumo", "batido", "fruta", "licuado"]):
|
| 705 |
+
inferred.update(["fructosa", "salicilatos", "histamina", "fenoles"])
|
| 706 |
+
elif any(w in food_clean for w in ["queso", "leche", "crema", "yogur", "mantequilla"]):
|
| 707 |
+
inferred.update(["caseína", "lactosa", "histamina", "tiramina"])
|
| 708 |
+
elif any(w in food_clean for w in ["pan", "galleta", "pasta", "harina", "bizcocho", "tortilla"]):
|
| 709 |
+
inferred.update(["gluten", "fodmaps"])
|
| 710 |
+
elif any(w in food_clean for w in ["frijol", "lenteja", "garbanzo", "legumbre", "soja"]):
|
| 711 |
+
inferred.update(["lectinas", "fitatos", "fodmaps", "saponinas"])
|
| 712 |
+
elif any(w in food_clean for w in ["salsa", "sazonador", "aderezo", "caldo", "adobo"]):
|
| 713 |
+
inferred.update(["glutamato", "histamina", "sulfitos"])
|
| 714 |
+
|
| 715 |
+
return list(inferred)
|
| 716 |
+
|
| 717 |
+
def build_local_hypothesis(entities, food_compound_map, neuro_map):
|
| 718 |
+
"""
|
| 719 |
+
Generador Local Universal de Hipótesis:
|
| 720 |
+
Construye diagnósticos para dermatología (piel/alergias), neurología,
|
| 721 |
+
gastroenterología, insomnio y síntomas generales.
|
| 722 |
+
"""
|
| 723 |
+
user_foods = entities.get("alimentos", [])
|
| 724 |
+
user_symptoms = entities.get("sintomas", [])
|
| 725 |
+
if not user_foods or not user_symptoms:
|
| 726 |
+
return None
|
| 727 |
+
|
| 728 |
+
symptom_text = " ".join(user_symptoms).lower()
|
| 729 |
+
|
| 730 |
+
# Categorización clínica del síntoma predominante
|
| 731 |
+
es_dermatologico = any(k in symptom_text for k in ["piel", "rasca", "picazón", "picazon", "manchas", "urticaria", "erupción", "alergia", "ronchas", "granitos"])
|
| 732 |
+
es_cefalea = any(k in symptom_text for k in ["dolor de cabeza", "migraña", "cefalea", "jaqueca"])
|
| 733 |
+
es_insomnio = any(k in symptom_text for k in ["insomnio", "no puedo dormir", "sueño", "desvelado"])
|
| 734 |
+
es_gastro = any(k in symptom_text for k in ["hinchazón", "hinchazon", "gases", "diarrea", "dolor abdominal", "retortijón", "cólico"])
|
| 735 |
+
|
| 736 |
+
candidates = []
|
| 737 |
+
for food in user_foods:
|
| 738 |
+
comps = get_compounds_for_food(food, food_compound_map)
|
| 739 |
+
for compound in comps:
|
| 740 |
+
info = neuro_map.get(compound, {})
|
| 741 |
+
effect = info.get('efecto_neuropsicologico', f'Sustancia bioactiva ({compound}) presente en {food}')
|
| 742 |
+
candidates.append({"alimento": food, "compuesto": compound, "efecto": effect})
|
| 743 |
+
|
| 744 |
+
if not candidates:
|
| 745 |
+
alimentos_str = ", ".join(user_foods).capitalize()
|
| 746 |
+
sintomas_str = ", ".join(user_symptoms)
|
| 747 |
+
return {
|
| 748 |
+
"condicion_asociada": f"Sensibilidad o Intolerancia Alimentaria a {alimentos_str}",
|
| 749 |
+
"mecanismo_fisiologico": f"Respuesta inmunológica o enzimática a los componentes de {alimentos_str}, manifestándose con {sintomas_str}.",
|
| 750 |
+
"compuesto_alimento": f"{alimentos_str} (Compuestos bioactivos)",
|
| 751 |
+
"sintomas_clave": user_symptoms,
|
| 752 |
+
"es_hipotesis_local": True
|
| 753 |
+
}
|
| 754 |
+
|
| 755 |
+
compuestos_unicos = sorted(set(c['compuesto'] for c in candidates))
|
| 756 |
+
alimentos_unicos = sorted(set(c['alimento'] for c in candidates))
|
| 757 |
+
compuestos_str = ", ".join(compuestos_unicos)
|
| 758 |
+
alimentos_str = ", ".join(alimentos_unicos).capitalize()
|
| 759 |
+
|
| 760 |
+
# Construcción sintáctica dinámica por sistema orgánico
|
| 761 |
+
if es_dermatologico:
|
| 762 |
+
condicion = f"Reacción Histaminérgica / Dermatológica por {compuestos_str} en {alimentos_str}"
|
| 763 |
+
elif es_cefalea:
|
| 764 |
+
condicion = f"Cefalea o Migraña por Aminas Vasoactivas / Compuestos ({compuestos_str}) en {alimentos_str}"
|
| 765 |
+
elif es_insomnio:
|
| 766 |
+
condicion = f"Alteración del Sueño e Insomnio por respuesta a {compuestos_str} en {alimentos_str}"
|
| 767 |
+
elif es_gastro:
|
| 768 |
+
condicion = f"Distensión Abdominal e Intolerancia Digestiva a {compuestos_str} en {alimentos_str}"
|
| 769 |
+
else:
|
| 770 |
+
condicion = f"Hipersensibilidad o Intolerancia a {compuestos_str} en {alimentos_str}"
|
| 771 |
+
|
| 772 |
+
mecanismo = " | ".join(sorted(set(c['efecto'] for c in candidates)))
|
| 773 |
+
|
| 774 |
+
return {
|
| 775 |
+
"condicion_asociada": condicion,
|
| 776 |
+
"mecanismo_fisiologico": mecanismo,
|
| 777 |
+
"compuesto_alimento": f"{alimentos_str} ({compuestos_str})",
|
| 778 |
+
"sintomas_clave": user_symptoms,
|
| 779 |
+
"es_hipotesis_local": True
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
|
| 783 |
|
| 784 |
def generate_ai_hypothesis_on_the_fly(query, entities):
|
| 785 |
+
"""
|
| 786 |
+
Agente Rescatador Universal por IA:
|
| 787 |
+
Sintetiza una hipótesis clínica para CUALQUIER caso, alimento o síntoma no registrado localmente.
|
| 788 |
+
"""
|
| 789 |
gemini_client = get_gemini_client()
|
| 790 |
if not gemini_client: return None
|
| 791 |
|
| 792 |
+
foods = entities.get('alimentos', [])
|
| 793 |
+
symptoms = entities.get('sintomas', [])
|
| 794 |
+
|
| 795 |
+
foods_text = ", ".join(foods) if foods else "alimentos mencionados en la consulta"
|
| 796 |
+
symptoms_text = ", ".join(symptoms) if symptoms else "síntomas expresados por el paciente"
|
| 797 |
+
|
| 798 |
+
prompt = (
|
| 799 |
+
"Rol: Médico Especialista en Nutrición Funcional e Inmunología de Alimentos.\n"
|
| 800 |
+
f'Consulta del paciente: "{query}"\n'
|
| 801 |
+
f"Alimentos clave: {foods_text}\n"
|
| 802 |
+
f"Síntomas clave: {symptoms_text}\n\n"
|
| 803 |
+
"Analiza la fisiología de este caso y genera un JSON estricto con las siguientes 4 claves:\n"
|
| 804 |
+
'1. "condicion_asociada": Título médico conciso de la hipersensibilidad o intolerancia.\n'
|
| 805 |
+
'2. "mecanismo_fisiologico": Explicación concisa del mecanismo celular o enzimático subyacente.\n'
|
| 806 |
+
'3. "compuesto_alimento": Nombre de los alimentos y las moléculas responsables.\n'
|
| 807 |
+
f'4. "sintomas_clave": {json.dumps(symptoms if symptoms else ["malestar"])}\n\n'
|
| 808 |
+
"Devuelve ÚNICAMENTE el código JSON válido, sin bloques de código markdown ni texto adicional."
|
| 809 |
+
)
|
| 810 |
try:
|
| 811 |
response = gemini_client.models.generate_content(
|
| 812 |
model='gemini-2.5-flash-lite',
|
| 813 |
contents=prompt
|
| 814 |
)
|
| 815 |
+
raw = response.text.strip()
|
| 816 |
+
if "```json" in raw: raw = raw.split("```json")[1].split("```")[0]
|
| 817 |
+
elif "```" in raw: raw = raw.split("```")[1].split("```")[0]
|
| 818 |
+
data = json.loads(raw.strip())
|
| 819 |
data["es_hipotesis_local"] = True
|
| 820 |
return data
|
| 821 |
except Exception as e:
|
| 822 |
+
logger.error(f"Error en Agente Rescatador Universal de IA: {e}")
|
| 823 |
return None
|
| 824 |
|
| 825 |
def find_best_foodb_matches(user_foods_es, _foodb_index, _foodb_lower_map, food_name_map, limit=3):
|