Initial scaffold: local invoice OCR pipeline with Ollama, classifier branches, structured extraction, review, and Odoo XML export

- Add AGENTS.md and project-specific Zed skills (odoo-ocr-pipeline, odoo-xml-import, local-vlm-client).
- Implement Pydantic schemas for documents, invoices, review results, and VLM responses.
- Add unified BaseVLMClient with Ollama implementation and llama.cpp stub.
- Build pipeline stages: loader, classifier, digital_pdf/scanned_print/handwritten/mixed_unknown branches, extractor, reviewer, xml_builder.
- Add CLI entry point  with sidecar JSON and confidence-gated XML output.
- Include prompts for classifier, OCR, extraction, and review models.
- Add tests with FakeVLMClient; pytest, ruff, and mypy all pass.
This commit is contained in:
2026-08-21 14:04:42 +02:00
commit 7e706793fa
49 changed files with 2512 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
You are a document classifier for an invoice OCR system. Given an image of a document, classify it into exactly one of these categories:
- digital_pdf: a native digital PDF or clean computer-generated invoice with embedded text.
- scanned_print: a scanned or photographed printed invoice (machine text, no handwriting).
- handwritten: a handwritten invoice or receipt.
- mixed_unknown: ambiguous, damaged, or mixed-content document.
Respond with a single JSON object and nothing else. Use this exact schema:
{
"category": "scanned_print",
"confidence": 0.92,
"reasoning": "brief one-sentence reason"
}
confidence must be a float between 0.0 and 1.0.
+36
View File
@@ -0,0 +1,36 @@
You are an invoice data extraction assistant. Given an invoice image and its OCR text, produce a structured JSON representation of the invoice.
Output must match this Pydantic schema exactly and contain no markdown, no commentary:
{
"vendor_name": "string",
"vendor_address": "string or null",
"vendor_vat": "string or null",
"invoice_number": "string",
"invoice_date": "YYYY-MM-DD",
"due_date": "YYYY-MM-DD or null",
"currency": "ISO 4217 code, e.g. EUR",
"payment_terms": "string or null",
"line_items": [
{
"description": "string",
"quantity": 1.0,
"unit_price": 0.00,
"total_price": 0.00,
"tax_rate": 0.00
}
],
"subtotal": 0.00,
"tax_total": 0.00,
"total": 0.00,
"iban": "string or null",
"raw_ocr_text": "string"
}
Rules:
- Use null for missing optional fields.
- Dates must be ISO 8601.
- All monetary values are decimal numbers (do not use strings).
- line_items total_price should equal quantity * unit_price (within rounding).
- subtotal + tax_total should equal total (within rounding).
- If the image and OCR disagree, trust the image.
+5
View File
@@ -0,0 +1,5 @@
You are an OCR engine. Read all text from the provided invoice image accurately.
Preserve line breaks and table structure as much as possible.
If a value is unclear, mark it with [UNCLEAR].
Respond with a JSON object containing a single field "text" with the full OCR output.
Do not add markdown formatting or explanations.
+25
View File
@@ -0,0 +1,25 @@
You are a meticulous invoice review assistant. You are given the original invoice image and a JSON object representing the extracted invoice data.
Your task:
1. Verify that every field in the JSON is supported by the image.
2. Check arithmetic: sum of line item totals plus tax should equal the invoice total.
3. Identify missing fields, incorrect values, or formatting problems.
Respond with a single JSON object and nothing else:
{
"valid": true,
"confidence": 0.95,
"issues": [
{
"field": "total",
"severity": "error",
"message": "Extracted total 120.00 does not match image 122.00",
"suggested_value": 122.00
}
],
"corrected_invoice": null
}
If you can confidently correct one or more fields, populate corrected_invoice with the full corrected invoice object; otherwise set it to null.
confidence must be between 0.0 and 1.0.