DEV Community

Damin Mutti
Damin Mutti

Posted on

Truist statements: merging Credits and Debits into one signed column and checking every running balance

When a Truist statement prints separate Credits and Debits columns with a running Balance, convert each line to one signed amount (credits minus debits) and check it against the balance printed on that same line, chaining from the printed balance rather than your own running total. A single misread then flags exactly one row. ZeraBooks checks Truist statements line by line this way. (I founded ZeraBooks. The code below is plain Python.)

What does a Truist activity table look like?

In a Truist checking statement copy we've seen, activity sits in one table with the columns Date, Check #, Transaction Description, Details, Credits, Debits and Balance. Both amount columns print positive. Truist has more than one statement format, so check yours against it first.

It is friendlier than most banks, because every line carries a balance. It still breaks a spreadsheet in two ways. A SUM over two positive columns means nothing until you sign them. And the Details text under a transaction, such as the ACH company, comes out of most table extractors as a row of its own with no date and no amount.

How do you parse and check it?

The example is Piedmont Roofing Co., a fictional roofer with Truist business checking ending 3047, September 2026. The lines below are what a table extractor typically returns, one tuple per printed line. I planted one misread: check 4412 was really 1,085.00 and came out as 1,035.00.

from decimal import Decimal as D

BEGIN, ENDING = D("14210.65"), D("6862.28")
LINES = [
    ("09/02", "", "Deposit", "5,800.00", "", "20,010.65"),
    ("09/03", "4410", "Check", "", "2,145.00", "17,865.65"),
    ("09/09", "", "ACH Debit", "", "6,380.22", "11,485.43"),
    ("", "", "ABC Supply Co", "", "", ""),
    ("09/16", "", "ACH Deposit", "3,912.75", "", "15,398.18"),
    ("", "", "Stripe Transfer", "", "", ""),
    ("09/22", "4412", "Check", "", "1,035.00", "14,313.18"),  # true value 1,085.00
    ("09/29", "", "ACH Debit", "", "7,450.90", "6,862.28"),
    ("", "", "ADP Payroll", "", "", ""),
]

def money(s):
    return D(s.replace(",", "")) if s else D("0")

def rows(lines):
    out = []
    for date, chk, desc, cr, dr, bal in lines:
        if not date:                      # a Details line: belongs to the row above
            out[-1]["desc"] += " / " + desc
            continue
        out.append({"date": date, "check": chk, "desc": desc,
                    "amount": money(cr) - money(dr), "balance": money(bal)})
    return out

def check(rs, begin, ending):
    prev, bad = begin, []
    for r in rs:
        if prev + r["amount"] != r["balance"]:
            bad.append(f'{r["date"]} {r["desc"]} {r["check"]}: '
                       f'{prev} + {r["amount"]} != {r["balance"]}, '
                       f'off by {prev + r["amount"] - r["balance"]}')
        prev = r["balance"]               # chain from the PRINTED balance
    total = sum(r["amount"] for r in rs)
    bad.append(f"{begin} + {total} = {begin + total}, printed ending {ending}")
    return bad

for line in check(rows(LINES), BEGIN, ENDING):
    print(line)
Enter fullscreen mode Exit fullscreen mode

Output:

09/22 Check 4412: 15398.18 + -1035.00 != 14313.18, off by 50.00
14210.65 + -7298.37 = 6912.28, printed ending 6862.28
Enter fullscreen mode Exit fullscreen mode

Change the 09/22 debit to 1,085.00 and the only line printed is 14210.65 + -7348.37 = 6862.28, printed ending 6862.28.

Why chain from the printed balance?

If you carry your own running total forward, one bad row poisons every row after it. Here, the 09/29 payroll line would also fail, and you'd have two suspects for one mistake. Resetting prev to the balance Truist printed means each line is tested on its own. The misread check is the only row flagged, and the 50.00 gap tells you which digit went wrong: a 3 read where an 8 was printed.

The end-of-month check still runs on your own total. It catches what the line check can't, like a whole line that never made it into the extract.

Does the corrected month add up?

Credits: 5,800.00 + 3,912.75 = 9,712.75. Debits: 2,145.00 + 6,380.22 + 1,085.00 + 7,450.90 = 17,061.12.

14,210.65 + 9,712.75 - 17,061.12 = 6,862.28, the printed ending balance. Line by line: 14,210.65 + 5,800.00 = 20,010.65, then 17,865.65, 11,485.43, 15,398.18, 14,313.18 and 6,862.28. Every one matches the Balance column.

What about the Details lines and check numbers?

The rows() function folds a dateless line into the description above it, so "ACH Debit / ABC Supply Co" stays one transaction. Keep the Check # in its own field rather than gluing it into the description. Check 4411 is missing from this month. That is normal for an uncleared or voided check, and it is far easier to spot in a sorted number column than in free text.

Two Truist-specific notes. Business accounts on account analysis get a separate Account Analysis statement that explains fees; don't feed it to this parser, because it isn't a list of transactions. And the account history download covers up to 18 months but usually has no balances, so none of the checks above can run on it. Use the statement PDF.

ZeraBooks reconciliation screen for a statement that doesn't tie: opening plus transactions minus closing is off by a set amount, with the difference highlighted and a button to verify against the document

That is what a statement that doesn't tie looks like in ZeraBooks: the gap is named, not hidden. Download steps and a worked Truist month are on Truist bank statement to Excel. ZeraBooks reads Credits and Debits into one signed Amount, or keeps them as Money In and Money Out with the balance if you prefer. No bank feeds; you upload the PDF. It is 1,000 pages a month for $79 a month billed annually or $149 monthly, after a 7-day trial.

The balance column is free test data. Use every line of it.

Top comments (0)