Skip to content

Instantly share code, notes, and snippets.

@sliceofcode
Last active December 3, 2023 15:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sliceofcode/0bdcea77a8d8b4f0a89da537fbfd5800 to your computer and use it in GitHub Desktop.
Save sliceofcode/0bdcea77a8d8b4f0a89da537fbfd5800 to your computer and use it in GitHub Desktop.
Spendee to Beancount | spendee2beancount

Spendee to Beancount

This is a simple importer for beancount which imports transactions from a Spendee CSV file export. You may need to modify it to your own needs.

Example usage:

$ bean-extract config.py ./ > spendee.beancount

You will have to open the accounts if you want the transactions to be valid. Additionally, this is a single entry import, and the transactions won't balance out and that's not covered in this gist.

import os
import sys
sys.path.append(os.path.dirname(__file__))
from spendee import SpendeeImporter
CONFIG = [
SpendeeImporter(),
]
beancount
titlecase
;; -*- mode: beancount -*-
**** /home/beancount/fava/spendee2beancount/spendee.csv
2023-03-01 * "My Monthly Salary" ""
Income:Salary 10000.00000000 USD
2023-03-12 * "Walmart" ""
Expenses:Groceries 154.35000000 USD
Date Wallet Type Category name Amount Currency Note Labels Author
2023-03-01T09:00:00+00:00 Wallet Income Salary 10000.00000000 USD My monthly salary My Name
2023-03-12T15:06:00+00:00 Wallet Expense Groceries 154.35000000 USD Walmart My Name
from beancount.core.number import D
from beancount.ingest import importer
from beancount.core import amount
from beancount.core import flags
from beancount.core import data
from dateutil.parser import parse
from titlecase import titlecase
import csv
import os
import re
class SpendeeImporter(importer.ImporterProtocol):
def __init__(self, expenses_account_prefix: str = 'Expenses', income_account_prefix: str = 'Income'):
self.expenses_account_prefix = expenses_account_prefix
self.income_account_prefix = income_account_prefix
def identify(self, f):
return re.match('spendee.csv', os.path.basename(f.name))
def extract(self, file, existing_entries=None):
entries = []
with open(file.name, encoding='utf8') as f:
for index, row in enumerate(csv.DictReader(f)):
# Type: Expense, Income, Outgoing Transfer, Incoming Transfer
if row['Type'] in ['Expense', 'Outgoing Transfer']:
account_prefix = self.expenses_account_prefix
elif row['Type'] in ['Income', 'Incoming Transfer']:
account_prefix = self.income_account_prefix
else:
# Unknown transaction type
continue
account = f'{account_prefix}:' + re.sub(r'\W+', '', titlecase(row['Category name'].rstrip()))
txn = data.Transaction(
meta=data.new_metadata(file.name, index),
date=parse(row['Date']).date(),
flag=flags.FLAG_OKAY,
payee=titlecase(row['Note'].rstrip()),
narration="",
tags=set(),
links=set(),
postings=[
data.Posting(
account,
amount.Amount(D(row['Amount']), row['Currency']),
None, None, None, None
)
]
)
entries.append(txn)
return entries
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment