Files
arrr-erp/app/modules/bank_statement_analyzer/parsers/idfc.py
T
2026-07-13 10:12:19 +05:30

57 lines
3.2 KiB
Python

from __future__ import annotations
import re
import pandas as pd
from pathlib import Path
from .base import BaseParser, StatementMeta, extract_text, amount, norm, finalize, page_of_line
from .common import find, date_iso
class IDFCFirstParser(BaseParser):
bank_name='IDFC FIRST Bank'; parser_name='IDFCFirstParser'
@classmethod
def detect(cls,text):
u=text.upper(); return 0.99 if 'IDFC FIRST BANK' in u and 'STATEMENT PERIOD' in u 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='High')
meta.customer_name=find(r'CUSTOMER NAME\s*:\s*([^\n]+)',text)
meta.account_number=find(r'ACCOUNT NO\s*:\s*([0-9X*]+)',text)
meta.customer_id=find(r'CUSTOMER ID\s*:\s*([0-9X*]+)',text)
meta.ifsc=find(r'IFSC\s*:\s*([A-Z0-9]+)',text)
m=re.search(r'STATEMENT PERIOD\s*:\s*(\d{4}-\d{2}-\d{2})\s+TO\s+(\d{4}-\d{2}-\d{2})',text,re.I)
if m: meta.period_from,meta.period_to=m.groups()
m=re.search(r'Opening Balance\s+Total Debit\s+Total Credit\s+Closing Balance\s*\n\s*([\d,.]+)\s+([\d,.]+)\s+([\d,.]+)\s+([\d,.]+)',text,re.I)
if m: meta.opening_balance,meta.total_debit,meta.total_credit,meta.closing_balance=map(amount,m.groups())
lines=text.splitlines(); rows=[]; current=None; pos=0
pat=re.compile(r'^\s*(\d{2}-[A-Za-z]{3}-\d{4})\s+(\d{2}-[A-Za-z]{3}-\d{4})\s+(.*)$')
for line in lines:
mm=pat.match(line)
if mm:
if current: rows.append(current)
current={'transaction_date':mm.group(1),'value_date':mm.group(2),'body':mm.group(3),'source_page':page_of_line(text,pos)}
elif current and line.strip() and not re.match(r'^(STATEMENT|Opening Balance|REGISTERED OFFICE|Page \d+)',line.strip(),re.I):
current['body']+=' '+line.strip()
pos+=len(line)+1
if current: rows.append(current)
out=[]; prev=meta.opening_balance
for r in rows:
body=norm(r['body']); nums=list(re.finditer(r'(?<!\d)(?:\d{1,3}(?:,\d{2,3})+|\d+)\.\d{2}(?!\d)',body))
if not nums: continue
bal=amount(nums[-1].group()); rem=body[:nums[-1].start()].strip(); vals=[amount(x.group()) for x in nums[:-1]]
debit=credit=None
if vals:
txn=vals[-1]
if prev is not None and bal is not None:
d1=round(prev-txn,2); c1=round(prev+txn,2)
if abs(d1-bal)<0.02: debit=txn
elif abs(c1-bal)<0.02: credit=txn
else:
# explicit columns often leave one amount only; infer from change
credit=txn if bal>=prev else None; debit=txn if bal<prev else None
else: credit=txn
ref=''
z=re.search(r'(?:UPI|NEFT|IMPS|RTGS)[/-](?:MOB/|OPM/|INET/|DR/|CR/)?([A-Z0-9]{8,})',rem,re.I)
if z: ref=z.group(1)
out.append({**r,'narration':rem,'reference_no':ref,'debit':debit,'credit':credit,'balance':bal})
if bal is not None: prev=bal
return meta,finalize(pd.DataFrame(out),meta)