Sistema de Análisis de Tendencias de Indicadores de Exámenes de Salud
Propósito
Unifica la interpretación de informes de exámenes de salud de múltiples años y diversos formatos (PDF electrónico, escaneos, fotos) en una serie temporal estandarizada de indicadores, produciendo un informe de análisis de tendencias y un panel de control interactivo de salud. Procesamiento completamente local, anonimización automática y extensibilidad dinámica (al agregar un nuevo informe, basta con re-ejecutar el pipeline).
Palabras Clave de Activación
- Informes de exámenes de salud históricos / de múltiples años
- Digitalización de datos de exámenes de salud / interpretación de exámenes
- Tendencias de indicadores de salud / análisis de tendencias de indicadores
- Comparación de indicadores de exámenes a lo largo de los años / cambios anuales
- Análisis de análisis clínicos / interpretación de análisis / análisis de informes de laboratorio
- Escaneos de informes de exámenes de salud / OCR de escaneos
- Fotos de informes de exámenes de salud / informes tomados con teléfono móvil
- Panel de control de salud / tablero de salud / visualización de salud
- Tendencia de glucosa / cambio de glucosa / ácido úrico elevado / tendencia de lípidos / tendencia de presión arterial
- Indicadores anormales en exámenes de salud / interpretación de anomalías
- Agregar nuevo informe de examen / incorporar nuevo año
- Anonimización de datos de exámenes de salud / conjunto de datos anonimizado / datos compartibles
Preparación del Entorno
# Dependencias (omitir si ya instaladas): pymupdf es obligatorio; Pillow (para fotos), pyobjc-framework-Vision/Quartz (OCR, solo macOS) son opcionales
export REPORT_DIR=/ruta/al/directorio/de/informes # Directorio donde se almacenan los informes (PDF o carpetas por año + JPG)
export WORK_DIR=/ruta/al/directorio/de/trabajo # Directorio para los datos generados (crea automáticamente data/ y output/)
export PY=/ruta/a/python # Se recomienda usar un intérprete de Python con las dependencias instaladas
Paso 0 (obligatorio al inicio de cada nueva sesión):
$PY scripts/check_deps.py # Verificación rápida: qué falta, qué scripts afecta, cómo instalarlo
$PY scripts/check_deps.py --json # Salida legible por máquina para automatización
Código de salida 0 = dependencias obligatorias completas, listo para trabajar; 1 = faltan dependencias obligatorias (el flujo principal no funcionará, seguir instrucciones para instalar); la falta de dependencias opcionales solo afecta al tipo de entrada correspondiente, sin bloquear la ruta principal de PDF electrónico.
Flujo de Ejecución
Todos los scripts soportan --help; los parámetros tienen prioridad sobre las variables de entorno; ejecutar sin parámetros = usar el comportamiento predeterminado de las variables de entorno.
- Identificar el tipo de entrada: Recorrer
REPORT_DIR- Archivos
*.pdfen el directorio raíz con capa de texto → Electrónico (procesado porparse_reports.py) - Archivos
*.pdfen el directorio raíz sin capa de texto → Escaneo (vision_ocr.py+extract.py) - Subdirectorios por año
YYYYinformes/*.JPG→ Versión foto (make_year_pdfs.pypara convertir → OCR →extract.py)
- Archivos
- Interpretar PDF electrónico (método de ubicación por coordenadas, límites de columnas en
references/Interpretación_y_calibre.md):$PY scripts/parse_reports.py # → data/reports_raw.json $PY scripts/parse_reports.py --year 2027 --dry-run # Prueba para un solo año, sin escribir en disco - Convertir fotos a PDF + OCR + extracción (si existen directorios JPG / escaneos):
$PY scripts/make_year_pdfs.py --year 2015-2020 # Varias JPG de un año → un único PDF (corrección automática de rotación para fotos horizontales) $PY scripts/vision_ocr.py <pdf> data/ocr_2022.json # OCR de escaneo/foto → JSON con coordenadas $PY scripts/extract.py --year 2022 # Resultado OCR → indicadores (estrategia automática) $PY scripts/extract.py --years 2015-2020 # OCR tipo dict multianual (abreviatura + rango de referencia para deducción)extract.pyse parametriza por año, no necesita modificación de código para años nuevos: colocardata/ocr_<año>.jsony ejecutar directamente--year <año>. La estrategia por defecto esauto(la parte superior del JSON de entrada es dict →abbr, es list →row); se puede forzar con--strategy. - Normalización + construcción del conjunto de datos (mapeo de nombres/unidades, filtrado de datos sucios, inyección automática de OCR, generación de versión anonimizada):
Produce$PY scripts/build_dataset.pydata/dataset_std.json(versión completa, control local) +data/anonymized/anonymized_dataset_anon_shareable.json(versión anonimizada, subdirectorio separado + solo lectura 444 + compartible) - Análisis de tendencias (determinación de anomalías según el rango del año, cambios significativos ≥20% o ≥30% del ancho de referencia, mejora/empeoramiento, enfoque en los últimos tres años):
$PY scripts/trend_analysis.py # Por defecto, últimos tres años $PY scripts/trend_analysis.py --focus-years 2020,2023,2026 - Generar entregables:
$PY scripts/report_generator.py # output/Informe_de_análisis_de_tendencias_de_salud.md (por defecto **con** anonimización en el cuerpo) $PY scripts/build_dashboard.py # output/health_dashboard.html (archivo único offline)
Guía para Incorporar Nuevos Informes
Al agregar un informe de un nuevo año, seguir la rama correspondiente según el tipo de entrada (ver references/Guía_para_incorporar_nuevos_informes.md para más detalles):
- Colocar el informe: Poner
*.pdfen la raíz deREPORT_DIR, o crear un subdirectorioYYYYinformes/con las JPG - Identificar el tipo: Verificar la capa de texto del PDF (
get_text()longitud >0 = electrónico)- Electrónico → ejecutar directamente
parse_reports.py; si hay anomalías en los límites de columnas, imprimir primero una muestra de coordenadas de 1 página - Escaneo/foto →
make_year_pdfs.py→vision_ocr.py→extract.py --year <año>(no crear un script por año)
- Electrónico → ejecutar directamente
- Actualizar diccionario (si hay indicadores no cubiertos): Agregar una línea en
NAME_MAPdeindicator_dict.py(el registro se encuentra endata/unrecognized)$PY scripts/indicator_dict.py --search ácido úrico # Verificar si ya está cubierto (incluye alias) $PY scripts/indicator_dict.py --validate # Autoverificar integridad del diccionario (alias huérfanos, claves duplicadas) $PY scripts/indicator_dict.py --count # Contar el total de indicadores estándar actuales - Re-ejecutar el pipeline: build_dataset → trend_analysis → report_generator → build_dashboard
- Verificación: Confirmar que el nuevo año aparece en
trend_analysis.json["years"]y en la línea de tiempo del dashboard; verificar aleatoriamente 3 indicadores clave para que coincidan con el informe; generar y validar la versión anonimizada
Reglas Clave
- Restricción de privacidad estricta (tolerancia cero): Todo el proceso es local, el skill no contiene ninguna llamada de red externa; al completar la tarea, se debe generar y verificar la versión anonimizada (sin nombres, números de identificación, teléfonos, direcciones, hospitales); para compartir, usar solo
data/anonymized/anonymized_dataset_anon_shareable.json(aislamiento estricto en directorios separados deldataset_std.jsoncompleto, solo lectura 444, para evitar compartir accidentalmente la versión completa) - Verificación de anonimización antes de escribir en disco:
_write_anon_verified()enbuild_dataset.pyes la única salida para escribir la versión anonimizada en disco: primero verifica en memoria, y solo escribe en disco si pasa la verificación. Una vez escrita, se establece como solo lectura 444. Si la verificación ocurriera después de escribir, cuando se detectara información de identificación personal (PII), el archivo con PII ya estaría en el disco y no se podría modificar, y[FAIL]sería solo una advertencia posterior. Está prohibido omitir esta salida y llamar directamente a_write_anon();--skip-verifyes solo para depuración, el producto no se puede compartir - Anonimización del cuerpo del informe:
mask_identity_textenreport_generator.pyestá activado por defecto (enmascara nombres de instituciones, nombres de médicos, números de teléfono, números de identificación); solo se desactiva con--no-mask, y es únicamente para verificación local del texto original; el producto generado no debe compartirse - Nuevos informes anuales: Colocar en
REPORT_DIR→ re-ejecutar los pasos 2→6; los indicadores no cubiertos en el diccionario se registran endata/unrecognized, agregar una línea enNAME_MAPdeindicator_dict.py - Ubicación de datos OCR: Los indicadores extraídos por OCR se usan solo para visualización en gráficos (marcados con Ⓞ gris), no participan en la determinación de cambios significativos, para evitar contaminar las estadísticas
- Enfoque de tendencias: La tendencia general y la comparación del primer y último año se basan únicamente en los años de enfoque (por defecto, los últimos tres años, se puede anular con
--focus-years); los datos históricos solo sirven como fondo de visualización - Consistencia de calibre: La determinación de anomalías utiliza el rango de referencia del propio informe de ese año (se adapta automáticamente a cambios de reactivo, por ejemplo, bilirrubina directa de 0.0–4.0 a 1.7–6.8 a partir de 2025)
Entregables
output/Informe_de_análisis_de_tendencias_de_salud.md— Tabla resumen de indicadores, resaltado de anomalías, interpretación autorizada, recomendaciones estratificadas (estilo de vida/dieta/ejercicio/cuándo consultar al médico), metodologíaoutput/health_dashboard.html— Resumen de salud, filtros de mejora/empeoramiento, gráficos de líneas con bandas de referencia, cambio de escala de tiempo (archivo único, sin dependencias externas)data/anonymized/anonymized_dataset_anon_shareable.json— Conjunto de datos anonimizado compartible
Lista de Scripts
| Script | Propósito | Parámetros Principales |
|---|---|---|
check_deps.py | Autoverificación de dependencias (Paso 0) | --json --quiet |
parse_reports.py | Interpretación de PDF electrónico | --report-dir --data-dir --year --out --dry-run |
make_year_pdfs.py | Conversión de JPG anual a PDF | --report-dir --year --out-dir --dry-run |
vision_ocr.py | OCR de escaneos/fotos | pdf out (argumentos posicionales) --dpi --quiet |
extract.py | Resultado OCR → indicadores (parametrizado) | --year --strategy --ocr-json --out --data-dir --quiet |
build_dataset.py | Normalización + versión anonimizada | --work-dir --data-dir --out --skip-verify --dry-run |
trend_analysis.py | Determinación de tendencias | --work-dir --focus-years --out --dry-run |
report_generator.py | Informe Markdown | --work-dir --out --no-mask --dry-run |
build_dashboard.py | Dashboard HTML | --work-dir --data-dir --out --quiet |
indicator_dict.py | Diccionario de indicadores (importado) | --list --search --count --validate --category (solo cuando se ejecuta directamente) |
El código de salida es de tres estados: 0 éxito / 1 fallo en tiempo de ejecución / 2 parámetros o condiciones previas insatisfechas.
Documentos de Referencia
| Documento | Contenido |
|---|---|
references/Guía_para_incorporar_nuevos_informes.md | Pasos completos y solución de problemas para incorporar informes de nuevos tipos/años |
references/Interpretación_y_calibre.md | Coordenadas de límites de columnas de PDF electrónico, definiciones de calibre |
references/Metodología_OCR.md | Estrategias de OCR para escaneos/fotos, mantenimiento de tabla de mapeo de abreviaturas |
evals/ | 8 pruebas de regresión (incluyendo 3 líneas de defensa de privacidad), se pueden ejecutar con eval_loop.py --run |
Ejemplo de Uso (Sesión Completa)
Usuario: «Agrega el informe del examen de salud de 2027 y actualiza el análisis de tendencias.»
- Paso 0:
python3 scripts/check_deps.py→ Dependencias obligatorias completas - Verificar
REPORT_DIR: encontrar nuevo archivoInforme_270510004567.pdf(electrónico, capa de texto completa) - Confirmar límites de columnas con una muestra pequeña: imprimir coordenadas de 1 página, coinciden con
references/Interpretación_y_calibre.md→ no requiere ajuste - Ejecutar pipeline:
export REPORT_DIR=informes_de_salud WORK_DIR=/ruta/a/trabajo python3 scripts/parse_reports.py python3 scripts/build_dataset.py python3 scripts/trend_analysis.py python3 scripts/report_generator.py python3 scripts/build_dashboard.py - Los registros de interpretación muestran: nuevo año con 98 indicadores, 1 nombre no reconocido →
indicator_dict.py --searchconfirma que no está cubierto, agregar 1 línea enNAME_MAPy re-ejecutar build_dataset - Verificación:
trend_analysis.json["years"]contiene 2027; aparece 2027 en la línea de tiempo del dashboard; verificar aleatoriamente valores de glucosa/lípidos/IMC para que coincidan con el informe;grepen la versión anonimizada no muestra información de identidad - Entrega: Informar al usuario la ruta del informe y del dashboard; explicar los cambios y recomendaciones para los indicadores anormales de 2027
Disciplina de Reducción de Costos (Obligatorio durante la Ejecución)
- Primero la muestra pequeña: Antes de interpretar un nuevo formato, imprimir primero una muestra de coordenadas de 1 página para confirmar los límites de columnas, luego procesar por completo (evitar volcar todo el archivo)
- Compresión de salida: Los comandos de depuración deben usar
| head -N/python -cpara extraer con precisión; está prohibido hacer cat de archivos grandes JSON - Aislamiento de sub-agentes: La lectura de archivos grandes / la detección de formatos debe delegarse al sub-agente Explore; el hilo principal solo recibe la conclusión
- La verificación se realiza solo con una captura de pantalla final; las iteraciones intermedias se verifican con DOM/JS
Historial de Cambios
| Versión | Fecha | Cambios |
|---|---|---|
| 1.1.0 | 2026-08-29 | Reforma de ingeniería concentrada: ① Los 10 scripts ahora soportan --help + argparse + código de salida de tres estados (antes 0/10, el peor); ② Nuevo check_deps.py para autoverificación de dependencias y se coloca como Paso 0; ③ Los scripts extract_2015_2020.py / extract_2022.py codificados por año se fusionan en extract.py --year parametrizado (la incorporación de nuevos años ya no requiere modificar código, el formato de salida coincide campo por campo con los scripts antiguos); ④ Nuevo capítulo ## Palabras Clave de Activación con 12 entradas + evals/ con 8 pruebas de regresión (incluyendo 3 casos de uso de líneas de defensa de privacidad); ⑤ Corrección de privacidad: La versión anonimizada ahora se «verifica primero en memoria, y solo se escribe en disco si pasa» (antes se escribía primero y luego se verificaba, cuando se detectaba PII, el archivo con información de identidad ya estaba en el disco y era de solo lectura, [FAIL] era solo una advertencia posterior); ⑥ Se corrigió código muerto en trend_analysis.py que perdía elementos de medición (measurement_items se reiniciaba después de agregar, ahora se desduplica por clave) |
| 1.0.3 | 2026-08-23 | Corrección P2 M-1 (Seguridad del skill): Aislamiento estricto de la versión anonimizada — anonymized_dataset.json → data/anonymized/anonymized_dataset_anon_shareable.json (subdirectorio separado + solo lectura 444 + marcado de nombre _anon_shareable), para evitar mezclar y compartir accidentalmente la versión completa; build_dataset.py añadió _write_anon() (escritura atómica tmp+replace + chmod 444); actualización de la ruta de la declaración de privacidad en report_generator |
| 1.0.2 | 2026-08-17 | Corrección P1: build_dataset.py añadió verify_anonymized al final (escaneo con regex de ID/teléfono/correo/nombre de hospital, error de salida si se detecta) |
| 1.0.1 | 2026-08-17 | Corrección P0: report_generator.py añadió función de anonimización mask_identity_text (enmascaramiento de teléfono/ID/firma del médico + generalización de institución), anonimización de la capa de visualización del resumen/conclusión; alineación de la declaración de privacidad con la salida real |
| 1.0.0 | 2026-08-16 | Versión inicial: Sistema de análisis de tendencias de indicadores de exámenes de salud |