JairoCesar commited on
Commit
93dbc98
·
verified ·
1 Parent(s): 3502efb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -52
app.py CHANGED
@@ -5,16 +5,18 @@
5
 
6
  import streamlit as st
7
  import google.generativeai as genai
8
- import google.api_core.exceptions # Import para manejar errores de API
9
  import os
10
  import json
11
  import logging
12
  import datetime
 
 
13
 
14
  # --- CONFIGURACIÓN BÁSICA ---
15
  st.set_page_config(
16
  page_title="Buscador Inteligente SIVIGILA Colombia",
17
- page_icon="buho.png", # Añadido el ícono del búho
18
  layout="wide"
19
  )
20
 
@@ -24,10 +26,9 @@ logger = logging.getLogger("sivigila_app")
24
 
25
  # --- CONFIGURACIÓN DE GEMINI ---
26
  try:
27
-
28
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
29
  if not GEMINI_API_KEY:
30
- st.error("No se encontró la variable de entorno GEMINI_API_KEY. Por favor, configúrala en los Secrets de tu Space.")
31
  logger.error("GEMINI_API_KEY no encontrada.")
32
  st.stop()
33
  genai.configure(api_key=GEMINI_API_KEY)
@@ -55,21 +56,12 @@ def load_data():
55
  try:
56
  path_codigos = os.path.join('PLANTILLAS', 'DIC_NOMBRES_CODIGOS_FICHAS.json')
57
  path_notificacion = os.path.join('PLANTILLAS', 'DATOS_NOTIFICACION.json')
58
-
59
- with open(path_codigos, 'r', encoding='utf-8') as f:
60
- data_codigos = json.load(f)
61
-
62
- with open(path_notificacion, 'r', encoding='utf-8') as f:
63
- data_notificacion = json.load(f)
64
-
65
  notificacion_map = {item['FICHA']: item for item in data_notificacion}
66
-
67
-
68
  nombres_eventos_limpios = sorted(list(set(item['Evento'] for item in data_notificacion)))
69
-
70
  logger.info(f"✅ Datos cargados: {len(data_codigos)} registros de códigos y {len(data_notificacion)} eventos de notificación.")
71
  return data_codigos, data_notificacion, notificacion_map, nombres_eventos_limpios
72
-
73
  except FileNotFoundError as e:
74
  st.error(f"❌ Error: No se encontró el archivo {e.filename}. Asegúrate de que los archivos JSON estén en la carpeta 'PLANTILLAS'.")
75
  return None, None, None, None
@@ -107,54 +99,89 @@ def search_direct(query, data, notif_map):
107
 
108
 
109
  def search_with_gemini(query, event_list):
110
- """Usa la API de Gemini para encontrar el evento más relevante."""
111
  if not model: return "Error: Modelo Gemini no disponible."
112
-
113
-
114
  system_prompt = f"""
115
  Eres un asesor experto en SIVIGILA, el sistema de vigilancia en salud pública de Colombia.
116
  Tu tarea es interpretar la consulta de un profesional de la salud, que puede ser un término coloquial, un sinónimo o una descripción, y asociarla al nombre del evento oficial más relevante de una lista.
117
-
118
  LISTA DE EVENTOS OFICIALES:
119
  {'; '.join(event_list)}
120
-
121
  Analiza la siguiente consulta y devuelve ÚNICAMENTE el nombre exacto del evento oficial de la lista que mejor corresponda.
122
  Por ejemplo, si la consulta es 'mordedura de culebra', la respuesta debe ser 'Accidente Ofídico'.
123
  Si encuentras varios, sepáralos con un punto y coma (;).
124
  Si la consulta no corresponde a ningún evento de la lista, responde con "NO_ENCONTRADO".
125
  No añadas explicaciones ni texto adicional.
126
-
127
  Consulta del usuario: "{query}"
128
  """
129
-
130
-
131
  try:
132
- logger.info(f"🔄 Enviando consulta a Gemini API: '{query}'")
133
  response = model.generate_content(system_prompt)
134
-
135
  if response.parts:
136
  result_text = response.text.strip()
137
- logger.info(f"✅ Respuesta de Gemini API: '{result_text}'")
138
  if result_text == "NO_ENCONTRADO": return []
139
  return [name.strip() for name in result_text.split(';') if name.strip()]
140
  else:
141
- logger.warning(f"⚠️ Respuesta de Gemini vacía o bloqueada. Razón: {response.prompt_feedback}")
142
  return "Respuesta bloqueada por filtros de seguridad."
143
-
144
  except google.api_core.exceptions.ResourceExhausted as e:
145
- logger.warning(f"Límite de frecuencia de la API alcanzado: {e}")
146
  return "Se ha alcanzado el límite de consultas. Por favor, espere un minuto y vuelva a intentarlo."
147
  except Exception as e:
148
- logger.error(f"❌ Error en la llamada a Gemini: {e}")
149
  return f"Error en la comunicación con la API de Gemini: {e}"
150
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
- # --- INTERFAZ DE USUARIO (UI) ---
 
 
 
 
 
 
 
153
 
 
 
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  col_img, col_text = st.columns([1, 5], gap="medium")
156
- with col_img:
157
- st.image("buho.png", width=100)
158
  with col_text:
159
  st.title("Buscador Inteligente SIVIGILA")
160
  st.markdown("Herramienta de apoyo para la notificación de eventos de salud pública en Colombia.")
@@ -177,37 +204,26 @@ if submitted:
177
  with st.spinner("Buscando con IA (Gemini)..."):
178
  results = []
179
  direct_results = search_direct(query, data_codigos, notificacion_map)
180
-
181
  if not direct_results:
182
- logger.info(f"Búsqueda directa para '{query}' no arrojó resultados. Usando IA (Gemini)...")
183
  event_names = search_with_gemini(query, nombres_eventos_limpios)
184
-
185
  if isinstance(event_names, str):
186
  st.error(event_names)
187
  event_names = []
188
-
189
-
190
  if event_names:
191
  fichas_encontradas = set()
192
  for name in event_names:
193
  for item in data_notificacion:
194
- if item['Evento'] == name:
195
- fichas_encontradas.add(item['FICHA'])
196
-
197
  for ficha in fichas_encontradas:
198
  info_notif = notificacion_map.get(ficha)
199
  detalles_cods = [d for d in data_codigos if d.get("FICHA") == ficha]
200
- if info_notif:
201
- results.append({"info_notificacion": info_notif, "detalles_codigos": detalles_cods})
202
-
203
  else:
204
  results = direct_results
205
-
206
  st.session_state.search_results = results
207
  st.session_state.last_query = query
208
 
209
  if st.session_state.search_results is not None: st.button("Limpiar Búsqueda", on_click=clear_search_state)
210
-
211
  st.markdown("---")
212
 
213
  if st.session_state.search_results is not None:
@@ -220,8 +236,12 @@ if st.session_state.search_results is not None:
220
  info = result["info_notificacion"]
221
  codigos = result["detalles_codigos"]
222
  with st.expander(f"**Evento: {info.get('Evento', 'Sin nombre')} (Ficha: {info.get('FICHA', 'N/A')})**", expanded=True):
223
- tab1, tab2 = st.tabs(["Información General", "📖 Definición de Caso"])
224
- with tab1:
 
 
 
 
225
  st.subheader("Información de Notificación")
226
  notif_super = info.get("Notificación superinmedita", "NO") == "SI"
227
  if notif_super: st.error(f"**¡ATENCIÓN! Requiere Notificación SUPERINMEDIATA:** {info.get('Descripción superinmedita', '')}")
@@ -263,7 +283,29 @@ if st.session_state.search_results is not None:
263
  st.subheader("Códigos Relacionados (CIE-10 y CIE-11)")
264
  for codigo in codigos:
265
  st.markdown(f"""- **Nombre Protocolo:** `{codigo.get('NOMBRES DE PROTOCOLOS', 'N/A')}`\n - **CIE-10:** `{codigo.get('CIE 10', 'N/A')}`\n - **CIE-11:** `{codigo.get('CIE 11', 'N/A')}` | **Nombre CIE-11:** *{codigo.get('NOMBRES DE CIE 11', 'N/A')}*""")
266
- with tab2:
267
- definicion_de_caso=info.get("Definición de caso","").strip()
268
- if definicion_de_caso: st.markdown(definicion_de_caso.replace('\n',' \n'))
269
- else: st.info("No se encontró una definición de caso oficial para este evento.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  import streamlit as st
7
  import google.generativeai as genai
8
+ import google.api_core.exceptions
9
  import os
10
  import json
11
  import logging
12
  import datetime
13
+ from dotenv import load_dotenv
14
+ load_dotenv()
15
 
16
  # --- CONFIGURACIÓN BÁSICA ---
17
  st.set_page_config(
18
  page_title="Buscador Inteligente SIVIGILA Colombia",
19
+ page_icon="buho.png",
20
  layout="wide"
21
  )
22
 
 
26
 
27
  # --- CONFIGURACIÓN DE GEMINI ---
28
  try:
 
29
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
30
  if not GEMINI_API_KEY:
31
+ st.error("No se encontró la variable de entorno GEMINI_API_KEY. Por favor, configúrala en tu archivo .env.")
32
  logger.error("GEMINI_API_KEY no encontrada.")
33
  st.stop()
34
  genai.configure(api_key=GEMINI_API_KEY)
 
56
  try:
57
  path_codigos = os.path.join('PLANTILLAS', 'DIC_NOMBRES_CODIGOS_FICHAS.json')
58
  path_notificacion = os.path.join('PLANTILLAS', 'DATOS_NOTIFICACION.json')
59
+ with open(path_codigos, 'r', encoding='utf-8') as f: data_codigos = json.load(f)
60
+ with open(path_notificacion, 'r', encoding='utf-8') as f: data_notificacion = json.load(f)
 
 
 
 
 
61
  notificacion_map = {item['FICHA']: item for item in data_notificacion}
 
 
62
  nombres_eventos_limpios = sorted(list(set(item['Evento'] for item in data_notificacion)))
 
63
  logger.info(f"✅ Datos cargados: {len(data_codigos)} registros de códigos y {len(data_notificacion)} eventos de notificación.")
64
  return data_codigos, data_notificacion, notificacion_map, nombres_eventos_limpios
 
65
  except FileNotFoundError as e:
66
  st.error(f"❌ Error: No se encontró el archivo {e.filename}. Asegúrate de que los archivos JSON estén en la carpeta 'PLANTILLAS'.")
67
  return None, None, None, None
 
99
 
100
 
101
  def search_with_gemini(query, event_list):
 
102
  if not model: return "Error: Modelo Gemini no disponible."
 
 
103
  system_prompt = f"""
104
  Eres un asesor experto en SIVIGILA, el sistema de vigilancia en salud pública de Colombia.
105
  Tu tarea es interpretar la consulta de un profesional de la salud, que puede ser un término coloquial, un sinónimo o una descripción, y asociarla al nombre del evento oficial más relevante de una lista.
 
106
  LISTA DE EVENTOS OFICIALES:
107
  {'; '.join(event_list)}
 
108
  Analiza la siguiente consulta y devuelve ÚNICAMENTE el nombre exacto del evento oficial de la lista que mejor corresponda.
109
  Por ejemplo, si la consulta es 'mordedura de culebra', la respuesta debe ser 'Accidente Ofídico'.
110
  Si encuentras varios, sepáralos con un punto y coma (;).
111
  Si la consulta no corresponde a ningún evento de la lista, responde con "NO_ENCONTRADO".
112
  No añadas explicaciones ni texto adicional.
 
113
  Consulta del usuario: "{query}"
114
  """
 
 
115
  try:
 
116
  response = model.generate_content(system_prompt)
 
117
  if response.parts:
118
  result_text = response.text.strip()
 
119
  if result_text == "NO_ENCONTRADO": return []
120
  return [name.strip() for name in result_text.split(';') if name.strip()]
121
  else:
 
122
  return "Respuesta bloqueada por filtros de seguridad."
 
123
  except google.api_core.exceptions.ResourceExhausted as e:
 
124
  return "Se ha alcanzado el límite de consultas. Por favor, espere un minuto y vuelva a intentarlo."
125
  except Exception as e:
 
126
  return f"Error en la comunicación con la API de Gemini: {e}"
127
 
128
+ # =========================================================================
129
+ # ========= INICIO DE LA FUNCIÓN DE ANÁLISIS ACTUALIZADA ====================
130
+ # =========================================================================
131
+ def analyze_query_with_gemini(query, definition, evento_name, ficha_number):
132
+ """
133
+ Usa la API de Gemini para analizar la consulta y emitir una recomendación
134
+ prescriptiva sobre la notificación.
135
+ """
136
+ if not model: return "Error: Modelo Gemini no disponible."
137
+
138
+ analysis_prompt = f"""
139
+ Eres un auditor experto del sistema SIVIGILA de Colombia. Tu única misión es determinar si un caso descrito por un profesional de la salud cumple con los criterios de la definición de caso oficial para ser de notificación obligatoria.
140
 
141
+ **Reglas Estrictas:**
142
+ 1. Tu decisión final solo puede ser "NOTIFICAR" o "NO CUMPLE CRITERIOS".
143
+ 2. Debes basar tu justificación únicamente en la comparación entre la consulta y la definición de caso proporcionada.
144
+ 3. No debes inventar información ni hacer suposiciones clínicas.
145
+
146
+ **CONTEXTO DEL EVENTO YA IDENTIFICADO:**
147
+ - Nombre del Evento: "{evento_name}"
148
+ - Ficha de Notificación: "{ficha_number}"
149
 
150
+ **CONSULTA DEL USUARIO:**
151
+ "{query}"
152
 
153
+ **DEFINICIÓN DE CASO OFICIAL:**
154
+ "{definition}"
155
+
156
+ **TU ANÁLISIS ESTRUCTURADO:**
157
+ Basado en la comparación, responde OBLIGATORIAMENTE en el siguiente formato, sin añadir texto adicional:
158
+
159
+ **Decisión:** [Escribe aquí "NOTIFICAR" o "NO CUMPLE CRITERIOS"]
160
+
161
+ **Justificación:**
162
+ [Si la decisión es NOTIFICAR, explica brevemente qué criterios de la definición de caso se cumplen en la consulta del usuario. Si la decisión es NO CUMPLE CRITERIOS, explica qué criterios clave faltan o se contradicen explícitamente.]
163
+
164
+ **Recomendación:**
165
+ [Si la decisión es NOTIFICAR, redacta una recomendación para proceder con la notificación, mencionando explícitamente el número de Ficha y el nombre del Evento proporcionados en el contexto. Si la decisión es NO CUMPLE CRITERIOS, escribe: "Se recomienda revisar las guías y protocolos del Instituto Nacional de Salud de Colombia para determinar el manejo adecuado del caso."]
166
+ """
167
+ try:
168
+ response = model.generate_content(analysis_prompt)
169
+ if response.parts:
170
+ return response.text
171
+ else:
172
+ return "El análisis no pudo ser completado. La respuesta de la IA fue bloqueada por filtros de seguridad."
173
+ except google.api_core.exceptions.ResourceExhausted:
174
+ return "Se ha alcanzado el límite de consultas para el análisis. Por favor, espere un minuto y vuelva a intentarlo."
175
+ except Exception as e:
176
+ return f"Ocurrió un error durante el análisis de la IA: {e}"
177
+ # =========================================================================
178
+ # ========= FIN DE LA FUNCIÓN DE ANÁLISIS ACTUALIZADA =====================
179
+ # =========================================================================
180
+
181
+
182
+ # --- INTERFAZ DE USUARIO (UI) ---
183
  col_img, col_text = st.columns([1, 5], gap="medium")
184
+ with col_img: st.image("buho.png", width=100)
 
185
  with col_text:
186
  st.title("Buscador Inteligente SIVIGILA")
187
  st.markdown("Herramienta de apoyo para la notificación de eventos de salud pública en Colombia.")
 
204
  with st.spinner("Buscando con IA (Gemini)..."):
205
  results = []
206
  direct_results = search_direct(query, data_codigos, notificacion_map)
 
207
  if not direct_results:
 
208
  event_names = search_with_gemini(query, nombres_eventos_limpios)
 
209
  if isinstance(event_names, str):
210
  st.error(event_names)
211
  event_names = []
 
 
212
  if event_names:
213
  fichas_encontradas = set()
214
  for name in event_names:
215
  for item in data_notificacion:
216
+ if item['Evento'] == name: fichas_encontradas.add(item['FICHA'])
 
 
217
  for ficha in fichas_encontradas:
218
  info_notif = notificacion_map.get(ficha)
219
  detalles_cods = [d for d in data_codigos if d.get("FICHA") == ficha]
220
+ if info_notif: results.append({"info_notificacion": info_notif, "detalles_codigos": detalles_cods})
 
 
221
  else:
222
  results = direct_results
 
223
  st.session_state.search_results = results
224
  st.session_state.last_query = query
225
 
226
  if st.session_state.search_results is not None: st.button("Limpiar Búsqueda", on_click=clear_search_state)
 
227
  st.markdown("---")
228
 
229
  if st.session_state.search_results is not None:
 
236
  info = result["info_notificacion"]
237
  codigos = result["detalles_codigos"]
238
  with st.expander(f"**Evento: {info.get('Evento', 'Sin nombre')} (Ficha: {info.get('FICHA', 'N/A')})**", expanded=True):
239
+
240
+ # Se mantienen las 3 pestañas
241
+ tab_info, tab_def, tab_analysis = st.tabs(["Información General", "📖 Definición de Caso", "🤖 Análisis por IA"])
242
+
243
+ with tab_info:
244
+ # El contenido de esta pestaña no cambia
245
  st.subheader("Información de Notificación")
246
  notif_super = info.get("Notificación superinmedita", "NO") == "SI"
247
  if notif_super: st.error(f"**¡ATENCIÓN! Requiere Notificación SUPERINMEDIATA:** {info.get('Descripción superinmedita', '')}")
 
283
  st.subheader("Códigos Relacionados (CIE-10 y CIE-11)")
284
  for codigo in codigos:
285
  st.markdown(f"""- **Nombre Protocolo:** `{codigo.get('NOMBRES DE PROTOCOLOS', 'N/A')}`\n - **CIE-10:** `{codigo.get('CIE 10', 'N/A')}`\n - **CIE-11:** `{codigo.get('CIE 11', 'N/A')}` | **Nombre CIE-11:** *{codigo.get('NOMBRES DE CIE 11', 'N/A')}*""")
286
+
287
+ with tab_def:
288
+ definicion_de_caso = info.get("Definición de caso", "").strip()
289
+ if definicion_de_caso: st.markdown(definicion_de_caso.replace('\n', ' \n'))
290
+ else: st.info("No se encontró una definición de caso oficial para este evento.")
291
+
292
+ with tab_analysis:
293
+ st.info("Este análisis compara su búsqueda original con la definición oficial del evento encontrado.")
294
+ definicion_de_caso_para_analisis = info.get("Definición de caso", "").strip()
295
+
296
+ if not definicion_de_caso_para_analisis:
297
+ st.warning("No se puede realizar el análisis porque no hay una definición de caso disponible para este evento.")
298
+ else:
299
+ with st.spinner("Realizando análisis con IA..."):
300
+ # =========================================================================
301
+ # ========= INICIO DE LA LLAMADA A LA FUNCIÓN ACTUALIZADA =================
302
+ # =========================================================================
303
+ # Extraemos el nombre y la ficha del evento para pasarlos a la función
304
+ evento_name = info.get("Evento", "N/A")
305
+ ficha_number = info.get("FICHA", "N/A")
306
+
307
+ analysis_result = analyze_query_with_gemini(st.session_state.last_query, definicion_de_caso_para_analisis, evento_name, ficha_number)
308
+ st.markdown(analysis_result)
309
+ # =========================================================================
310
+ # ========= FIN DE LA LLAMADA A LA FUNCIÓN ACTUALIZADA =====================
311
+ # =========================================================================