Where AI helps, where it struggles, and why the best systems combine it with plain code.

"Can AI analyse my bank statement?" is one of the most common questions people ask about AI and personal finance. The short version of anwering this question is yes. The more useful answer is that AI is very good at some parts of the job and surprisingly bad at others.

This article breaks bank statement analysis into its parts and looks at which ones AI should handle, which ones plain code should handle, and how to connect the two. The Python examples are small enough to run and adapt.

Why rule-based categorisation stops working

The heart of bank statement analysis is categorisation: deciding whether each transaction is salary, an EMI, rent, a transfer or cash.

The traditional approach is rules. Look for keywords in the narration and assign a category.

import re RULES = [ ("salary", r"\bSAL(ARY)?\b"), ("emi", r"\bEMI\b|\bNACH\b"), ("cash", r"\bATM\b|\bCASH\b"), ("transfer", r"\bUPI\b|\bIMPS\b|\bNEFT\b"), ] def categorise_by_rules(narration): text = narration.upper() for category, pattern in RULES: if re.search(pattern, text): return category return "uncategorised" print(categorise_by_rules("NEFT/CR/ACME PVT LTD/SAL SEP")) # salary print(categorise_by_rules("UPI/DR/RAVI KUMAR/RENT OCT")) # transfer 

The first result is right. The second is wrong. That UPI payment is rent, but the rule only sees "UPI" and calls it a transfer.

You can keep adding rules, but it is like patching a leaking roof one tile at a time. Every bank writes narrations differently, new payment apps appear, and abbreviations vary. A rule set that works for one bank becomes a maintenance burden across hundreds.

How a machine learning model categorises transactions

Instead of writing rules, you can train a model on narrations that people have already labelled. The model learns patterns in the text, including ones you would never think to write a rule for.

Here is a minimal version using scikit-learn. Character n-grams work well here because narrations are full of abbreviations and fragments rather than real words.

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import make_pipeline narrations = [ "NEFT/CR/ACME PVT LTD/SAL SEP", "NEFT/CR/ACME PVT LTD/SALARY OCT", "NACH/DR/ABC FINANCE LTD/EMI", "ECS/DR/XYZ BANK LOAN EMI 042", "UPI/DR/RAVI KUMAR/RENT OCT", "UPI/DR/LANDLORD/HOUSE RENT", "ATM WDL/CHENNAI MAIN", "CASH WITHDRAWAL SELF", ] labels = ["salary", "salary", "emi", "emi", "rent", "rent", "cash", "cash"] model = make_pipeline( TfidfVectorizer(analyzer="char_wb", ngram_range=(3, 5)), LogisticRegression(max_iter=1000), ) model.fit(narrations, labels) print(model.predict(["UPI/DR/S MEENA/RENT NOV"])) # ['rent'] 

Eight examples are only enough to show the idea. A production model needs thousands of labelled transactions from many banks. But the principle holds: the model learns that "RENT" matters more than "UPI", without anyone writing that rule.

Why every AI prediction needs a confidence score

Here is the part many demos skip. A model will always give an answer, even when it is guessing.

In lending, a wrong category has real consequences. Label an EMI as a transfer and a borrower's obligations look lower than they are. So instead of trusting every prediction, use the model's confidence and send uncertain cases to a person.

def categorise(narration, model, threshold=0.75): """Return a category, or flag the transaction for human review.""" probs = model.predict_proba([narration])[0] best = probs.argmax() label = str(model.classes_[best]) confidence = float(probs[best]) if confidence < threshold: return {"category": "needs_review", "suggested": label, "confidence": round(confidence, 2)} return {"category": label, "confidence": round(confidence, 2)} 

Try it on the toy model above and even the rent example comes back as needs_review with a suggestion of rent. Eight training examples are not enough for confidence, and the code says so instead of pretending. That is exactly the behaviour you want.

This turns AI from an all-or-nothing decision into a triage system. Confident predictions flow straight through. Uncertain ones go to a reviewer with a suggestion attached. The threshold becomes a business decision: lower it for speed, raise it for safety.

Which checks should never be left to AI?

Some parts of bank statement analysis are pure arithmetic and logic. AI adds nothing there except the risk of being wrong.

Checking whether a statement covers the full period it claims to is a good example. If a borrower submits six months but one month has no transactions at all, pages may be missing.

def missing_months(txns, start, end): """List months in the statement period with no transactions.""" seen = {t["date"][:7] for t in txns} # "YYYY-MM" missing = [] y, m = start.year, start.month while (y, m) <= (end.year, end.month): key = f"{y}-{m:02d}" if key not in seen: missing.append(key) m += 1 if m == 13: y, m = y + 1, 1 return missing 

The same goes for running-balance checks, totals and date ordering. These should be deterministic code that gives the same answer every time and can be explained line by line.

This matters even more with large language models. An LLM can read a messy statement surprisingly well, but it can also add two numbers incorrectly with complete confidence. Use it for what it is good at, such as interpreting narrations and layouts, and use code for the maths.

Where general-purpose AI struggles with bank statements

General-purpose AI, such as basic OCR tools or chatbots, tends to break on the same hard cases:

  • Poor scans and phone photos: Generic OCR misreads skewed, shadowed or compressed pages, and those errors flow into everything after it. Purpose-built document AI cleans up the image first and is trained on real-world scans, which is why scan handling is one of the clearest differences between tools.
  • Transfers between your own accounts: Money moving from savings to current account can look like income unless the system links the accounts.
  • Ambiguous narrations: "UPI/DR/9876543210" tells you almost nothing about what was paid for.
  • Context outside the statement: A large deposit could be a loan, a gift or a property sale. The statement alone cannot say which.

The last three are limits of the statement itself, not of any tool. That is why good systems design for review, confidence scores and explanations from the start.

What a practical AI bank statement analysis pipeline looks like

Putting it together, a sensible split looks like this:

  1. Extract: OCR and layout models read the document. (AI)
  2. Structure: Rows are normalised into one consistent format. (AI plus code)
  3. Validate: Balances, dates and coverage are checked. (Code)
  4. Categorise: Each transaction gets a label and a confidence score. (AI)
  5. Review: Low-confidence transactions go to a person. (Human)
  6. Summarise: Income, obligations and risk flags are calculated. (Code)

Building steps 1 and 2 to cover hundreds of bank formats is the hardest part to do well at scale. Production tools such as DocuGenie.AI's Bank Statement Analyzer take care of that layer across thousands of statement formats, including low-quality scans, but the design principles above apply whether you build or buy.

Wrap Up

So, can AI analyse a bank statement? Yes.

AI is excellent at the messy, variable parts: reading layouts, understanding narrations and categorising transactions. Plain code is better at the parts that must be exact: balances, totals and coverage checks. And people are still needed for the cases where even a good model is unsure.

The systems that work best are not the ones with the cleverest model. They are the ones that know which part of the job belongs to AI, which belongs to code, and which belongs to a person.