57 lines
3.2 KiB
Python
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)
|