Lightweight tools for extracting invoice data via OCR + LLM or VLLM and generating GiroCode QR codes to make paying a breeze. Works on Dropbox or locally.
Paying medical bills and submitting them to health insurance is time-consuming, so I built this tool to remove the busywork. I scan invoices with the Dropbox mobile app, and a Raspberry Pi job runs ocr-invoice every few minutes. Whenever ocr-invoice finds a new bill, it renames the file and generates a PDF with a GiroCode QR code that most banking apps can scan. In my tests, the OCR + LLM pipeline succeeds about 8/10 (it struggles with rough scans), while the VLLM vision backend using qwen2.5-vl-72b-instruct scored 10/10.
pip install -e . # core CLI
pip install -e .[dropbox] # add Dropbox SDK
pip install -e .[vision] # add vision + evaluation depsocr-invoice ./invoices/sample.pdf # local file
ocr-invoice /Remote/Folder --storage dropbox # Dropbox pathUse --output-dir to redirect artifacts and --debug to keep originals in place. Source PDFs are auto-recompressed before processing; override the 20 MiB limit via MAX_PDF_SIZE (bytes) in your shell or .env file.
- Install the Dropbox extras:
pip install -e .[dropbox]. - Create a Dropbox app in the developer console, then copy its App key and App secret into your
.envasDROPBOX_KEYandDROPBOX_SECRET. - Run the login helper to exchange an authorization code for long-lived tokens:
ocr-invoice-dropboxlogin. It opens a browser, savesDROPBOX_REFRESH_TOKEN/DROPBOX_ACCESS_TOKENinto.env, and reuses them on subsequent runs. - Point the CLI at a Dropbox folder (leading slash required) to verify access:
ocr-invoice /Remote/Invoices --storage dropbox --recursive.
.envis loaded automatically by the CLI (viapython-dotenv).MISTRAL_API_KEYpowers the OCR step.- The default text LLM is
groq/llama-3.3-70b-versatile; setGROQ_API_KEYfor it to work. - If you swap in another LiteLLM model, export the corresponding provider key (e.g.,
OPENAI_API_KEY,ANTHROPIC_API_KEY,MISTRAL_API_KEY) following LiteLLM's<PROVIDER>_API_KEYnaming convention.
Point the CLI at a directory (local or Dropbox) and it skips PDFs that already have _qr, _bill, _prescription, or _unknown siblings. Use --recursive for nested folders.
Switch to multimodal models with --extraction-backend vllm plus the --vision-* flags (model, DPI, page limit, detail). Requires the vision extras.
Define PDF_FORM_SUBMIT_URL (and optional redirect/base vars) to embed a "Regenerate QR" button inside generated PDFs. Run the helper server locally:
python -m form_submit_server --host 0.0.0.0 --port 8745
# or
FORM_SUBMIT_SERVER_PORT=9000 docker compose -f form_submit_server/docker-compose.yaml up --buildThe Flask app prints incoming payloads, exposes /tokens, and can regenerate QR PNGs via /redirect.
Field mappings + prompts live in ocr_invoice/default_config.yaml. Pass --config custom.yaml to override without changing code.
ocr-invoice-eval replays the extraction flow against reviewed _qr.pdf files and writes a JSONL report:
ocr-invoice-eval --input-dir ./invoices --report evaluation/report.jsonlUse the same flags as the main CLI (--model, --ocr, --extraction-backend, --vision-*) to benchmark alternative setups.
Much of the code for this application was written with the help of Codex. This is the first project in which I have used AI to such an extent. However, I reviewed all the code and often had to change the structure, since Codex tended to produce spaghetti code. Please note that the automatically generated tests were not audited.
