JairoCesar commited on
Commit
fc2b4ec
·
verified ·
1 Parent(s): 70eb63c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -102
app.py CHANGED
@@ -34,30 +34,32 @@ Este análisis es una herramienta informativa de IA y **NO es un diagnóstico m
34
  **Si tus síntomas son severos, busca ayuda médica urgente.**
35
  """
36
 
37
- # --- CONFIGURACIÓN DE LA API GEMINI ---
38
- try:
39
- if 'APIkeydetective' in st.secrets:
40
- APIkeydetective = st.secrets['APIkeydetective']
41
- else:
42
- APIkeydetective = os.getenv("APIkeydetective")
43
- if not APIkeydetective:
44
- st.error("No se encontró la APIkeydetective.")
45
- st.stop()
46
-
47
- client = genai.Client(
48
- api_key=APIkeydetective,
49
- http_options=types.HttpOptions(timeout=30000)
50
- )
51
-
52
- except Exception as e:
53
- st.error(f"❌ Error al configurar Gemini API: {e}")
54
- st.stop()
55
-
56
  @st.cache_resource
57
  def get_gemini_client():
58
- return client
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- # --- CARGA ULTRA-RÁPIDA DE DATOS SIN COPIA EN MEMORIA ---
 
 
 
 
61
  @st.cache_resource
62
  def load_data():
63
  try:
@@ -110,7 +112,7 @@ FOOD_TO_COMPOUND_MAP = {
110
  "cholado": ["fructosa", "lactosa", "histamina", "azúcar refinado"]
111
  }
112
 
113
- # --- SEGURO LÓGICO: REGLAS ESTRICTAS DE EXCLUSIÓN BIOQUÍMICA ---
114
  STRICT_COMPOUND_TRIGGERS = {
115
  "cafeína": ["cafeína", "caffeine", "café"],
116
  "gluten": ["gluten", "trigo", "cebada", "centeno"],
@@ -122,12 +124,6 @@ STRICT_COMPOUND_TRIGGERS = {
122
  }
123
 
124
  def validar_seguro_logico(entry, user_foods, food_compound_map):
125
- """
126
- SEGURO LÓGICO: Si una condición atribuida en la BD o hipótesis requiere un
127
- compuesto exclusivo (ej: cafeína, gluten, lactosa, tiramina), verifica que AL MENOS
128
- UNO de los alimentos ingresados por el usuario contenga efectivamente dicho compuesto.
129
- Evita falsos positivos como asociar cafeína al jugo de naranja.
130
- """
131
  if not user_foods:
132
  return True
133
 
@@ -275,7 +271,7 @@ INTEGRATED_NEURO_FOOD_MAP = {
275
  }
276
  }
277
 
278
- # --- FUNCIONES DE AUXILIO ---
279
  @st.cache_data(show_spinner=False)
280
  def generate_word_report_cached(report_text):
281
  try:
@@ -321,7 +317,9 @@ def extract_keywords(text):
321
 
322
  @retry(wait=wait_fixed(2), stop=stop_after_attempt(2))
323
  def extract_entities_with_gemini(query):
324
- if not client: return None
 
 
325
  system_prompt = f"""
326
  Rol: Extractor Médico JSON experto en nutrición y síntomas.
327
  Tarea: Analiza el texto del usuario y extrae un JSON estricto con dos claves:
@@ -332,7 +330,7 @@ def extract_entities_with_gemini(query):
332
  Output (JSON estricto):
333
  """
334
  try:
335
- response = client.models.generate_content(
336
  model='gemini-2.5-flash-lite',
337
  contents=system_prompt
338
  )
@@ -395,7 +393,7 @@ def find_best_matches_hybrid(entities, data, food_compound_map=None):
395
 
396
  results = []
397
  for entry in data:
398
- # APLICACIÓN DEL SEGURO LÓGICO
399
  if food_compound_map and not validar_seguro_logico(entry, user_foods_list, food_compound_map):
400
  continue
401
 
@@ -497,7 +495,8 @@ def find_best_foodb_matches(user_foods_es, _foodb_index, _foodb_lower_map, food_
497
 
498
  @retry(wait=wait_fixed(2), stop=stop_after_attempt(2))
499
  def generate_detailed_analysis(query, match, entities=None, food_compound_map=None, neuro_map=None):
500
- if not client: return "Error: IA no disponible."
 
501
 
502
  compound_context = ""
503
  if entities and food_compound_map and neuro_map:
@@ -528,7 +527,7 @@ def generate_detailed_analysis(query, match, entities=None, food_compound_map=No
528
  NO incluyas descargos de responsabilidad.
529
  """
530
  try:
531
- response = client.models.generate_content(
532
  model='gemini-2.5-flash-lite',
533
  contents=prompt
534
  )
@@ -604,7 +603,14 @@ def create_relevance_chart(results):
604
  x='Relevancia', y=alt.Y('Condición', sort='-x'), tooltip=['Condición', 'Relevancia']
605
  ).properties(title='Top Coincidencias')
606
 
607
- # --- INTERFAZ STREAMLIT ---
 
 
 
 
 
 
 
608
  col_img1, col_text, col_img2 = st.columns([1, 4, 1])
609
  with col_img1:
610
  if os.path.exists("imagen.png"): st.image("imagen.png", width=150)
@@ -614,77 +620,86 @@ with col_text:
614
  with col_img2:
615
  if os.path.exists("buho.png"): st.image("buho.png", width=120)
616
 
617
- if 'search_results' not in st.session_state: st.session_state.search_results = None
618
- if 'user_query' not in st.session_state: st.session_state.user_query = ""
619
- if 'entities' not in st.session_state: st.session_state.entities = None
620
- if 'analysis_cache' not in st.session_state: st.session_state.analysis_cache = {}
621
-
622
- with st.form(key="search_form"):
623
- query = st.text_area("Tu Caso:", height=150, placeholder="Ej: Cuando tomo mucho jugo de naranja no puedo dormir.")
624
  submitted = st.form_submit_button("Analizar Caso", type="primary")
625
 
626
- if submitted and query:
627
- st.session_state.user_query = query
628
- st.session_state.search_results = None
629
- st.session_state.analysis_cache = {}
630
-
631
- with st.spinner("🔍 Analizando pistas..."):
632
- raw_entities = extract_entities_with_gemini(query)
633
- reinforced = reinforce_entities_with_keywords(raw_entities, query, FOOD_TO_COMPOUND_MAP, MASTER_SYMPTOM_MAP)
634
- final_symptoms = translate_symptoms_local(reinforced.get("sintomas", []), MASTER_SYMPTOM_MAP)
635
- final_entities = {"alimentos": reinforced.get("alimentos", []), "sintomas": final_symptoms}
636
- st.session_state.entities = final_entities
637
 
638
- results = find_best_matches_hybrid(final_entities, alimentos_data, FOOD_TO_COMPOUND_MAP)
639
-
640
- if not results:
641
- local_hypothesis = build_local_hypothesis(final_entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP)
642
- if local_hypothesis:
643
- results = [{'entry': local_hypothesis, 'score': {'total': 50}, 'matched_keywords': []}]
644
-
645
- st.session_state.search_results = results
 
 
 
 
 
 
 
 
 
646
 
647
- if st.session_state.search_results:
 
648
  results = st.session_state.search_results
649
- best_match = results[0]['entry']
650
-
651
- st.success(f"🔎 Coincidencia Principal: **{best_match.get('condicion_asociada')}**")
652
 
653
- cache_key = f"analysis_{best_match.get('condicion_asociada')}"
654
- if cache_key not in st.session_state.analysis_cache:
655
- with st.spinner("✍️ Redactando informe..."):
656
- analysis = generate_detailed_analysis(
657
- st.session_state.user_query, best_match,
658
- st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  )
660
- st.session_state.analysis_cache[cache_key] = analysis
661
-
662
- tab_main, tab_neuro, tab_mol = st.tabs(["💡 Interpretación Clínica", "🧠 Efectos Neuropsicológicos", "🔬 Análisis Molecular"])
663
-
664
- with tab_main:
665
- st.markdown(st.session_state.analysis_cache[cache_key])
666
- if len(results) > 1:
667
- st.altair_chart(create_relevance_chart(results), use_container_width=True)
668
-
669
- with tab_neuro:
670
- st.markdown(generate_neuro_report_text(st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP))
671
-
672
- with tab_mol:
673
- st.markdown(generate_molecular_report_text(
674
- best_match, st.session_state.entities, foodb_index, foodb_lower_map,
675
- FOOD_NAME_TO_FOODB_KEY, COMPOUND_SYNONYM_MAP, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
676
- ))
677
-
678
- full_report = f"REPORTE\n\n{st.session_state.analysis_cache[cache_key]}"
679
- word_bytes = generate_word_report_cached(full_report)
680
- if word_bytes:
681
- st.download_button(
682
- label="📄 Descargar Informe",
683
- data=word_bytes,
684
- file_name="Reporte.docx",
685
- mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
686
- )
687
-
688
- elif submitted:
689
- st.warning("⚠️ No pudimos identificar una causa clara.")
690
- st.info("Intenta reformular: 'Siento [SÍNTOMA] cuando como [ALIMENTO]'.")
 
34
  **Si tus síntomas son severos, busca ayuda médica urgente.**
35
  """
36
 
37
+ # --- CONFIGURACIÓN DE LA API GEMINI (google-genai V3.0) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  @st.cache_resource
39
  def get_gemini_client():
40
+ try:
41
+ if 'APIkeydetective' in st.secrets:
42
+ APIkeydetective = st.secrets['APIkeydetective']
43
+ else:
44
+ APIkeydetective = os.getenv("APIkeydetective")
45
+
46
+ if not APIkeydetective:
47
+ st.error("No se encontró la APIkeydetective.")
48
+ return None
49
+
50
+ return genai.Client(
51
+ api_key=APIkeydetective,
52
+ http_options=types.HttpOptions(timeout=30000) # 30 segundos
53
+ )
54
+ except Exception as e:
55
+ st.error(f"❌ Error al configurar Gemini API: {e}")
56
+ return None
57
 
58
+ client = get_gemini_client()
59
+ if not client:
60
+ st.stop()
61
+
62
+ # --- CARGA ULTRA-RÁPIDA DE DATOS ---
63
  @st.cache_resource
64
  def load_data():
65
  try:
 
112
  "cholado": ["fructosa", "lactosa", "histamina", "azúcar refinado"]
113
  }
114
 
115
+ # --- SEGURO LÓGICO DE INCOMPATIBILIDAD BIOQUÍMICA ---
116
  STRICT_COMPOUND_TRIGGERS = {
117
  "cafeína": ["cafeína", "caffeine", "café"],
118
  "gluten": ["gluten", "trigo", "cebada", "centeno"],
 
124
  }
125
 
126
  def validar_seguro_logico(entry, user_foods, food_compound_map):
 
 
 
 
 
 
127
  if not user_foods:
128
  return True
129
 
 
271
  }
272
  }
273
 
274
+ # --- FUNCIONES DE APOYO Y EXTRACCIÓN ---
275
  @st.cache_data(show_spinner=False)
276
  def generate_word_report_cached(report_text):
277
  try:
 
317
 
318
  @retry(wait=wait_fixed(2), stop=stop_after_attempt(2))
319
  def extract_entities_with_gemini(query):
320
+ gemini_client = get_gemini_client()
321
+ if not gemini_client: return {"alimentos": [], "sintomas": []}
322
+
323
  system_prompt = f"""
324
  Rol: Extractor Médico JSON experto en nutrición y síntomas.
325
  Tarea: Analiza el texto del usuario y extrae un JSON estricto con dos claves:
 
330
  Output (JSON estricto):
331
  """
332
  try:
333
+ response = gemini_client.models.generate_content(
334
  model='gemini-2.5-flash-lite',
335
  contents=system_prompt
336
  )
 
393
 
394
  results = []
395
  for entry in data:
396
+ # SEGURO LÓGICO
397
  if food_compound_map and not validar_seguro_logico(entry, user_foods_list, food_compound_map):
398
  continue
399
 
 
495
 
496
  @retry(wait=wait_fixed(2), stop=stop_after_attempt(2))
497
  def generate_detailed_analysis(query, match, entities=None, food_compound_map=None, neuro_map=None):
498
+ gemini_client = get_gemini_client()
499
+ if not gemini_client: return "Error: IA no disponible."
500
 
501
  compound_context = ""
502
  if entities and food_compound_map and neuro_map:
 
527
  NO incluyas descargos de responsabilidad.
528
  """
529
  try:
530
+ response = gemini_client.models.generate_content(
531
  model='gemini-2.5-flash-lite',
532
  contents=prompt
533
  )
 
603
  x='Relevancia', y=alt.Y('Condición', sort='-x'), tooltip=['Condición', 'Relevancia']
604
  ).properties(title='Top Coincidencias')
605
 
606
+ # --- INICIALIZACIÓN DE ESTADO PERSISTENTE ---
607
+ if 'search_results' not in st.session_state: st.session_state.search_results = None
608
+ if 'user_query' not in st.session_state: st.session_state.user_query = ""
609
+ if 'entities' not in st.session_state: st.session_state.entities = None
610
+ if 'analysis_cache' not in st.session_state: st.session_state.analysis_cache = {}
611
+ if 'has_searched' not in st.session_state: st.session_state.has_searched = False
612
+
613
+ # --- INTERFAZ GRAFICA STREAMLIT ---
614
  col_img1, col_text, col_img2 = st.columns([1, 4, 1])
615
  with col_img1:
616
  if os.path.exists("imagen.png"): st.image("imagen.png", width=150)
 
620
  with col_img2:
621
  if os.path.exists("buho.png"): st.image("buho.png", width=120)
622
 
623
+ with st.form(key="search_form", clear_on_submit=False):
624
+ query_input = st.text_area(
625
+ "Tu Caso:",
626
+ height=150,
627
+ placeholder="Ej: Cuando tomo mucho jugo de naranja no puedo dormir.",
628
+ key="input_query_text"
629
+ )
630
  submitted = st.form_submit_button("Analizar Caso", type="primary")
631
 
632
+ if submitted:
633
+ if query_input and query_input.strip():
634
+ current_query = query_input.strip()
635
+ st.session_state.user_query = current_query
636
+ st.session_state.has_searched = True
637
+ st.session_state.analysis_cache = {} # Limpiar análisis previo
638
+ st.session_state.search_results = None
639
+ st.session_state.entities = None
 
 
 
640
 
641
+ with st.spinner("🔍 Analizando pistas..."):
642
+ raw_entities = extract_entities_with_gemini(current_query)
643
+ reinforced = reinforce_entities_with_keywords(raw_entities, current_query, FOOD_TO_COMPOUND_MAP, MASTER_SYMPTOM_MAP)
644
+ final_symptoms = translate_symptoms_local(reinforced.get("sintomas", []), MASTER_SYMPTOM_MAP)
645
+ final_entities = {"alimentos": reinforced.get("alimentos", []), "sintomas": final_symptoms}
646
+ st.session_state.entities = final_entities
647
+
648
+ results = find_best_matches_hybrid(final_entities, alimentos_data, FOOD_TO_COMPOUND_MAP)
649
+
650
+ if not results:
651
+ local_hypothesis = build_local_hypothesis(final_entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP)
652
+ if local_hypothesis:
653
+ results = [{'entry': local_hypothesis, 'score': {'total': 50}, 'matched_keywords': []}]
654
+
655
+ st.session_state.search_results = results if results else []
656
+ else:
657
+ st.warning("⚠️ Por favor describe tus síntomas o alimentos consumidos antes de analizar.")
658
 
659
+ # --- DISPLAY DE RESULTADOS INDEPENDIENTE Y REUTILIZABLE ---
660
+ if st.session_state.has_searched:
661
  results = st.session_state.search_results
 
 
 
662
 
663
+ if results and len(results) > 0:
664
+ best_match = results[0]['entry']
665
+ st.success(f"🔎 Coincidencia Principal: **{best_match.get('condicion_asociada')}**")
666
+
667
+ # Clave de caché única por consulta y condición
668
+ cache_key = f"analysis_{hash(st.session_state.user_query)}_{best_match.get('condicion_asociada')}"
669
+ if cache_key not in st.session_state.analysis_cache:
670
+ with st.spinner("✍️ Redactando informe..."):
671
+ analysis = generate_detailed_analysis(
672
+ st.session_state.user_query, best_match,
673
+ st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
674
+ )
675
+ st.session_state.analysis_cache[cache_key] = analysis
676
+
677
+ tab_main, tab_neuro, tab_mol = st.tabs(["💡 Interpretación Clínica", "🧠 Efectos Neuropsicológicos", "🔬 Análisis Molecular"])
678
+
679
+ with tab_main:
680
+ st.markdown(st.session_state.analysis_cache[cache_key])
681
+ if len(results) > 1:
682
+ st.altair_chart(create_relevance_chart(results), use_container_width=True)
683
+
684
+ with tab_neuro:
685
+ st.markdown(generate_neuro_report_text(st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP))
686
+
687
+ with tab_mol:
688
+ st.markdown(generate_molecular_report_text(
689
+ best_match, st.session_state.entities, foodb_index, foodb_lower_map,
690
+ FOOD_NAME_TO_FOODB_KEY, COMPOUND_SYNONYM_MAP, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
691
+ ))
692
+
693
+ full_report = f"REPORTE\n\n{st.session_state.analysis_cache[cache_key]}"
694
+ word_bytes = generate_word_report_cached(full_report)
695
+ if word_bytes:
696
+ st.download_button(
697
+ label="📄 Descargar Informe",
698
+ data=word_bytes,
699
+ file_name="Reporte.docx",
700
+ mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
701
+ key=f"dl_btn_{hash(st.session_state.user_query)}"
702
  )
703
+ else:
704
+ st.warning("⚠️ No pudimos identificar una causa clara para este caso específico.")
705
+ st.info("Intenta reformular especificando el alimento y el síntoma. Ej: 'Siento [SÍNTOMA] cuando como [ALIMENTO]'.")