Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1024,12 +1024,17 @@ def _extract_textlayer_rows(pdf_path: str, page_num: int):
|
|
| 1024 |
# Line looks like: Jan 16 Jan 16 CAPITAL ONE MOBILE PYMT - $500.00
|
| 1025 |
# After consuming first date, check if next tokens are also a date
|
| 1026 |
remaining = line[desc_start:]
|
|
|
|
|
|
|
| 1027 |
if remaining and month_re.match(remaining[0]):
|
| 1028 |
if len(remaining) > 1 and day_re.match(remaining[1]):
|
| 1029 |
-
|
| 1030 |
remaining = remaining[2:]
|
| 1031 |
elif len(remaining) > 0:
|
| 1032 |
remaining = remaining[1:]
|
|
|
|
|
|
|
|
|
|
| 1033 |
|
| 1034 |
if len(remaining) < 2:
|
| 1035 |
continue
|
|
@@ -1051,7 +1056,7 @@ def _extract_textlayer_rows(pdf_path: str, page_num: int):
|
|
| 1051 |
if not desc:
|
| 1052 |
continue
|
| 1053 |
|
| 1054 |
-
rows.append({"date": date_str, "desc": desc, "amount": amount_candidate})
|
| 1055 |
|
| 1056 |
# Guard: require at least 2 rows to avoid false positives on non-transaction pages
|
| 1057 |
return rows if len(rows) >= 2 else []
|
|
@@ -1187,26 +1192,47 @@ def _patch_ocr_with_textlayer(page_md: str, pdf_path: str, page_num: int) -> str
|
|
| 1187 |
new_grid.append(list(row))
|
| 1188 |
to_inject[key] = 0
|
| 1189 |
|
| 1190 |
-
# Case B: append completely missing rows from the text layer.
|
| 1191 |
-
# These are rows the OCR model dropped entirely (not just undercounted).
|
| 1192 |
-
# We build new table rows using the text-layer data, placing values in
|
| 1193 |
-
# the correct columns as identified from the OCR table header.
|
| 1194 |
-
ncols = len(grid[0])
|
| 1195 |
-
for key, (count, tl_row_data) in to_inject_new.items():
|
| 1196 |
-
for _ in range(count):
|
| 1197 |
-
new_row = [""] * ncols
|
| 1198 |
-
new_row[date_col] = tl_row_data["date"]
|
| 1199 |
-
new_row[desc_col] = tl_row_data["desc"]
|
| 1200 |
-
new_row[amt_col] = tl_row_data["amount"]
|
| 1201 |
-
new_grid.append(new_row)
|
| 1202 |
-
log.info(
|
| 1203 |
-
"page %d: injected missing row from text layer: %s %s",
|
| 1204 |
-
page_num, tl_row_data["date"], tl_row_data["desc"][:40]
|
| 1205 |
-
)
|
| 1206 |
-
|
| 1207 |
new_table_html = _grid_to_html(new_grid)
|
| 1208 |
result = result.replace(table_html, new_table_html, 1)
|
| 1209 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1210 |
return result
|
| 1211 |
|
| 1212 |
except Exception as e:
|
|
|
|
| 1024 |
# Line looks like: Jan 16 Jan 16 CAPITAL ONE MOBILE PYMT - $500.00
|
| 1025 |
# After consuming first date, check if next tokens are also a date
|
| 1026 |
remaining = line[desc_start:]
|
| 1027 |
+
# Capture post_date if next tokens are also a date
|
| 1028 |
+
post_date_str = ""
|
| 1029 |
if remaining and month_re.match(remaining[0]):
|
| 1030 |
if len(remaining) > 1 and day_re.match(remaining[1]):
|
| 1031 |
+
post_date_str = remaining[0] + " " + remaining[1]
|
| 1032 |
remaining = remaining[2:]
|
| 1033 |
elif len(remaining) > 0:
|
| 1034 |
remaining = remaining[1:]
|
| 1035 |
+
elif remaining and date_re.match(remaining[0]) and not month_re.match(remaining[0]):
|
| 1036 |
+
post_date_str = remaining[0]
|
| 1037 |
+
remaining = remaining[1:]
|
| 1038 |
|
| 1039 |
if len(remaining) < 2:
|
| 1040 |
continue
|
|
|
|
| 1056 |
if not desc:
|
| 1057 |
continue
|
| 1058 |
|
| 1059 |
+
rows.append({"date": date_str, "post_date": post_date_str, "desc": desc, "amount": amount_candidate})
|
| 1060 |
|
| 1061 |
# Guard: require at least 2 rows to avoid false positives on non-transaction pages
|
| 1062 |
return rows if len(rows) >= 2 else []
|
|
|
|
| 1192 |
new_grid.append(list(row))
|
| 1193 |
to_inject[key] = 0
|
| 1194 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1195 |
new_table_html = _grid_to_html(new_grid)
|
| 1196 |
result = result.replace(table_html, new_table_html, 1)
|
| 1197 |
|
| 1198 |
+
# Case B: completely missing rows → build a SEPARATE new table
|
| 1199 |
+
# appended after the patched OCR table. Using a separate table
|
| 1200 |
+
# preserves the original PDF structure (e.g. Capital One has
|
| 1201 |
+
# separate "Payments" and "Transactions" sections).
|
| 1202 |
+
if to_inject_new:
|
| 1203 |
+
ncols = len(grid[0])
|
| 1204 |
+
header_row = grid[0]
|
| 1205 |
+
|
| 1206 |
+
# Find post_date column index if it exists in the header
|
| 1207 |
+
post_date_col = None
|
| 1208 |
+
for ci, h in enumerate(header_row):
|
| 1209 |
+
hh = str(h or "").strip().upper()
|
| 1210 |
+
if "POST" in hh and "DATE" in hh:
|
| 1211 |
+
post_date_col = ci
|
| 1212 |
+
break
|
| 1213 |
+
|
| 1214 |
+
new_rows = []
|
| 1215 |
+
for key, (count, tl_row_data) in to_inject_new.items():
|
| 1216 |
+
for _ in range(count):
|
| 1217 |
+
new_row = [""] * ncols
|
| 1218 |
+
new_row[date_col] = tl_row_data["date"]
|
| 1219 |
+
new_row[desc_col] = tl_row_data["desc"]
|
| 1220 |
+
new_row[amt_col] = tl_row_data["amount"]
|
| 1221 |
+
if post_date_col is not None:
|
| 1222 |
+
new_row[post_date_col] = tl_row_data.get("post_date", "")
|
| 1223 |
+
new_rows.append(new_row)
|
| 1224 |
+
log.info(
|
| 1225 |
+
"page %d: new table row from text layer: %s %s",
|
| 1226 |
+
page_num, tl_row_data["date"], tl_row_data["desc"][:40]
|
| 1227 |
+
)
|
| 1228 |
+
|
| 1229 |
+
if new_rows:
|
| 1230 |
+
extra_grid = [header_row] + new_rows
|
| 1231 |
+
extra_html = "\n\n" + _grid_to_html(extra_grid)
|
| 1232 |
+
# Insert immediately after the patched table
|
| 1233 |
+
insert_pos = result.find(new_table_html) + len(new_table_html)
|
| 1234 |
+
result = result[:insert_pos] + extra_html + result[insert_pos:]
|
| 1235 |
+
|
| 1236 |
return result
|
| 1237 |
|
| 1238 |
except Exception as e:
|