Special projects

PayMatch

Reconciles internal payments with a bank statement: matches, amount differences, missing rows and duplicates. An ambiguous pair stays ambiguous instead of going to whichever row came first.

Type
Payment reconciliation
Stack
Python · PostgreSQL
Code
Open on GitHub
Year
2026

A two-deck sorter

Our payments and the bank statement ride as two decks. The comb clips a pair only when the cards chose each other; ambiguous and unmatched ones go to their own trays.

PAYMATCH
The vise: a disputed set stays held, not given to whichever row came firstThe comb: three tines 100, 50 and 20 tall, like the rule weightsBelts and cards: our payments and the bank statementA tabletop model of PayMatch, a Python command-line tool that reconciles internal payments with a bank statement. Two belts bring the two decks. The comb’s tines rise by the weights of the example rules: an equal reference 100, an amount within 0.01 is 50, a timestamp within 300 seconds is 20. A pair is accepted only when each card is the other’s unique best candidate; the claw carries it to the matched tray, or to the amount-difference tray. A card with no partner goes to missing internal or missing external, and rows that repeat a normalised reference within one source are pushed aside as duplicates before matching begins. Whatever cannot be decided uniquely, here two payments for one bank row, stays in the vise as ambiguous. A click runs a greedy pass for contrast, which PayMatch avoids. The batch on the bench is synthetic; the real report is JSON with an optional PostgreSQL record.

How it matches

Rows are normalised and duplicate references are quarantined. Candidates are generated by currency, time window and normalised reference, then scored on configurable evidence. A pair is accepted only when each side is the other’s unique best candidate.

Why this way

A greedy pass hands a disputed payment to whichever row came first, and the mistake goes unnoticed. Here anything that can’t be decided uniquely is marked ambiguous, with the evidence listed.

Under the hood

  • Statuses: matched, amount mismatch, missing internal, missing external, duplicate, ambiguous.
  • Decimal amounts, timestamps only with an explicit offset.
  • Mutual best pairs instead of a greedy pass.
  • A JSON report and an optional PostgreSQL audit record.
Code on GitHub

Shall we talk about your project?

Discuss your project