English |
German
An agentic Python library for extracting structured data from receipts and invoices and preparing essential German tax return statements.
- German Tax Alignment — Category taxonomy and VAT handling aligned with German fiscal practice
- Local-First — Everything runs completely offline; models are auto-downloaded from HuggingFace and cached locally, data is stored in a local database
- 4-Agent Pipeline — Sequential specialised agents for metadata, counterparty, amounts, and line items; short focused prompts for reliable local model performance
- Web UI — Full browser interface for uploading, reviewing, editing, and managing receipts and invoices and preparing tax returns
- ELSTER submission — Annual Umsatzsteuererklärung (USt), E-Bilanz, and UStVA submitted directly to the Finanzamt via the official ERiC library; confirmed working end-to-end with a formal Finanzamt acknowledgement
Backend
Python — package language
FastAPI — backend for the web UI
PaddleOCR — OCR for scanned PDFs
Tesseract — OCR for scanned PDFs and images when PaddleOCR fails or times out
HuggingFace Hub — models downloaded and cached automatically on first use; no separate server required
Mistral –
mistral:7bis the recommended default (maps tomlx-community/Mistral-7B-Instruct-v0.3-4biton Apple Silicon)Qwen –
qwen2.5:7b-instruct-q4_K_Mis a good alternative (maps tomlx-community/Qwen2.5-7B-Instruct-4biton Apple Silicon)mlx-lm — 4-bit inference on Apple Silicon (M-series) via the MLX framework; ~13 % faster than Ollama
transformers — cross-platform inference fallback (Linux / Windows / Intel Mac)
SQLite – local database for original receipts and extracted data
Frontend
React — interactive frontend
Vite — fast dev server and production bundler
Tailwind CSS — utility-first styling
TypeScript — type-safe component and API code
CLI
Typer — CLI with coloured progress output
Packaging
PyPI — distributed as an installable Python package
pip install finamtFor CLI usage, installing via pipx is recommended — it places finamt into its own dedicated virtual environment, ensuring its dependencies never interfere with your other projects, while still exposing the finamt command globally without requiring you to activate a virtualenv:
pipx install finamtNote for Python 3.14+ users:
finamtcurrently requires Python 3.13. If your system Python is 3.14 or newer, install uv to manage Python versions and pass the resolved path to pipx:uv python install 3.13 pipx install finamt --python $(uv python find 3.13)
- Python 3.10+
- Tesseract OCR (optional fallback when PaddleOCR times out)
No Ollama required. LLM models are downloaded automatically from HuggingFace on first use (~4 GB per model) and cached at
~/.cache/huggingface/hub. On Apple Silicon themlx-lmbackend is used; on other platformstransformersis used.
Ubuntu / Debian
sudo apt-get install tesseract-ocr tesseract-ocr-deumacOS
brew install tesseract tesseract-langWindows
Download the installer from https://github.com/UB-Mannheim/tesseract/wiki and add it to your PATH.
finamt serve
Interactive UI to upload receipts and manage tax statements
from finamt import FinanceAgent
agent = FinanceAgent()
result = agent.process_receipt("receipt.pdf")
if result.success:
data = result.data
print(f"Counterparty: {data.vendor}")
print(f"Date: {data.receipt_date}")
print(f"Total: {data.total_amount} EUR")
print(f"VAT: {data.vat_percentage}% ({data.vat_amount} EUR)")
print(f"Net: {data.net_amount} EUR")
print(f"Category: {data.category}")
print(f"Items: {len(data.items)}")
# Serialise to JSON
with open("extracted.json", "w", encoding="utf-8") as f:
f.write(data.to_json())
else:
print(f"Extraction failed: {result.error_message}")result = agent.process_receipt("invoice_to_client.pdf", receipt_type="sale")from pathlib import Path
from finamt import FinanceAgent
agent = FinanceAgent()
results = agent.batch_process(list(Path("receipts/").glob("*.pdf")))
for path, result in results.items():
if result.success:
print(f"{path}: {result.data.total_amount} EUR")
else:
print(f"{path}: ERROR — {result.error_message}")Settings are read in priority order from: environment variables → .env file → built-in defaults.
# .env
# OCR and general settings
FINAMT_OCR_LANGUAGE=german
FINAMT_OCR_TIMEOUT=60
FINAMT_TESSERACT_CMD=tesseract
FINAMT_OCR_PREPROCESS=true
FINAMT_PDF_DPI=150
# Extraction agents — all 4 agents use this model
FINAMT_AGENT_MODEL=mistral:7b
FINAMT_AGENT_TIMEOUT=60
FINAMT_AGENT_NUM_CTX=4096
FINAMT_AGENT_MAX_RETRIES=2You can also pass config objects directly:
from finamt import FinanceAgent
from finamt.agents.config import Config, AgentsConfig
agent = FinanceAgent(
config=Config(ocr_language="deu+eng", pdf_dpi=150),
agents_cfg=AgentsConfig(agent_model="mistral:7b"),
)class FinanceAgent:
def __init__(
self,
config: Config | None = None,
db_path: str | Path | None = "~/.finamt/default/finamt.db",
agents_cfg: AgentsConfig | None = None,
) -> None: ...
def process_receipt(
self,
pdf_path: str | Path | bytes,
receipt_type: str = "purchase", # "purchase" or "sale"
) -> ExtractionResult: ...
def batch_process(
self,
pdf_paths: list[str | Path],
receipt_type: str = "purchase",
) -> dict[str, ExtractionResult]: ...Always check success before accessing data.
@dataclass
class ExtractionResult:
success: bool
data: ReceiptData | None
error_message: str | None
duplicate: bool # True if already in the database
existing_id: str | None # ID of the original if duplicate
processing_time: float | None # seconds
def to_dict(self) -> dict: ...@dataclass
class ReceiptData:
id: str # SHA-256 of OCR text — stable dedup key
receipt_type: ReceiptType # "purchase" or "sale"
counterparty: Counterparty | None # vendor (purchase) or client (sale)
receipt_number: str | None
receipt_date: datetime | None
total_amount: Decimal | None
currency: str | "EUR"
vat_percentage: Decimal | None # e.g. Decimal("19.0")
vat_amount: Decimal | None
net_amount: Decimal | None # computed: total - vat
category: ReceiptCategory
items: list[ReceiptItem]
vat_splits: list[dict] # for mixed-rate invoices
vendor: str | None # alias for counterparty.name
def to_dict(self) -> dict: ...
def to_json(self) -> str: ...@dataclass
class Counterparty:
id: str # UUID assigned by the database
name: str | None
vat_id: str | None # EU format, e.g. DE123456789
tax_number: str | None # German Steuernummer, e.g. 123/456/78901
address: Address
verified: bool # manually confirmed in the UI@dataclass
class ReceiptItem:
position: int | None
description: str
quantity: Decimal | None
unit_price: Decimal | None
total_price: Decimal | None
vat_rate: Decimal | None
vat_amount: Decimal | None
category: ReceiptCategory
def to_dict(self) -> dict: ...A validated string subclass. Invalid values are silently normalised to "other".
from finamt.agents.prompts import RECEIPT_CATEGORIES # list[str]
from finamt.models import ReceiptCategory
cat = ReceiptCategory("software") # valid
cat = ReceiptCategory("unknown_value") # normalised to "other"
cat = ReceiptCategory.other() # explicit fallbackAll exceptions inherit from FinanceAgentError.
| Exception | Raised when |
|---|---|
OCRProcessingError |
PDF cannot be opened or text extraction fails |
LLMExtractionError |
Model returns invalid JSON after all retries |
InvalidReceiptError |
Extracted data fails business-logic validation |
from finamt.exceptions import FinanceAgentError, OCRProcessingError
try:
result = agent.process_receipt("scan.pdf")
except OCRProcessingError as e:
print(e)Each receipt goes through four sequential LLM calls, each with a short focused prompt:
| Agent | Extracts |
|---|---|
| Agent 1 | Receipt number, date, category |
| Agent 2 | Counterparty name, VAT ID, Steuernummer, address |
| Agent 3 | Total amount, VAT percentage, VAT amount |
| Agent 4 | Line items (description, VAT rate, VAT amount, price) |
Results are merged in Python — no additional LLM validation step. Debug output for every agent (prompt, raw response, parsed JSON) is saved to ~/.finamt/debug/<receipt_id>/.
Every receipt is tagged with a category and optional subcategory. Categories map directly to line items in the German ELSTER tax forms (EÜR / UStVA), so the correct totals land in the right fields without manual re-sorting.
Receipt processing
- OCR pipeline (PaddleOCR + Tesseract fallback)
- 4-agent extraction (metadata, counterparty, amounts, line items)
- Deduplication, database storage, batch processing
Tax calculation
- UStVA — VAT pre-return (monthly / quarterly)
- UStE — annual VAT return
- EÜR — income-surplus statement
- KSt 1 — corporate income tax return
- GewSt — trade tax return
- Jahresabschluss — annual accounts (Bilanz + GuV, § 267a HGB)
Validation
- XSD validation of generated XBRL against HGB taxonomy
- ELSTER dry-run / test-server validation before live submission
ELSTER transmission
- UStVA — ELSTER XML builder + Kennzahlen mapper + RSA signing + HTTP submission
- E-Bilanz — XBRL instance document (HGB taxonomy v6, MicroBilG schema)
- E-Bilanz — ERiC ctypes bridge for actual transmission
- USt — ELSTER XML builder + ERiC ctypes submission (confirmed end-to-end)
- UStVA — ELSTER XML builder + ERiC ctypes submission (confirmed end-to-end)
- KSt — ELSTER XML builder
- GewSt — ELSTER XML builder
- EÜR, ESt — ELSTER XML builder
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-change) and make your changes - Run the test suite:
pytest --cov=src --cov-report=term-missing
- Lint and format with
Ruff:
ruff check --fix src/ tests/ ruff format src/ tests/
- Update the documentation
- Submit a pull request
AGPL-3.0 — see LICENSE for details.
finamt is available under the AGPL-3.0 license.
If you wish to use finamt in a proprietary setting, without the obligations of the AGPL (e.g. without releasing source code or for use in a commercial SaaS product), a commercial license is available.
For inquiries, contact: info@spaceoctahedron.com
This software depends on external libraries and services, including:
- PaddleOCR (Apache License 2.0)
- Tesseract OCR (Apache License 2.0)
- HuggingFace Hub / transformers (Apache License 2.0)
- mlx-lm (MIT License, Apple Silicon only)
finamt downloads language models from HuggingFace (e.g. Mistral, Qwen) on first use and caches them locally.
These models are not distributed with this software and are subject to their own licenses. Users are responsible for complying with the respective terms when downloading and using such models.
This software is provided for informational and automation purposes only.
It does not constitute tax, legal, or accounting advice.
While finamt is designed to assist with the preparation of German tax-related data (e.g. VAT returns, EÜR, ELSTER submissions), no guarantee is made regarding:
- correctness of extracted data
- completeness of financial records
- compliance with applicable tax laws and regulations
- acceptance by tax authorities
Users are solely responsible for verifying all outputs before submission to any authority.
Always consult a qualified tax advisor (Steuerberater) for legally binding guidance.
To the maximum extent permitted by law, Space Octahedron GmbH assumes no liability for:
- errors in OCR or LLM-based extraction
- incorrect classifications or calculations
- rejected or incorrect tax filings
- financial losses or penalties arising from use of this software
- Produktname: Space Octahedron® finamt
- Hersteller: Space Octahedron GmbH
- Kontakt: info@spaceoctahedron.com