JairoCesar commited on
Commit
7de5686
·
verified ·
1 Parent(s): c83d53a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +49 -39
app.py CHANGED
@@ -1,4 +1,5 @@
1
- # ==================== Buscador de Eventos para Notificación SIVIGILA - Colombia 2025 =====================================
 
2
 
3
  # JAIRO ALEXANDER ERASO MD U Nacional de Colombia.
4
  # DIANA MILENA SOLER MARTINEZ U Juan N. Corpas
@@ -12,8 +13,9 @@ import logging
12
  import datetime
13
 
14
 
 
15
  st.set_page_config(
16
- page_title="Buscador Inteligente de eventos para notificación SIVIGILA Colombia",
17
  page_icon="buho.png",
18
  layout="wide"
19
  )
@@ -26,7 +28,7 @@ logger = logging.getLogger("sivigila_app")
26
  try:
27
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
28
  if not GEMINI_API_KEY:
29
- st.error("No se encontró la variable de entorno GEMINI_API_KEY. Por favor, configúrala en tu archivo .env.")
30
  logger.error("GEMINI_API_KEY no encontrada.")
31
  st.stop()
32
  genai.configure(api_key=GEMINI_API_KEY)
@@ -54,12 +56,19 @@ def load_data():
54
  try:
55
  path_codigos = os.path.join('PLANTILLAS', 'DIC_NOMBRES_CODIGOS_FICHAS.json')
56
  path_notificacion = os.path.join('PLANTILLAS', 'DATOS_NOTIFICACION.json')
57
- with open(path_codigos, 'r', encoding='utf-8') as f: data_codigos = json.load(f)
58
- with open(path_notificacion, 'r', encoding='utf-8') as f: data_notificacion = json.load(f)
 
 
 
 
 
59
  notificacion_map = {item['FICHA']: item for item in data_notificacion}
60
  nombres_eventos_limpios = sorted(list(set(item['Evento'] for item in data_notificacion)))
61
- logger.info(f"✅ Datos cargados: {len(data_codigos)} registros de códigos y {len(data_notificacion)} eventos de notificación.")
 
62
  return data_codigos, data_notificacion, notificacion_map, nombres_eventos_limpios
 
63
  except FileNotFoundError as e:
64
  st.error(f"❌ Error: No se encontró el archivo {e.filename}. Asegúrate de que los archivos JSON estén en la carpeta 'PLANTILLAS'.")
65
  return None, None, None, None
@@ -125,57 +134,43 @@ def search_with_gemini(query, event_list):
125
 
126
 
127
  def analyze_query_with_gemini(query, definition, evento_name, ficha_number):
128
- """
129
- Usa la API de Gemini para analizar la consulta y emitir una recomendación
130
- prescriptiva sobre la notificación.
131
- """
132
  if not model: return "Error: Modelo Gemini no disponible."
133
-
134
  analysis_prompt = f"""
135
  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.
136
-
137
  **Reglas Estrictas:**
138
- 1. Tu decisión final solo puede ser "NOTIFICAR" o "NO CUMPLE CRITERIOS".
139
- 2. Debes basar tu justificación únicamente en la comparación entre la consulta y la definición de caso proporcionada.
140
- 3. No debes inventar información ni hacer suposiciones clínicas.
141
-
142
  **CONTEXTO DEL EVENTO YA IDENTIFICADO:**
143
  - Nombre del Evento: "{evento_name}"
144
  - Ficha de Notificación: "{ficha_number}"
145
-
146
  **CONSULTA DEL USUARIO:**
147
  "{query}"
148
-
149
  **DEFINICIÓN DE CASO OFICIAL:**
150
  "{definition}"
151
-
152
  **TU ANÁLISIS ESTRUCTURADO:**
153
  Basado en la comparación, responde OBLIGATORIAMENTE en el siguiente formato, sin añadir texto adicional:
154
-
155
  **Decisión:** [Escribe aquí "NOTIFICAR" o "NO CUMPLE CRITERIOS"]
156
-
157
  **Justificación:**
158
  [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.]
159
-
160
  **Recomendación:**
161
  [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."]
162
  """
163
  try:
164
  response = model.generate_content(analysis_prompt)
165
- if response.parts:
166
- return response.text
167
- else:
168
- return "El análisis no pudo ser completado. La respuesta de la IA fue bloqueada por filtros de seguridad."
169
  except google.api_core.exceptions.ResourceExhausted:
170
  return "Se ha alcanzado el límite de consultas para el análisis. Por favor, espere un minuto y vuelva a intentarlo."
171
  except Exception as e:
172
  return f"Ocurrió un error durante el análisis de la IA: {e}"
173
 
 
174
  # --- INTERFAZ DE USUARIO (UI) ---
175
  col_img, col_text = st.columns([1, 5], gap="medium")
176
  with col_img: st.image("buho.png", width=100)
177
  with col_text:
178
- st.title("Buscador Inteligente para notificar SIVIGILA CIE 10-11")
179
  st.markdown("Herramienta de apoyo para la notificación de eventos de salud pública en Colombia.")
180
 
181
  if 'search_results' not in st.session_state: st.session_state.search_results = None
@@ -186,7 +181,7 @@ def clear_search_state():
186
  st.session_state.last_query = ""
187
 
188
  with st.form(key="search_form"):
189
- query = st.text_input("Ingrese su búsqueda:", placeholder="Ej: Lepra, T630, mordedura de serpiente, Niño 3 años con 9 kilos, 3MC...", help="Puede buscar por nombre de la enfermedad, palabras clave, o códigos CIE.")
190
  submitted = st.form_submit_button("Buscar", type="primary")
191
 
192
  if submitted:
@@ -196,22 +191,44 @@ if submitted:
196
  with st.spinner("Buscando con IA (Gemini)..."):
197
  results = []
198
  direct_results = search_direct(query, data_codigos, notificacion_map)
 
199
  if not direct_results:
 
200
  event_names = search_with_gemini(query, nombres_eventos_limpios)
 
201
  if isinstance(event_names, str):
202
  st.error(event_names)
203
  event_names = []
 
204
  if event_names:
205
  fichas_encontradas = set()
206
  for name in event_names:
207
  for item in data_notificacion:
208
  if item['Evento'] == name: fichas_encontradas.add(item['FICHA'])
 
 
 
209
  for ficha in fichas_encontradas:
210
  info_notif = notificacion_map.get(ficha)
211
- detalles_cods = [d for d in data_codigos if d.get("FICHA") == ficha]
212
- if info_notif: results.append({"info_notificacion": info_notif, "detalles_codigos": detalles_cods})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  else:
214
  results = direct_results
 
215
  st.session_state.search_results = results
216
  st.session_state.last_query = query
217
 
@@ -228,12 +245,8 @@ if st.session_state.search_results is not None:
228
  info = result["info_notificacion"]
229
  codigos = result["detalles_codigos"]
230
  with st.expander(f"**Evento: {info.get('Evento', 'Sin nombre')} (Ficha: {info.get('FICHA', 'N/A')})**", expanded=True):
231
-
232
- # Se mantienen las 3 pestañas
233
  tab_info, tab_def, tab_analysis = st.tabs(["Información General", "📖 Definición de Caso", "🤖 Análisis por IA"])
234
-
235
  with tab_info:
236
- # El contenido de esta pestaña no cambia
237
  st.subheader("Información de Notificación")
238
  notif_super = info.get("Notificación superinmedita", "NO") == "SI"
239
  if notif_super: st.error(f"**¡ATENCIÓN! Requiere Notificación SUPERINMEDIATA:** {info.get('Descripción superinmedita', '')}")
@@ -275,7 +288,7 @@ if st.session_state.search_results is not None:
275
  st.subheader("Códigos Relacionados (CIE-10 y CIE-11)")
276
  for codigo in codigos:
277
  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')}*""")
278
-
279
  with tab_def:
280
  definicion_de_caso = info.get("Definición de caso", "").strip()
281
  if definicion_de_caso: st.markdown(definicion_de_caso.replace('\n', ' \n'))
@@ -284,15 +297,12 @@ if st.session_state.search_results is not None:
284
  with tab_analysis:
285
  st.info("Este análisis compara su búsqueda original con la definición oficial del evento encontrado.")
286
  definicion_de_caso_para_analisis = info.get("Definición de caso", "").strip()
287
-
288
  if not definicion_de_caso_para_analisis:
289
  st.warning("No se puede realizar el análisis porque no hay una definición de caso disponible para este evento.")
290
  else:
291
- with st.spinner("Realizando análisis con IA..."):
292
 
293
- # Extraemos el nombre y la ficha del evento para pasarlos a la función
294
  evento_name = info.get("Evento", "N/A")
295
  ficha_number = info.get("FICHA", "N/A")
296
-
297
  analysis_result = analyze_query_with_gemini(st.session_state.last_query, definicion_de_caso_para_analisis, evento_name, ficha_number)
298
  st.markdown(analysis_result)
 
1
+ # ==================== Buscador de Eventos de Notificación SIVIGILA - Colombia 2025 =====================================
2
+ # Versión API Web Final con Lógica de IA Mejorada y Segundo Filtro
3
 
4
  # JAIRO ALEXANDER ERASO MD U Nacional de Colombia.
5
  # DIANA MILENA SOLER MARTINEZ U Juan N. Corpas
 
13
  import datetime
14
 
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
  )
 
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 o en los Secrets.")
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
+
60
+ with open(path_codigos, 'r', encoding='utf-8') as f:
61
+ data_codigos = json.load(f)
62
+
63
+ with open(path_notificacion, 'r', encoding='utf-8') as f:
64
+ data_notificacion = json.load(f)
65
+
66
  notificacion_map = {item['FICHA']: item for item in data_notificacion}
67
  nombres_eventos_limpios = sorted(list(set(item['Evento'] for item in data_notificacion)))
68
+
69
+ logger.info(f"✅ Datos cargados.")
70
  return data_codigos, data_notificacion, notificacion_map, nombres_eventos_limpios
71
+
72
  except FileNotFoundError as e:
73
  st.error(f"❌ Error: No se encontró el archivo {e.filename}. Asegúrate de que los archivos JSON estén en la carpeta 'PLANTILLAS'.")
74
  return None, None, None, None
 
134
 
135
 
136
  def analyze_query_with_gemini(query, definition, evento_name, ficha_number):
 
 
 
 
137
  if not model: return "Error: Modelo Gemini no disponible."
 
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
  **Reglas Estrictas:**
141
+ 1. Tu decisión final solo puede ser "NOTIFICAR" o "NO CUMPLE CRITERIOS".
142
+ 2. Debes basar tu justificación únicamente en la comparación entre la consulta y la definición de caso proporcionada.
143
+ 3. No debes inventar información ni hacer suposiciones clínicas.
 
144
  **CONTEXTO DEL EVENTO YA IDENTIFICADO:**
145
  - Nombre del Evento: "{evento_name}"
146
  - Ficha de Notificación: "{ficha_number}"
 
147
  **CONSULTA DEL USUARIO:**
148
  "{query}"
 
149
  **DEFINICIÓN DE CASO OFICIAL:**
150
  "{definition}"
 
151
  **TU ANÁLISIS ESTRUCTURADO:**
152
  Basado en la comparación, responde OBLIGATORIAMENTE en el siguiente formato, sin añadir texto adicional:
 
153
  **Decisión:** [Escribe aquí "NOTIFICAR" o "NO CUMPLE CRITERIOS"]
 
154
  **Justificación:**
155
  [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.]
 
156
  **Recomendación:**
157
  [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."]
158
  """
159
  try:
160
  response = model.generate_content(analysis_prompt)
161
+ if response.parts: return response.text
162
+ else: return "El análisis no pudo ser completado. La respuesta de la IA fue bloqueada por filtros de seguridad."
 
 
163
  except google.api_core.exceptions.ResourceExhausted:
164
  return "Se ha alcanzado el límite de consultas para el análisis. Por favor, espere un minuto y vuelva a intentarlo."
165
  except Exception as e:
166
  return f"Ocurrió un error durante el análisis de la IA: {e}"
167
 
168
+
169
  # --- INTERFAZ DE USUARIO (UI) ---
170
  col_img, col_text = st.columns([1, 5], gap="medium")
171
  with col_img: st.image("buho.png", width=100)
172
  with col_text:
173
+ st.title("Buscador Inteligente SIVIGILA")
174
  st.markdown("Herramienta de apoyo para la notificación de eventos de salud pública en Colombia.")
175
 
176
  if 'search_results' not in st.session_state: st.session_state.search_results = None
 
181
  st.session_state.last_query = ""
182
 
183
  with st.form(key="search_form"):
184
+ query = st.text_input("Ingrese su búsqueda:", placeholder="Ej: Lepra, T630, mordedura de serpiente, 3MC...", help="Puede buscar por nombre de la enfermedad, palabras clave, o códigos CIE.")
185
  submitted = st.form_submit_button("Buscar", type="primary")
186
 
187
  if submitted:
 
191
  with st.spinner("Buscando con IA (Gemini)..."):
192
  results = []
193
  direct_results = search_direct(query, data_codigos, notificacion_map)
194
+
195
  if not direct_results:
196
+ logger.info(f"Búsqueda directa para '{query}' no arrojó resultados. Usando IA (Gemini)...")
197
  event_names = search_with_gemini(query, nombres_eventos_limpios)
198
+
199
  if isinstance(event_names, str):
200
  st.error(event_names)
201
  event_names = []
202
+
203
  if event_names:
204
  fichas_encontradas = set()
205
  for name in event_names:
206
  for item in data_notificacion:
207
  if item['Evento'] == name: fichas_encontradas.add(item['FICHA'])
208
+
209
+ query_keywords = [kw for kw in query.lower().split() if len(kw) > 2] # Keywords de la búsqueda original
210
+
211
  for ficha in fichas_encontradas:
212
  info_notif = notificacion_map.get(ficha)
213
+ all_details_for_ficha = [d for d in data_codigos if d.get("FICHA") == ficha]
214
+
215
+ filtered_details = []
216
+ if query_keywords:
217
+ for detail in all_details_for_ficha:
218
+ protocol_name_lower = detail.get("NOMBRES DE PROTOCOLOS", "").lower()
219
+ if any(keyword in protocol_name_lower for keyword in query_keywords):
220
+ filtered_details.append(detail)
221
+
222
+ final_details = filtered_details if filtered_details else all_details_for_ficha
223
+
224
+ if info_notif:
225
+ results.append({
226
+ "info_notificacion": info_notif,
227
+ "detalles_codigos": final_details
228
+ })
229
  else:
230
  results = direct_results
231
+
232
  st.session_state.search_results = results
233
  st.session_state.last_query = query
234
 
 
245
  info = result["info_notificacion"]
246
  codigos = result["detalles_codigos"]
247
  with st.expander(f"**Evento: {info.get('Evento', 'Sin nombre')} (Ficha: {info.get('FICHA', 'N/A')})**", expanded=True):
 
 
248
  tab_info, tab_def, tab_analysis = st.tabs(["Información General", "📖 Definición de Caso", "🤖 Análisis por IA"])
 
249
  with tab_info:
 
250
  st.subheader("Información de Notificación")
251
  notif_super = info.get("Notificación superinmedita", "NO") == "SI"
252
  if notif_super: st.error(f"**¡ATENCIÓN! Requiere Notificación SUPERINMEDIATA:** {info.get('Descripción superinmedita', '')}")
 
288
  st.subheader("Códigos Relacionados (CIE-10 y CIE-11)")
289
  for codigo in codigos:
290
  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')}*""")
291
+
292
  with tab_def:
293
  definicion_de_caso = info.get("Definición de caso", "").strip()
294
  if definicion_de_caso: st.markdown(definicion_de_caso.replace('\n', ' \n'))
 
297
  with tab_analysis:
298
  st.info("Este análisis compara su búsqueda original con la definición oficial del evento encontrado.")
299
  definicion_de_caso_para_analisis = info.get("Definición de caso", "").strip()
 
300
  if not definicion_de_caso_para_analisis:
301
  st.warning("No se puede realizar el análisis porque no hay una definición de caso disponible para este evento.")
302
  else:
 
303
 
304
+ with st.spinner("Realizando análisis con IA..."):
305
  evento_name = info.get("Evento", "N/A")
306
  ficha_number = info.get("FICHA", "N/A")
 
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)