LedgerMap · Linear INF-80 · 16/10/2026

One transfer, two entries.
Contra groups explained.

Today the app pairs a contra entry only as one debit with one credit. Sometimes a bank shows one transfer as two entries. This page shows the problem, how the app should handle it, and the choices we must make.

Today: 1 debit ↔ 1 credit only Needed: 1 ↔ 2 and 2 ↔ 1 Database and export: mostly ready Main work: finding and choosing groups Window: 1 day only Real data: 1 in 3 loan instalments split

01 The problem

A real case from the local development data (ASMARK ENGINEERS, Central Bank of India). Every month the current account pays the loan ₹43,100. Usually the loan statement shows one credit. On 25/04/2023 it showed two credits.

Current account sends

25/04/2023WITHDRAWAL TRANSFER−43,100.00
One debit43,100.00

Loan account receives

25/04/2023O.S. DEPOSIT TRANSFER+19,097.67
25/04/2023O.S DEPOSIT+24,002.33
Two credits, total43,100.00

Today: the app does not pair these three entries automatically. It does not suggest them. You cannot pair them by hand (Alt+C) either, because the dialog compares one amount with one amount. Neither side prints a UTR/RRN reference.

02 What happens today

The app makes contra entries in four ways. All four compare one entry with one entry.

WayRule todayResult for 1 → 2
Automatic pair
server/contra.go
Shared UTR/RRN, equal amount (or fee ≤ ₹50 and 1%), ≤ 3 days, only candidate✕ No pair no reference, no equal amount
Possible contra entry
server/contra_suggest.go
Same amount, ≤ 1 day, no conflicting reference✕ Not suggested no entry is ₹43,100
Pair by hand (Alt+C)
contra-pair-dialog.tsx
Pick one entry of the same amount (± fee)✕ Not in the list
Contra from the books
lib/book-contra.ts
One entry against one Miracle posting of the same amount✕ No match

The only workaround books the transfer twice

The user can choose the other bank's ledger by hand on all three entries. But the export leaves out a deposit only when it is in a contra pair. So all three entries are written. Switch the views to compare.

Voucher file rows written

Bank (file)LedgerWithdrawalDeposit

What Miracle books

LedgerMovedCorrect?

03 How the app should handle it

Two terms: the single side is the one entry on one bank. The split side is the two (or more) entries on the other bank. Together they make one contra group.

STEP 1

Find the group

  • By hand (first): Alt+C, then choose several entries. The dialog shows the total.
  • Suggestion (second): the Contra step lists a group only when it is the one possible combination.
  • Never saved without the user.
STEP 2

Save it as one pair

  • All entries get the other bank's ledger (source auto, confirmed).
  • All share one contra_pair number.
  • Note: "Contra with Loan account rows 43, 44".
  • Unpair or a ledger change breaks the whole group.
STEP 3

Export it once

  • The withdrawal side writes the voucher, as now.
  • 1 → 2: one voucher of ₹43,100. Both credits are left out.
  • The books check treats both credits as one Miracle posting of ₹43,100.

Rules a group must pass

AmountThe split side's total equals the single side. Or the debit side is higher by a transfer fee: at most ₹50 and 1% of the total.
DatesEvery split entry is at most 1 day from the single entry. We use only this 1-day window.
MinimumThe single side is at least ₹500. A part can be any amount (a real part was ₹97.50).
BanksAll split entries are on one other bank ledger of the party.
KindNot cash, ATM or card (POS). No conflicting UTR/RRN.
FreeEvery entry has no ledger yet, or already the other bank's ledger. An entry is in one pair or group only.
DirectionBoth: 1 debit → 2 credits, and 2 debits → 1 credit.
Older appsKeep working. contra.with is already a list. Group suggestions go in a new API field.

04 Try the new pair dialog

A working mock of the proposed Alt+C dialog. Click in the list, or press Tab to reach it. Then use ↑ ↓ to move, Space to add or remove, and Ctrl+Enter to pair. The third and fourth entries are made up for the demo.

Pair as a contra entry

Current account · 25/04/2023 · WITHDRAWAL TRANSFER · debit 43,100.00. Choose the credits on another bank that record the same transfer.

This debit43,100.00 Chosen credits (0)0.00 Difference43,100.00 Choose credits that add up to this debit.
Space add/remove · Ctrl+Enter pair · Enter pairs one entry at once · Esc cancels

05 Shapes we support

Red dot = debit (money leaves). Green dot = credit (money arrives).

1 debit → 2 credits
By handSuggested

The loan example.

2 debits → 1 credit
By handSuggested

The same thing in reverse.

1 → 3 or more
By handNot suggested

Searching all sums is slow and noisy.

Many ↔ many
Out of scope

Too hard to check. Not needed yet.

06 What is already ready

I read the code. The data model and the export already allow groups. Changes are needed in finding and choosing them, and in the books checks.

✓ ReadyDatabase

Many rows can share one contra_pair number. Copies from overlapping statements use this today.

✓ ReadyPair structure

contraPair{out, in []entryRef}: each side is already a list.

✓ ReadyAPI contra.with

Already a list. Older apps will show a group as a normal contra entry.

✓ ReadyExport

The rule works per entry. A deposit whose withdrawal is in the import is left out.

✓ ReadyUnpair / Not contra

rejectContra already rejects every debit–credit combination.

✎ ChangePair by hand

pairable and contraPartners compare one amount. They must compare a total.

✎ ChangeAccept endpoint

POST …/contra names one entry. Add an optional list.

✎ ChangeCopy joining

joinCopies assumes one row per statement in a pair.

✎ ChangeBooks check

exportsInBooks: one Miracle posting must cover both split entries.

✎ NewGroup suggestions

A new search for two-entry sums in contra_suggest.go.

LaterContra from the books

Two statement credits against one Miracle posting. Do this as a follow-up.

07 Real data: what happens with correct links

We use only the 1-day rule. The test data had PDFs linked to the wrong ledgers. So I linked every statement to its correct ledger, by the account number printed in the PDF, and ran the rules again. Use Show in PDF to see a row on its page, or Open PDF to open the original file.

What "correct links" means here (click to see the links I used)
Account (from the PDF)LedgerStatements
5149380565CBI C/A 5149380565 (current account)11 PDFs: 2243–2252, 2254
5164197786CBI MACHINERY TERM LOAN 5164197786 - 4310013 PDFs: 44, 2253, 2255–2258, 2260–2266
5701294918CBI MACHINERY TERM LOAN 5701294918 - 550002 PDFs: 45, 2259
  • Only ASMARK ENGINEERS has PDFs of its own accounts. The test PDFs of "Patel offset" (TRIDEV POLYMERS, YES BANK…) and "MOHIT RAIYANI" (Riken and Bipinbhai Bakori's Kotak and SBI) belong to other people, so with correct data they are not in those parties at all.
  • Overlapping statements of one account repeat rows. A repeated row counts as one entry, as the server already does for pairs. The cards list the other copies.

ASMARK's loan instalments: how the loan statement records them

Show as a table
LoanInstalmentsOne creditTwo credits

What the rules leave, step by step

Combinations whose amounts add up (± fee), dated at most 1 day apart.

1 in 3 instalments split

14 of 40 instalments arrive as two credits on the loan: interest + principal ("TRANSFER REP-INTEREST" + "O.S. DEPOSIT TRANSFER"), or overdue + the rest ("O.S DEPOSIT" + "O.S. DEPOSIT TRANSFER").

All 14 suggested, none wrong

Every split instalment passes all the rules. Nothing that is not a transfer is suggested.

4 correctly not groups

Here the instalment is a normal one-to-one pair, and the current account has a separate SMS-charges row the same day. The "no 1:1 pair" rule keeps these as normal pairs.

Your ₹1,564 question. The ₹1,564 SBI credit and the ₹1,564 Kotak debit (12/07/2020) both print the RRN 019419468878. Today's normal contra matching already pairs them: in the database they are contra pair #1, saved automatically. The ₹10 freecharge row is not part of that transfer. So the group rules correctly ignore it: a 1:1 pair already exists. (With correct data, these two PDFs belong in Riken Bakori's party, not MOHIT RAIYANI's, but the result is the same there.)

Open PDF ↗

08 Options and recommendation

A blue border shows my recommendation.

A How groups are found

Phase 1A1By hand only

Alt+C with several entries chosen. Small work, no risk: the user chooses.

Phase 2A2Suggest 1↔2 and 2↔1

≤ 1 day, reference rule, only candidate, no 1:1 pair. Medium work. With correct links: 14 suggestions, all real, none wrong.

A3Automatic pairing by reference

No real cases found. Skip.

A4Suggest 1↔3 and more

Large work and very noisy. By hand only.

C Storage

UseC1Reuse contra_pair

The schema already allows groups. Only joinCopies needs a fix.

C2New contra_groups table

More migration and API work. No gain.

D Export — see section 9

UseD1Withdrawal side writes

Today's rule. No change. Matches what pairs do.

D2Side with more entries writes

Only if the split side must match the statement line by line.

D3One voucher, several lines

Exact on both sides. Not known if Miracle's import allows it.

E API for older installed apps

UseE1New field for group suggestions

Old contraSuggestions unchanged. Optional with: [...] on accept.

E2Make contraSuggestions.with a list

Breaks older apps. No.

09 Export choices: what Miracle sees

All three choices give correct balances. They differ in how the Miracle lines compare with each bank statement.

Current account in Miracle

LineAmount

Loan account in Miracle

LineAmount

10 Suggested plan

Phase 1
by hand

Pair a group by hand

  • Server: pairableGroup(single, parts); the accept endpoint takes a list; fix joinCopies.
  • Frontend: multi-select in the Alt+C dialog with a running total; keyboard tests; docs/keyboard.md and the help dialog.
  • Books check: one Miracle posting covers the split side.
Phase 2
suggest

Suggest groups in the Contra step

  • Find 1↔2 and 2↔1 sums: ≤ 1 day, reference rule, only candidate, no 1:1 pair, single side ≥ ₹500 (parts any amount).
  • Accept saves the group. Not contra rejects every combination.
  • Run the scan in section 7 again on more parties to measure how many suggestions are real.
Later

Contra from the books, one-to-two

  • Two statement credits against one Miracle posting from another bank.

Open question: can Miracle's voucher import read one voucher with several lines? Only option D3 needs this. The recommended plan does not.

·· Words used

Contra entry
A transfer between two of the party's own bank accounts.
Contra group
One entry on one bank plus two or more entries on another bank that record one transfer.
Single side
The one entry of a group (here: the ₹43,100 debit).
Split side
The two or more entries of a group (here: the two loan credits).
UTR / RRN
The network reference of a NEFT/RTGS (UTR) or IMPS/UPI (RRN) transfer. When both sides print it, the app pairs them automatically.
Transfer fee
The sender may pay a fee in the same row. The debit can be higher by at most ₹50 and 1%.

✓ In short

  • Banks sometimes record one transfer as 1 debit and 2 credits (or the reverse). Today the app cannot pair these entries. The workaround books the transfer twice.
  • The database, the API list and the export already allow groups. The work is in finding and choosing groups, and in the books check.
  • Phase 1: pair a group by hand with Alt+C. Phase 2: suggest groups only when one combination fits. Never save a group without the user.
  • Keep "the withdrawal side writes the voucher". Ask whether Miracle accepts vouchers with several lines.