Reunion is both a library and webapp for repeatable and verifiable accounting, expense categorization, and reporting.
Year-end accounting in one day — automate with a DSL or edit with a streamlined web app.
Automatic reconciliation — never lose a transaction (or confidence in your data). Balances are perpetually checked against intermediate transactions.
Reunion ❤️ Git — DIFF ALL THE THINGS. Know when anything changes and why
Eliminate audit anxiety — access deduction reports with evidence already attached.
All work is described in the form of input files, rules, and deltas. Re-calculate everything in under a second.
Evolve your system as your accounting knowledge improves.
Clone the sample project and reunion itself to a directory to get started.
- Import overlapping and unorganized QFX, CSV, TSV, TXT files from your bank, credit card, or PayPal.
- Multi-currency
- Merge metadata from multiple sources (Chase Jot, PayPal, Amazon, etc).
- Precision transaction deduplication and sanitization
- Reconcile against monthly balances to prove correctness
- Customize your schema, then define your rules in clean DSL.
- Store manual corrections as a set of deltas.
- Reunion ❤️ Git - See diffs of everything that changes — know exactly what a new file or rule changes.
- Detect transfers between accounts
- Speed - optimized decision trees (hashes and tries) are used to prevent exponential slowdown. With 300 rules, we're seeing 0.5ms per transaction.
- Configure. Define Bank accounts, file->account/parser convention, and the schema
- Input files are parsed & sanitized per Schema
- Input files are merged and deduplicated to create each account. Associated data files (like Jot) are merged.
- Balance statements are checked against intermediate transactions to ensure none are missing or duplicated.
- Manual deltas (overrides) are applied
- Rules are applied
- Manual deltas are applied again
- Transfers are matched
- Results are generated
We have a sample project in progress.
Describe the bank accounts you will be importing transactions for.
paypal = BankAccount.new(name: "PayPal", currency: :USD, permanent_id: :paypal)
paypal_euro = BankAccount.new(name: "PayPal_Euro", currency: :EUR, permanent_id: :paypal_euro)
chase = BankAccount.new(name: "ChaseCC", currency: :USD, permanent_id: :chasecc)
amex = BankAccount.new(name: "Amex", currency: :USD, permanent_id: :amex)
pnc = BankAccount.new(name: "PNC", currency: :USD, permanent_id: :pnc)
@bank_accounts = [paypal, paypal_euro, chase, amex, pnc]
If you want to use the default convention and StandardFileLocator, each input file needs to be named [account]-[parser]-humanname.ext
.
Map account tags to bank account instances
@bank_file_tags = {paypal: [paypal, paypal_euro], #files with both EUR and USD txns
paypal_usd: paypal,
paypal_euro: paypal_euro,
chasecc: chase,
amex: amex,
pnc: pnc}
Map the parser tags to classes.
@parsers = {
pncs: PncStatementActivityCsvParser,
pncacsv: PncActivityCsvParser,
ppbaptsv: PayPalBalanceAffectingPaymentsTsvParser,
chasejotcsv: ChaseJotCsvParser,
chasecsv: ChaseCsvParser,
tsv: TsvParser,
tjs: TsvJsParser,
cjs: CsvJsParser,
amexqfx: OfxParser,
chaseqfx: OfxTransactionsParser}
Some account files can't be merged, and need to have overlaps deleted instead.
#Because PNC statements and activity exports have different transaction descriptions.
pnc.add_parser_overlap_deletion(keep_parser: PncStatementActivityCsvParser, discard_parser: PncActivityCsvParser)
Configure where to look for files
@locator = l = StandardFileLocator.new
l.working_dir = File.dirname(__FILE__)
l.input_dirs = ["./input/imports","./input/manual","./input/categorize"]
Fields defined in the schema can be indexed and searched with the DSL. They also can be exposed for manual editing in the web app, and can be (de)serialized with accuracy. Temporary fields that doesn't need any of these advantages can simply use the hash interface provided by all transactions.
The schema ensures that data types are verified at each stage.
@schema = Schema.new({
account_sym: SymbolField.new(readonly:true), #the permanent ID of the bank account
id: StringField.new(readonly:true), #bank-provided txn id, if available
date: DateField.new(readonly:true, critical:true),
amount: AmountField.new(readonly:true, critical:true, default_value: 0),
description: DescriptionField.new(readonly:true, default_value: ""),
currency: UppercaseSymbolField.new(readonly:true),
balance_after: AmountField.new(readonly:true), #The balance of the account following the application of the transaction
txn_type: SymbolField.new, #Sometimes provided. purchase, income, refund, return, transfer, fee, or nil
transfer: BoolField.new, #If true, transaction will be paird with a matching one in a different account
discard_if_unmerged: BoolField.new(readonly:true), #If true, this transaction should only contain optional metadata
description2: DescriptionField.new(readonly:true), #For additional details, like Amazon order items
#The remainer are simply commonly used conventions
tags: TagsField.new,
vendor: SymbolField.new,
vendor_description: DescriptionField.new,
vendor_tags: TagsField.new,
client: SymbolField.new,
client_tags: TagsField.new,
tax_expense: SymbolField.new,
subledger: SymbolField.new,
chase_tags: TagsField.new,
memo: DescriptionField.new
})
Sanity-check. Ensure transactions add up to balances. If your exported files don't inclue them, you should enter ending statement balances in a tab-delimited file, like this:
Date Balance
2013-12-04 -3843.84
2014-01-04 -415.04
2014-02-04 -115.00
2014-03-04 -2,238.79
2014-03-25 -5245.79
Discrepancies are identified and windowed to a particular time span.
Reconciliation helps catch duplicate or missing transactions.
Check out the sample project rules for more, but here's a taste.
tag(:expense) do
tax_expense :insurance do
for_vendors :auto_owners_insurance
end
tax_expense :advertising do
for_vendors :repository_hosting, :github, :appharbor, :aws, :microsoft, :app_net, :heroku
vendor_tags :advertising
vendor_tags :job_listings
vendor_tags :domains
end
end