← Back to projects

CASE STUDY · PERSONAL PROJECT · AI & AUTOMATION

AI invoice automation

End-to-end pipeline that turns PDF and text invoices into structured data, stored in PostgreSQL and exposed through a REST API deployed on Azure.

TYPE Personal project
ROLE Design and development
STATUS In production
5 fields extracted per invoice: supplier, date, CUIT, number, and total
PDF + TXT input formats, also in batches

Project presentation video.

Context

PDF and text invoices are unstructured documents. This system automates the end-to-end extraction, validation, persistence, and exposure of their accounting data.

The result is exposed as a Swagger-documented REST API, ready to integrate with external systems and n8n automation workflows.

What I built

  • AI extraction: uses the Groq API with the gpt-oss-120b model to structure supplier, date, CUIT, number, and total as validated JSON from PDF and TXT.
  • REST API: FastAPI with public OpenAPI (Swagger) documentation. Endpoints to query and filter invoices and integrate them with other systems.
  • Persistence: PostgreSQL on Neon with SQLAlchemy and psycopg2 (with SSL), using transactional inserts and parameterized queries.
  • Batch processing: scripts to ingest complete PDF and TXT directories (pypdf, pathlib), with exception handling and per-file transactions.
  • Automation: an n8n workflow consumes the API (HTTP Request), splits the response into individual invoices (Split Out), and applies a conditional rule to amounts (If). The workflow JSON is in the repository.
  • Deployment: Azure App Service (Linux) with continuous integration through GitHub Actions.

How I worked

A real problem: dropped connections

Neon is serverless PostgreSQL and suspends inactive connections. During PDF batch processing and occasional API queries, this caused psycopg2.OperationalError: SSL connection has been closed unexpectedly errors and HTTP 500 responses in production.

How I solved it

  • In the API: I configured the SQLAlchemy engine with pool_pre_ping=True, which validates the connection before each statement and reconnects transparently, plus pool_recycle=300.
  • In the batch script: I stopped keeping a connection open while reading files. It now opens a new connection for each file and closes it in a finally block, so a problematic file does not stop the rest of the batch.

How I verified it works

  • Local integration tests with mixed PDF batches (valid, empty, and anomalous) to verify that errors remain isolated per file (try/except with rollback).
  • I validated the endpoints in Swagger UI and confirmed each Azure deployment in the GitHub Actions logs.
NEXT CASE Intelligent document processing