Add containerized OCR pipeline for fuel receipts

Self-hosted, verifiable pipeline for extracting structured data (station,
date/time, price per litre, litres, discount, total) from German fuel
receipt photos: grayscale + mild bilateral filter + contrast stretch,
Tesseract OCR, then LLM structuring via a small local model (Qwen 2.5,
served by Ollama). Each step is independently runnable and inspectable
through numbered scripts against a shared tooling container.

Ollama runs as its own long-lived container (0_run_ollama.sh), separate
from the stateless per-receipt pipeline steps, so the model stays loaded
in memory between runs. Includes a containerized ground-truth batch test
(test_batch.sh) that reproduces the full pipeline over the test receipts
and reports per-field accuracy.

Targets eventual deployment on a Raspberry Pi 4 k3s cluster.
This commit is contained in:
2026-09-28 19:35:29 +02:00
commit 7678d28717
17 changed files with 794 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
"""Step 2: run Tesseract OCR (German) over a preprocessed receipt image,
writing the raw recognized text to the output directory for inspection
before it is handed to the structuring step."""
import argparse
import os
import pytesseract
from PIL import Image
def ocr(path: str, out_dir: str) -> str:
os.makedirs(out_dir, exist_ok=True)
img = Image.open(path)
text = pytesseract.image_to_string(img, lang="deu", config="--psm 3")
out_path = os.path.join(out_dir, "ocr.txt")
with open(out_path, "w", encoding="utf-8") as f:
f.write(text)
return out_path
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-in", dest="in_path", required=True)
parser.add_argument("-out", dest="out_dir", required=True)
args = parser.parse_args()
out_path = ocr(args.in_path, args.out_dir)
print(out_path)
if __name__ == "__main__":
main()