Files
2026-08-03 16:32:40 +05:30

97 lines
4.5 KiB
Python

from __future__ import annotations
import re
from pathlib import Path
import pandas as pd
import pdfplumber
from .base import BaseParser, StatementMeta, amount, extract_text, finalize, norm
from .common import date_iso, find, infer_mode
class YesBankParser(BaseParser):
bank_name = "YES Bank"
parser_name = "YesBankParser"
@classmethod
def detect(cls, text: str) -> float:
upper = (text or "").upper()
score = 0.0
if "YES BANK" in upper:
score += 0.55
if "YESB000" in upper or "IFSC CODE: YESB" in upper:
score += 0.20
if "TRANSACTION DETAILS FOR YOUR ACCOUNT NUMBER" in upper:
score += 0.15
if all(token in upper for token in ("TRANSACTION DATE", "VALUE DATE", "WITHDRAWALS", "DEPOSITS", "RUNNING")):
score += 0.10
return min(score, 0.99)
def parse(self, path, text=None):
pdf_path = Path(path)
text = text or extract_text(pdf_path)
meta = StatementMeta(
bank_name=self.bank_name,
source_file=pdf_path.name,
parser_name=self.parser_name,
confidence="High",
)
meta.customer_name = norm(find(r"Primary Holder:\s*([^\n]+?)(?:\s+A/C Opening Date:|\n)", text, flags=re.I))
if not meta.customer_name:
meta.customer_name = norm(find(r"Primary Account Holder Name:\s*([^\n]+)", text, flags=re.I))
meta.customer_id = find(r"(?:Cust Id|Customer Id):\s*([0-9]+)", text, flags=re.I)
meta.account_number = find(r"Statement of account:\s*([0-9]+)", text, flags=re.I)
if not meta.account_number:
meta.account_number = find(r"account number\s+([0-9]+)", text, flags=re.I)
meta.ifsc = find(r"IFSC Code:\s*([A-Z0-9]+)", text, flags=re.I)
period = re.search(r"Period:\s*From\s+(.+?)\s+To\s+([^\n]+)", text, re.I)
if period:
meta.period_from = date_iso(period.group(1).strip())
meta.period_to = date_iso(period.group(2).strip())
rows: list[dict] = []
with pdfplumber.open(str(pdf_path)) as pdf:
for page_no, page in enumerate(pdf.pages, start=1):
for table in page.extract_tables() or []:
if not table:
continue
header = [norm(cell).upper() for cell in table[0]]
if not ("TRANSACTION DATE" in header and "VALUE DATE" in header and "DESCRIPTION" in header):
continue
for raw in table[1:]:
cells = list(raw) + [None] * (7 - len(raw))
txn_date, value_date, reference_no, description, withdrawal, deposit, balance = cells[:7]
txn_date = norm(txn_date)
if not re.fullmatch(r"\d{2}-[A-Za-z]{3}-\d{4}", txn_date):
continue
debit_value = amount(withdrawal)
credit_value = amount(deposit)
balance_value = amount(balance)
if debit_value is None and credit_value is None:
continue
narration = norm(description)
rows.append({
"transaction_date": txn_date,
"value_date": norm(value_date) or txn_date,
"narration": narration,
"reference_no": norm(reference_no),
"debit": debit_value,
"credit": credit_value,
"balance": balance_value,
"source_page": page_no,
"mode": infer_mode(narration),
})
data = pd.DataFrame(rows)
if not data.empty:
first = data.iloc[0]
first_balance = float(first["balance"])
first_debit = float(first["debit"]) if pd.notna(first["debit"]) else 0.0
first_credit = float(first["credit"]) if pd.notna(first["credit"]) else 0.0
meta.opening_balance = round(first_balance + first_debit - first_credit, 2)
meta.total_debit = round(float(pd.to_numeric(data["debit"], errors="coerce").fillna(0).sum()), 2)
meta.total_credit = round(float(pd.to_numeric(data["credit"], errors="coerce").fillna(0).sum()), 2)
meta.closing_balance = round(float(data.iloc[-1]["balance"]), 2)
return meta, finalize(data, meta)