rehan953 commited on
Commit
309a064
·
verified ·
1 Parent(s): e5c70c7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +80 -1
app.py CHANGED
@@ -752,7 +752,8 @@ def mask_non_monetary_long_numbers(md: str) -> str:
752
  # Keep YYYYMMDD-like date compact tokens.
753
  if len(tok) == 8 and tok.startswith(("19", "20")):
754
  return tok
755
- return f"ID#{tok}"
 
756
 
757
  return _LONG_NUM_TOKEN.sub(repl, md)
758
 
@@ -1161,6 +1162,83 @@ def normalize_ambiguous_amount_balance_tables(md: str) -> str:
1161
  )
1162
 
1163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1164
  def _extract_daily_balance_by_date(md: str):
1165
  """
1166
  Build a date->balance map from daily-balance style tables.
@@ -1672,6 +1750,7 @@ def run_ocr(uploaded_file):
1672
  merged = mask_non_monetary_long_numbers(merged)
1673
  merged = directionalize_amount_headers(merged)
1674
  merged = normalize_ambiguous_amount_balance_tables(merged)
 
1675
  merged = stabilize_table_markup(merged, rounds=4)
1676
  merged = enrich_transaction_tables_with_daily_balances(merged)
1677
  # Keep transaction amounts untouched; add explicit snapshot for metadata extraction.
 
752
  # Keep YYYYMMDD-like date compact tokens.
753
  if len(tok) == 8 and tok.startswith(("19", "20")):
754
  return tok
755
+ # Replace digits entirely so downstream cannot reinterpret ID tokens as money.
756
+ return "IDNUM"
757
 
758
  return _LONG_NUM_TOKEN.sub(repl, md)
759
 
 
1162
  )
1163
 
1164
 
1165
+ def normalize_amount_only_transaction_tables(md: str) -> str:
1166
+ """
1167
+ Convert transaction-like tables with Date/Description/Amount (no Credit/Debit)
1168
+ into explicit Credit/Debit columns using generic lexical heuristics.
1169
+ """
1170
+ if not md or "<table" not in md.lower():
1171
+ return md
1172
+
1173
+ credit_hint_re = re.compile(
1174
+ r"\b(deposit|deposits|credit|credits|incoming|received|return|refund|interest|pmt cr)\b",
1175
+ re.IGNORECASE,
1176
+ )
1177
+ debit_hint_re = re.compile(
1178
+ r"\b(debit|debits|withdrawal|withdrawals|check|checks|fee|fees|charge|charges|payment|purchase|ach|bill)\b",
1179
+ re.IGNORECASE,
1180
+ )
1181
+
1182
+ def repl_table(m: re.Match) -> str:
1183
+ full = m.group(0)
1184
+ rows = _extract_rows_plain_from_table(full)
1185
+ if len(rows) < 3:
1186
+ return full
1187
+ hdr = [c.strip().lower() for c in rows[0]]
1188
+ if any("credit" in c for c in hdr) or any("debit" in c for c in hdr):
1189
+ return full
1190
+ amount_idxs = [i for i, c in enumerate(hdr) if "amount" in c]
1191
+ date_idxs = [i for i, c in enumerate(hdr) if "date" in c]
1192
+ desc_idxs = [i for i, c in enumerate(hdr) if "description" in c or "memo" in c or "details" in c]
1193
+ if not amount_idxs or not date_idxs:
1194
+ return full
1195
+ amount_idx = amount_idxs[0]
1196
+ date_idx = date_idxs[0]
1197
+ desc_idx = desc_idxs[0] if desc_idxs else min(1, len(rows[0]) - 1)
1198
+ table_ctx = _cell_plain_text(full)
1199
+
1200
+ new_rows = [["Date", "Description", "Credit", "Debit"]]
1201
+ converted = 0
1202
+ for r in rows[1:]:
1203
+ if not r:
1204
+ continue
1205
+ padded = r + [""] * (max(amount_idx, date_idx, desc_idx) + 1 - len(r))
1206
+ date_txt = (padded[date_idx] or "").strip()
1207
+ if not _is_date_like(date_txt):
1208
+ continue
1209
+ desc_txt = (padded[desc_idx] or "").strip()
1210
+ amt = _parse_amount_or_none((padded[amount_idx] or "").strip())
1211
+ if amt is None:
1212
+ continue
1213
+ ctx = f"{table_ctx} {desc_txt}"
1214
+ credit = ""
1215
+ debit = ""
1216
+ if credit_hint_re.search(ctx) and not debit_hint_re.search(ctx):
1217
+ credit = _fmt_money(abs(amt))
1218
+ elif debit_hint_re.search(ctx) and not credit_hint_re.search(ctx):
1219
+ debit = _fmt_money(abs(amt))
1220
+ else:
1221
+ # Conservative default for unknown direction.
1222
+ debit = _fmt_money(abs(amt))
1223
+ new_rows.append([date_txt, desc_txt, credit, debit])
1224
+ converted += 1
1225
+
1226
+ if converted < 5:
1227
+ return full
1228
+
1229
+ html_rows = ["<tr>" + "".join(f"<th>{html.escape(c)}</th>" for c in new_rows[0]) + "</tr>"]
1230
+ for rr in new_rows[1:]:
1231
+ html_rows.append("<tr>" + "".join(f"<td>{html.escape(c)}</td>" for c in rr) + "</tr>")
1232
+ return "<table>\n" + "\n".join(html_rows) + "\n</table>"
1233
+
1234
+ return re.sub(
1235
+ r"<table\b[^>]*>.*?</table>",
1236
+ repl_table,
1237
+ md,
1238
+ flags=re.IGNORECASE | re.DOTALL,
1239
+ )
1240
+
1241
+
1242
  def _extract_daily_balance_by_date(md: str):
1243
  """
1244
  Build a date->balance map from daily-balance style tables.
 
1750
  merged = mask_non_monetary_long_numbers(merged)
1751
  merged = directionalize_amount_headers(merged)
1752
  merged = normalize_ambiguous_amount_balance_tables(merged)
1753
+ merged = normalize_amount_only_transaction_tables(merged)
1754
  merged = stabilize_table_markup(merged, rounds=4)
1755
  merged = enrich_transaction_tables_with_daily_balances(merged)
1756
  # Keep transaction amounts untouched; add explicit snapshot for metadata extraction.