count-api
Microservicio Go que cuenta servicios facturados en facturas médicas PDF. Aplica OCR (Tesseract) + clustering espacial (DBSCAN) + clasificación ML (Random Forest vía ONNX Runtime).
Es un servicio sin estado: recibe PDFs por HTTP, procesa en memoria y devuelve el resultado. No persiste datos ni depende de bases de datos, colas ni almacenamiento de objetos.
La persistencia de resultados la maneja el servicio extraction (Python) a través de los endpoints /count-jobs.
Endpoints
| Método | Ruta | Descripción |
|---|---|---|
GET |
/health |
Liveness — {"status":"ok"} |
GET |
/ready |
Readiness — {"status":"ready"} |
GET |
/openapi.json |
Especificación OpenAPI |
POST |
/v1/count |
Conteo síncrono de PDFs |
POST /v1/count
Request
Content-Type: multipart/form-data
Query: include_rejected=true|false (default: false)
Field: files — uno o más archivos PDF (máx. SYNC_MAX_FILES)
Response 200
{
"results": [
{
"filename": "factura_001.pdf",
"service_count": 3,
"status": "ok",
"services": [
{ "text": "CONSULTA MEDICA", "probability": 0.91 },
{ "text": "LABORATORIO HEMOGRAMA", "probability": 0.87 },
{ "text": "RADIOGRAFIA TORAX", "probability": 0.83 }
]
}
],
"processed": 1,
"failed": 0
}
Response 4xx/5xx
{ "error": "descripción del error" }
Pipeline de procesamiento
PDF bytes (memoria)
│
▼ gen2brain/go-fitz (MuPDF)
Render de la primera página a imagen (OCR_DPI, default 300)
│
▼ otiai10/gosseract (Tesseract)
OCR palabra a palabra con coordenadas (X, Y, W, H)
│
▼ DBSCAN 1D sobre coordenada Y
Agrupación de palabras en filas candidatas
│
▼ 8 features por fila
first_token_is_code · currency_count · integer_count · x_span_norm
y_norm · token_count · special_char_ratio · digit_ratio
│
▼ ONNX Runtime (Random Forest)
P(servicio) por fila → filtrar por threshold (inference.json)
│ Si el modelo no está disponible → heurística: todas las filas = servicio
▼
PDFResult { service_count, services[], status }
Modelo ONNX
El modelo es opcional. Si no está disponible, el servicio arranca con una advertencia y usa heurística de conteo.
Los archivos deben montarse en /app/models/:
/app/models/model.onnx — modelo Random Forest exportado con skl2onnx
/app/models/inference.json — threshold y feature_columns
Para generar el modelo desde un model.joblib existente:
cd scripts
pip install -r ../requirements-export.txt
python export_onnx.py --joblib /ruta/model.joblib --out ../models/latest
Variables de entorno
| Variable | Default | Descripción |
|---|---|---|
PORT |
8080 |
Puerto HTTP |
MODEL_PATH |
/app/models/model.onnx |
Ruta al modelo ONNX |
INFERENCE_CONFIG |
/app/models/inference.json |
Threshold y metadatos del modelo |
OPENAPI_PATH |
api/openapi.yaml |
Ruta a la especificación OpenAPI |
OCR_DPI |
300 |
DPI de renderizado PDF para OCR |
OCR_LANG |
spa |
Idioma Tesseract |
OCR_POOL_SIZE |
0 (→ 4) |
Tamaño del pool de clientes Tesseract |
DBSCAN_EPS |
10 |
Épsilon de clustering DBSCAN (píxeles en Y) |
DBSCAN_MIN_SAMPLES |
1 |
Mínimo de puntos por cluster DBSCAN |
SYNC_MAX_FILES |
50 |
Máximo de PDFs por request a /v1/count |
SYNC_PROCESS_CONCURRENCY |
0 (→ OCR_POOL_SIZE) |
Goroutines paralelas por request |
MAX_PDF_SIZE_MB |
20 |
Tamaño máximo permitido por PDF |
PDF_PROCESS_TIMEOUT_SEC |
120 |
Timeout por PDF |
LOG_PIPELINE_TIMING |
false |
Loguear tiempos de cada etapa |
Dependencias en producción (Docker)
libonnxruntime.so— ONNX Runtime (copiado en build multi-etapa)tesseract-ocr tesseract-ocr-spa— OCR en españollibmupdf— via go-fitz, renderizado de PDFs
Sin Redis, MinIO, NATS ni bases de datos.
Estructura del código
cmd/api/ — punto de entrada del servidor HTTP
internal/
api/ — router y handler countSync
config/ — carga de variables de entorno
domain/ — tipos PDFResult, CountResponse, ServiceRow
model/ — sesión ONNX Runtime (Predictor)
ocr/ — pool de clientes Tesseract + renderizado MuPDF
pipeline/ — orquestación OCR → DBSCAN → features → ML
processor.go — ProcessPDF por documento
clustering.go — DBSCAN 1D en Y
features.go — 8 features por fila
api/openapi.yaml — especificación OpenAPI 3.1
models/latest/ — model.onnx + inference.json (gitignored, montar en Docker)
scripts/ — export_onnx.py para generar el modelo
Dockerfile — build multi-etapa: ONNX download + Go builder + runtime slim
Arrancar localmente (Docker)
# Desde la raíz del repositorio:
docker compose up --build count-api
El servicio queda disponible en http://localhost:8081.
Para probar el endpoint:
curl -X POST http://localhost:8081/v1/count \
-F "files=@factura.pdf" | jq .