Spaces:
Sleeping
Sleeping
| """Informe Claro — traduce informes médicos a lenguaje llano. | |
| Pega el texto de un informe (o sube un PDF) y obtén una explicación en | |
| español corriente: qué dice el documento, un glosario de términos y | |
| preguntas útiles para llevar a la consulta. | |
| Esto es una ayuda de LECTURA. No interpreta clínicamente, no diagnostica | |
| y no sustituye a un médico. | |
| Ejecutar en local: python app.py (requiere GROQ_API_KEY) | |
| """ | |
| import gradio as gr | |
| from pypdf import PdfReader | |
| from translator import llm_available, translate_report | |
| PRIVACY_WARNING = ( | |
| "Antes de pegar un informe, borra los datos identificativos (nombre, DNI, " | |
| "número de historia). El texto se envía a la API de Groq para procesarlo." | |
| ) | |
| MEDICAL_DISCLAIMER = ( | |
| "Esta herramienta explica el lenguaje del informe, nada más. No es consejo " | |
| "médico, puede contener errores y no sustituye la interpretación de un " | |
| "profesional. Las decisiones sobre tu salud, siempre con tu médico." | |
| ) | |
| EXAMPLE_REPORT = """INFORME DE RESULTADOS - ANALITICA DE SANGRE | |
| Hemograma: Hematies 4.2 mill/mm3 (VN 4.5-5.9). Hemoglobina 12.8 g/dL (VN 13.5-17.5). | |
| Hematocrito 38% (VN 41-53). VCM 91 fL (VN 80-100). Leucocitos 6.800/mm3 (VN 4.000-11.000). | |
| Plaquetas 245.000/mm3 (VN 150.000-450.000). | |
| Bioquimica: Glucosa basal 108 mg/dL (VN 70-100). Creatinina 0.9 mg/dL (VN 0.7-1.3). | |
| Filtrado glomerular estimado (CKD-EPI) >90 mL/min. Colesterol total 215 mg/dL (VN <200). | |
| LDL 142 mg/dL (VN <130). HDL 48 mg/dL (VN >40). Trigliceridos 125 mg/dL (VN <150). | |
| GOT 22 U/L, GPT 28 U/L (VN <40). TSH 2.1 mUI/L (VN 0.4-4.5). | |
| Ferritina 18 ng/mL (VN 30-400). | |
| Conclusion: Anemia leve normocitica con ferropenia. Glucemia basal alterada. | |
| Dislipemia leve. Se recomienda valoracion por su medico de atencion primaria.""" | |
| EXAMPLE_MRI = """RESONANCIA MAGNETICA DE COLUMNA LUMBAR | |
| Tecnica: Secuencias sagitales T1, T2 y STIR; axiales T2 de L3 a S1. | |
| Hallazgos: Rectificacion de la lordosis lumbar fisiologica. Adecuada alineacion | |
| de los cuerpos vertebrales, sin listesis. Altura de los cuerpos vertebrales | |
| conservada. Cambios degenerativos discales tipo deshidratacion en L4-L5 y L5-S1. | |
| En L4-L5 se observa protrusion discal circunferencial con predominio posterocentral, | |
| que contacta con el saco tecal sin evidencia de compromiso radicular significativo. | |
| Discreta hipertrofia de facetas articulares. En L5-S1, protrusion focal paracentral | |
| izquierda que contacta con la raiz S1 izquierda en su trayecto intracanal, sin | |
| claros signos de compresion. Canal lumbar de amplitud conservada. Conos medular | |
| de morfologia y senal normales, finalizando a nivel de L1. | |
| Conclusion: Discopatia degenerativa L4-L5 y L5-S1 con protrusiones discales, | |
| la de L5-S1 en contacto con la raiz S1 izquierda. Sin estenosis de canal. | |
| Se recomienda correlacion clinica.""" | |
| EXAMPLE_DISCHARGE = """INFORME DE ALTA - SERVICIO DE URGENCIAS | |
| Motivo de consulta: Dolor toracico de 3 horas de evolucion. | |
| Enfermedad actual: Paciente que acude por dolor centrotoracico opresivo, no | |
| irradiado, de inicio en reposo, sin cortejo vegetativo acompanante. No disnea, | |
| no palpitaciones. Refiere episodio similar hace un mes autolimitado. | |
| Exploracion: Consciente y orientado. Eupneico en reposo. TA 132/84 mmHg, | |
| FC 78 lpm, SatO2 98% basal. Auscultacion cardiaca ritmica sin soplos. | |
| Auscultacion pulmonar con murmullo vesicular conservado. No edemas. | |
| Pruebas complementarias: ECG en ritmo sinusal a 76 lpm, sin alteraciones agudas | |
| de la repolarizacion. Troponina T ultrasensible seriada: 6 ng/L y 7 ng/L | |
| (VN <14). Radiografia de torax sin hallazgos patologicos agudos. | |
| Juicio clinico: Dolor toracico de perfil no coronario, probablemente | |
| osteomuscular. Sindrome coronario agudo descartado. | |
| Tratamiento al alta: Paracetamol 1 g cada 8 horas si dolor, maximo 5 dias. | |
| Control por su medico de atencion primaria. Volver a urgencias si el dolor | |
| cambia de caracteristicas, se irradia a brazo o mandibula, o asocia sudoracion | |
| o falta de aire.""" | |
| def read_pdf(file_path: str) -> str: | |
| reader = PdfReader(file_path) | |
| return "\n".join(page.extract_text() or "" for page in reader.pages) | |
| def analyze(report_text: str, pdf_file, level: str): | |
| text = (report_text or "").strip() | |
| if pdf_file is not None: | |
| try: | |
| text = read_pdf(pdf_file).strip() | |
| except Exception as e: | |
| raise gr.Error(f"No se pudo leer el PDF: {e}") | |
| if not text: | |
| raise gr.Error( | |
| "El PDF no contiene texto extraíble (probablemente es un escaneo). " | |
| "Copia el texto a mano en el cuadro de la izquierda." | |
| ) | |
| if not text: | |
| raise gr.Error("Pega el texto del informe o sube un PDF.") | |
| if len(text.split()) < 15: | |
| raise gr.Error("El texto es demasiado corto para ser un informe.") | |
| try: | |
| return translate_report(text, level) | |
| except RuntimeError as e: | |
| raise gr.Error(str(e)) | |
| with gr.Blocks(title="Informe Claro") as demo: | |
| gr.Markdown("# Informe Claro") | |
| gr.Markdown( | |
| "Traduce un informe médico a lenguaje llano: qué dice, glosario de " | |
| "términos y preguntas para llevar a tu médico." | |
| ) | |
| gr.Markdown(f"**Privacidad:** {PRIVACY_WARNING}") | |
| if not llm_available(): | |
| gr.Markdown( | |
| "*Aviso: no hay GROQ_API_KEY configurada — la app no podrá analizar " | |
| "informes hasta que la configures (gratis en console.groq.com).*" | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| report_input = gr.Textbox( | |
| lines=16, | |
| label="Texto del informe", | |
| placeholder="Pega aquí el informe (sin datos personales)...", | |
| ) | |
| pdf_input = gr.File( | |
| label="... o sube un PDF (si subes PDF, se usa el PDF)", | |
| file_types=[".pdf"], | |
| type="filepath", | |
| ) | |
| level_input = gr.Radio( | |
| choices=["Sencillo", "Intermedio"], | |
| value="Sencillo", | |
| label="Nivel de la explicación", | |
| ) | |
| analyze_button = gr.Button("Explicar en llano", variant="primary") | |
| gr.Examples( | |
| examples=[[EXAMPLE_REPORT], [EXAMPLE_MRI], [EXAMPLE_DISCHARGE]], | |
| example_labels=[ | |
| "Analítica de sangre", | |
| "Resonancia de columna lumbar", | |
| "Alta de urgencias por dolor torácico", | |
| ], | |
| inputs=report_input, | |
| label="Prueba con un ejemplo (todos con datos ficticios)", | |
| ) | |
| with gr.Column(scale=2): | |
| output = gr.Markdown(label="Explicación") | |
| analyze_button.click( | |
| analyze, | |
| inputs=[report_input, pdf_input, level_input], | |
| outputs=output, | |
| ) | |
| gr.Markdown(f"---\n*{MEDICAL_DISCLAIMER}*") | |
| if __name__ == "__main__": | |
| demo.launch(theme=gr.themes.Soft()) | |