Skip to content

Instantly share code, notes, and snippets.

@pwalkr
Last active April 1, 2022 01:55
Show Gist options
  • Save pwalkr/4b20a2e5c22f8ba3a504f2b0c79944a6 to your computer and use it in GitHub Desktop.
Save pwalkr/4b20a2e5c22f8ba3a504f2b0c79944a6 to your computer and use it in GitHub Desktop.
Get a report of statement balances (sorted by oldest, needs-attention, last)
#!/usr/bin/env python3
import argparse
from beancount import loader
from beancount.core.data import Balance, Open, Close
class Report:
def __init__(self):
self.accounts = {}
def find_account(self, entry):
if not entry.account in self.accounts:
self.accounts[entry.account] = AccountState(entry.account)
return self.accounts[entry.account]
def is_watched(self, entry):
return entry.account.startswith('Assets') or entry.account.startswith('Liabilities')
def ingest(self, entries):
for entry in entries:
if isinstance(entry, Balance) or isinstance(entry, Open) or isinstance(entry, Close):
if self.is_watched(entry):
account = self.find_account(entry)
account.update(entry)
def print(self):
no_balance = []
good = []
for statement in list(self.accounts.values()):
if statement.is_open():
if statement.date is None:
no_balance.append(statement)
else:
good.append(statement)
for statement in sorted(good, key=lambda x: x.date, reverse=True):
print('{} {}\t\t{}'.format(statement.date, statement.account, statement.balance))
print('\nAccounts with no balance:\n')
for statement in sorted(no_balance, key=lambda x: x.account):
print(' {}'.format(statement.account))
class AccountState:
def __init__(self, account):
self.account = account
self.open = None
self.close = None
self.balance = None
self.date = None
def update(self, entry):
if entry.account != self.account:
raise ValueError('{} != {}'.format(entry.account, self.account))
elif isinstance(entry, Balance):
if self.date is None or entry.date > self.date:
self.date = entry.date
self.balance = entry.amount
elif isinstance(entry, Open):
if self.open is None or entry.date > self.open:
self.open = entry.date
elif isinstance(entry, Close):
if self.close is None or entry.date > self.close:
self.close = entry.date
def is_open(self):
# Could potentially close then reopen
return not self.close or self.close < self.open
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("file", help="Path to main beancount file")
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
entries, errors, options = loader.load_file(args.file)
report = Report()
report.ingest(entries)
report.print()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment