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.
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-120bmodel 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, pluspool_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
finallyblock, 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/exceptwith rollback). - I validated the endpoints in Swagger UI and confirmed each Azure deployment in the GitHub Actions logs.