Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -2643,6 +2643,58 @@ def _split_fused_date_transaction_header(grid):
|
|
| 2643 |
new_grid.append(split_row)
|
| 2644 |
return new_grid
|
| 2645 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2646 |
def _normalize_fused_date_posted_amount_table(grid):
|
| 2647 |
"""
|
| 2648 |
Normalize 2-column OCR tables with fused header like:
|
|
@@ -2799,8 +2851,12 @@ def normalize_html_tables(text: str) -> str:
|
|
| 2799 |
|
| 2800 |
# Fix 3: fused key-value rows in summary sections (no-space guard — safe on all PDFs)
|
| 2801 |
grid = _fix_fused_keyvalue_rows(grid)
|
| 2802 |
-
|
| 2803 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2804 |
except Exception:
|
| 2805 |
out.append(table_html)
|
| 2806 |
last = m.end()
|
|
|
|
| 2643 |
new_grid.append(split_row)
|
| 2644 |
return new_grid
|
| 2645 |
|
| 2646 |
+
def _split_combined_summary_daily_balance_grid(grid):
|
| 2647 |
+
"""
|
| 2648 |
+
Some OCR outputs fuse an account summary table and a Daily Balance block
|
| 2649 |
+
into one HTML table. Split them into two tables when structure is clear.
|
| 2650 |
+
Generic guards:
|
| 2651 |
+
- A row containing "Daily Balance" exists
|
| 2652 |
+
- There are rows after it with date-like + amount-like tokens
|
| 2653 |
+
"""
|
| 2654 |
+
if not grid or len(grid) < 4:
|
| 2655 |
+
return None
|
| 2656 |
+
|
| 2657 |
+
def _norm(s):
|
| 2658 |
+
return re.sub(r"\s+", " ", str(s or "").strip().lower())
|
| 2659 |
+
|
| 2660 |
+
daily_idx = None
|
| 2661 |
+
for i, row in enumerate(grid):
|
| 2662 |
+
row_text = " ".join(_norm(c) for c in row if str(c or "").strip())
|
| 2663 |
+
if "daily balance" in row_text:
|
| 2664 |
+
daily_idx = i
|
| 2665 |
+
break
|
| 2666 |
+
if daily_idx is None or daily_idx <= 1 or daily_idx >= len(grid) - 2:
|
| 2667 |
+
return None
|
| 2668 |
+
|
| 2669 |
+
date_re = re.compile(r"^\d{1,2}/\d{2}(?:/\d{2,4})?$")
|
| 2670 |
+
money_re = re.compile(r"^-?\$?\d{1,3}(?:,\d{3})*(?:\.\d{2})-?$")
|
| 2671 |
+
|
| 2672 |
+
daily_rows = []
|
| 2673 |
+
for row in grid[daily_idx + 1:]:
|
| 2674 |
+
tokens = []
|
| 2675 |
+
for cell in row:
|
| 2676 |
+
ct = str(cell or "").strip()
|
| 2677 |
+
if not ct:
|
| 2678 |
+
continue
|
| 2679 |
+
tokens.extend(re.split(r"\s+", ct))
|
| 2680 |
+
dates = [t for t in tokens if date_re.match(t)]
|
| 2681 |
+
amts = [t for t in tokens if money_re.match(t)]
|
| 2682 |
+
if not dates or not amts:
|
| 2683 |
+
continue
|
| 2684 |
+
for d, a in zip(dates, amts):
|
| 2685 |
+
daily_rows.append([d, a])
|
| 2686 |
+
|
| 2687 |
+
# Need meaningful daily table to split safely.
|
| 2688 |
+
if len(daily_rows) < 2:
|
| 2689 |
+
return None
|
| 2690 |
+
|
| 2691 |
+
summary = [r for r in grid[:daily_idx] if any(str(c or "").strip() for c in r)]
|
| 2692 |
+
if len(summary) < 2:
|
| 2693 |
+
return None
|
| 2694 |
+
|
| 2695 |
+
daily = [["Date", "Ledger Balance"]] + daily_rows
|
| 2696 |
+
return [summary, daily]
|
| 2697 |
+
|
| 2698 |
def _normalize_fused_date_posted_amount_table(grid):
|
| 2699 |
"""
|
| 2700 |
Normalize 2-column OCR tables with fused header like:
|
|
|
|
| 2851 |
|
| 2852 |
# Fix 3: fused key-value rows in summary sections (no-space guard — safe on all PDFs)
|
| 2853 |
grid = _fix_fused_keyvalue_rows(grid)
|
| 2854 |
+
# Fix 12: split fused summary+daily-balance combined table, if detected.
|
| 2855 |
+
split_tables = _split_combined_summary_daily_balance_grid(grid)
|
| 2856 |
+
if split_tables:
|
| 2857 |
+
out.append("\n\n".join(_grid_to_html(g) for g in split_tables if g))
|
| 2858 |
+
else:
|
| 2859 |
+
out.append(_grid_to_html(grid) if grid else table_html)
|
| 2860 |
except Exception:
|
| 2861 |
out.append(table_html)
|
| 2862 |
last = m.end()
|