Extracción de estados financieros
Convierte informes PDF en hechos verificados: financial_fact(documento, periodo, etiqueta, alcance, valor, unidad, estado de auditoría).
Construido y probado en dos años de presentaciones de la Bolsa de Valores de Colombo (2,707 informes, 720,000 cifras, 99.4% completamente cargados).
El método es lo que lo hace rápido: un analizador automático procesa la mayoría de los informes, las verificaciones aritméticas deciden qué es confiable, un paso de triaje indica exactamente por qué falló cada estado restante, y solo esos estados se leen de imágenes de página, en lotes, por subagentes. Cada cambio llega a la base de datos publicada solo a través de una carga escalonada y una auditoría.
La única regla en la que todo se basa
Una cifra se carga solo si su columna pasa las verificaciones aritméticas (activos = patrimonio + pasivos; utilidad antes de impuestos - impuesto = utilidad; controladora + NCI = utilidad; las secciones del flujo de efectivo suman al cambio neto; saldo inicial + cambio = saldo final). No se ajusta nada para que una verificación pase. Las verificaciones solo permiten el redondeo impreso (unas pocas unidades del último dígito impreso: 2 para dos términos, 3 para tres, 5 para más), por lo que un total que se desvía 1-2 unidades de sus partes pasa, y más es un error de lectura o impresión. Esta es la razón por la que un analizador, un motor OCR, una transcripción por visión y un humano pueden alimentar la misma base de datos: las verificaciones, no la fuente, deciden. Cuando una verificación falla, o la lectura es incorrecta (releer) o la impresión es incorrecta (registrar por qué, omitirla).
Corolarios importantes en la práctica:
- Las etiquetas se mantienen exactamente como están impresas y se asignan a partidas estándar solo mediante alias exactos (
label_alias), nunca con coincidencias difusas. Una etiqueta mal impresa hace que un estado no se pueda cargar en lugar de ser erróneo silenciosamente. - Un total impreso sin etiqueta puede recibir una etiqueta entre corchetes (
[Total activos]) porque su posición lo define. - "UNCHECKED" (sin verificación posible) no es "FAIL". Por lo general, significa un diseño que el verificador aún no conoce: corrige el verificador una vez, y cada informe con ese diseño se carga (consulta referencias/checks.md).
Dónde están las cosas
scripts/pipeline/- el pipeline:parse_report.py(PDF -> tablas JSON),segment.py,columns.py,tokens.py,checks.py,ocr_words.py(OCR como capa de texto falsa),load_facts.py(JSON -> DB, solo columnas que pasan),vision_tables.py(transcripciones de imágenes -> tablas JSON),extract_all.py,normalize.py. Mapa, formas de datos y cómo adaptarlo a otro mercado: references/pipeline.md.scripts/triage.py,scripts/make_batches.py,scripts/render.py,scripts/stage_publish.py- las herramientas para los pasos siguientes.assets/schema.sql(base de datos),assets/dictionary.sql(252 partidas estándar + 471 alias de etiquetas exactas),assets/worker_brief.md(instrucciones para los trabajadores de transcripción),assets/examples/(archivos de transcripción).- El directorio del proyecto: donde residen la base de datos, los PDF y las salidas. El usuario lo nombra en
CLAUDE.md/AGENTS.md(por ejemplo, "Proyecto de finanzas: ~/stocks/cse_financials"); preguntar si no está. Estructura:db/<nombre>.db,reports/<SÍMBOLO>/<doc>.pdf, salida del analizadorextracted_final/, fuente publicadaextracted_final_v/, transcripcionesreviewed/vision/, notasdocs/. Si el proyecto tiene su propiopipeline/, tratarlo como el código actual (esta copia de la habilidad puede ser más antigua).
Ejecutar Python siempre con -I (aislado) y CSE_OCR=0 a menos que se desee OCR (el prefijo CSE_ es histórico). Un PDF de informe: select local_path from report_document where document_id=? (relativo al directorio del proyecto), luego python -I scripts/render.py <pdf> <páginas> <dir_salida> --text para volcar la capa de texto y renderizar las páginas.
Flujo de trabajo
1. Alcance y descarga
Seleccionar el rango de períodos; tomar los cierres de período de los títulos de los informes (las fechas de presentación en la bolsa no son confiables). Llenar filas de report_document (una por informe: empresa, categoría ANNUAL/INTERIM, título, cierre de período, URL de origen), luego descargar en paralelo y reanudablemente. La parte específica de la bolsa (API, URL, peculiaridades) pertenece a una habilidad complementaria (cse-financials para Colombo). Mantener cada bloque de años como su propia ejecución de estos pasos. Antes de un nuevo mercado o nuevos años, leer references/traps.md: enumera las trampas de datos que más tiempo cuestan.
2. Analizar todo con una versión del analizador
python -I scripts/pipeline/extract_all.py <db> <dir_salida> --workers N con CSE_OCR=0 (aproximadamente 9 min para 2,700 informes), luego una pasada OCR separada para escaneos (GPU si está disponible; ver trampas). Después de cualquier cambio en el analizador, reanalizar todos los informes y comparar por informe: los reanálisis parciales hacen que la base de datos no sea reproducible.
3. Cargar y leer el estado
load_facts.py (a través de stage_publish.py, paso 6) asigna a cada informe EXTRACTED (IS y BS cargados), PARTIAL o FAILED. Esperar aproximadamente un 80% de EXTRACTED solo con el analizador.
4. Triaje de lo que queda - antes de mirar cualquier página
python -I scripts/triage.py <db> <dir_extraído> triage.json --pdf-hints
python -I scripts/make_batches.py triage.json batches/ --target=12
El triaje asigna a cada estado faltante su razón (not_found, UNCHECKED, FAIL, no_columns, no_months, unit_unproven, pass_refused...) y dónde mirar. Leer los conteos: un grupo grande con una sola razón (por ejemplo, 100 UNCHECKED) a menudo significa una brecha en el verificador o una regla del cargador: corregir código es mejor que transcribir cientos de páginas. También verificar si los informes FAILED son realmente estados: avisos, listas de accionistas y erratas son comunes y solo necesitan una razón registrada.
5. Transcribir desde imágenes, en lotes
Cada trabajador recibe un archivo de lote y assets/worker_brief.md (completar las rutas). Encuentra la página del estado, la lee (capa de texto primero, imagen para diseño y cualquier cosa dudosa), escribe reviewed/vision/<doc>.txt, ejecuta vision_tables.py en sus documentos hasta que cada columna pase, y escribe bNN.result.json. El trabajador necesita un modelo que pueda leer imágenes. Formato y reglas: references/vision-format.md. Ejecución de trabajadores en paralelo, límites de uso, reanudación y revisión de resultados: references/orchestration.md. Sin subagentes, trabajar en los lotes uno tras otro con las mismas instrucciones.
Mientras se ejecutan los lotes, leer cada resumen de resultados a medida que llega:
- "UNCHECKED porque el verificador no reconoce X" recurrente -> extender
checks.py(referencias/checks.md), volver a probar los archivos afectados convision_tables.py, agregar la regla a las instrucciones para que los lotes posteriores lo sepan. Las tablas ya escritas luego pasan sin necesidad de retranscripción. - Decisiones subjetivas (estado de auditoría, columnas reexpresadas, períodos de duración inusual, etiquetas proporcionadas) -> verificar que sigan las instrucciones; corregir centralmente (por ejemplo, un sed sobre líneas COLS) en lugar de volver a ejecutar un trabajador.
6. Escalonar, auditar, publicar
python -I scripts/stage_publish.py --project <dir> # solo escalonar + auditar
python -I scripts/stage_publish.py --project <dir> --publish # después de leer la auditoría
La publicación se rechaza si la auditoría muestra informes que salen de EXTRACTED, informes que quedan vacíos, errores de integridad/FK, nuevos valores atípicos de unidad, o hechos perdidos/cambiados en informes que no tienen un archivo de transcripción (eso significa que un cambio de código tocó datos que nadie volvió a leer). Las ganancias fuera de los informes de visión son normales (un conflicto resuelto libera la columna de otro informe) pero revisar cada una. Publicar en etapas (los lotes terminados primero) para que el trabajo sobreviva a interrupciones; luego ejecutar las pruebas del proyecto si las tiene.
7. Cerrar cada estado
El objetivo es: cada estado faltante está cargado o tiene una razón declarada. Construir la tabla de resultados por estado (cargado / no cargado + razón de las líneas MISSING y los archivos de resultado). Para cada estado no cargado, verificar si las mismas cifras ya están cargadas de otro informe (comparativos del año siguiente, el interim correspondiente) - por lo general lo están, y eso convierte una "brecha" en "cubierta". Registrar los resultados en la documentación del proyecto.
Economía de tokens y tiempo
- Triaje primero; nunca enviar a un trabajador a "encontrar qué está mal" sin la razón y las pistas de página.
- Capa de texto antes que imagen: una capa de texto limpia confirma los dígitos gratis; la imagen resuelve el diseño. Renderizar a 1500 px; recortar (
render.py --clip) en lugar de renderizar páginas enteras más grandes. Los escaneos con capas de texto basura ("9,O4349L") necesitan imágenes. - Agrupar por empresa: un diseño, muchos trimestres. ~10 informes o ~12 unidades de costo por lote, aproximadamente 6 trabajadores a la vez.
- Los trabajadores escriben la tabla completa incluso cuando una verificación permanece UNCHECKED, más una línea
MISSING: si el verificador se extiende más tarde, la tabla se carga sin otra lectura. - Corregir problemas sistemáticos en el código (verificador, cargador) una vez, centralmente; volver a ejecutar
vision_tables.py(segundos) en lugar de volver a leer páginas. - El orquestador no ve imágenes de página excepto para resolver una disputa; lee resúmenes, ejecuta verificaciones y auditorías.
- Mantener los resúmenes de los trabajadores en 3-6 líneas; los detalles pertenecen a los comentarios de archivo.
Definición de finalizado
Por informe: EXTRACTED, o PARTIAL/FAILED con una razón por estado faltante que nombre la evidencia impresa (por ejemplo, "total impreso 3,115,995,977 vs patrimonio + pasivos 3,115,995,968"), o "cubierto por el informe N". Publicado solo a través de stage_publish.py, con una copia de seguridad, auditoría limpia (o cada bandera explicada), pruebas pasando, y la tabla de resultados más una nota fechada en la documentación del proyecto.