DEV Community

Damin Mutti for ZeraBooks

Posted on Originally published at zerabooks.com

The trust account invariant: one bank balance, many owners, and the check that catches commingling

A property manager's trust account obeys one invariant: bank balance = every owner's balance + every security deposit held, at every moment. A second rule catches quieter commingling: no single owner's balance may go below zero, because if it does, another owner's money paid that owner's bill. Check both after every posting, not only at month end.

I work on ZeraBooks, and this is the cleanest data model I know for trust money. Below is a Python sketch. All figures are fictional and match the worked month on our property management page.

How do you model a trust account in code?

from collections import defaultdict
from decimal import Decimal as D

class Trust:
    def __init__(self):
        self.bank = D("0")
        self.owner = defaultdict(lambda: D("0"))     # owed to each owner
        self.deposits = defaultdict(lambda: D("0"))  # held for each owner's tenants

    def post(self, owner: str, amount: D, kind: str):
        """amount is signed from the bank's view: + in, - out."""
        self.bank += amount
        if kind == "deposit":
            self.deposits[owner] += amount
        else:
            self.owner[owner] += amount
        self.check(owner)

    def check(self, owner: str):
        held = sum(self.owner.values()) + sum(self.deposits.values())
        assert self.bank == held, f"bank {self.bank} vs held {held}: does not reconcile"
        assert self.owner[owner] >= 0, f"{owner} is negative: another owner's money was used"
Enter fullscreen mode Exit fullscreen mode

The first assertion is the reconciliation. The second is the one most spreadsheets don't have.

What does a clean month in the trust account look like?

Twelve units, three owners, an 8% fee on rent collected. The trust opens May with $9,600 of deposits.

t = Trust()
for owner, amt in [("Maple", "4800"), ("Elm", "3200"), ("Cedar", "1600")]:
    t.post(owner, D(amt), "deposit")                 # opening deposits, 9,600

t.post("Maple", D("8700.00"), "rent")
t.post("Elm",   D("6400.00"), "rent")
t.post("Elm",   D("75.00"),   "late fee")            # owner's under this agreement
t.post("Cedar", D("4200.00"), "rent")
t.post("Cedar", D("2100.00"), "deposit")             # new tenant

for owner, fee in [("Maple", "696.00"), ("Elm", "512.00"), ("Cedar", "336.00")]:
    t.post(owner, -D(fee), "fee to operating")

t.post("Maple", D("-640.00"), "repair")
t.post("Elm",   D("-380.00"), "repair")

for owner, paid in [("Maple", "7364.00"), ("Elm", "5583.00"), ("Cedar", "3864.00")]:
    t.post(owner, -D(paid), "owner payout")

print(t.bank, dict(t.owner), sum(t.deposits.values()))
# 11700.00, every owner balance 0.00, deposits 11700.00
Enter fullscreen mode Exit fullscreen mode
Trust account, May Maple Court Elm Street Cedar Lane Total
Rent collected 8,700.00 6,400.00 4,200.00 19,300.00
Late fee 0.00 75.00 0.00 75.00
Management fee at 8% (696.00) (512.00) (336.00) (1,544.00)
Repairs paid (640.00) (380.00) 0.00 (1,020.00)
Paid out to owner (7,364.00) (5,583.00) (3,864.00) (16,811.00)
Owner balance, May 31 0.00 0.00 0.00 0.00

Every post passes both assertions. On May 31 the bank shows $11,700.00, exactly the deposits held.

A statement that ties in ZeraBooks: deposits, withdrawals and net agree between the statement and the ledger with a $0.00 difference
The bank side of the same idea in ZeraBooks: deposits, withdrawals and net must agree with the statement before a month is signed off.

How does commingling show up in the data?

Run the same month in a different order. Pay the Elm Street plumber on May 2, before Elm's rent has come in:

t = Trust()
# ...opening deposits as above...
t.post("Elm", D("-380.00"), "repair")
# AssertionError: Elm is negative: another owner's money was used
Enter fullscreen mode Exit fullscreen mode

The bank balance is still positive, and the first assertion still holds, because the bank doesn't know whose money is whose. The $380 came out of money that belongs to Maple's owner or Cedar's tenants. In many states, that is what trust account rules prohibit, even if Elm's rent arrives the next day.

A month-end reconciliation can pass even when an owner went negative for two weeks in the middle. Check after every posting.

No code fixes this after the fact. The practice does: pay a repair from trust only when that owner's balance covers it, or have the owner fund a reserve first.

Which design choices keep the model honest?

  1. Keep deposits separate from owner balances. Both are liabilities, but deposits belong to tenants. An owner payout should never reach into the deposit total.
  2. Move fees out of trust as a transfer. The 8% fee is the manager's income only once it has moved to the operating account. In the trust model it is money leaving that owner's balance.
  3. Check after every post. Month-end checks miss mid-month negatives.
  4. Use Decimal for money. The invariant is an equality to the cent, and float sums drift.

Key takeaways

  • Invariant: trust bank balance = owner balances + deposits held, always.
  • Second rule: no owner below zero, or another owner's money paid their bill.
  • In the worked May, $1,544.00 of fees moved to operating and the trust closed at $11,700.00, all deposits.
  • Assert after every posting; month-end reconciliation alone can hide commingling.

FAQ

Why can the bank balance be fine while trust rules are broken?

Because the bank balance is a total. It can't tell whose money paid a bill. Only per-owner balances show that.

Is a security deposit an owner's money?

No. It is a liability to the tenant until the lease ends, so it is tracked apart from what is owed to the owner.

Does ZeraBooks keep tenant ledgers?

No. ZeraBooks is a general ledger, not property management software. It has no tenant ledgers, rent collection or portals; managers send reports from their property software.

The full month, with the owner statement mapped to Schedule E lines, is in the guide to bookkeeping for property management. Disclosure: I'm on the ZeraBooks team. ZeraBooks bookkeeping services reconcile trust and operating accounts monthly from uploaded statements, from $150 a month in U.S. dollars.

Top comments (0)