Add bank statement analyzer with automatic work storage

This commit is contained in:
A R R R Associates
2026-07-13 10:12:19 +05:30
parent d391e9b443
commit bbc5afe1c0
23 changed files with 1047 additions and 0 deletions
@@ -0,0 +1,53 @@
from __future__ import annotations
import re, pandas as pd
from pathlib import Path
from .base import *
from .common import find
class IndusIndParser(BaseParser):
bank_name='IndusInd Bank'; parser_name='IndusIndParser'
@classmethod
def detect(cls,text): return 0.98 if 'INDUSIND' in text.upper() and 'STATEMENT OF ACCOUNT' in text.upper() else 0
def parse(self,path,text=None):
text=text or extract_text(path); meta=StatementMeta(bank_name=self.bank_name,source_file=Path(path).name,parser_name=self.parser_name,confidence='Medium')
meta.account_number=find(r'Account Number\s*:\s*([0-9X*]+)',text)
meta.customer_id=find(r'Cust\.Reln\.No\s*:\s*([0-9X*]+)',text)
meta.ifsc=find(r'IFSC Code\s*:\s*([A-Z0-9]+)',text)
m=re.search(r'Period\s*:\s*(\d{2}[/-][A-Za-z0-9]{2,3}[/-]\d{2,4})\s*(?:to|TO|-)\s*(\d{2}[/-][A-Za-z0-9]{2,3}[/-]\d{2,4})',text,re.I)
if m:
meta.period_from=pd.to_datetime(m.group(1),dayfirst=True).strftime('%Y-%m-%d'); meta.period_to=pd.to_datetime(m.group(2),dayfirst=True).strftime('%Y-%m-%d')
meta.total_debit=amount(find(r'Total Withdrawal Amount\s*:\s*([\d,]+\.\d{2})',text))
meta.total_credit=amount(find(r'Total Deposit Amount\s*:\s*([\d,]+\.\d{2})',text))
# rows start with date and often end with amount Dr/Cr and balance Cr
date_re=re.compile(r'^\s*(\d{2}[-/]?[A-Za-z]{3}[-/]?\d{2,4}|\d{2}-\d{2}-\d{4})\s+(.*)$')
rows=[]; cur=None; page=1
for line in text.splitlines():
if '\f' in line: page+=line.count('\f')
m=date_re.match(line)
if m:
if cur: rows.append(cur)
cur={'transaction_date':m.group(1),'value_date':m.group(1),'lines':[m.group(2)],'source_page':page}
elif cur and line.strip() and not re.match(r'^(Statement Summary|Opening Balance|Total Withdrawal|Branch Address|The limits)',line.strip(),re.I): cur['lines'].append(line)
if cur: rows.append(cur)
out=[]; prev=None
for r in rows:
first=r['lines'][0]; alltxt=norm(' '.join(r['lines']))
# Typical: narration ref debit Dr credit Cr balance Cr
tokens=list(re.finditer(r'([\d,]+\.\d{2})\s*(Dr|Cr)',first,re.I))
if not tokens: continue
debit=credit=bal=None
if len(tokens)>=2:
bal=amount(tokens[-1].group(1))
txn=amount(tokens[-2].group(1)); typ=tokens[-2].group(2).upper()
if typ=='DR': debit=txn
else: credit=txn
elif len(tokens)==1:
# carried forward / balance-only line; skip
continue
narr=first[:tokens[-2].start()].strip()+' '+' '.join(x.strip() for x in r['lines'][1:])
ref=''; z=re.search(r'\b([A-Z0-9]{8,})\b',narr); ref=z.group(1) if z else ''
out.append({**r,'narration':norm(narr),'reference_no':ref,'debit':debit,'credit':credit,'balance':bal})
if out:
f=out[0]; meta.opening_balance=round((f['balance'] or 0)+(f.get('debit') or 0)-(f.get('credit') or 0),2)
meta.closing_balance=out[-1]['balance']
return meta,finalize(pd.DataFrame(out),meta)