Fix SBI multiline table parsing and restore template-first bank analysis

This commit is contained in:
A R R R Associates
2026-08-05 17:49:44 +05:30
parent 8bf409049b
commit 47cd5c2cf3
2 changed files with 165 additions and 49 deletions
@@ -151,6 +151,10 @@ def detect_bank_identity(
) -> BankIdentityMatch | None:
raw_text = str(text or "")
text_upper = _normalise(raw_text)
# Identity headings and account metadata are normally in the first page.
# Restrict strong name evidence to the beginning so counterparty bank names
# inside hundreds of transaction narrations cannot relabel the statement.
header_upper = _normalise(raw_text[:800])
ifsc_upper = re.sub(r"[^A-Z0-9]", "", str(ifsc or "").upper())
file_upper = _normalise(Path(source_file or "").stem)
@@ -159,25 +163,36 @@ def detect_bank_identity(
score = 0.0
evidence: list[str] = []
# Longest/special IFSC prefixes carry the strongest identity signal.
matching_prefixes = [prefix for prefix in bank.ifsc_prefixes if ifsc_upper.startswith(prefix.upper())]
matching_prefixes = [
prefix for prefix in bank.ifsc_prefixes
if ifsc_upper.startswith(prefix.upper())
]
if matching_prefixes:
prefix = max(matching_prefixes, key=len)
score += 0.72 if len(prefix) > 4 else 0.58
# The account IFSC belongs to the issuing bank and therefore outranks
# bank names appearing merely as transaction counterparties.
score += 0.96 if len(prefix) > 4 else 0.90
evidence.append(f"IFSC prefix {prefix}")
for domain in bank.domains:
if domain.lower() in raw_text.lower():
score += 0.72
score += 0.92
evidence.append(f"domain {domain}")
break
alias_hits = [alias for alias in bank.aliases if _alias_present(text_upper, alias)]
if alias_hits:
longest = max(alias_hits, key=len)
# Long formal headings are stronger than abbreviations such as SBI.
score += 0.72 if len(_normalise(longest)) >= 12 else 0.36
evidence.append(f"name {longest}")
header_alias_hits = [alias for alias in bank.aliases if _alias_present(header_upper, alias)]
if header_alias_hits:
longest = max(header_alias_hits, key=len)
score += 0.88 if len(_normalise(longest)) >= 12 else 0.55
evidence.append(f"header name {longest}")
else:
# Full-document matches are weak because narrations commonly mention
# beneficiary and remitter banks. They may support another signal but
# cannot identify the issuer by themselves.
body_hits = [alias for alias in bank.aliases if _alias_present(text_upper, alias)]
if body_hits:
score += 0.12
evidence.append("body mention")
if any(_alias_present(file_upper, alias) for alias in bank.aliases):
score += 0.10
@@ -186,7 +201,9 @@ def detect_bank_identity(
confidence = min(score, 0.99)
if confidence < 0.50:
continue
candidate = BankIdentityMatch(bank.name, bank.category, confidence, tuple(dict.fromkeys(evidence)))
candidate = BankIdentityMatch(
bank.name, bank.category, confidence, tuple(dict.fromkeys(evidence))
)
if best is None or candidate.confidence > best.confidence:
best = candidate
@@ -194,9 +211,21 @@ def detect_bank_identity(
def apply_detected_bank_identity(meta, df, text: str):
meta_ifsc = str(getattr(meta, "ifsc", "") or "")
if not meta_ifsc:
# Some PDFs place the IFSC value far from its label in text-reading
# order. Recover the first header-area IFSC token directly; limiting
# the search to the beginning avoids beneficiary-bank IFSC codes.
header_match = re.search(r"\b[A-Z]{4}0[A-Z0-9]{6}\b", str(text or "")[:2500], re.I)
if header_match:
meta_ifsc = header_match.group(0).upper()
try:
meta.ifsc = meta_ifsc
except Exception:
pass
match = detect_bank_identity(
text,
ifsc=str(getattr(meta, "ifsc", "") or ""),
ifsc=meta_ifsc,
source_file=str(getattr(meta, "source_file", "") or ""),
)
if match is None: