beancount-helper
Convert bank statement Excel files into Beancount transactions with a rule-based account matching engine.
2025-01-15 * "Coffee Shop" "Latte"
Expenses:Food:Coffee 25.00 CNY
Assets:CCB
Supported banks
| Parser | Source |
|---|---|
ccb |
China Construction Bank (建设银行) statement .xls |
wechat |
WeChat Pay (微信支付) statement .xlsx |
Installation
pip install beancount-helper
For development:
git clone https://github.com/your-org/beancount-helper.git
cd beancount-helper
uv sync
Quick start
Step 1 — Export your statement from the bank app and save it locally.
Step 2 — (Optional) Create a rules file:
# rules.txt
p=opposite:eq:Coffee Shop;;direct=out=>Expenses:Food:Coffee
direct=in=>Income:Misc
Step 3 — Run:
beancount-helper ccb.xls -p ccb -r rules.txt -o ledger.beancount
That's it. Every record falls into a Beancount account — either matched by your rules, or placed in Equity:Opening-Balances as a fallback.
CLI reference
beancount-helper INPUT -p PARSER [-r RULES] [-o OUTPUT] [--asset ASSET] [--default-account ACCOUNT]
| Argument | Required | Default | Description |
|---|---|---|---|
INPUT |
yes | — | Path to the bank statement file (.xls / .xlsx). |
-p, --parser |
yes | — | ccb or wechat. |
-r, --rules |
no | — | Path to a rules file. Without it, all records use the default account. |
-o, --output |
no | output.beancount |
Output file path. |
--asset |
no | Assets:CCB / Assets:WeChat |
Asset account for the source of funds. |
--default-account |
no | Equity:Opening-Balances |
Fallback account when no rule matches. |
Examples
# Bare minimum — everything goes to Equity:Opening-Balances
beancount-helper ccb.xls -p ccb
# With rules
beancount-helper wechat.xlsx -p wechat -r rules.txt
# Custom accounts
beancount-helper ccb.xls -p ccb -r rules.txt \
--asset Assets:CCB:Savings \
--default-account Expenses:Pending
Rule system
Rules tell the tool which Beancount account a bill should be posted to. Each rule has this shape:
condition1;;condition2;;...=>TargetAccount
Conditions are AND-ed: a bill must satisfy all of them for the rule to fire. Rules are tried in order; the first match wins.
Condition reference
Direction (direct / direction)
Matches whether money flows in or out.
direct=in # income
direct=out # expense
Property (p / property)
Matches a field on the bill record.
| Method | Syntax | Example |
|---|---|---|
| exact | p=field:eq:value |
p=opposite:eq:Coffee Shop |
| contains | p=field:in:substring |
p=note:in:lunch |
| regex | p=field:re:pattern |
p=product:re:^ATMS$ |
Available fields: opposite, note, product (wechat only).
Amount comparisons
| Operator | Meaning |
|---|---|
lt=N |
amount < N |
le=N |
amount ≤ N |
gt=N |
amount > N |
ge=N |
amount ≥ N |
eq=N |
amount = N |
Negative amounts are expenses, positive are income (from the bank's perspective).
Combining conditions
Separate conditions with ;;:
# Small lunch expenses from a specific vendor
direct=out;;p=opposite:in:Restaurant;;lt=50.0=>Expenses:Food:Lunch
# Any income
direct=in=>Income:Misc
Rule file format
One rule per line. Blank lines and #-comments are ignored.
# Food & drinks
p=opposite:eq:Starbucks=>Expenses:Food:Coffee
p=opposite:in:Pizza;;lt=80.0=>Expenses:Food:Takeout
# Transport
p=opposite:eq:DiDi=>Expenses:Transport:Ride
p=note:re:metro|subway=>Expenses:Transport:Transit
# Income
direct=in;;gt=5000.0=>Income:Salary
direct=in=>Income:Misc
How it works
Bank Excel (.xls/.xlsx)
│
▼
bill_resolver ← parses bank-specific formats into Bill objects
│
▼
transformer.rule ← matches each Bill against the rule set
│
▼
beancount.core ← assembles BeanCount transactions, dumps to text
│
▼
output.beancount
Project structure
beancount-helper/
├── main.py CLI entry point
├── rules.example.txt Sample rule set
├── src/
│ ├── beancount/core.py BeanCount model + serialization
│ ├── bill_resolver/
│ │ ├── ccb.py CCB Excel parser
│ │ └── wechat.py WeChat Excel parser
│ └── transformer/rule.py Rule matching engine
└── test/
├── test_beancount_core.py
└── rule_matcher.py
Development
# Install dev dependencies
uv sync
# Run tests
uv run pytest
# Lint
uv run ruff check .
License
MIT
Metadata
Release files for beancount-helper 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| beancount_helper-0.1.0.tar.gz | 32.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| beancount_helper-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 43.7 kB
Release files / beancount_helper-0.1.0.tar.gz
| Download URL | beancount_helper-0.1.0.tar.gz |
|---|---|
| Size | 32.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d9280a1047943db1e48bc2d425bde84055a1a6b1928936618a2f672fe0848003
|
|
BLAKE2b-256 checksum How to use checksums |
b3a53973b477aba6d521eb65e51cfa62a90aadd513355636c257fd3a928aec27
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Release files / beancount_helper-0.1.0-py3-none-any.whl
| Download URL | beancount_helper-0.1.0-py3-none-any.whl |
|---|---|
| Size | 11.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
33d7a4abe03c7164ded85ecd8e40214decfb7b08f74c6ded86586274e8c892f0
|
|
BLAKE2b-256 checksum How to use checksums |
77e36e91feaf8069f42442d375d08ed3fbd57efeaa908c6c03e356a2172a9999
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Security for beancount-helper 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.