#!/usr/bin/env python3 # simplefincsv 1.1 - (c) Simon Michael 2025 __version__ = "1.1" __author__ = "Simon Michael" versionmsg = f"%prog {__version__}, by {__author__} 2025; part of the hledger project." usagemsg = """%prog [options] [JSONFILE|-] [REGEX] Read SimpleFIN /accounts JSON from JSONFILE or stdin, and for each account with transactions, or just the ones where "ORGNAME ACCTNAME ACCTID" is case-insensitively infix-matched by the given regular expression, print CSV records to stdout: 1. an account info record: "account",ORGNAME,ACCTNAME,ACCTID,BALANCE,CURRENCY 2. a field headings record: "date","amount","description","payee","memo","id" 3. any transaction records, in date order. Also if the JSON includes error messages, they will be displayed on stderr, and the exit code will be non-zero. Requirements: python 3 a SimpleFIN account with financial institution(s) and app connection configured. Examples: Download the JSON for one account and print as CSV: $ simplefinjson ACT-00b02b825-7cf-495f-8f49-b097d310dd4 | simplefincsv Download the JSON for all accounts, then print the CSV for one of them: $ simplefinjson >sf.json $ simplefincsv sf.json 'chase.*card' """ from pprint import pprint as pp import csv import datetime import decimal import json import optparse import re import sys def parse_options(): parser = optparse.OptionParser(usage=usagemsg, version=versionmsg) opts, args = parser.parse_args() if len(args) > 2: parser.print_help() sys.exit() return opts, args def main(): opts, args = parse_options() infile = args[0] if len(args) > 0 else '-' r = re.compile(args[1], re.I) if len(args) > 1 else None with open(infile,'r') if infile != '-' else sys.stdin as inp: with sys.stdout as out: j = json.load(inp) w = csv.writer(out, quoting=csv.QUOTE_ALL) for a in j['accounts']: oname = a['org']['name'] aname = a['name'] aid = a['id'] if r and not r.search(f"{oname} {aname} {aid}"): continue ts = a['transactions'] if ts: w.writerow([ "account", oname, aname, aid, a['balance'], a['currency'] ]) w.writerow([ "date", "id", "amount", "description", "payee", "memo" ]) for t in reversed(a['transactions']): dt = datetime.datetime.fromtimestamp(t['posted']) # dtl = dt.astimezone() w.writerow([ dt.strftime('%Y-%m-%d'), # %H:%M:%S %Z'), t['id'], t['amount'], t['description'], t['payee'], t['memo'] ]) errors = j['errors'] if errors: for e in errors: print(f"simplefincsv: {e}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()