Duplicates

Finding the same charge imported twice, deciding what to keep, and undoing a delete.

Re-uploading a statement you already loaded is harmless — every transaction has an id derived from its date, description, and amounts, so the second copy is recognized and skipped.

The problem this screen solves is the other kind: the same charge arriving twice by different routes, where the two rows aren't byte-identical and so aren't the same transaction as far as the id is concerned. A credit-card CSV and the PDF statement covering the same month. A checking export downloaded in two overlapping date ranges from a bank that words its descriptors slightly differently each time. Those become two transactions, and they inflate every total you look at.

Running a scan

Duplicate detection is a scan you run, not something happening in the background. Open Duplicates and choose Scan now. The scan reads your whole history in date order and reports progress as it goes; you can leave the screen while it runs.

Results are stored, so a report stays on the screen after a reload, and everyone in a shared workspace sees the same one. When transactions have changed since a report was generated, the screen says the report is stale and offers a re-scan rather than quietly showing you old findings.

What counts as a duplicate

A pair becomes a candidate only when all of these hold:

  • Both rows fall on the same calendar day. This used to be a two-day window; it was tightened because real duplicates post on the same day, and the wider window swept in genuinely distinct same-amount charges a day or two apart.
  • The debit and credit amounts match exactly. No tolerance.
  • The descriptions are similar — either identical after normalization, or sharing enough words to clear a similarity threshold. That's what lets AMAZON.COM*2H4XY9 and AMAZON MKTP US pair up.

And the pair is skipped outright when any of these is true:

  • Both rows came from the same uploaded file. A statement that genuinely lists a charge twice — two identical coffees on the same day — is preserved at import, and it is almost never a duplicate.
  • Either row carries reference numbers that the other doesn't share. Two rows with clearly different confirmation, check, or order numbers are different transactions no matter how alike the rest reads.
  • You have already said they aren't duplicates. See below.

Candidates are grouped into clusters — everything transitively linked — so three copies of one charge come to you as one group, not three pairs.

Working through the results

Each cluster lists its transactions with a decision per row: keep or delete. Nothing is applied while you're deciding.

  • Keep one picks a survivor and marks the rest for deletion. It prefers a row you have already edited — one carrying an override category or tags — because that's the copy holding your work. Otherwise it keeps the first.
  • Keep one in every cluster does that across the whole report, for clusters you haven't already dismissed.
  • Not duplicates dismisses the cluster. That's a durable decision, not a hide: those transactions will not be paired again in future scans. Use it for the real same-day-same-amount coincidences — two identical fares, two identical coffees.

Your in-progress decisions survive a page reload. A fresh scan starts them over, because it's a fresh report.

When you're satisfied, commit. The confirmation names the counts before anything happens.

Deleting here is permanent

This is the one place in the app where a delete is a hard delete. Rows removed by a duplicate commit are gone — they are not in the Deleted transactions view and cannot be undone.

That's deliberate: the whole point is that these rows should never have existed, and leaving tombstones for them would mean carrying the noise you just removed. Deleting a transaction anywhere else in the app — a row on the Transactions screen, a row in the uncategorized queue — is a recoverable soft delete instead. See Your data.

If you're unsure about a cluster, Not duplicates is the safe answer. It costs you nothing and it teaches the scanner.

When a duplicate isn't caught

The scan is conservative by design — a false negative leaves you with a number that's too big, and a false positive offers to delete a real charge. If two copies of a charge post on different days, or their descriptions have nothing in common, they won't be paired. Delete one by hand from the Transactions screen; that delete is recoverable.