Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -697,27 +697,28 @@ def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_m
|
|
| 697 |
return entities
|
| 698 |
def find_best_matches_hybrid(entities, data):
|
| 699 |
"""
|
| 700 |
-
Motor de búsqueda semántico y
|
| 701 |
-
|
|
|
|
| 702 |
"""
|
| 703 |
if not entities or not data: return []
|
| 704 |
|
| 705 |
-
# 1.
|
| 706 |
-
|
| 707 |
-
|
| 708 |
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
|
|
|
| 715 |
if food in FOOD_TO_COMPOUND_MAP:
|
| 716 |
-
|
|
|
|
|
|
|
| 717 |
|
| 718 |
-
# Usamos la lista expandida para la búsqueda
|
| 719 |
-
user_food_keywords = user_food_keywords_expanded
|
| 720 |
-
|
| 721 |
RARE_CONDITIONS = [
|
| 722 |
"Porfiria Aguda Intermitente (PAI).", "Enfermedad de Refsum del Adulto.",
|
| 723 |
"Ataxia por Gluten.", "Encefalopatía por Gluten.", "Enfermedad de Wilson.",
|
|
@@ -726,33 +727,23 @@ def find_best_matches_hybrid(entities, data):
|
|
| 726 |
|
| 727 |
results = []
|
| 728 |
for entry in data:
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
# 2. PROCESAR INPUT DE LA BASE DE DATOS
|
| 732 |
db_food_text = entry.get("compuesto_alimento", "")
|
| 733 |
-
db_food_keywords = set(re.findall(r'\b\w+\b', sanitize_text(re.sub(r'\(.*?\)', '', db_food_text))))
|
| 734 |
-
|
| 735 |
db_symptoms_text = " ".join(entry.get("sintomas_clave", []))
|
| 736 |
-
db_symptom_keywords = set(re.findall(r'\b\w+\b', sanitize_text(db_symptoms_text)))
|
| 737 |
-
|
| 738 |
-
# 3. COMPARACIÓN INTELIGENTE Y PUNTUACIÓN
|
| 739 |
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
if food_intersection:
|
| 743 |
-
score_details['food'] = 20
|
| 744 |
-
# Bonus por alta confianza
|
| 745 |
-
if len(food_intersection) / len(user_food_keywords.union(db_food_keywords)) > 0.1: # Umbral más bajo
|
| 746 |
-
score_details['bonus'] = 30
|
| 747 |
|
| 748 |
-
#
|
| 749 |
-
|
| 750 |
-
score_details['symptoms'] = len(symptom_intersection) * 30
|
| 751 |
|
| 752 |
-
# Solo
|
| 753 |
-
if
|
| 754 |
-
|
|
|
|
|
|
|
| 755 |
|
|
|
|
| 756 |
condition_name = entry.get("condicion_asociada", "")
|
| 757 |
if condition_name in RARE_CONDITIONS:
|
| 758 |
final_score = base_score * 0.4
|
|
@@ -761,11 +752,13 @@ def find_best_matches_hybrid(entities, data):
|
|
| 761 |
|
| 762 |
score_details['total'] = int(final_score)
|
| 763 |
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
|
|
|
|
|
|
|
| 769 |
|
| 770 |
if not results: return []
|
| 771 |
return sorted(results, key=lambda x: x['score']['total'], reverse=True)
|
|
@@ -1187,53 +1180,43 @@ if st.session_state.start_analysis:
|
|
| 1187 |
st.error("No se pudieron identificar alimentos o síntomas claros en tu descripción. Intenta ser más específico.")
|
| 1188 |
st.session_state.search_results = []
|
| 1189 |
|
| 1190 |
-
|
| 1191 |
if st.session_state.search_results is not None:
|
| 1192 |
results = st.session_state.search_results
|
| 1193 |
|
| 1194 |
if not results:
|
| 1195 |
st.warning(f"No se encontraron coincidencias claras para tu caso: '{st.session_state.user_query}'. Prueba a describir los síntomas de otra manera.")
|
| 1196 |
else:
|
| 1197 |
-
# --- PASO 1: DEFINIR EL DIAGNÓSTICO PRINCIPAL Y GENERAR TODO EL CONTENIDO ---
|
| 1198 |
best_match_data = results[0]
|
| 1199 |
best_match = best_match_data['entry']
|
| 1200 |
|
| 1201 |
-
|
| 1202 |
-
if
|
| 1203 |
-
|
| 1204 |
try:
|
| 1205 |
analysis_text = generate_detailed_analysis(st.session_state.user_query, best_match)
|
| 1206 |
st.session_state.analysis_cache['best_match_analysis'] = analysis_text
|
|
|
|
| 1207 |
except Exception as e:
|
| 1208 |
-
|
| 1209 |
-
st.session_state.analysis_cache['best_match_analysis']
|
| 1210 |
-
|
| 1211 |
-
ai_analysis_text = st.session_state.analysis_cache['best_match_analysis']
|
| 1212 |
|
| 1213 |
-
# Generar los otros componentes de texto para el informe
|
| 1214 |
-
base_report_text = generate_report_text(st.session_state.user_query, results)
|
| 1215 |
neuro_report_text = generate_neuro_report_text(st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP)
|
| 1216 |
molecular_report_text = generate_molecular_report_text(best_match, st.session_state.entities, foodb_index, FOOD_NAME_TO_FOODB_KEY, COMPOUND_SYNONYM_MAP, KNOWN_TRIGGERS_MAP)
|
| 1217 |
|
| 1218 |
-
# Unir todo en un solo string para el informe de Word
|
| 1219 |
-
complete_report_string = f"{base_report_text}\n\n{ai_analysis_text}\n{neuro_report_text}\n{molecular_report_text}"
|
| 1220 |
-
|
| 1221 |
-
# Generar el archivo de Word en memoria
|
| 1222 |
-
word_file_buffer = generate_word_report(complete_report_string)
|
| 1223 |
-
|
| 1224 |
-
# --- PASO 2: CONSTRUIR LA INTERFAZ DE USUARIO CON EL CONTENIDO YA GENERADO ---
|
| 1225 |
col1, col2 = st.columns([3,1])
|
| 1226 |
with col1:
|
| 1227 |
st.success(f"Hemos encontrado {len(results)} posible(s) causa(s) relacionada(s) con tu caso.")
|
| 1228 |
with col2:
|
|
|
|
|
|
|
|
|
|
| 1229 |
if word_file_buffer:
|
| 1230 |
st.download_button(
|
| 1231 |
label="📄 Descargar Informe (Word)",
|
| 1232 |
data=word_file_buffer,
|
| 1233 |
file_name=f"Informe_Detective_Alimentos_{datetime.now().strftime('%Y%m%d')}.docx",
|
| 1234 |
mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
| 1235 |
-
key="download_word_report"
|
| 1236 |
-
help="Genera y descarga un informe completo en formato Word con todos los análisis."
|
| 1237 |
)
|
| 1238 |
|
| 1239 |
st.subheader("Análisis de Relevancia de las Coincidencias")
|
|
@@ -1244,11 +1227,10 @@ if st.session_state.search_results is not None:
|
|
| 1244 |
|
| 1245 |
with col1_expander:
|
| 1246 |
st.markdown("##### Desglose de la Puntuación de Relevancia:")
|
| 1247 |
-
|
| 1248 |
-
|
| 1249 |
-
|
| 1250 |
-
|
| 1251 |
-
|
| 1252 |
with col2_expander:
|
| 1253 |
with st.popover("🔬 Componentes Moleculares"):
|
| 1254 |
st.markdown(molecular_report_text.replace("=", ""))
|
|
@@ -1265,18 +1247,16 @@ if st.session_state.search_results is not None:
|
|
| 1265 |
for i, result in enumerate(results[1:5]):
|
| 1266 |
with st.container(border=True):
|
| 1267 |
entry = result['entry']
|
| 1268 |
-
score = result['score']
|
| 1269 |
-
|
| 1270 |
st.subheader(f"{i+2}. {entry.get('condicion_asociada')}")
|
| 1271 |
col_info, col_action = st.columns([3, 1])
|
| 1272 |
|
| 1273 |
with col_info:
|
| 1274 |
-
if result
|
| 1275 |
-
st.markdown(f"**Pistas Clave
|
| 1276 |
st.markdown(f"**Alimentos Típicos Asociados:** {entry.get('compuesto_alimento')}")
|
| 1277 |
|
| 1278 |
with col_action:
|
| 1279 |
-
st.metric("Relevancia", score['total'])
|
| 1280 |
analysis_key = f"analysis_{i+2}"
|
| 1281 |
|
| 1282 |
if st.button("Generar análisis", key=analysis_key, help=f"Generar análisis de IA para {entry.get('condicion_asociada')}"):
|
|
@@ -1285,10 +1265,10 @@ if st.session_state.search_results is not None:
|
|
| 1285 |
analysis_text = generate_detailed_analysis(st.session_state.user_query, entry)
|
| 1286 |
st.session_state.analysis_cache[analysis_key] = analysis_text
|
| 1287 |
except Exception as e:
|
| 1288 |
-
st.session_state.analysis_cache[analysis_key] = f"❌ Error al generar análisis
|
| 1289 |
|
| 1290 |
if analysis_key in st.session_state.analysis_cache:
|
| 1291 |
st.info(st.session_state.analysis_cache[analysis_key])
|
| 1292 |
|
| 1293 |
if i < len(results[1:5]) - 1:
|
| 1294 |
-
st.markdown("---")
|
|
|
|
| 697 |
return entities
|
| 698 |
def find_best_matches_hybrid(entities, data):
|
| 699 |
"""
|
| 700 |
+
Motor de búsqueda semántico y holístico (Versión Final).
|
| 701 |
+
Crea una "nube de palabras clave" para el usuario y para cada entrada de la BD,
|
| 702 |
+
y puntúa basándose en el tamaño de su intersección.
|
| 703 |
"""
|
| 704 |
if not entities or not data: return []
|
| 705 |
|
| 706 |
+
# --- 1. CREAR LA "NUBE DE PALABRAS CLAVE DEL USUARIO" ---
|
| 707 |
+
user_symptoms_list = entities.get("sintomas", [])
|
| 708 |
+
user_foods_list = entities.get("alimentos", [])
|
| 709 |
|
| 710 |
+
# Combinar síntomas y alimentos en un solo texto
|
| 711 |
+
user_text = " ".join(user_symptoms_list) + " " + " ".join(user_foods_list)
|
| 712 |
+
user_keywords_base = set(re.findall(r'\b\w+\b', sanitize_text(user_text)))
|
| 713 |
+
|
| 714 |
+
# Expandir con el conocimiento bioquímico de FOOD_TO_COMPOUND_MAP
|
| 715 |
+
user_keywords_expanded = set(user_keywords_base)
|
| 716 |
+
for food in user_foods_list:
|
| 717 |
if food in FOOD_TO_COMPOUND_MAP:
|
| 718 |
+
user_keywords_expanded.update(FOOD_TO_COMPOUND_MAP[food])
|
| 719 |
+
|
| 720 |
+
user_keywords = user_keywords_expanded
|
| 721 |
|
|
|
|
|
|
|
|
|
|
| 722 |
RARE_CONDITIONS = [
|
| 723 |
"Porfiria Aguda Intermitente (PAI).", "Enfermedad de Refsum del Adulto.",
|
| 724 |
"Ataxia por Gluten.", "Encefalopatía por Gluten.", "Enfermedad de Wilson.",
|
|
|
|
| 727 |
|
| 728 |
results = []
|
| 729 |
for entry in data:
|
| 730 |
+
# --- 2. CREAR LA "NUBE DE PALABRAS CLAVE DE LA BASE DE DATOS" ---
|
|
|
|
|
|
|
| 731 |
db_food_text = entry.get("compuesto_alimento", "")
|
|
|
|
|
|
|
| 732 |
db_symptoms_text = " ".join(entry.get("sintomas_clave", []))
|
|
|
|
|
|
|
|
|
|
| 733 |
|
| 734 |
+
db_text = db_food_text + " " + db_symptoms_text
|
| 735 |
+
db_keywords = set(re.findall(r'\b\w+\b', sanitize_text(db_text)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 736 |
|
| 737 |
+
# --- 3. PUNTUACIÓN POR INTERSECCIÓN ---
|
| 738 |
+
intersection = user_keywords.intersection(db_keywords)
|
|
|
|
| 739 |
|
| 740 |
+
# Solo procesar si hay al menos una palabra en común
|
| 741 |
+
if intersection:
|
| 742 |
+
score_details = {}
|
| 743 |
+
# La puntuación base es el número de palabras clave coincidentes al cuadrado para dar más peso a coincidencias múltiples
|
| 744 |
+
base_score = (len(intersection) ** 2) * 10
|
| 745 |
|
| 746 |
+
# Ponderación por rareza
|
| 747 |
condition_name = entry.get("condicion_asociada", "")
|
| 748 |
if condition_name in RARE_CONDITIONS:
|
| 749 |
final_score = base_score * 0.4
|
|
|
|
| 752 |
|
| 753 |
score_details['total'] = int(final_score)
|
| 754 |
|
| 755 |
+
# Añadir a resultados si supera un umbral mínimo para evitar ruido
|
| 756 |
+
if score_details['total'] > 10: # Umbral bajo para permitir coincidencias débiles pero relevantes
|
| 757 |
+
results.append({
|
| 758 |
+
'entry': entry,
|
| 759 |
+
'score': score_details,
|
| 760 |
+
'matched_keywords': list(intersection) # Guardamos las palabras coincidentes
|
| 761 |
+
})
|
| 762 |
|
| 763 |
if not results: return []
|
| 764 |
return sorted(results, key=lambda x: x['score']['total'], reverse=True)
|
|
|
|
| 1180 |
st.error("No se pudieron identificar alimentos o síntomas claros en tu descripción. Intenta ser más específico.")
|
| 1181 |
st.session_state.search_results = []
|
| 1182 |
|
|
|
|
| 1183 |
if st.session_state.search_results is not None:
|
| 1184 |
results = st.session_state.search_results
|
| 1185 |
|
| 1186 |
if not results:
|
| 1187 |
st.warning(f"No se encontraron coincidencias claras para tu caso: '{st.session_state.user_query}'. Prueba a describir los síntomas de otra manera.")
|
| 1188 |
else:
|
|
|
|
| 1189 |
best_match_data = results[0]
|
| 1190 |
best_match = best_match_data['entry']
|
| 1191 |
|
| 1192 |
+
ai_analysis_text = st.session_state.analysis_cache.get('best_match_analysis', "")
|
| 1193 |
+
if not ai_analysis_text:
|
| 1194 |
+
with st.spinner("✍️ Generando análisis personalizado con IA..."):
|
| 1195 |
try:
|
| 1196 |
analysis_text = generate_detailed_analysis(st.session_state.user_query, best_match)
|
| 1197 |
st.session_state.analysis_cache['best_match_analysis'] = analysis_text
|
| 1198 |
+
ai_analysis_text = analysis_text
|
| 1199 |
except Exception as e:
|
| 1200 |
+
st.session_state.analysis_cache['best_match_analysis'] = "❌ No se pudo generar el análisis detallado."
|
| 1201 |
+
ai_analysis_text = st.session_state.analysis_cache['best_match_analysis']
|
|
|
|
|
|
|
| 1202 |
|
|
|
|
|
|
|
| 1203 |
neuro_report_text = generate_neuro_report_text(st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP)
|
| 1204 |
molecular_report_text = generate_molecular_report_text(best_match, st.session_state.entities, foodb_index, FOOD_NAME_TO_FOODB_KEY, COMPOUND_SYNONYM_MAP, KNOWN_TRIGGERS_MAP)
|
| 1205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1206 |
col1, col2 = st.columns([3,1])
|
| 1207 |
with col1:
|
| 1208 |
st.success(f"Hemos encontrado {len(results)} posible(s) causa(s) relacionada(s) con tu caso.")
|
| 1209 |
with col2:
|
| 1210 |
+
base_report_text = generate_report_text(st.session_state.user_query, results)
|
| 1211 |
+
complete_report_string = f"{base_report_text}\n\n{ai_analysis_text}\n{neuro_report_text}\n{molecular_report_text}"
|
| 1212 |
+
word_file_buffer = generate_word_report(complete_report_string)
|
| 1213 |
if word_file_buffer:
|
| 1214 |
st.download_button(
|
| 1215 |
label="📄 Descargar Informe (Word)",
|
| 1216 |
data=word_file_buffer,
|
| 1217 |
file_name=f"Informe_Detective_Alimentos_{datetime.now().strftime('%Y%m%d')}.docx",
|
| 1218 |
mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
| 1219 |
+
key="download_word_report"
|
|
|
|
| 1220 |
)
|
| 1221 |
|
| 1222 |
st.subheader("Análisis de Relevancia de las Coincidencias")
|
|
|
|
| 1227 |
|
| 1228 |
with col1_expander:
|
| 1229 |
st.markdown("##### Desglose de la Puntuación de Relevancia:")
|
| 1230 |
+
st.metric("Puntuación de Relevancia Total", f"{best_match_data['score']['total']}", delta="Máxima coincidencia")
|
| 1231 |
+
if 'matched_keywords' in best_match_data and best_match_data['matched_keywords']:
|
| 1232 |
+
st.caption(f"Pistas Clave Coincidentes: {', '.join(best_match_data['matched_keywords'])}")
|
| 1233 |
+
|
|
|
|
| 1234 |
with col2_expander:
|
| 1235 |
with st.popover("🔬 Componentes Moleculares"):
|
| 1236 |
st.markdown(molecular_report_text.replace("=", ""))
|
|
|
|
| 1247 |
for i, result in enumerate(results[1:5]):
|
| 1248 |
with st.container(border=True):
|
| 1249 |
entry = result['entry']
|
|
|
|
|
|
|
| 1250 |
st.subheader(f"{i+2}. {entry.get('condicion_asociada')}")
|
| 1251 |
col_info, col_action = st.columns([3, 1])
|
| 1252 |
|
| 1253 |
with col_info:
|
| 1254 |
+
if 'matched_keywords' in result and result['matched_keywords']:
|
| 1255 |
+
st.markdown(f"**Pistas Clave Coincidentes:** {', '.join(result['matched_keywords']).capitalize()}")
|
| 1256 |
st.markdown(f"**Alimentos Típicos Asociados:** {entry.get('compuesto_alimento')}")
|
| 1257 |
|
| 1258 |
with col_action:
|
| 1259 |
+
st.metric("Relevancia", result['score']['total'])
|
| 1260 |
analysis_key = f"analysis_{i+2}"
|
| 1261 |
|
| 1262 |
if st.button("Generar análisis", key=analysis_key, help=f"Generar análisis de IA para {entry.get('condicion_asociada')}"):
|
|
|
|
| 1265 |
analysis_text = generate_detailed_analysis(st.session_state.user_query, entry)
|
| 1266 |
st.session_state.analysis_cache[analysis_key] = analysis_text
|
| 1267 |
except Exception as e:
|
| 1268 |
+
st.session_state.analysis_cache[analysis_key] = f"❌ Error al generar análisis."
|
| 1269 |
|
| 1270 |
if analysis_key in st.session_state.analysis_cache:
|
| 1271 |
st.info(st.session_state.analysis_cache[analysis_key])
|
| 1272 |
|
| 1273 |
if i < len(results[1:5]) - 1:
|
| 1274 |
+
st.markdown("---")
|