Adds Beancount import helper script
Implements a script to import Beancount ledger transactions into the Castle accounting extension. The script fetches BTC/EUR rates, retrieves accounts from the Castle API, maps users, parses Beancount transactions, converts EUR to sats, and uploads the data to Castle. Adds error handling, dry-run mode, and detailed logging for improved usability. Displays equity account status and validates the existence of user equity accounts.
This commit is contained in:
parent
4ae6a8f7d2
commit
9054b3eb62
3 changed files with 724 additions and 0 deletions
168
helper/README.md
Normal file
168
helper/README.md
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
# Castle Beancount Import Helper
|
||||
|
||||
Import Beancount ledger transactions into Castle accounting extension.
|
||||
|
||||
## 📁 Files
|
||||
|
||||
- `import_beancount.py` - Main import script
|
||||
- `btc_eur_rates.csv` - Daily BTC/EUR rates (create your own)
|
||||
- `README.md` - This file
|
||||
|
||||
## 🚀 Setup
|
||||
|
||||
### 1. Create BTC/EUR Rates CSV
|
||||
|
||||
Create `btc_eur_rates.csv` in this directory with your actual rates:
|
||||
|
||||
```csv
|
||||
date,btc_eur_rate
|
||||
2025-07-01,86500
|
||||
2025-07-02,87200
|
||||
2025-07-03,87450
|
||||
```
|
||||
|
||||
### 2. Update User Mappings
|
||||
|
||||
Edit `import_beancount.py` and update the `USER_MAPPINGS` dictionary:
|
||||
|
||||
```python
|
||||
USER_MAPPINGS = {
|
||||
"Pat": "actual_wallet_id_for_pat",
|
||||
"Alice": "actual_wallet_id_for_alice",
|
||||
"Bob": "actual_wallet_id_for_bob",
|
||||
}
|
||||
```
|
||||
|
||||
**How to get wallet IDs:**
|
||||
- Check your LNbits admin panel
|
||||
- Or query: `curl -X GET http://localhost:5000/api/v1/wallet -H "X-Api-Key: user_invoice_key"`
|
||||
|
||||
### 3. Set API Key
|
||||
|
||||
```bash
|
||||
export CASTLE_ADMIN_KEY="your_lnbits_admin_invoice_key"
|
||||
export LNBITS_URL="http://localhost:5000" # Optional
|
||||
```
|
||||
|
||||
## 📖 Usage
|
||||
|
||||
```bash
|
||||
cd /path/to/castle/helper
|
||||
|
||||
# Test with dry run
|
||||
python import_beancount.py ledger.beancount --dry-run
|
||||
|
||||
# Actually import
|
||||
python import_beancount.py ledger.beancount
|
||||
```
|
||||
|
||||
## 📄 Beancount File Format
|
||||
|
||||
Your Beancount transactions must have an `Equity:<name>` account:
|
||||
|
||||
```beancount
|
||||
2025-07-06 * "Foix market"
|
||||
Expenses:Groceries 69.40 EUR
|
||||
Equity:Pat
|
||||
|
||||
2025-07-07 * "Gas station"
|
||||
Expenses:Transport 45.00 EUR
|
||||
Equity:Alice
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
- Every transaction must have an `Equity:<name>` account
|
||||
- Account names must match exactly what's in Castle
|
||||
- The name after `Equity:` must be in `USER_MAPPINGS`
|
||||
|
||||
## 🔄 How It Works
|
||||
|
||||
1. **Loads rates** from `btc_eur_rates.csv`
|
||||
2. **Loads accounts** from Castle API automatically
|
||||
3. **Maps users** - Extracts user name from `Equity:Name` accounts
|
||||
4. **Parses** Beancount transactions
|
||||
5. **Converts** EUR → sats using daily rate
|
||||
6. **Uploads** to Castle with metadata
|
||||
|
||||
## 📊 Example Output
|
||||
|
||||
```bash
|
||||
$ python import_beancount.py ledger.beancount
|
||||
======================================================================
|
||||
🏰 Beancount to Castle Import Script
|
||||
======================================================================
|
||||
|
||||
📊 Loaded 15 daily rates from btc_eur_rates.csv
|
||||
Date range: 2025-07-01 to 2025-07-15
|
||||
|
||||
🏦 Loaded 28 accounts from Castle
|
||||
|
||||
👥 User ID mappings:
|
||||
- Pat → wallet_abc123
|
||||
- Alice → wallet_def456
|
||||
- Bob → wallet_ghi789
|
||||
|
||||
📄 Found 25 potential transactions in ledger.beancount
|
||||
|
||||
✅ Transaction 1: 2025-07-06 - Foix market (User: Pat) (Rate: 87,891 EUR/BTC)
|
||||
✅ Transaction 2: 2025-07-07 - Gas station (User: Alice) (Rate: 88,100 EUR/BTC)
|
||||
✅ Transaction 3: 2025-07-08 - Restaurant (User: Bob) (Rate: 88,350 EUR/BTC)
|
||||
|
||||
======================================================================
|
||||
📊 Summary: 25 succeeded, 0 failed, 0 skipped
|
||||
======================================================================
|
||||
|
||||
✅ Successfully imported 25 transactions to Castle!
|
||||
```
|
||||
|
||||
## ❓ Troubleshooting
|
||||
|
||||
### "No account found in Castle"
|
||||
**Error:** `No account found in Castle with name 'Expenses:XYZ'`
|
||||
|
||||
**Solution:** Create the account in Castle first with that exact name.
|
||||
|
||||
### "No user ID mapping found"
|
||||
**Error:** `No user ID mapping found for 'Pat'`
|
||||
|
||||
**Solution:** Add Pat to the `USER_MAPPINGS` dictionary in the script.
|
||||
|
||||
### "No BTC/EUR rate found"
|
||||
**Error:** `No BTC/EUR rate found for 2025-07-15`
|
||||
|
||||
**Solution:** Add that date to `btc_eur_rates.csv`.
|
||||
|
||||
### "Could not determine user ID"
|
||||
**Error:** `Could not determine user ID for transaction`
|
||||
|
||||
**Solution:** Every transaction needs an `Equity:<name>` account (e.g., `Equity:Pat`).
|
||||
|
||||
## 📝 Transaction Metadata
|
||||
|
||||
Each imported transaction includes:
|
||||
|
||||
```json
|
||||
{
|
||||
"meta": {
|
||||
"source": "beancount_import",
|
||||
"imported_at": "2025-11-08T12:00:00",
|
||||
"btc_eur_rate": 87891.0,
|
||||
"user_id": "wallet_abc123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And each line includes:
|
||||
|
||||
```json
|
||||
{
|
||||
"metadata": {
|
||||
"fiat_currency": "EUR",
|
||||
"fiat_amount": "69.400",
|
||||
"fiat_rate": 1137.88,
|
||||
"btc_rate": 87891.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This preserves the original EUR amount and exchange rate for auditing.
|
||||
Loading…
Add table
Add a link
Reference in a new issue