Matching algorithm

All amounts are signed so that money in is positive on both sides: a bank credit and a cash book debit are both +. Two items can only match if their totals are equal to the paisa. Every match records the pass (rule), a confidence, a status and human-readable reasons; bankrecon explain prints them.

Pass order

Passes run in the configured order (default below). Items consumed by an earlier pass are not visible to later ones, so cheap, high-certainty passes go first.

# Pass Pairs Status
1 reference 1:1 auto
2 cheque 1:1 auto
3 amount_date 1:1 auto
4 fuzzy 1:1 suggested
5 one_to_many 1 bank : n book auto, or suggested if a tie was broken
6 many_to_one n bank : 1 book same
7 many_to_many m bank : n book (m, n between 2 and 3) suggested
8 bank_reversal bank debit + its reversing credit auto

auto matches count immediately; suggested matches also count towards the BRS but are queued for bankrecon review. Rejecting one releases its items.

One-to-one passes and ambiguity

Candidates for a bank item are the free book items with the same amount that satisfy the pass's predicate:

A pair is committed only if each side's best candidate is the other side and the best candidate beats the runner-up. Ranking is by date distance for reference and cheque, by similarity (with a margin of 10 points) for fuzzy, and amount_date has no ranking at all: it requires exactly one candidate in each direction.

Example: two customers each pay 59,717.76 on the same day and the book has two receipts for that amount. amount_date sees 2 candidates per side and matches nothing. fuzzy scores UPI-SHARMA TEXTILES-sharma@okicici... against Receipt from Sharma Textiles at 100 and against the Gupta receipt at a low score, so each pairs up and is queued as a suggestion. Two walk-in cash receipts with identical amounts and no names score below the threshold and stay unmatched: the engine records the ambiguity instead of guessing.

Subset sums (one-to-many, many-to-one)

For a bank deposit D (or a book payment, for many-to-one), the candidates are the free items on the other side with the same sign, a smaller absolute amount, and a date within 10 days. At most 25 candidates are kept, nearest in date first. find_subsets then looks for groups of 2 to 6 items that add up to D, one group size at a time, smallest size first, and stops at the first size that has any solution. That is how "prefer fewer items" is implemented.

Within one size the search is a depth-first search over candidates sorted by amount, descending, in integer paise. Two bounds prune it:

A node budget (200,000) caps the worst case, and at most 20 solutions are collected.

Worked example: bank credit 6,000.60 on 6 Sep, book receipts 1,000.10 (3 Sep), 2,000.20 (4 Sep), 3,000.30 (5 Sep) and 999.99 (5 Sep). Pairs cannot reach the target, size 3 finds {1,000.10, 2,000.20, 3,000.30} as the only solution, so the match is auto.

If several solutions of the smallest size exist, each is scored by shared references (100 points each) plus the mean narration similarity. The best is taken as a suggested match only if it leads the runner-up by the fuzzy margin; otherwise nothing is matched and the ambiguity is recorded.

Many-to-many

For each still-unmatched bank item (the anchor), take up to 12 same-sign bank and book items within 10 days. Enumerate book groups of size 2-3 into a table keyed by total, then enumerate bank groups of size 2-3 containing the anchor and look their totals up. The pair with the fewest items in total wins if it is unique. Groups whose amounts are identical on both sides (interchangeable 1:1 pairs) are rejected as ambiguity, because pairing them any way is equally valid. Every many-to-many match is suggested.

Bank reversal

A bank credit whose narration contains REV, RETURN, REFUND or REJECT is paired with the single unmatched debit of the same amount in the preceding 10 days. The pair nets to zero, so it needs no book entry and no journal. It runs last, so a debit that the books already recorded is matched to the book entry first, and the returned credit is then classified as a returned payment.

Reference extraction

Kind Pattern (simplified) Stored as
Cheque CHQ/CHEQUE/CHK optionally followed by DEP, PAID, NO, CLG... then 6-8 digits cheque_no
UPI / IMPS RRN a standalone 12-digit number when the text mentions UPI or IMPS RRN:...
NEFT / RTGS numeric id NEFT- or RTGS- followed by 10-22 digits UTR:...
UTR four letters + up to three alphanumerics + 9-16 digits, optional separator (HDFCR52026091234567) UTR:...
Invoice INV, INVOICE or BILL followed by a number such as 26/0412 (regex configurable) INV:...

IFSC codes (HDFC0001234) are too short to look like UTRs, so they are not extracted. Everything is upper-cased first, so a book entry that says utr hdfcr52026091234567 still matches.

Classification of bank-only items

After matching, each unmatched bank item runs through the ordered rule list; the first rule whose regex matches the narration and whose sign (money in or out) fits wins. Defaults: GST on charges, bank charges, cash withdrawals, TDS on interest, interest credit, returned customer cheque, returned payment. Anything else is parked in Suspense so the entry can still be posted and the BRS still ties.

Narration Category Suggested entry
GST @18% ON SMS ALERT CHARGES gst_on_charges Dr GST Input Credit, Cr Bank
SMS ALERT CHARGES Sep-2026 bank_charges Dr Bank Charges, Cr Bank
INT.PD:01-07-2026 TO 30-09-2026 interest_credit Dr Bank, Cr Interest Income
TDS DEDUCTED ON INTEREST CREDIT tds_on_interest Dr TDS Receivable, Cr Bank
CHQ RETURN-INSUFFICIENT FUNDS-206758 returned_receipt Dr Accounts Receivable, Cr Bank
NEFT RETURN-...-ACCOUNT CLOSED returned_payment Dr Bank, Cr Accounts Payable

Book-side flags

Unmatched book items are categorised as cheque_not_presented, payment_not_debited, deposit_in_transit or possible_duplicate. An item is a possible duplicate when another book entry has the same amount, narration and party within 3 days and either that twin is matched or has a smaller id. In the sample data the receipt booked twice shows up as the duplicate, while the original is matched to the bank credit.