Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -16,7 +16,7 @@ import altair as alt
|
|
| 16 |
from datetime import datetime
|
| 17 |
from io import BytesIO
|
| 18 |
import docx
|
| 19 |
-
import difflib
|
| 20 |
|
| 21 |
# --- CONFIGURACI脫N DE P脕GINA Y LOGGING ---
|
| 22 |
st.set_page_config(page_title="El Detective de Alimentos", page_icon="馃崕", layout="wide")
|
|
@@ -464,6 +464,7 @@ def extract_keywords(text):
|
|
| 464 |
words = re.findall(r'\b\w+\b', sanitize_text(text))
|
| 465 |
return set(w for w in words if w not in STOPWORDS_ES and len(w) > 2)
|
| 466 |
|
|
|
|
| 467 |
def get_compounds_for_food(food_name, food_map):
|
| 468 |
if not food_name: return []
|
| 469 |
food_clean = food_name.lower().strip()
|
|
@@ -473,19 +474,50 @@ def get_compounds_for_food(food_name, food_map):
|
|
| 473 |
return food_map[food_clean]
|
| 474 |
|
| 475 |
# 2. Limpieza de Plurales
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
matched_compounds = set()
|
| 483 |
for key, compounds in food_map.items():
|
| 484 |
if key in food_clean or food_clean in key:
|
| 485 |
matched_compounds.update(compounds)
|
| 486 |
-
|
| 487 |
-
|
| 488 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 489 |
def validar_seguro_logico(entry, user_foods, user_query, food_compound_map):
|
| 490 |
if not user_foods:
|
| 491 |
return True
|
|
@@ -664,39 +696,60 @@ def build_local_hypothesis(entities, food_compound_map, neuro_map):
|
|
| 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.
|
| 672 |
-
3.
|
| 673 |
-
4.
|
| 674 |
-
|
| 675 |
"""
|
| 676 |
-
if not food_name:
|
|
|
|
|
|
|
| 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.
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 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"]):
|
|
@@ -716,9 +769,9 @@ def get_compounds_for_food(food_name, food_map):
|
|
| 716 |
|
| 717 |
def build_local_hypothesis(entities, food_compound_map, neuro_map):
|
| 718 |
"""
|
| 719 |
-
Generador Local
|
| 720 |
-
Construye
|
| 721 |
-
|
| 722 |
"""
|
| 723 |
user_foods = entities.get("alimentos", [])
|
| 724 |
user_symptoms = entities.get("sintomas", [])
|
|
@@ -727,26 +780,39 @@ def build_local_hypothesis(entities, food_compound_map, neuro_map):
|
|
| 727 |
|
| 728 |
symptom_text = " ".join(user_symptoms).lower()
|
| 729 |
|
| 730 |
-
# Categorizaci贸n cl铆nica
|
| 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})
|
| 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":
|
| 749 |
-
"mecanismo_fisiologico": f"Respuesta inmunol贸gica o
|
| 750 |
"compuesto_alimento": f"{alimentos_str} (Compuestos bioactivos)",
|
| 751 |
"sintomas_clave": user_symptoms,
|
| 752 |
"es_hipotesis_local": True
|
|
@@ -757,11 +823,11 @@ def build_local_hypothesis(entities, food_compound_map, neuro_map):
|
|
| 757 |
compuestos_str = ", ".join(compuestos_unicos)
|
| 758 |
alimentos_str = ", ".join(alimentos_unicos).capitalize()
|
| 759 |
|
| 760 |
-
# Construcci贸n sint谩ctica
|
| 761 |
-
if
|
|
|
|
|
|
|
| 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:
|
|
@@ -779,8 +845,6 @@ def build_local_hypothesis(entities, food_compound_map, neuro_map):
|
|
| 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:
|
|
|
|
| 16 |
from datetime import datetime
|
| 17 |
from io import BytesIO
|
| 18 |
import docx
|
| 19 |
+
import difflib
|
| 20 |
|
| 21 |
# --- CONFIGURACI脫N DE P脕GINA Y LOGGING ---
|
| 22 |
st.set_page_config(page_title="El Detective de Alimentos", page_icon="馃崕", layout="wide")
|
|
|
|
| 464 |
words = re.findall(r'\b\w+\b', sanitize_text(text))
|
| 465 |
return set(w for w in words if w not in STOPWORDS_ES and len(w) > 2)
|
| 466 |
|
| 467 |
+
# --- BUSCADOR UNIVERSAL CON CORRECTOR DIFUSO DE ERRATAS (difflib) ---
|
| 468 |
def get_compounds_for_food(food_name, food_map):
|
| 469 |
if not food_name: return []
|
| 470 |
food_clean = food_name.lower().strip()
|
|
|
|
| 474 |
return food_map[food_clean]
|
| 475 |
|
| 476 |
# 2. Limpieza de Plurales
|
| 477 |
+
for suffix in ["es", "s"]:
|
| 478 |
+
if food_clean.endswith(suffix):
|
| 479 |
+
norm = food_clean[:-len(suffix)]
|
| 480 |
+
if norm in food_map:
|
| 481 |
+
return food_map[norm]
|
| 482 |
+
|
| 483 |
+
# 3. CORRECTOR DIFUSO DE ERRATAS (Corrige "vino tino" -> "vino tinto", "chotanduro" -> "chontaduro")
|
| 484 |
+
fuzzy_matches = difflib.get_close_matches(food_clean, list(food_map.keys()), n=1, cutoff=0.6)
|
| 485 |
+
if fuzzy_matches:
|
| 486 |
+
logger.info(f"馃挕 Errata corregida autom谩ticamente: '{food_clean}' -> '{fuzzy_matches[0]}'")
|
| 487 |
+
return food_map[fuzzy_matches[0]]
|
| 488 |
+
|
| 489 |
+
# 4. B煤squeda por subcadena en el diccionario
|
| 490 |
matched_compounds = set()
|
| 491 |
for key, compounds in food_map.items():
|
| 492 |
if key in food_clean or food_clean in key:
|
| 493 |
matched_compounds.update(compounds)
|
| 494 |
+
if matched_compounds:
|
| 495 |
+
return list(matched_compounds)
|
| 496 |
|
| 497 |
+
# 5. Correcci贸n difusa por palabras individuales dentro de frases largas
|
| 498 |
+
inferred = set()
|
| 499 |
+
words = food_clean.split()
|
| 500 |
+
for word in words:
|
| 501 |
+
if len(word) > 3:
|
| 502 |
+
sub_fuzzy = difflib.get_close_matches(word, list(food_map.keys()), n=1, cutoff=0.7)
|
| 503 |
+
if sub_fuzzy:
|
| 504 |
+
inferred.update(food_map[sub_fuzzy[0]])
|
| 505 |
+
|
| 506 |
+
if inferred:
|
| 507 |
+
return list(inferred)
|
| 508 |
+
|
| 509 |
+
# 6. INFERENCIA UNIVERSAL DE CLASES PARA ALIMENTOS NO REGISTRADOS
|
| 510 |
+
if any(w in food_clean for w in ["vino", "cerveza", "licor", "trago", "bebida"]):
|
| 511 |
+
return ["histamina", "tiramina", "sulfitos", "alcohol", "taninos"]
|
| 512 |
+
elif any(w in food_clean for w in ["carne", "cerdo", "res", "pollo", "pavo", "cordero"]):
|
| 513 |
+
return ["histamina", "purinas", "prote铆nas"]
|
| 514 |
+
elif any(w in food_clean for w in ["jugo", "zumo", "batido", "fruta", "licuado"]):
|
| 515 |
+
return ["fructosa", "salicilatos", "histamina", "fenoles"]
|
| 516 |
+
elif any(w in food_clean for w in ["queso", "leche", "crema", "yogur"]):
|
| 517 |
+
return ["case铆na", "lactosa", "histamina", "tiramina"]
|
| 518 |
+
|
| 519 |
+
return []
|
| 520 |
+
|
| 521 |
def validar_seguro_logico(entry, user_foods, user_query, food_compound_map):
|
| 522 |
if not user_foods:
|
| 523 |
return True
|
|
|
|
| 696 |
"sintomas_clave": user_symptoms,
|
| 697 |
"es_hipotesis_local": True
|
| 698 |
}
|
| 699 |
+
|
| 700 |
+
|
| 701 |
def get_compounds_for_food(food_name, food_map):
|
| 702 |
"""
|
| 703 |
+
Buscador e Inferidor Universal de Compuestos (Definitivo):
|
| 704 |
1. Coincidencia exacta.
|
| 705 |
+
2. Normalizaci贸n de plurales.
|
| 706 |
+
3. Corrector difuso de errores de tipeo (difflib).
|
| 707 |
+
4. B煤squeda por subcadena en el diccionario.
|
| 708 |
+
5. Inferencia universal por familia biom茅dica para alimentos no registrados.
|
| 709 |
"""
|
| 710 |
+
if not food_name:
|
| 711 |
+
return []
|
| 712 |
+
|
| 713 |
food_clean = food_name.lower().strip()
|
| 714 |
|
| 715 |
# 1. Coincidencia exacta
|
| 716 |
if food_clean in food_map:
|
| 717 |
return food_map[food_clean]
|
| 718 |
|
| 719 |
+
# 2. Normalizaci贸n de Plurales
|
| 720 |
for suffix in ["es", "s"]:
|
| 721 |
if food_clean.endswith(suffix):
|
| 722 |
norm = food_clean[:-len(suffix)]
|
| 723 |
if norm in food_map:
|
| 724 |
return food_map[norm]
|
| 725 |
|
| 726 |
+
# 3. Correcci贸n difusa de erratas de tipeo ("vino tino" -> "vino tinto", "chotanduro" -> "chontaduro")
|
| 727 |
+
fuzzy_matches = difflib.get_close_matches(food_clean, list(food_map.keys()), n=1, cutoff=0.6)
|
| 728 |
+
if fuzzy_matches:
|
| 729 |
+
return food_map[fuzzy_matches[0]]
|
| 730 |
+
|
| 731 |
+
# 4. B煤squeda por subcadena dentro del diccionario
|
| 732 |
matched_compounds = set()
|
| 733 |
for key, compounds in food_map.items():
|
| 734 |
if key in food_clean or food_clean in key:
|
| 735 |
matched_compounds.update(compounds)
|
| 736 |
+
|
| 737 |
if matched_compounds:
|
| 738 |
return list(matched_compounds)
|
| 739 |
|
| 740 |
+
# 5. Inferencia difusa por palabras individuales en nombres compuestos
|
| 741 |
inferred = set()
|
| 742 |
+
words = food_clean.split()
|
| 743 |
+
for word in words:
|
| 744 |
+
if len(word) > 3:
|
| 745 |
+
sub_fuzzy = difflib.get_close_matches(word, list(food_map.keys()), n=1, cutoff=0.7)
|
| 746 |
+
if sub_fuzzy:
|
| 747 |
+
inferred.update(food_map[sub_fuzzy[0]])
|
| 748 |
+
|
| 749 |
+
if inferred:
|
| 750 |
+
return list(inferred)
|
| 751 |
+
|
| 752 |
+
# 6. INFERENCIA UNIVERSAL POR FAMILIA BIOL脫GICA (Alimentos raros o no registrados)
|
| 753 |
if any(w in food_clean for w in ["carne", "cerdo", "res", "pollo", "pavo", "cordero", "pato", "chicharr贸n", "tocino"]):
|
| 754 |
inferred.update(["histamina", "purinas", "prote铆nas"])
|
| 755 |
elif any(w in food_clean for w in ["pescado", "at煤n", "marisco", "camar贸n", "sardina", "langostino", "pulpo"]):
|
|
|
|
| 769 |
|
| 770 |
def build_local_hypothesis(entities, food_compound_map, neuro_map):
|
| 771 |
"""
|
| 772 |
+
Generador Local Definitivo de Hip贸tesis:
|
| 773 |
+
Construye respuestas cl铆nicas estructuradas clasificando los s铆ntomas en 4 grandes
|
| 774 |
+
sistemas org谩nicos (Neurolog铆a, Dermatolog铆a, Insomnio, Gastroenterolog铆a).
|
| 775 |
"""
|
| 776 |
user_foods = entities.get("alimentos", [])
|
| 777 |
user_symptoms = entities.get("sintomas", [])
|
|
|
|
| 780 |
|
| 781 |
symptom_text = " ".join(user_symptoms).lower()
|
| 782 |
|
| 783 |
+
# Categorizaci贸n cl铆nica por sistemas de s铆ntomas
|
| 784 |
+
es_dermatologico = any(k in symptom_text for k in ["piel", "rasca", "picaz贸n", "picazon", "manchas", "urticaria", "erupci贸n", "erupcion", "alergia", "ronchas", "granitos"])
|
| 785 |
+
es_cefalea = any(k in symptom_text for k in ["dolor de cabeza", "migra帽a", "cefalea", "jaqueca", "dolor", "cabeza"])
|
| 786 |
+
es_insomnio = any(k in symptom_text for k in ["insomnio", "no puedo dormir", "sue帽o", "desvelado", "desvelo", "despierto"])
|
| 787 |
+
es_gastro = any(k in symptom_text for k in ["hinchaz贸n", "hinchazon", "gases", "diarrea", "dolor abdominal", "retortij贸n", "retortijon", "c贸lico", "colico", "inflamado", "inflama", "pesadez", "est贸mago", "estomago"])
|
| 788 |
|
| 789 |
candidates = []
|
| 790 |
for food in user_foods:
|
| 791 |
comps = get_compounds_for_food(food, food_compound_map)
|
| 792 |
for compound in comps:
|
| 793 |
info = neuro_map.get(compound, {})
|
| 794 |
+
effect = info.get('efecto_neuropsicologico', f'Sustancia bioactiva ({compound}) con impacto inmunol贸gico o metabolico en {food}')
|
| 795 |
candidates.append({"alimento": food, "compuesto": compound, "efecto": effect})
|
| 796 |
|
| 797 |
+
# Si no hay candidatos bioqu铆micos espec铆ficos, genera la hip贸tesis con la familia inferida
|
| 798 |
if not candidates:
|
| 799 |
alimentos_str = ", ".join(user_foods).capitalize()
|
| 800 |
sintomas_str = ", ".join(user_symptoms)
|
| 801 |
+
|
| 802 |
+
if es_cefalea:
|
| 803 |
+
condicion_fallback = f"Cefalea o Migra帽a Alimentaria relacionada con {alimentos_str}"
|
| 804 |
+
elif es_insomnio:
|
| 805 |
+
condicion_fallback = f"Alteraci贸n del Sue帽o o Insomnio relacionado con {alimentos_str}"
|
| 806 |
+
elif es_gastro:
|
| 807 |
+
condicion_fallback = f"Distensi贸n o Malestar Gastrointestinal relacionado con {alimentos_str}"
|
| 808 |
+
elif es_dermatologico:
|
| 809 |
+
condicion_fallback = f"Reacci贸n Dermatol贸gica o Urticaria relacionada con {alimentos_str}"
|
| 810 |
+
else:
|
| 811 |
+
condicion_fallback = f"Sensibilidad o Intolerancia Alimentaria a {alimentos_str}"
|
| 812 |
+
|
| 813 |
return {
|
| 814 |
+
"condicion_asociada": condicion_fallback,
|
| 815 |
+
"mecanismo_fisiologico": f"Respuesta inmunol贸gica, enzim谩tica o metab贸lica inducida por los componentes bioactivos de {alimentos_str}, manifest谩ndose con {sintomas_str}.",
|
| 816 |
"compuesto_alimento": f"{alimentos_str} (Compuestos bioactivos)",
|
| 817 |
"sintomas_clave": user_symptoms,
|
| 818 |
"es_hipotesis_local": True
|
|
|
|
| 823 |
compuestos_str = ", ".join(compuestos_unicos)
|
| 824 |
alimentos_str = ", ".join(alimentos_unicos).capitalize()
|
| 825 |
|
| 826 |
+
# Construcci贸n sint谩ctica por sistema afectado
|
| 827 |
+
if es_cefalea:
|
| 828 |
+
condicion = f"Cefalea o Migra帽a Vasoactiva asociada a {compuestos_str} en {alimentos_str}"
|
| 829 |
+
elif es_dermatologico:
|
| 830 |
condicion = f"Reacci贸n Histamin茅rgica / Dermatol贸gica por {compuestos_str} en {alimentos_str}"
|
|
|
|
|
|
|
| 831 |
elif es_insomnio:
|
| 832 |
condicion = f"Alteraci贸n del Sue帽o e Insomnio por respuesta a {compuestos_str} en {alimentos_str}"
|
| 833 |
elif es_gastro:
|
|
|
|
| 845 |
"es_hipotesis_local": True
|
| 846 |
}
|
| 847 |
|
|
|
|
|
|
|
| 848 |
def generate_ai_hypothesis_on_the_fly(query, entities):
|
| 849 |
"""
|
| 850 |
Agente Rescatador Universal por IA:
|