IPSExtractor — Documentación técnica (Desarrolladores)
Este documento explica la arquitectura, módulos, flujos, modelo de datos/CSV, estados de la GUI, configuración y extensibilidad del proyecto. Contiene diagramas Mermaid incrustados.
Índice
- Visión general
- Arquitectura de componentes
- Módulos y responsabilidades
- Flujos de ejecución
- Modelo de datos y CSV
- Estados de la GUI y manejo de errores
- Configuración y ejecución
- Extensibilidad
- Registro y métricas
Visión general
La aplicación procesa carpetas con “radicados” que contienen PDFs/TIFFs de facturas. Orquesta la conversión de documentos a imágenes Base64, llama a una estrategia de extracción para obtener un JSON, normaliza el resultado a un esquema seleccionado por el usuario y exporta CSV incremental. Incluye GUI con selección interactiva de campos y estimación de costos por tokens.
Arquitectura de componentes
flowchart LR
subgraph GUI
V[gui/view.py]
C[gui/controller.py]
FS[gui/field_selector.py]
end
subgraph APP[application/*]
S[service.py]
STG[strategies.py<br/>(InvoiceExtractorStrategy)]
NRM[normalizer.py]
PR[profiles.py]
PB[prompts.py]
CFG[config.py]
end
subgraph CORE
PDF[core/pdf_utils.py]
TXT[core/text_utils.py]
CSV[core/csv_utils.py]
UT[core/utils.py]
end
V --> C
C --> S
C --> FS
S --> STG
S --> NRM
S --> PR
S --> PB
S --> CFG
S --> PDF
S --> TXT
S --> CSV
S --> UT
S --> LOG[(logs/*)]
Módulos y responsabilidades
application/service.py: orquestación principal (extracción con fallback, schema inicial, normalización, CSV incremental, estimación de costos/tokens).- Funciones clave:
extract_full_from_pdf(pdf_path, instruction?): conversión a imágenes, llamada a extractor, parseo JSON, fallback DPI/páginas.build_schema_from_first(pdf_path, instruction?): generaschemaa partir del primer documento.build_schema_from_first_for_types(root_path, doc_types): generaschema_by_typeusando el primer radicado y la primera coincidencia por keyword de cada tipo.process_root_with_field_selection(root_path, keywords|doc_types, fields?, ...): flujo por radicado; soporta multi-documento; selección de campos; normalización y CSV incremental.estimate_tokens_and_cost(root_path, keywords|doc_types, ...): simulación con primer radicado; en multi suma por tipo.
- Funciones clave:
application/profiles.py: perfiles por tipo de documento e instrucciones por defecto.application/strategies.py: interfaz/estrategia de extracción (InvoiceExtractorStrategy).application/normalizer.py:normalize_to_schema(...)para ajustar el JSON al esquema elegido.application/prompts.py:PromptBuilder.build(...)para prompts reducidos según campos seleccionados.application/config.py:settings(imaging, hilos, DPI, etc.).core/pdf_utils.py: conversión PDF/TIFF → imágenes Base64.core/text_utils.py:extract_json_from_text,build_schema.core/csv_utils.py: aplanado de campos no-lista, detección de la primera lista de ítems, agregaciones y encabezados.core/utils.py: utilidades (p. ej.,collect_radicado_docs).gui/controller.py: coordina GUI ↔ servicio, selector de campos y callbacks de progreso/uso.gui/field_selector.py: diálogo de selección de paths y alias (valida duplicados y permite reproceso). En modo multi, agrupa visualmente porTipo(p. ej.,Factura/...,Remision/...).gui/view.py: añade lista dinámica de Tipos de Documento (Nombre Lógico + Keyword).gui/view.py: componentes visuales y actualizaciones (KPIs, barras, líneas de costo, etc.).
Flujos de ejecución
Incluye: disparo desde GUI, descubrimiento de radicados, creación de schema, selección de campos/alias, estimación y confirmación, extracción por documento con fallback, normalización y exportación incremental a CSV.
Secuencia general (GUI → Service → Normalización → CSV)
sequenceDiagram
autonumber
actor U as Usuario
participant V as gui/view.py
participant C as gui/controller.py
participant S as application/service.py
participant FS as gui/field_selector.py
participant ST as application/strategies.py
participant N as application/normalizer.py
participant CSV as core/csv_utils.py
participant TXT as core/text_utils.py
participant UT as core/utils.py
U->>V: Click "Procesar"
V->>C: on_process()
note over C: Lee root, keywords, fields opcionales
C->>S: process_root_with_field_selection(fields=None, doc_types?)
S->>UT: collect_radicado_docs()
S->>S: build_schema_from_first(first.pdf)
S->>TXT: extract_json_from_text(raw)
S-->>C: [{ _needs_fields: true, schema, first_full }]
C->>FS: Abrir diálogo de selección (schema + sample)
FS-->>C: on_submit(selected_paths, alias_map) Note: paths con prefijo de tipo `Tipo/Subruta`
C->>S: estimate_tokens_and_cost(..., doc_types?, schema_by_type?, selected_paths?)
C->>U: Confirmación (costos estimados)
U-->>C: Aceptar
C->>S: process_root_with_field_selection(fields=selected_fields, alias_map)
loop Por cada radicado
alt Multi-documento
loop Por cada Tipo
S->>ST: extract(images, prompt reducido por Tipo)
S->>N: normalize_to_schema(data, paths del Tipo)
end
S-->>S: fusiona por Tipo => fused_data = {'radicado': R, 'Factura': {...}, 'Remision': {...}}
S->>CSV: rows_from_data(fused_data, allowed_paths con prefijo)
else Single-documento
S->>ST: extract(images, prompt reducido)
S->>N: normalize_to_schema(data, paths)
S->>CSV: rows_from_data(data, allowed_paths, aggregate_list_paths?)
end
S->>S: _append_rows_to_csv(...)
S-->>C: on_usage(phase, radicado, usage, cost)
C->>V: Actualiza KPIs/barras/líneas
end
S-->>C: resultados finales (JSON)
C->>V: Imprime resultados en Output
Secuencia de extracción con fallback
sequenceDiagram
autonumber
participant S as application/service.py
participant PDF as core/pdf_utils.py
participant ST as application/strategies.py
participant TXT as core/text_utils.py
S->>PDF: document_to_images_bs64(pdf, dpi=config, threads)
S->>ST: extract(images_b64, instruction)
alt Éxito
ST-->>S: {raw, usage}
else Error/Timeout/Payload
S->>PDF: document_to_images_bs64(pdf, dpi=fallback, pages<=5)
S->>ST: extract(images_b64_fb, instruction)
ST-->>S: {raw, usage}
end
S->>TXT: extract_json_from_text(raw)
TXT-->>S: json_text
S-->>S: json.loads(json_text) => data
Modelo de datos y CSV
Describe cómo se aplanan campos no-lista, cómo se detecta la primera lista de ítems, cómo se agregan listas a columnas y cómo allowed_paths y los alias determinan el encabezado y las filas del CSV.
Diagrama de datos
classDiagram
class ExtractedData {
+string radicado
+dict ...campos_no_lista
+Item[] items
+string _source_pdf
}
class Item {
+string Codigo
+int Cantidad
+float V/Unitario
+... otros subcampos anidados
}
ExtractedData "1" --> "0..*" Item : items
Notas:
- Los “campos no lista” se aplanan con separador
/, por ejemplo:Paciente/Nombre,Factura/Fecha. - La primera lista de objetos detectada se considera “lista de ítems principal” y se expande en columnas.
Mapeo a CSV (core/csv_utils.py)
- Aplanado no-lista:
flatten_non_list_fields(data)produce columnas para todas las hojas escalar (excluye listas). - Detección de lista principal:
_find_first_items_list(data)devuelveitems_keyyitems. - Subcolumnas de ítems: se aplanan como
items_key/*/subruta, por ejemplo:items/*/V/Unitario. - Filtrado por
allowed_paths(selección del usuario): solo se mantienen rutas de esa lista (másradicadosiempre). - Agregación opcional:
aggregate_list_paths=[base_path]genera columnasbase_path/*/subclavecon valores unidos por coma (para listas no principales). - Encabezado final:
['radicado', ...campos_no_lista, ...campos_items]respetandoallowed_pathsy orden alfabético de subrutas. - Alias: el
alias_mapde la GUI renombra columnas en el CSV final (encabezado y filas).
Ejemplos de columnas resultantes:
radicadoFactura/NúmeroPaciente/Nombreitems/*/Codigoitems/*/V/Unitario
Estados de la GUI y manejo de errores
Estados principales: Idle → Processing → (NeedsFields) → Estimating → Confirming → Processing2 → Done/Error. La extracción tiene fallback controlado y los errores se registran y opcionalmente se reflejan como filas de error en el CSV.
stateDiagram-v2
[*] --> Idle
Idle --> Processing: Click "Procesar"
Processing --> NeedsFields: Service devuelve _needs_fields
NeedsFields --> Estimating: on_submit(selected_fields, alias_map)
Estimating --> Confirming: Mostrar costos estimados
Confirming --> Processing2: Usuario acepta
Confirming --> Idle: Usuario cancela
Processing2 --> Done: Todos los radicados OK
Processing2 --> Error: Excepción por radicado
Done --> Idle
Error --> Idle
Manejo de errores/fallback:
- Extracción: si falla la primera llamada (timeout/payload), se reprocesa con DPI reducido y límite de páginas (≤5).
- Escritura CSV: filas de error con
radicado,_source_pdf,errorcuando hay excepción. - Logs: se emiten
logger.info/debug/exceptioncon contexto (pdf,radicado, métricas de tiempo y tokens). - GUI: mantiene KPIs y barreras por fase; errores no bloquean el procesamiento de los siguientes radicados.
Configuración y ejecución
Parámetros clave en application/config.py:
settings.imaging.pdf_dpi: DPI para rasterizar PDF/TIFF (impacta calidad/tamaño).settings.imaging.pdf_thread_count: paralelismo al generar imágenes.settings.imaging.tiff_pages: páginas a considerar por defecto en TIFF multi-página.
Ejecución (GUI):
- Inicie la aplicación GUI.
- Seleccione la carpeta raíz de radicados (cada radicado contiene PDFs/TIFFs).
- Opcional: defina palabras clave de búsqueda (por defecto:
Factura_O_Documento_Equivalente, factura). - Opcional: defina campos a extraer (si se deja vacío se abrirá el selector).
- Opcional: elija ruta de salida para el CSV.
- Presione “Procesar”.
Exportación CSV:
- Si se especifica
csv_output_path, el servicio añadirá filas incrementalmente conforme procesa cada documento. - Los encabezados se generan en la primera escritura; si existe un CSV previo con contenido, se preserva su encabezado.
Extensibilidad
Nuevos perfiles, estrategias de extracción o ajustes en normalización y costos se pueden incorporar con cambios locales y mínimos acoplamientos.
- Perfiles (
application/profiles.py): agregue una entrada enPROFILESdefiniendodefault_instruction,aggregate_list_paths, etc. - Estrategias de extracción (
application/strategies.py): implementeInvoiceExtractorStrategy.extract(images_b64, instruction)para soportar nuevos backends/LLMs. - Normalización (
application/normalizer.py): adaptenormalize_to_schemapara modelos y políticas de mapeo; los costos por fase se calculan en el servicio. - Prompts (
application/prompts.py):PromptBuilder.build(selected_fields, schema)puede ajustarse para controlar la precisión y tamaño del prompt. - Estimación de costos (
application/service.py::estimate_tokens_and_cost): modifique tarifas/modelos según su despliegue.
Preferencia de diseño:
- Siempre que sea posible, se recomienda ajustar el prompt para conseguir un JSON estable en vez de depender de mapeos/alias complejos en código memory:10659104.
Registro y métricas
La app registra progreso, tiempos y costos por fase y por documento. Los logs se escriben en logs/* y la GUI muestra KPIs agregados.