Alejo760 commited on
Commit
125f1fa
·
verified ·
1 Parent(s): f215c33

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +106 -252
app.py CHANGED
@@ -1,15 +1,18 @@
1
  import gradio as gr
2
  import os
3
- import fitz # PyMuPDF
4
- from groq import Groq
5
- from langchain_groq import ChatGroq
6
  import json
7
  import logging
 
 
 
 
 
8
 
9
  # Configuración de logging
10
  logging.basicConfig(level=logging.INFO)
11
  logger = logging.getLogger(__name__)
12
 
 
13
  RAID_WEIGHTS = {
14
  "Dolor": 0.21,
15
  "Discapacidad Funcional": 0.16,
@@ -20,40 +23,40 @@ RAID_WEIGHTS = {
20
  "Afronte": 0.12
21
  }
22
 
 
23
  RAID_PROMPT_TEMPLATE = """
24
- Analiza la siguiente conversación y registro clínico del paciente con artritis reumatoide.
25
 
26
- Si tanto el "Texto del paciente" como el "Registro clínico" están vacíos o no contienen información relevante, responde únicamente con el siguiente mensaje:
27
  "No hay información suficiente para calcular el RAID."
28
 
29
  En caso de haber información, extrae y asigna una puntuación del 0 al 10 para cada una de las siguientes categorías, basándote en los detalles proporcionados:
30
 
31
  1. Dolor:
32
- - Circule el número que mejor describa el dolor que sintió debido a su RA durante la última semana.
33
  - Escala: 0 = Ninguno, 10 = Extremo.
34
  2. Discapacidad Funcional:
35
- - Circule el número que mejor describa la dificultad que tuvo para realizar actividades diarias debido a su RA durante la última semana.
36
  - Escala: 0 = Sin dificultad, 10 = Dificultad extrema.
37
  3. Fatiga:
38
- - Circule el número que mejor describa la fatiga que experimentó debido a su RA durante la última semana.
39
  - Escala: 0 = Sin fatiga, 10 = Totalmente exhausto.
40
  4. Sueño:
41
- - Circule el número que mejor describa las dificultades para dormir que experimentó durante la última semana.
42
  - Escala: 0 = Sin dificultad, 10 = Dificultad extrema.
43
  5. Bienestar Físico:
44
- - Circule el número que mejor describa su nivel de bienestar físico en relación a su RA durante la última semana.
45
  - Escala: 0 = Muy bueno, 10 = Muy malo.
46
  6. Bienestar Emocional:
47
- - Circule el número que mejor describa su nivel de bienestar emocional en relación a su RA durante la última semana.
48
  - Escala: 0 = Muy bueno, 10 = Muy malo.
49
  7. Afronte:
50
- - Circule el número que mejor describa cómo enfrentó o se adaptó a su enfermedad durante la última semana.
51
  - Escala: 0 = Muy bien, 10 = Muy mal.
52
 
53
  Utiliza los siguientes pesos para calcular el valor final del RAID:
54
  {weights}
55
 
56
-
57
  La fórmula de cálculo es:
58
  RAID final = (Dolor × 0.21) + (Discapacidad Funcional × 0.16) + (Fatiga × 0.15) + (Bienestar Físico × 0.12) + (Sueño × 0.12) + (Bienestar Emocional × 0.12) + (Afronte × 0.12)
59
 
@@ -80,27 +83,53 @@ Ejemplo de respuesta JSON:
80
 
81
  Texto del paciente:
82
  {patient_text}
83
-
84
- Registro clínico:
85
- {clinical_record}
86
  """
87
 
 
 
 
 
 
 
 
 
 
88
 
89
- def evaluate_raid(patient_text, clinical_record):
90
- """Evalúa el RAID score basado en el texto del paciente y registro clínico.
91
- Si la respuesta es parcial, se asigna 0 a las categorías faltantes y se añade
92
- una clave 'error' con la descripción del problema."""
93
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  prompt = RAID_PROMPT_TEMPLATE.format(
95
- patient_text=patient_text[:2000], # Limitar texto para contexto
96
- clinical_record=clinical_record[:2000],
97
  weights=json.dumps(RAID_WEIGHTS, ensure_ascii=False)
98
  )
99
- logger.info("Evaluando RAID con prompt: %s", prompt)
100
 
101
  response = chat_groq.invoke(prompt)
102
  content = response.content
103
- logger.info("Respuesta de chat_groq: %s", content)
104
 
105
  # Extraer JSON de la respuesta
106
  start = content.find('{')
@@ -154,8 +183,7 @@ def evaluate_raid(patient_text, clinical_record):
154
  return partial_result
155
 
156
  def format_raid_results(raid_data):
157
- """Formatea los resultados del RAID para visualización.
158
- Si se produjo un error, se añade una nota indicando el problema."""
159
  if not raid_data:
160
  return "No se pudo calcular el RAID score"
161
 
@@ -188,151 +216,29 @@ def format_raid_results(raid_data):
188
  result += f"\n\n**Nota:** Se produjo un error durante el cálculo del RAID: {raid_data['error']}"
189
  return result
190
 
191
- # Inicialización del cliente
192
- api_key = os.environ.get("GROQ_API_KEY")
193
- if not api_key:
194
- raise ValueError("GROQ_API_KEY no está configurada en las variables de entorno.")
195
- client = Groq(api_key=api_key)
196
- model_name = "llama-3.3-70b-versatile"
197
- chat_groq = ChatGroq(model=model_name)
198
-
199
- # Funciones de procesamiento
200
- def transcribe_audio(audio_filepath):
201
- if not audio_filepath:
202
- return ""
203
- try:
204
- with open(audio_filepath, "rb") as file:
205
- transcription = client.audio.transcriptions.create(
206
- file=(audio_filepath, file.read()),
207
- model="whisper-large-v3",
208
- response_format="json",
209
- temperature=0.0
210
- )
211
- logger.info("Transcripción de audio exitosa.")
212
- return transcription.text
213
- except Exception as e:
214
- logger.error("Error en transcripción de audio: %s", e, exc_info=True)
215
- return ""
216
-
217
- def extract_text_from_pdf(pdf_path):
218
- try:
219
- text = ''
220
- with fitz.open(pdf_path) as doc:
221
- for page in doc:
222
- text += page.get_text()
223
- logger.info("Extracción de texto de PDF exitosa para: %s", pdf_path)
224
- return text
225
- except Exception as e:
226
- logger.error("Error al extraer texto de PDF %s: %s", pdf_path, e, exc_info=True)
227
- return ""
228
-
229
- def extract_texts_from_pdfs(pdfs):
230
- text = ''
231
- if not pdfs:
232
- return text
233
- for pdf in pdfs:
234
- if isinstance(pdf, dict) and 'name' in pdf:
235
- pdf_path = pdf['name']
236
- elif isinstance(pdf, str):
237
- pdf_path = pdf
238
- else:
239
- continue
240
- pdf_text = extract_text_from_pdf(pdf_path)
241
- text += pdf_text + "\n"
242
- return text
243
-
244
- def split_text_into_chunks(text, max_words_per_chunk):
245
- words = text.split()
246
- chunks = []
247
- for i in range(0, len(words), max_words_per_chunk):
248
- chunk = ' '.join(words[i:i + max_words_per_chunk])
249
- chunks.append(chunk)
250
- return chunks
251
-
252
- def organize_clinical_record(current_text, transcription_text, pdf_text):
253
- clinical_record_template = """
254
- MOTIVO DE CONSULTA: usa una frase en palabras del paciente entre comillas
255
- ENFERMEDAD ACTUAL:
256
- (usa terminología médica. En orden cronológico desde el inicio de los síntomas, no incluir la edad ni los antecedentes en esta sección, evolución de los síntomas, factores desencadenantes, hitos de la enfermedad del paciente, finaliza describiendo cómo se siente hoy)
257
- REVISIÓN POR SISTEMAS:
258
- (usa terminología médica)
259
- ANTECEDENTES:
260
- **Patológicos: (describir en lenguaje técnico médico la enfermedad con clasificación y complicaciones relacionadas de cada antecedente)
261
- **Alérgicos: (con tipo de reacción y a cuál medicamento)
262
- **Tóxicos: (exposición a biomasa, IPA, Alcohol por unidades estándar, otros, naturales)
263
- **Familiares:
264
- **Transfusionales:
265
- **Traumáticos:
266
- **Ginecológicos:
267
- **Quirúrgicos: (fecha y procedimiento)
268
- **Estado de vacunación:
269
- **Hospitalizaciones previas (fecha y descripción breve)
270
- **Medicamentos:
271
- AYUDAS DIAGNÓSTICAS:
272
- (ordenar todas las ayudas diagnósticas por fecha de forma que sea simple y sencillo leer los resultados para el médico, cuando se requiera presenta los resultados en miles ya x10e3 o similares; es decir, multiplica el resultado por 1000, asegurándote de no omitir ninguna ayuda, y no interpretes, solo pon los valores sin rango de normalidad en prosa. Separa cada examen con una coma, usa minúsculas y organiza por fechas.
273
- Por ejemplo:
274
- 11/10/2024: resultado 1 , resultado 2, ...
275
- 12/11/2023: resultado 1 , resultado 2, ...)
276
- """
277
- prompt = f"""
278
- Toma el siguiente borrador del registro clínico y actualízalo con la nueva información proporcionada, siguiendo la estructura dada:
279
- Estructura del Registro Clínico:
280
- {clinical_record_template}
281
- Borrador Actual del Registro Clínico:
282
- {current_text}
283
- Nueva Información de Audio:
284
- {transcription_text}
285
- Nueva Información del PDF:
286
- {pdf_text}
287
- Actualiza el borrador incorporando la nueva información en las secciones correspondientes, sin eliminar información previa que aún sea relevante.
288
- """
289
- try:
290
- organized_text = chat_groq.invoke(prompt)
291
- logger.info("Organización del registro clínico exitosa.")
292
- return organized_text.content
293
- except Exception as e:
294
- logger.error("Error al invocar ChatGroq para organizar el registro clínico: %s", e, exc_info=True)
295
- return current_text # Se retorna el texto actual si falla la invocación
296
-
297
- def process_input(audio, pdfs, current_text):
298
- try:
299
- transcription_text = transcribe_audio(audio)
300
- except Exception as e:
301
- transcription_text = ""
302
- logger.error("Error en transcripción de audio: %s", e, exc_info=True)
303
 
304
- try:
305
- pdf_text = extract_texts_from_pdfs(pdfs)
306
- except Exception as e:
307
- pdf_text = ""
308
- logger.error("Error en extracción de PDFs: %s", e, exc_info=True)
309
-
310
- updated_text = current_text
311
- raid_results = None
312
-
313
- # Procesamiento en lotes
314
- max_chunk_words = 2500
315
 
316
- for text_label, text_content in [("Audio", transcription_text), ("PDF", pdf_text)]:
317
- if not text_content:
318
- continue
319
-
320
- text_chunks = split_text_into_chunks(text_content, max_chunk_words)
321
-
322
- for chunk in text_chunks:
323
- transcription_chunk = chunk if text_label == "Audio" else ""
324
- pdf_chunk = chunk if text_label == "PDF" else ""
325
-
326
- # Actualizar registro clínico
327
- organized_record = organize_clinical_record(updated_text, transcription_chunk, pdf_chunk)
328
- if organized_record:
329
- updated_text = organized_record
330
-
331
- # Evaluar RAID solo con el audio (conversación actual)
332
- if text_label == "Audio" and chunk:
333
- raid_results = evaluate_raid(chunk, updated_text)
334
-
335
- return updated_text, format_raid_results(raid_results), ""
336
 
337
  # Configuración del tema
338
  theme = gr.themes.Base(
@@ -346,95 +252,43 @@ theme = gr.themes.Base(
346
  neutral_hue="neutral",
347
  )
348
 
349
- # Texto inicial
350
- initial_text = """
351
- MOTIVO DE CONSULTA:
352
-
353
- ENFERMEDAD ACTUAL:
354
-
355
- REVISIÓN POR SISTEMAS:
356
-
357
- ANTECEDENTES:
358
- **Patológicos:
359
- **Alérgicos:
360
- **Tóxicos:
361
- **Familiares:
362
- **Transfusionales:
363
- **Traumáticos:
364
- **Ginecológicos:
365
- **Quirúrgicos:
366
- **Estado de vacunación:
367
- **Hospitalizaciones previas:
368
- **Medicamentos:
369
-
370
- AYUDAS DIAGNÓSTICAS:
371
- """
372
-
373
- # Interfaz de Gradio
374
  with gr.Blocks(theme=theme) as iface:
375
- gr.Markdown("# Sistema de Evaluación Reumatológica")
376
 
377
  with gr.Row():
378
- iterative_output = gr.Textbox(
379
- label="Registro Clínico",
380
- value=initial_text,
381
- lines=20,
382
- elem_id="clinical_record"
383
- )
 
 
 
 
 
 
384
 
385
- raid_output = gr.Markdown(
386
- "### Resultados RAID\nEsperando evaluación...",
387
- elem_id="raid_results"
388
- )
389
-
390
- current_state = gr.State(value=initial_text)
391
-
392
- with gr.Row():
393
- audio_filepath = gr.Audio(sources=["microphone"], type="filepath",
394
- label="Conversación con el Paciente")
395
- pdf_files = gr.File(file_types=[".pdf"], label="Documentos Médicos",
396
- file_count="multiple")
397
-
398
- debug_output = gr.Textbox(label="Registros", lines=5, visible=True)
399
-
400
- # Actualizar current_state cuando se edite el registro clínico
401
- def on_text_change(updated_text):
402
- return updated_text
403
-
404
- iterative_output.change(
405
- fn=on_text_change,
406
- inputs=iterative_output,
407
- outputs=current_state
408
- )
409
-
410
- def on_audio_change(audio_filepath, pdfs, current_text):
411
- logger.info("on_audio_change: audio_filepath = %s", audio_filepath)
412
- if not audio_filepath:
413
- return current_text, "No se proporcionó audio.", current_text
414
- if not current_text or not current_text.strip():
415
- current_text = initial_text
416
- updated_text, debug_info, _ = process_input(audio_filepath, pdfs, current_text)
417
- return updated_text, debug_info, updated_text
418
 
419
- audio_filepath.change(
420
- fn=on_audio_change,
421
- inputs=[audio_filepath, pdf_files, iterative_output],
422
- outputs=[iterative_output, debug_output, current_state]
423
  )
424
 
425
- def on_pdfs_change(audio_filepath, pdfs, current_text):
426
- logger.info("on_pdfs_change: pdfs = %s", pdfs)
427
- if not pdfs:
428
- return current_text, "No se proporcionaron PDFs.", current_text
429
- if not current_text or not current_text.strip():
430
- current_text = initial_text
431
- updated_text, debug_info, _ = process_input(audio_filepath, pdfs, current_text)
432
- return updated_text, debug_info, updated_text
433
-
434
- pdf_files.upload(
435
- fn=on_pdfs_change,
436
- inputs=[audio_filepath, pdf_files, iterative_output],
437
- outputs=[iterative_output, debug_output, current_state]
438
  )
439
 
440
- iface.launch()
 
 
 
 
1
  import gradio as gr
2
  import os
 
 
 
3
  import json
4
  import logging
5
+ from groq import Groq
6
+ from langchain_groq import ChatGroq
7
+
8
+ # Cargar variables de entorno desde archivo .env
9
+
10
 
11
  # Configuración de logging
12
  logging.basicConfig(level=logging.INFO)
13
  logger = logging.getLogger(__name__)
14
 
15
+ # Pesos para el cálculo del RAID
16
  RAID_WEIGHTS = {
17
  "Dolor": 0.21,
18
  "Discapacidad Funcional": 0.16,
 
23
  "Afronte": 0.12
24
  }
25
 
26
+ # Plantilla de prompt para evaluación RAID
27
  RAID_PROMPT_TEMPLATE = """
28
+ Analiza la siguiente conversación o texto del paciente con artritis reumatoide.
29
 
30
+ Si el texto está vacío o no contiene información relevante, responde únicamente con el siguiente mensaje:
31
  "No hay información suficiente para calcular el RAID."
32
 
33
  En caso de haber información, extrae y asigna una puntuación del 0 al 10 para cada una de las siguientes categorías, basándote en los detalles proporcionados:
34
 
35
  1. Dolor:
36
+ - Asigne el número que mejor describa el dolor que sintió debido a su Artritis durante la última semana.
37
  - Escala: 0 = Ninguno, 10 = Extremo.
38
  2. Discapacidad Funcional:
39
+ - Asigne el número que mejor describa la dificultad que tuvo para realizar actividades diarias debido a su Artritis Reumatoidea durante la última semana.
40
  - Escala: 0 = Sin dificultad, 10 = Dificultad extrema.
41
  3. Fatiga:
42
+ - Asigne el número que mejor describa la fatiga que experimentó debido a su Artritis Reumatoidea durante la última semana.
43
  - Escala: 0 = Sin fatiga, 10 = Totalmente exhausto.
44
  4. Sueño:
45
+ - Asigne el número que mejor describa las dificultades para dormir que experimentó durante la última semana.
46
  - Escala: 0 = Sin dificultad, 10 = Dificultad extrema.
47
  5. Bienestar Físico:
48
+ - Asigne el número que mejor describa su nivel de bienestar físico en relación a su Artritis Reumatoidea durante la última semana.
49
  - Escala: 0 = Muy bueno, 10 = Muy malo.
50
  6. Bienestar Emocional:
51
+ - Asigne el número que mejor describa su nivel de bienestar emocional en relación a su Artritis Reumatoidea durante la última semana.
52
  - Escala: 0 = Muy bueno, 10 = Muy malo.
53
  7. Afronte:
54
+ - Asigne el número que mejor describa cómo enfrentó o se adaptó a su enfermedad durante la última semana.
55
  - Escala: 0 = Muy bien, 10 = Muy mal.
56
 
57
  Utiliza los siguientes pesos para calcular el valor final del RAID:
58
  {weights}
59
 
 
60
  La fórmula de cálculo es:
61
  RAID final = (Dolor × 0.21) + (Discapacidad Funcional × 0.16) + (Fatiga × 0.15) + (Bienestar Físico × 0.12) + (Sueño × 0.12) + (Bienestar Emocional × 0.12) + (Afronte × 0.12)
62
 
 
83
 
84
  Texto del paciente:
85
  {patient_text}
 
 
 
86
  """
87
 
88
+ # Inicialización del cliente Groq
89
+ # Inicialización del cliente
90
+ api_key = os.environ.get("GROQ_API_KEY")
91
+ if not api_key:
92
+ raise ValueError("GROQ_API_KEY no está configurada en las variables de entorno.")
93
+ client = Groq(api_key=api_key)
94
+ model_name = "llama-3.3-70b-versatile"
95
+ chat_groq = ChatGroq(model=model_name)
96
+
97
 
98
+ def transcribe_audio(audio_filepath):
99
+ """Transcribe el audio usando Whisper de Groq"""
100
+ if not audio_filepath:
101
+ return ""
102
  try:
103
+ with open(audio_filepath, "rb") as file:
104
+ transcription = client.audio.transcriptions.create(
105
+ file=(audio_filepath, file.read()),
106
+ model="whisper-large-v3",
107
+ response_format="json",
108
+ temperature=0.0
109
+ )
110
+ logger.info("Transcripción de audio exitosa.")
111
+ return transcription.text
112
+ except Exception as e:
113
+ logger.error("Error en transcripción de audio: %s", e, exc_info=True)
114
+ return ""
115
+
116
+ def evaluate_raid(patient_text):
117
+ """Evalúa el RAID score basado en el texto del paciente"""
118
+ try:
119
+ # Limitar texto para contexto
120
+ if len(patient_text) > 4000:
121
+ logger.warning("Texto demasiado largo, se truncará a 4000 caracteres")
122
+ patient_text = patient_text[:4000]
123
+
124
  prompt = RAID_PROMPT_TEMPLATE.format(
125
+ patient_text=patient_text,
 
126
  weights=json.dumps(RAID_WEIGHTS, ensure_ascii=False)
127
  )
128
+ logger.info("Evaluando RAID con prompt")
129
 
130
  response = chat_groq.invoke(prompt)
131
  content = response.content
132
+ logger.info("Respuesta de chat_groq recibida")
133
 
134
  # Extraer JSON de la respuesta
135
  start = content.find('{')
 
183
  return partial_result
184
 
185
  def format_raid_results(raid_data):
186
+ """Formatea los resultados del RAID para visualización"""
 
187
  if not raid_data:
188
  return "No se pudo calcular el RAID score"
189
 
 
216
  result += f"\n\n**Nota:** Se produjo un error durante el cálculo del RAID: {raid_data['error']}"
217
  return result
218
 
219
+ def process_input(audio, text_input):
220
+ """Procesa la entrada de audio y texto para calcular el RAID"""
221
+ patient_text = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
 
223
+ # Procesar audio si está disponible
224
+ if audio:
225
+ transcription = transcribe_audio(audio)
226
+ if transcription:
227
+ patient_text += transcription + "\n\n"
 
 
 
 
 
 
228
 
229
+ # Añadir texto manual si está disponible
230
+ if text_input:
231
+ patient_text += text_input
232
+
233
+ # Si no hay texto para analizar, retornar mensaje
234
+ if not patient_text.strip():
235
+ return "No se proporcionó texto ni audio para analizar. Por favor, añada información del paciente."
236
+
237
+ # Evaluar RAID con el texto disponible
238
+ raid_results = evaluate_raid(patient_text)
239
+ formatted_results = format_raid_results(raid_results)
240
+
241
+ return formatted_results
 
 
 
 
 
 
 
242
 
243
  # Configuración del tema
244
  theme = gr.themes.Base(
 
252
  neutral_hue="neutral",
253
  )
254
 
255
+ # Interfaz de Gradio simple para el cálculo de RAID
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  with gr.Blocks(theme=theme) as iface:
257
+ gr.Markdown("# Calculadora RAID para Artritis Reumatoide")
258
 
259
  with gr.Row():
260
+ with gr.Column():
261
+ audio_input = gr.Audio(
262
+ sources=["microphone", "upload"],
263
+ type="filepath",
264
+ label="Audio del paciente"
265
+ )
266
+ text_input = gr.Textbox(
267
+ label="Texto del paciente (opcional)",
268
+ placeholder="Ingrese aquí cualquier información adicional del paciente...",
269
+ lines=5
270
+ )
271
+ submit_btn = gr.Button("Calcular RAID", variant="primary")
272
 
273
+ with gr.Column():
274
+ raid_output = gr.Markdown(
275
+ "### Resultados RAID\n\nEspere mientras se procesa la información...",
276
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
+ submit_btn.click(
279
+ fn=process_input,
280
+ inputs=[audio_input, text_input],
281
+ outputs=raid_output
282
  )
283
 
284
+ # También calcular automáticamente cuando se cambia el audio
285
+ audio_input.change(
286
+ fn=process_input,
287
+ inputs=[audio_input, text_input],
288
+ outputs=raid_output
 
 
 
 
 
 
 
 
289
  )
290
 
291
+ if __name__ == "__main__":
292
+ iface.launch()
293
+
294
+