Fix modern ICICI template parsing and parser fallback

This commit is contained in:
A R R R Associates
2026-08-04 13:10:24 +05:30
parent 565425c914
commit 79bfc808d3
3 changed files with 213 additions and 44 deletions
@@ -77,8 +77,15 @@ def _upper(value) -> str:
return norm(value).upper().replace("\n", " ")
def _is_date(value) -> bool:
def _normalise_date_cell(value) -> str:
"""Normalise PDF table line-wrap artefacts without changing the date value."""
text = norm(value)
text = re.sub(r"\s*([/-])\s*", r"\1", text)
return text
def _is_date(value) -> bool:
text = _normalise_date_cell(value)
return bool(_DATE_CELL_RE.fullmatch(text)) and not pd.isna(parse_flexible_date(text))
@@ -102,7 +109,7 @@ def _find_header_mapping(row: list) -> dict[str, int]:
def _looks_like_data_row(row: list) -> bool:
return bool(row) and (_is_date(row[0]) or (len(row) > 1 and _is_date(row[1])))
return bool(row) and any(_is_date(row[index]) for index in range(min(4, len(row))))
def _infer_structural_template(table: list[list]) -> LayoutTemplate | None:
@@ -110,6 +117,23 @@ def _infer_structural_template(table: list[list]) -> LayoutTemplate | None:
if sample is None:
return None
width = len(sample)
# Modern ICICI and similar indexed statements:
# serial no, transaction id, transaction date, cheque no, description,
# withdrawal, deposit, available balance.
if width >= 8 and _is_date(sample[2]):
return LayoutTemplate(
"indexed_transaction_id_debit_credit_balance",
{
"transaction_date": 2,
"value_date": 2,
"reference_no": 1,
"narration": 4,
"debit": 5,
"credit": 6,
"balance": 7,
},
0.92,
)
# SBI Account Summary / SBI YONO family:
# value date, post date, details, reference, debit, credit, balance.
if width >= 7 and _is_date(sample[0]) and _is_date(sample[1]):
@@ -354,11 +378,11 @@ class TemplateBasedStatementParser(BaseParser):
max_index = max(mapping.values())
if len(cells) <= max_index:
cells += [None] * (max_index + 1 - len(cells))
txn_text = norm(cells[mapping["transaction_date"]])
txn_text = _normalise_date_cell(cells[mapping["transaction_date"]])
if not _is_date(txn_text):
continue
value_index = mapping.get("value_date", mapping["transaction_date"])
value_text = norm(cells[value_index]) or txn_text
value_text = _normalise_date_cell(cells[value_index]) or txn_text
narration = norm(cells[mapping["narration"]])
reference = norm(cells[mapping["reference_no"]]) if "reference_no" in mapping else ""
debit = amount(cells[mapping["debit"]])