Ir al contenido
Franklin Martinez
Todo el trabajo

docmatch

Conciliación de facturas con modelos de visión, reglas deterministas y un benchmark en CI

Resumen. docmatch lee facturas con modelos de visión, verifica lo que leyeron con código determinista, cruza cada factura con su orden de compra y su registro de recepción, y manda a una persona todo lo que no cuadra. Cada etapa tiene su número en un benchmark que nombra el commit que lo produjo. En desarrollo.

Extracción

Verificación

  1. received
  2. extracted
  3. validated
  4. resolved
  5. matched
  6. approved | needs_review

Después: resolver cada línea contra el catálogo y cruzar la factura con su orden de compra y su recepción.

Fig. 2. Una factura sintética del corpus de CI de docmatch, con sus campos etiquetados dibujados donde están en la página, y luego las dos reglas de la verificación. La verificación no necesita modelo ni etiquetas. Una lectura que falla se guarda y nunca se reintenta, porque reintentar sería buscar una respuesta que pase.

Por qué existe

Los equipos de cuentas por pagar todavía reciben la mayoría de las facturas como PDF, escaneos y fotos del celular. Convertir una en un asiento contable son tres problemas: leerla, compararla con lo que se pidió y se recibió, y decidir si una persona tiene que revisarla. La mayoría de los proyectos públicos se quedan en el primero con una sola llamada a un modelo. docmatch trata la lectura como una pieza reemplazable y pone la ingeniería en los otros dos.

Los modelos leen, el código decide

El pipeline es una máquina de estados explícita en Postgres: recibida, extraída, validada, resuelta, cruzada, y luego aprobada o en revisión. Los modelos solo estructuran texto. La aritmética y cada decisión son código determinista.

El control de validación (Fig. 2) solo tiene reglas que las propias etiquetas cumplen: los totales cuadran y las fechas tienen sentido. Probé la conciliación de totales por línea, la aritmética de impuestos y los dígitos verificadores, y los dejé fuera, porque fallan demasiado en los datos etiquetados o aplican a casi ningún documento. Un valor que el modelo no pudo leer nunca hace fallar una regla, y una lectura que falla se guarda y no se reintenta, porque reintentar sería buscar una respuesta que pase.

Fases con números, no con fechas

Cada fase termina con un número en el README, y la siguiente no empieza hasta que ese número existe.

Extracción. Tres backends comparados en un subconjunto fijo de 100 documentos (Fig. 5). El modelo más barato también sacó el F1 de campos más alto.

Cruce. Un generador inyecta discrepancias etiquetadas en órdenes de compra y registros de recepción. En ese set, la precisión y el recall son casi perfectos para cada tipo de discrepancia. De punta a punta, desde la imagen, la precisión baja a la mitad, y el README explica exactamente por qué: una línea mal leída que no se puede emparejar se convierte a la vez en una línea extra falsa y en una línea faltante falsa.

Resolución de entidades. Trigramas, vectores, híbrido, e híbrido con un reranker cross-encoder, sobre un catálogo de proveedores. La regla para quedarse con el reranker se escribió antes del número. Quedó por debajo del híbrido simple, así que se descartó.

Pipeline. El ciclo completo, de la carga a la aprobación, por la API HTTP, con el backend más barato.

Protegido en CI

Un control de regresión compara el F1 de campos y de líneas de cada backend contra la base del merge, y hace fallar un pull request que baje cualquiera de los dos.

Lo que dejé fuera, a propósito

Celery y Redis (un worker sobre Postgres basta en un solo nodo), frameworks de evaluación con modelos jueces (la extracción tiene datos de referencia), Langfuse (hospedarlo trae de vuelta a Redis, y la nube pondría el contenido de los documentos en manos de un tercero) y la orquestación con grafos, decidida en un ADR a favor de código simple.

Lo próximo

El empaquetado: un punto de entrada reproducible para que cualquiera pueda regenerar una fila del benchmark desde cero, y un análisis de las clases de error principales. Después, un adaptador dominicano: ingesta de XML de e-CF, validación de RNC y NCF, y el reporte 606 de compras.

Backend F1 por campo Costo por doc. Commit
gemini-3.1-flash-lite pages at 1600 px 0.615 0,00203 US$ 28d0738
gpt-5.6-luna pages at 1600 px 0.588 0,00344 US$ 42e69fa
Azure prebuilt-invoice API 2024-11-30 0.556 0,01120 US$ d3bb01e
Fig. 5. Tres backends de extracción en un subconjunto fijo de 100 documentos. Cada fila enlaza el commit que la produjo.

Hablar de este proyecto