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)