Beancount Tools Collection
🧮 My personal collection of beancount tools including importers, price fetchers, plugins, and utilities for various financial institutions.
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:
- Finpension - CSV transaction reports
- Interactive Brokers - FlexQuery configuration
- VIAC - JSON export setup
- Viseca - CSV bill exports (primary) and JSON (archival)
- Yuh - CSV account-activities exports
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
Yuh CSV exports
YuhImporter reads the account-activities CSV the Yuh app exports. It recognises
the file by its column header, so the download works unrenamed; pass regex as
well if you import several Yuh accounts separately.
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_accountset 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. Setflag_unverified=Falseto turn that off. - Amounts are posted verbatim from
Amount/Currency, which is always the settled amount in the card's currency.OriginalAmountis recorded as metadata but never used for arithmetic: it can differ fromAmounteven 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_accountsmapping) 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:
- Add new importers for financial institutions
- Improve existing importers with bug fixes and features
- Add price fetchers for different data sources
- Create plugins for common beancount workflows
- 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file beancount_tools_collection-1.4.0.tar.gz.
File metadata
- Download URL: beancount_tools_collection-1.4.0.tar.gz
- Upload date:
- Size: 41.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
09a5a2404b7f8875417b4ad69c6e0b147db3306bb52f7beea841951982838f73
|
|
| MD5 |
fc33b5acee05f2c8bb186d11edcc3435
|
|
| BLAKE2b-256 |
6a186905bde4da424599555cf70356488bfaf6638040ad3241f3d919ba5ee3ca
|
Provenance
The following attestation bundles were made for beancount_tools_collection-1.4.0.tar.gz:
Publisher:
release.yml on mekanics/beancount-tools-collection
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
beancount_tools_collection-1.4.0.tar.gz -
Subject digest:
09a5a2404b7f8875417b4ad69c6e0b147db3306bb52f7beea841951982838f73 - Sigstore transparency entry: 2498846945
- Sigstore integration time:
-
Permalink:
mekanics/beancount-tools-collection@92860de50f959bca14ee01b3c8359eaef3fa4145 -
Branch / Tag:
refs/tags/v1.4.0 - Owner: https://github.com/mekanics
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@92860de50f959bca14ee01b3c8359eaef3fa4145 -
Trigger Event:
push
-
Statement type:
File details
Details for the file beancount_tools_collection-1.4.0-py3-none-any.whl.
File metadata
- Download URL: beancount_tools_collection-1.4.0-py3-none-any.whl
- Upload date:
- Size: 50.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b22a65551a700cd6bc7a1a75d4ce3ceb212cb47d3a07ed18bb90f8da3cce2d5
|
|
| MD5 |
b579af5f4bb987ae8f6b0a3fd14dda41
|
|
| BLAKE2b-256 |
1ea59ae041683e1f9ec8ee8747e6c4e99fc7330983700d24c03aee8c18f97b4b
|
Provenance
The following attestation bundles were made for beancount_tools_collection-1.4.0-py3-none-any.whl:
Publisher:
release.yml on mekanics/beancount-tools-collection
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
beancount_tools_collection-1.4.0-py3-none-any.whl -
Subject digest:
4b22a65551a700cd6bc7a1a75d4ce3ceb212cb47d3a07ed18bb90f8da3cce2d5 - Sigstore transparency entry: 2498846947
- Sigstore integration time:
-
Permalink:
mekanics/beancount-tools-collection@92860de50f959bca14ee01b3c8359eaef3fa4145 -
Branch / Tag:
refs/tags/v1.4.0 - Owner: https://github.com/mekanics
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@92860de50f959bca14ee01b3c8359eaef3fa4145 -
Trigger Event:
push
-
Statement type: