# 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](#visión-general) - [Arquitectura de componentes](#arquitectura-de-componentes) - [Módulos y responsabilidades](#módulos-y-responsabilidades) - [Flujos de ejecución](#flujos-de-ejecución) - [Modelo de datos y CSV](#modelo-de-datos-y-csv) - [Estados de la GUI y manejo de errores](#estados-de-la-gui-y-manejo-de-errores) - [Configuración y ejecución](#configuración-y-ejecución) - [Extensibilidad](#extensibilidad) - [Registro y métricas](#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 ```mermaid 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
(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?)`: genera `schema` a partir del primer documento. - `build_schema_from_first_for_types(root_path, doc_types)`: genera `schema_by_type` usando 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. - `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 por `Tipo` (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) ```mermaid 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 ```mermaid 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 ```mermaid 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)` devuelve `items_key` y `items`. - 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ás `radicado` siempre). - Agregación opcional: `aggregate_list_paths=[base_path]` genera columnas `base_path/*/subclave` con valores unidos por coma (para listas no principales). - Encabezado final: `['radicado', ...campos_no_lista, ...campos_items]` respetando `allowed_paths` y orden alfabético de subrutas. - Alias: el `alias_map` de la GUI renombra columnas en el CSV final (encabezado y filas). Ejemplos de columnas resultantes: - `radicado` - `Factura/Número` - `Paciente/Nombre` - `items/*/Codigo` - `items/*/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. ```mermaid 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`, `error` cuando hay excepción. - Logs: se emiten `logger.info/debug/exception` con 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): 1) Inicie la aplicación GUI. 2) Seleccione la carpeta raíz de radicados (cada radicado contiene PDFs/TIFFs). 3) Opcional: defina palabras clave de búsqueda (por defecto: `Factura_O_Documento_Equivalente, factura`). 4) Opcional: defina campos a extraer (si se deja vacío se abrirá el selector). 5) Opcional: elija ruta de salida para el CSV. 6) 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 en `PROFILES` definiendo `default_instruction`, `aggregate_list_paths`, etc. - Estrategias de extracción (`application/strategies.py`): implemente `InvoiceExtractorStrategy.extract(images_b64, instruction)` para soportar nuevos backends/LLMs. - Normalización (`application/normalizer.py`): adapte `normalize_to_schema` para 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.