Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1255,63 +1255,44 @@ def _patch_ocr_with_textlayer(page_md: str, pdf_path: str, page_num: int) -> str
|
|
| 1255 |
insert_pos = result.find(new_table_html) + len(new_table_html)
|
| 1256 |
result = result[:insert_pos] + extra_html + result[insert_pos:]
|
| 1257 |
|
| 1258 |
-
# Case C:
|
| 1259 |
-
#
|
| 1260 |
-
# the page
|
| 1261 |
-
#
|
| 1262 |
-
|
| 1263 |
-
|
| 1264 |
-
|
| 1265 |
-
|
| 1266 |
-
|
| 1267 |
-
|
| 1268 |
-
|
| 1269 |
-
|
| 1270 |
-
|
| 1271 |
-
|
| 1272 |
-
|
| 1273 |
-
|
| 1274 |
-
|
| 1275 |
-
has_desc = any(re.search(r"(DESCRIPTION|DETAILS?|NARRATION|PARTICULARS?)", h) for h in hdr2)
|
| 1276 |
-
if has_date and has_desc:
|
| 1277 |
-
has_txn_table = True
|
| 1278 |
-
break
|
| 1279 |
-
|
| 1280 |
-
if not has_txn_table and tl_rows:
|
| 1281 |
-
# Infer date format from text layer to pick column header label
|
| 1282 |
-
_hy = re.compile(r"^\d{1,2}-\d{2}")
|
| 1283 |
-
_sl = re.compile(r"^\d{1,2}/\d{2}")
|
| 1284 |
-
_sample_dates = [_norm(r["date"]) for r in tl_rows[:3]]
|
| 1285 |
-
if any(_hy.match(d) for d in _sample_dates):
|
| 1286 |
-
date_label = "Date"
|
| 1287 |
-
elif any(_sl.match(d) for d in _sample_dates):
|
| 1288 |
-
date_label = "Date"
|
| 1289 |
-
else:
|
| 1290 |
-
date_label = "Trans Date"
|
| 1291 |
|
| 1292 |
-
|
| 1293 |
-
|
| 1294 |
-
|
| 1295 |
-
|
| 1296 |
-
|
| 1297 |
-
|
| 1298 |
-
|
| 1299 |
-
|
| 1300 |
-
|
| 1301 |
-
|
| 1302 |
-
|
| 1303 |
-
|
| 1304 |
-
|
| 1305 |
-
|
| 1306 |
-
|
| 1307 |
-
|
| 1308 |
-
|
| 1309 |
-
|
| 1310 |
-
|
| 1311 |
-
|
| 1312 |
-
new_table_html = _grid_to_html([header_row] + new_rows)
|
| 1313 |
-
result = new_table_html + "\n\n" + result
|
| 1314 |
-
log.info("page %d: created new table from text layer (%d rows)", page_num, len(new_rows))
|
| 1315 |
|
| 1316 |
return result
|
| 1317 |
|
|
@@ -1646,13 +1627,13 @@ def run_ocr(uploaded_file):
|
|
| 1646 |
# Guard 2: suppress footer that is a raw text-layer dump of
|
| 1647 |
# transaction rows (e.g. East West Bank two-column layout).
|
| 1648 |
# Detected when the footer contains 3+ date tokens (MM/DD or MM-DD)
|
| 1649 |
-
# AND the
|
| 1650 |
-
#
|
| 1651 |
_footer_date_re = re.compile(r"\b\d{1,2}[-/]\d{2}\b")
|
| 1652 |
-
|
| 1653 |
-
|
| 1654 |
-
|
| 1655 |
-
|
| 1656 |
|
| 1657 |
if not already_present and not is_txn_dump:
|
| 1658 |
parts.append(ftr_clean)
|
|
|
|
| 1255 |
insert_pos = result.find(new_table_html) + len(new_table_html)
|
| 1256 |
result = result[:insert_pos] + extra_html + result[insert_pos:]
|
| 1257 |
|
| 1258 |
+
# Case C: page has text-layer rows but NO transaction table in OCR output.
|
| 1259 |
+
# Build a new table from the text layer and prepend it.
|
| 1260 |
+
# Guard: only fires when the page has NO table with both DATE + DESCRIPTION cols.
|
| 1261 |
+
# This is placed AFTER the for-loop so it only runs once per page, not per table.
|
| 1262 |
+
if tl_rows:
|
| 1263 |
+
_has_txn_table = False
|
| 1264 |
+
for _tbl in table_pattern.finditer(result):
|
| 1265 |
+
_p2 = TableGridParser()
|
| 1266 |
+
_p2.feed(_tbl.group(0))
|
| 1267 |
+
_g2 = _build_grid(_p2.rows)
|
| 1268 |
+
if len(_g2) < 2:
|
| 1269 |
+
continue
|
| 1270 |
+
_h2 = [str(c or "").strip().upper() for c in _g2[0]]
|
| 1271 |
+
if (any(re.search(r"\bDATE\b", x) for x in _h2) and
|
| 1272 |
+
any(re.search(r"\b(DESCRIPTION|DETAILS?|NARRATION|PARTICULARS?)\b", x) for x in _h2)):
|
| 1273 |
+
_has_txn_table = True
|
| 1274 |
+
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1275 |
|
| 1276 |
+
if not _has_txn_table:
|
| 1277 |
+
_has_post = any(r.get("post_date") for r in tl_rows)
|
| 1278 |
+
if _has_post:
|
| 1279 |
+
_chdr = ["Date", "Post Date", "Transaction Description", "Amount"]
|
| 1280 |
+
_di2, _pi2, _xi2, _ai2 = 0, 1, 2, 3
|
| 1281 |
+
else:
|
| 1282 |
+
_chdr = ["Date", "Transaction Description", "Amount"]
|
| 1283 |
+
_di2, _xi2, _ai2 = 0, 1, 2
|
| 1284 |
+
_cnew_rows = []
|
| 1285 |
+
for r in tl_rows:
|
| 1286 |
+
_crow = [""] * len(_chdr)
|
| 1287 |
+
_crow[_di2] = r["date"]
|
| 1288 |
+
_crow[_xi2] = r["desc"]
|
| 1289 |
+
_crow[_ai2] = r["amount"]
|
| 1290 |
+
if _has_post:
|
| 1291 |
+
_crow[_pi2] = r.get("post_date", "")
|
| 1292 |
+
_cnew_rows.append(_crow)
|
| 1293 |
+
result = _grid_to_html([_chdr] + _cnew_rows) + "\n\n" + result
|
| 1294 |
+
log.info("page %d: Case C — new table (%d rows) from text layer",
|
| 1295 |
+
page_num, len(_cnew_rows))
|
|
|
|
|
|
|
|
|
|
| 1296 |
|
| 1297 |
return result
|
| 1298 |
|
|
|
|
| 1627 |
# Guard 2: suppress footer that is a raw text-layer dump of
|
| 1628 |
# transaction rows (e.g. East West Bank two-column layout).
|
| 1629 |
# Detected when the footer contains 3+ date tokens (MM/DD or MM-DD)
|
| 1630 |
+
# AND the footer itself contains amounts — meaning it is transaction
|
| 1631 |
+
# data, not a legitimate page footer like an address or disclaimer.
|
| 1632 |
_footer_date_re = re.compile(r"\b\d{1,2}[-/]\d{2}\b")
|
| 1633 |
+
_footer_amt_re = re.compile(r"\b\d{1,3}(?:,\d{3})*\.\d{2}\b")
|
| 1634 |
+
_date_hits = len(_footer_date_re.findall(ftr_clean))
|
| 1635 |
+
_amt_hits = len(_footer_amt_re.findall(ftr_clean))
|
| 1636 |
+
is_txn_dump = _date_hits >= 3 and _amt_hits >= 3
|
| 1637 |
|
| 1638 |
if not already_present and not is_txn_dump:
|
| 1639 |
parts.append(ftr_clean)
|