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 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 managing receipts
  • 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
  • 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 is the recommended default
ollama pull qwen2.5:7b-instruct-q4_K_M

Other models that work well: qwen3:8b, llama3.2, llama3.1.

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="qwen3:8b"),
)

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)
  3. Make your changes
  4. Lint and format with Ruff:
    ruff check --fix src/ tests/
    ruff format src/ tests/
    
  5. Run the test suite:
    pytest --cov=src --cov-report=term-missing
    
  6. Submit a pull request

License

MIT — see LICENSE for details.

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

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.17.2.tar.gz (530.9 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.17.2-py3-none-any.whl (506.3 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for finamt-0.17.2.tar.gz
Algorithm Hash digest
SHA256 615f9ee70825e0280672e99995550c8d1d7ab9ed8ba79c12e5bce86ed8f44521
MD5 a1533dd71ee16e44650e30184e57621c
BLAKE2b-256 b917ffa014f4c3aaec7a53d378f8960dbad0190f56117c513f4f416be086bbac

See more details on using hashes here.

File details

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

File metadata

  • Download URL: finamt-0.17.2-py3-none-any.whl
  • Upload date:
  • Size: 506.3 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.17.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a89904b62b0cd21edb55e4b9699ee110361a514504c2296aae3236fb25d98f06
MD5 d93d0ef7866fc4950b11e622cc8bc716
BLAKE2b-256 1bf53848c90f508862c1b2d36ed0a8df177423d7a16ffd10a96808ad92cadf08

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