diff --git a/app/modules/bank_statement_analyzer/parsers/registry.py b/app/modules/bank_statement_analyzer/parsers/registry.py index 68de980..1e87c70 100644 --- a/app/modules/bank_statement_analyzer/parsers/registry.py +++ b/app/modules/bank_statement_analyzer/parsers/registry.py @@ -15,7 +15,7 @@ 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 TemplateBasedStatementParser, parse_with_template +from .template_engine import parse_with_template PARSERS = [ IDFCFirstParser, @@ -89,71 +89,50 @@ def _selected_parser(bank_key: str, text: str): return None, 0 -def _valid_result(result) -> bool: - if not result or len(result) != 2: - return False - _meta, frame = result - return frame is not None and not frame.empty +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): - """Parse a bank statement without removing any existing parser behaviour. - - Existing bank-specific parsers remain first priority. The template engine - is an additive fallback when a bank parser does not recognise a new layout - or returns no transactions. A template result is accepted only after - transaction-level running-balance validation. - """ hint = (bank_hint or "auto").strip().lower() text = extract_text(path) - primary_error: Exception | None = None + + 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": - # Preserve HSBC's specialised OCR parser as the first route. - candidates = BANK_PARSERS.get(hint, []) - scored = sorted( - ((parser.detect(text), parser) for parser in candidates), - key=lambda item: item[0], - reverse=True, - ) - for score, parser_class in scored: - if score <= 0: - continue - try: - result = parser_class().parse(path, text) - if _valid_result(result): - return result - except (ValueError, RuntimeError) as exc: - primary_error = exc - - # New layouts from a known bank are routed by table structure rather - # than rejected merely because the bank-specific detector changed. - try: - return parse_with_template(path, text, hint) - except (ValueError, RuntimeError) as template_error: + parser, score = _selected_parser(hint, text) + if parser is None: label = dict(BANK_OPTIONS).get(hint, "the selected bank") - detail = str(primary_error or template_error) raise ValueError( - f"The uploaded statement could not be reliably parsed as {label}. " - f"{detail}" - ) from template_error + 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 not None: + parser, score = detect_parser(text) + if parser is None and not (text or "").strip(): try: - result = parser.parse(path, text) - if _valid_result(result): - return result - except (ValueError, RuntimeError) as exc: - primary_error = exc - - # Auto Detect now falls back to structural templates. Bank detection remains - # a hint for metadata and naming, never a hard gate for transaction parsing. - try: - return parse_with_template(path, text, "auto") - except (ValueError, RuntimeError) as template_error: - detail = str(primary_error or template_error) + return HSBCParser().parse(path, text) + except ValueError: + pass + if parser is None: raise ValueError( - "Unsupported or unreconciled statement format. " - f"{detail}" - ) from template_error + "Unsupported statement format. The validated template engine and all available bank-specific parsers " + "were unable to reconcile this statement." + ) + return parser.parse(path, text) diff --git a/app/modules/bank_statement_analyzer/templates/bank_statement_analyzer/index.html b/app/modules/bank_statement_analyzer/templates/bank_statement_analyzer/index.html index 0d4a790..ecb5cc6 100644 --- a/app/modules/bank_statement_analyzer/templates/bank_statement_analyzer/index.html +++ b/app/modules/bank_statement_analyzer/templates/bank_statement_analyzer/index.html @@ -13,7 +13,14 @@
-

Keep Auto Detect or select a bank for direct parser validation.

+
+ + +
+
Automatic layout detection
+

The validated template engine runs first. Existing bank-specific parsers are used automatically when the layout template cannot be reconciled.

+
+