Skip to main content

An agentic Python library for extracting key information from receipts and preparing essential German tax return statements.

Project description

finamt

finamt

Python 3.10+ PyPI version Downloads License: AGPL v3 Tests Coverage GitHub last commit Documentation Status

US English | DE German

An agentic Python library for extracting structured data from receipts and invoices and preparing essential German tax return statements.

Features

  • German Tax Alignment — Category taxonomy and VAT handling aligned with German fiscal practice
  • Local-First — Everything runs completely offline, with data 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

Tech Stack

Backend

  • Python Python — package language
  • FastAPI FastAPI — backend for the web UI
  • PaddleOCR PaddleOCR — OCR for scanned PDFs
  • Tesseract Tesseract — OCR for scanned PDFs and images when PaddleOCR fails or times out
  • Ollama Ollama — local LLMs for structured extraction of information from receipts and invoices
    • Qwen Qwen – laptop-compatible LLMs, with qwen2.5:7b-instruct-q4_K_M currently as preferred default for text-based extraction
    • Mistral Mistral – alternative open-weight performant models, with mistral:7b showing similar results to qwen2.5:7b-instruct-q4_K_M.
  • SQLite SQLite – local database for original receipts and extracted data

Frontend

  • React React — interactive frontend
  • Vite Vite — fast dev server and production bundler
  • Tailwind CSS Tailwind CSS — utility-first styling
  • TypeScript TypeScript — type-safe component and API code

CLI

  • Typer Typer — CLI with coloured progress output

Packaging

  • PyPI PyPI — distributed as an installable Python package

Installation

pip install finamt

For 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 finamt

Note for Python 3.14+ users: finamt currently 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)

System Requirements

  • Python 3.10+
  • Ollama running locally with a supported model pulled
  • Tesseract OCR (optional fallback when PaddleOCR times out)

Ollama

# Install Ollama
curl -fsSL https://ollama.ai/install.sh | sh

# Pull a model — qwen2.5:7b-instruct-q4_K_M is the recommended default
ollama pull qwen2.5:7b-instruct-q4_K_M

Other models that work well: qwen2.5:14b-instruct, mistral:7b — similar extraction quality, with qwen2.5:14b-instruct requiring roughly 2× the processing time.

Tesseract OCR (optional fallback from PaddleOCR)

Ubuntu / Debian

sudo apt-get install tesseract-ocr tesseract-ocr-deu

macOS

brew install tesseract tesseract-lang

Windows

Download the installer from https://github.com/UB-Mannheim/tesseract/wiki and add it to your PATH.

Quick Start

Interactive UI

finamt serve

Interactive UI to upload receipts and manage tax statements

Python API

Process a single receipt (expense)

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}")

Sale invoices (outgoing)

result = agent.process_receipt("invoice_to_client.pdf", receipt_type="sale")

Batch processing

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}")

Configuration

Settings are read in priority order from: environment variables → .env file → built-in defaults.

# .env

# OCR and general settings
FINAMT_OLLAMA_BASE_URL=http://localhost:11434
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=qwen2.5:7b-instruct-q4_K_M
FINAMT_AGENT_TIMEOUT=60
FINAMT_AGENT_NUM_CTX=4096
FINAMT_AGENT_MAX_RETRIES=2

You 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"),
)

API Reference

FinanceAgent

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]: ...

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: ...

ReceiptData

@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: ...

Counterparty

@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

ReceiptItem

@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: ...

ReceiptCategory

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 fallback

Exceptions

All exceptions inherit from FinanceAgentError.

Exception Raised when
OCRProcessingError PDF cannot be opened or text extraction fails
LLMExtractionError Ollama is unreachable or 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)

Extraction Pipeline

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>/.

Categories and Subcategories

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.

Category Subcategories
services services freelance consulting legal accounting notary
products products physical_goods digital_goods merchandise samples
material material consumables raw_materials packaging merchandise
equipment equipment low_value_asset computer machinery furniture tools
software software subscriptions pay_as_you_go licenses hosting domains
licensing licensing software_licenses media_licenses other_ip
telecommunication telecommunication phone internet bundled
travel travel transport accommodation meals per_diem incidental
car car fuel parking garage repair maintenance insurance leasing rental
education education courses books conferences certifications
utilities utilities electricity heating water waste
insurance insurance liability health vehicle property
financial financial bank_fees interest loan_costs payment_fees
office office rent coworking storage cleaning security
marketing marketing advertising print_media trade_fairs sponsorship gifts
donations donations charitable political church
public_fees public_fees broadcasting_fee ihk_hwk berufsgenossenschaft other_public_fee
other other membership_fees sundry

TODO

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)

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
  • KSt, GewSt, UStVA, USt — ELSTER XML builder
  • EÜR, ESt — ELSTER XML builder

Validation

  • XSD validation of generated XBRL against HGB taxonomy
  • ELSTER dry-run / test-server validation before live submission

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-change) and make your changes
  3. Run the test suite:
    pytest --cov=src --cov-report=term-missing
    
  4. Lint and format with Ruff:
    ruff check --fix src/ tests/
    ruff format src/ tests/
    
  5. Update the documentation
  6. Submit a pull request

License

AGPL-3.0 — see LICENSE for details.

Commercial Licensing

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

Third-Party Components and Models

This software depends on external libraries and services, including:

  • PaddleOCR (Apache License 2.0)
  • Tesseract OCR (Apache License 2.0)
  • Ollama (MIT License)

finamt uses locally installed language models (e.g. Qwen) via Ollama.

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.

Disclaimer

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

Product Information (ELSTER)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

finamt-0.18.0.tar.gz (560.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

finamt-0.18.0-py3-none-any.whl (524.8 kB view details)

Uploaded Python 3

File details

Details for the file finamt-0.18.0.tar.gz.

File metadata

  • Download URL: finamt-0.18.0.tar.gz
  • Upload date:
  • Size: 560.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.11

File hashes

Hashes for finamt-0.18.0.tar.gz
Algorithm Hash digest
SHA256 77f0092db008f2cac29ce8c76398c8ba781ed567820421b31d69038411407a28
MD5 880e29a47ed83956ba3254590d5a30fa
BLAKE2b-256 9841b7e411661dffbd5bfae45c8e08ad305aaf4881dd0a8e3e8788bfe035300d

See more details on using hashes here.

File details

Details for the file finamt-0.18.0-py3-none-any.whl.

File metadata

  • Download URL: finamt-0.18.0-py3-none-any.whl
  • Upload date:
  • Size: 524.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.11

File hashes

Hashes for finamt-0.18.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ecb1dfeaa5b932ddd26cdc1b51e1140ed1e77fae3325decfa1bae22403ef45e1
MD5 9f59b823e5f62956fd3ce56d6aa1a194
BLAKE2b-256 e8f1ca729df62a53be07ba46bc5ac1c36756bd45e38881f68a2e57479c730075

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page