Files
arrr-erp/app/modules/bank_statement_analyzer/parsers/registry.py
T
2026-08-04 12:49:58 +05:30

139 lines
4.4 KiB
Python

from __future__ import annotations
from .idfc import IDFCFirstParser
from .axis import AxisParser
from .hdfc import HDFCParser
from .icici import ICICIParser
from .hsbc import HSBCParser
from .indian_bank import IndianBankModernParser, IndianBankLegacyParser
from .indusind import IndusIndParser
from .kotak import KotakParser
from .sbi import SBIModernParser, SBIOtherParser
from .central_bank_of_india import CentralBankOfIndiaParser
from .yes_bank import YesBankParser
from .city_union_bank import CityUnionBankParser
from .bank_of_baroda import BankOfBarodaParser
from .rbl_bank import RBLBankParser
from .base import extract_text
from .template_engine import parse_with_template
PARSERS = [
IDFCFirstParser,
AxisParser,
CentralBankOfIndiaParser,
YesBankParser,
CityUnionBankParser,
BankOfBarodaParser,
RBLBankParser,
HDFCParser,
ICICIParser,
HSBCParser,
IndianBankModernParser,
IndianBankLegacyParser,
IndusIndParser,
KotakParser,
SBIOtherParser,
SBIModernParser,
]
BANK_OPTIONS = [
("auto", "Auto Detect"),
("axis", "Axis Bank"),
("central_bank_of_india", "Central Bank of India"),
("yes_bank", "YES Bank"),
("city_union_bank", "City Union Bank"),
("bank_of_baroda", "Bank of Baroda"),
("rbl_bank", "RBL Bank"),
("hdfc", "HDFC Bank"),
("icici", "ICICI Bank"),
("hsbc", "HSBC Bank"),
("idfc", "IDFC FIRST Bank"),
("indian_bank", "Indian Bank"),
("indusind", "IndusInd Bank"),
("kotak", "Kotak Mahindra Bank"),
("sbi", "State Bank of India"),
]
BANK_PARSERS = {
"axis": [AxisParser],
"central_bank_of_india": [CentralBankOfIndiaParser],
"yes_bank": [YesBankParser],
"city_union_bank": [CityUnionBankParser],
"bank_of_baroda": [BankOfBarodaParser],
"rbl_bank": [RBLBankParser],
"hdfc": [HDFCParser],
"icici": [ICICIParser],
"hsbc": [HSBCParser],
"idfc": [IDFCFirstParser],
"indian_bank": [IndianBankModernParser, IndianBankLegacyParser],
"indusind": [IndusIndParser],
"kotak": [KotakParser],
"sbi": [SBIOtherParser, SBIModernParser],
}
def detect_parser(text):
scored = sorted(((parser.detect(text), parser) for parser in PARSERS), key=lambda item: item[0], reverse=True)
if not scored or scored[0][0] <= 0:
return None, 0
return scored[0][1](), scored[0][0]
def _selected_parser(bank_key: str, text: str):
candidates = BANK_PARSERS.get(bank_key, [])
if not candidates:
return None, 0
scored = sorted(((parser.detect(text), parser) for parser in candidates), key=lambda item: item[0], reverse=True)
if scored and scored[0][0] > 0:
return scored[0][1](), scored[0][0]
return None, 0
def _template_first(path, text: str, hint: str):
"""Try the validated layout engine before bank-specific parsing.
Any template error is deliberately swallowed here so the existing bank
parser path remains an unchanged and reliable fallback.
"""
try:
return parse_with_template(path, text=text, bank_hint=hint)
except Exception:
return None
def parse_pdf(path, bank_hint: str | None = None):
hint = (bank_hint or "auto").strip().lower()
text = extract_text(path)
template_result = _template_first(path, text, hint)
if template_result is not None:
return template_result
# Preserve the existing HSBC OCR route for image-only statements and for
# users/API clients that explicitly provide an HSBC override.
if hint == "hsbc":
return HSBCParser().parse(path, text)
if hint and hint != "auto":
parser, score = _selected_parser(hint, text)
if parser is None:
label = dict(BANK_OPTIONS).get(hint, "the selected bank")
raise ValueError(
f"The uploaded statement could not be parsed by a validated layout template and does not match "
f"the selected {label} format. Please verify the statement or use automatic detection."
)
return parser.parse(path, text)
parser, score = detect_parser(text)
if parser is None and not (text or "").strip():
try:
return HSBCParser().parse(path, text)
except ValueError:
pass
if parser is None:
raise ValueError(
"Unsupported statement format. The validated template engine and all available bank-specific parsers "
"were unable to reconcile this statement."
)
return parser.parse(path, text)