Skip to main content

Beancount Tools Collection

🧮 My personal collection of beancount tools including importers, price fetchers, plugins, and utilities for various financial institutions.

CI Python 3.11+ License: MIT GitHub issues

Features

📥 Data Importers

Swiss Institutions:

  • Yuh - CSV exports
  • Viseca - CSV bill exports (primary) and JSON transaction exports (archival), including Migros Cumulus Credit Card
  • VIAC - JSON transaction exports (pillar 2 & 3a)
  • Finpension - CSV transaction reports (pillar 3a)

International Institutions:

  • Interactive Brokers - FlexQuery XML reports (global); fetch/credential failures surface as errors in Fava and exit non-zero in the CLI (a genuinely empty statement still shows "No entries to import")
  • Revolut - CSV exports (multi-country)

Other Formats:

  • Firefly III - CSV exports

💰 Price Fetchers

  • Interactive Brokers - Real-time prices from FlexQuery

🔌 Beancount Plugins

  • Crickets chirping 🦗 - This section is as empty as my wallet after buying crypto at the peak

🛠️ Utility Scripts

  • Transaction Processor - Example transaction processing with ImporterProtocolAdapter and TransactionInspector for automatic categorization and payee standardization

Installation

From PyPI

pip install beancount-tools-collection

From Source

git clone https://github.com/mekanics/beancount-tools-collection.git
cd beancount-tools-collection
make install
uv run pre-commit install

make install syncs the lockfile into .venv. The pre-commit hooks format staged files; pre-push runs make check (the same gate CI uses).

Quick Start

Basic Importer Configuration

from beancount_tools_collection.importers import (
    finpension,
    ibkr,
    revolut,
    viac,
    viseca_csv,
    yuh,
)

# Example configuration
CONFIG = [
    # Swiss institutions
    finpension.FinpensionImporter(
        root_account='Assets:Pension:S3:Finpension:Portfolio1',
        deposit_account='Assets:Checking',
        isin_lookup={
            'CH0132501898': 'CH0132501898',  # Example ISIN mapping
            # ... more ISINs
        },
    ),
    viac.ViacImporter(
        root_account='Assets:Pension:S3a:Viac:Portfolio1',
        deposit_account='Assets:Checking',
        share_lookup={
            'UBS SMI': {'isin': 'CH0033782431', 'symbol': 'CH0033782431'},
            # ... more share mappings
        },
    ),
    yuh.YuhImporter(account='Assets:Cash:Yuh:CHF', goals_base_account='Assets:Savings:Yuh'),
    viseca_csv.VisecaCsvImporter(
        account='Liabilities:CreditCard:Viseca',
        # The monthly "Ihre Zahlung - Danke" row settles the previous bill.
        # Point it at the account you pay from and the liability returns to
        # zero each cycle, so a dropped transaction shows up as a balance error.
        settlement_account='Assets:Cash:Yuh:CHF',
        # Optional. Unmapped merchants stay single-legged on purpose.
        merchant_map={
            'Coop': 'Expenses:Groceries',
            'Migros': 'Expenses:Groceries',
            'SBB CFF FFS': 'Expenses:Transport',
        },
    ),
    # International institutions
    ibkr.IBKRImporter(
        Mainaccount='Assets:Invest:InteractiveBrokers',
        DivAccount='Income:Dividends:InteractiveBrokers',
        WHTAccount='Expenses:Taxes:WithholdingTax',
        PnLAccount='Income:Invest:Gains',
        FeesAccount='Expenses:Invest:Fees',
        configFile='ibkr.yaml',  # Your IBKR FlexQuery config
    ),
    revolut.RevolutImporter('revolut_chf', 'Assets:Cash:Revolut:CHF', 'CHF'),
]

Automatic Categorization

The Viseca CSV export carries no category, so VisecaCsvImporter emits single-legged postings and leaves the expense account to you. Anything not in merchant_map is left incomplete on purpose, which is exactly what smart_importer needs — it fills in postings left open and will not touch one that already names an account:

from smart_importer import PredictPayees, PredictPostings

HOOKS = [PredictPostings().hook, PredictPayees().hook]

Fava passes your existing entries automatically; on the CLI use extract -e existing.beancount so there is something to learn from.

Price Fetcher Configuration

# In your beancount price configuration
from beancount_tools_collection.prices import ibkr

# The IBKR price source will be available for bean-price

Documentation

Importer-Specific Setup

Each importer has specific requirements and configuration options:

Account Structure Examples

The importers work best with structured account hierarchies:

Assets:
  Cash:
    Yuh:
      CHF
      USD
    Revolut:
      CHF
      EUR
  Invest:
    InteractiveBrokers:
      Long-Term:
        VTI
        VXUS
        USD
  Pension:
    S3a:
      Finpension:
        Portfolio1:
          CHF
      Viac:
        Portfolio1:
          CH0132501898

Income:
  Dividends:
    InteractiveBrokers:
      Long-Term:
        USD
  Pension:
    S3a:
      Finpension:
        Portfolio1:
          Interest:
            CHF

Expenses:
  Invest:
    Fees:
      CHF
      USD
  Taxes:
    WithholdingTax

Notes

Viseca CSV bill exports

VisecaCsvImporter reads the CSV bill the Viseca One app exports. It recognises the file by its column header, so the download works unrenamed; pass filename_regex as well if you import several Viseca accounts separately.

A few behaviours worth knowing:

  • Payments become transfers. The monthly "Ihre Zahlung - Danke" row settles the previous bill. With settlement_account set it posts as a balanced transfer, so the liability returns to zero each cycle. The JSON importer drops these rows, which lets the liability grow without bound.
  • Refunds and foreign-currency rows are flagged ! for review, since their semantics have not yet been confirmed against real data. Set flag_unverified=False to turn that off.
  • Amounts are posted verbatim from Amount/Currency, which is always the settled amount in the card's currency. OriginalAmount is recorded as metadata but never used for arithmetic: it can differ from Amount even at exchange rate 1.0 in the same currency.
  • Bad rows are skipped, not fatal, and the count is reported in the log so the loss is never silent. Failures affecting the whole file (unreadable file, unexpected header, several cards with no card_accounts mapping) raise instead, so Fava shows a real error rather than "No entries to import".
  • Entries carry transactionId, the same metadata key the JSON importer writes, so re-imports and overlapping bills deduplicate exactly rather than heuristically.

Interactive Brokers errors (v1.1.0+)

IBKR Flex fetch and credential failures (expired/invalid token, bad ibkr.yaml, network errors, unparseable statements) now raise typed errors instead of returning an empty entry list. In Fava this surfaces as an import/API error (rather than the yellow "No entries to import from this file." warning that used to appear on hard failures). The CLI exits non-zero with a short remediation message.

Exception messages, log lines, and raised tracebacks redact the Flex token (and do not chain secret-bearing upstream exceptions). If older logs were shared while a token was still live, rotate the token under Reports > Flex Web Service.

Publishing to PyPI

Requires Python 3.11+. Bump the version, commit, then push a matching tag:

uv version --bump minor   # or patch / major
git add pyproject.toml uv.lock
git commit -m "Release 1.3.0"
git tag v1.3.0
git push origin main --tags

The tag must equal v plus the version in pyproject.toml. A mismatch fails before any PyPI contact. That tag push is the only publish trigger: the Release workflow tests, uploads to PyPI, then creates the matching GitHub Release. Creating or editing a Release in the GitHub UI does not upload again.

Contributing

We welcome contributions! Here's how you can help:

  1. Add new importers for financial institutions
  2. Improve existing importers with bug fixes and features
  3. Add price fetchers for different data sources
  4. Create plugins for common beancount workflows
  5. Improve documentation and examples

Development Setup

Same as From Source: make install and uv run pre-commit install.

make check    # lint, format check, lockfile
make format   # rewrite the tree
make test     # pytest with a 30% coverage ratchet
make build    # sdist + wheel, no local path sources
make audit    # zizmor over the workflows

CI and pre-push call these targets. Do not invoke Ruff directly if you want the same result as the pipeline.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • The beancount project for the excellent accounting framework
  • Various open-source beancount importers that served as inspiration

Support


Made with ❤️ for personal finance tracking

Download files

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

Source Distribution

beancount_tools_collection-1.3.0.tar.gz (41.6 kB view details)

Uploaded Source

Built Distribution

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

beancount_tools_collection-1.3.0-py3-none-any.whl (49.9 kB view details)

Uploaded Python 3

File details

Details for the file beancount_tools_collection-1.3.0.tar.gz.

File metadata

File hashes

Hashes for beancount_tools_collection-1.3.0.tar.gz
Algorithm Hash digest
SHA256 7597f9ed7ea9385b11be77367c851b95c3117c97e55adf8b247c3d2d3003ecce
MD5 6843ba212b304a9bc075859c103d4ac4
BLAKE2b-256 86806f26d47466d9fed83a895c2b90c88e297379f247bfaf965cf114806cd04c

See more details on using hashes here.

Provenance

The following attestation bundles were made for beancount_tools_collection-1.3.0.tar.gz:

Publisher: release.yml on mekanics/beancount-tools-collection

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file beancount_tools_collection-1.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for beancount_tools_collection-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dcc20de068b4f719068320d569d839bd12ef12a7e0d127ed930208ef46d325bf
MD5 9753ebcc41dfb46a66ff8b01e9f38f46
BLAKE2b-256 f800a374d0c0b94f1c3dd9553f1d8f30e27bd3d868555a3ccc32cd7f43bb9bd7

See more details on using hashes here.

Provenance

The following attestation bundles were made for beancount_tools_collection-1.3.0-py3-none-any.whl:

Publisher: release.yml on mekanics/beancount-tools-collection

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.4.0

2 files

This release

1.3.0 This release

2 files

1.2.0

2 files

1.1.0

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page