LedgerMap · INF-79 · branch t3/2d4a705b · 16/10/2026

Several bank statements for one bank ledger

What changed, why it is safe, and what you will see in the app.

In short

1. What changed for the user

Before
March.pdf→HDFC 1234
April.pdf→HDFC 1234

April could not use HDFC. The app said “Already connected to another PDF — choose another”. You had to run a second import.

Now
March.pdf↘
HDFC 1234
April.pdf↗

Both PDFs connect to HDFC in one import. You review all HDFC entries together.

2. The danger: overlapping statements

Banks often give statements that overlap. For example, you download March on 31 March, and then April from 30 March. The two days in the middle are in both PDFs.

March.pdf

DateNarrationDebitBalance
01/03NEFT ACME+500.001,500.00
30/03RENT300.001,200.00
31/03SMS CHARGES10.001,190.00

April.pdf

DateNarrationDebitBalance
30/03RENT300.001,200.00
31/03SMS CHARGES10.001,190.00
02/04UPI SHOP90.001,100.00

amber rows are in both PDFs. If we only removed the old rule, the rent would go to Miracle twice.

This is why the old rule existed. Removing it safely needed the work below.

3. How the app finds a repeated entry

An entry in a later PDF is a copy of an entry in an earlier PDF when all of these are true:

  1. Both PDFs are connected to the same bank ledger.
  2. Same date.
  3. Same side (debit or credit) and same amount.
  4. Same reference (UTR, cheque no.). If the bank prints no reference, the same narration (spaces do not matter).
  5. If both rows print a balance, it is the same balance. The running balance is the strongest proof that two rows are one transaction.

One important exception

Two equal entries inside one PDF are never copies of each other. A bank can really charge ₹10 twice on one day. So the app counts:

March.pdf hasApril.pdf hasResult
2 × “SMS ₹10” on 31/032 × “SMS ₹10” on 31/032 entries both of April’s are copies
2 × “SMS ₹10” on 31/033 × “SMS ₹10” on 31/033 entries 2 copies + 1 new
—2 × “SMS ₹10” on 31/032 entries nothing removed

The same PDF uploaded twice is all copies, so it adds nothing.

4. What happens to a copy

All copies are one transaction. It comes in once when any PDF’s date range brings it in. The app shows the copy that already has a saved ledger or contra pair (if any), otherwise the first one. The other copies are left out of everything:

PlaceWhat happens
Review steps (Contra, Suggestions, Suspense, Review)The entry shows once. You choose its ledger once.
All entriesListed once, under the first PDF that brings it in.
Export (Miracle voucher file)Written once. no double booking
Contra (bank-to-bank transfers)A transfer pairs with one copy only. A deposit paired with the other copy is still left out of the export, as before.
Check against Miracle’s booksEach Miracle posting matches one entry, never two copies. Before, two copies could fight for one posting, and the loser looked “not booked” and was written again.
Server: possible contra entriesBefore, two copies looked like two candidates, so no transfer was suggested. Now copies count as one, and rejecting a suggestion covers every copy.

5. What you will see in the app

Bank ledgers step

The ledger you already used is still in the list and is still suggested. A short note says what sharing does:

HDFC 1234 is also connected to March.pdf. Entries both PDFs print come in once.

Review: a box above every step

It shows only when a bank has more than one PDF. Nothing in it stops you.

HDFC 1234 has several PDFs in this import. 1 thing to check.
ⓘ2 entries of April.pdf are also in March.pdf and come in once.
⚠The balance does not carry on from April.pdf to June.pdf: April.pdf ends on 30/04/2026 (balance 1,100.00 CR), and June.pdf starts on 01/06/2026 (balance 900.00 CR). Entries between them may be missing: add the statement for those days.

Tables now name the PDF

Sr. no.BankDateNarration
2HDFC 1234 · March.pdf30/03/2026RENT
3HDFC 1234 · March.pdf31/03/2026SMS CHARGES
3HDFC 1234 · April.pdf02/04/2026UPI SHOP

6. The four messages, and when they appear

MessageWhenWhat to do
Came in onceA PDF repeats entries of another PDF.Nothing. This is information.
Balance gapThe balance at the end of one PDF is not the balance at the start of the next one.A statement for the days between is probably missing. Add it.
No matchTwo PDFs have entries on the same days, but not one entry matches.Check that both are really this bank account.
Look-alikeAn entry matches another PDF’s entry on everything except the balance. Both are kept.Open All entries and check it. If it is a real repeat, map one of them to suspense or leave it out.

7. Decisions I made (please check)

8. Limits

9. How it was checked

10. Independent review (OpenQodex)

Six review rounds. Each finding was fixed and tested. The last round: Passed: no findings

RoundFoundFix
1A saved choice on the hidden copy was lost. A copy outside its date range was not linked, so a transfer could be written twice.Show the copy that holds the choice. Link every copy, in range or not.
2A copy without a balance could merge two different transactions. A cleared choice could come back through another copy.Remember a balance once a copy prints it. Clear copies’ old choices.
3Server grouped two different withdrawals as copies. False “no match” warning with three PDFs.Server also needs same narration and balance. Compare copy groups.
4False gap warning when one PDF is inside another.Compare with the PDF that reaches latest.
5A race: a new choice could be deleted by the cleanup.Lock the copies until the cleanup ends.
6No findings.

The Go linter (golangci) failed to start in every round. That is a tool problem, not a code finding.

11. Where the code is

FileWhat it does
frontend/src/lib/statement-copies.tsNew. Finds copies and checks the balance chain.
frontend/src/lib/use-statement-entries.tsHides copies, one entry per transaction for the books check, Sr. no. per PDF.
frontend/src/components/same-bank-notes.tsxNew. The box in the review.
frontend/src/components/statement-import.tsxRemoves the one-PDF rule; adds the “also connected” note.
frontend/src/lib/voucher-export.tsA transfer paired with a copy is still left out once.
server/contra_suggest.goCopies count as one candidate for possible contra entries.
docs/statement-import.md and othersUpdated docs.