Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1730,6 +1730,84 @@ def _split_fused_multicolumn_header(grid):
|
|
| 1730 |
return new_grid
|
| 1731 |
|
| 1732 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1733 |
def normalize_html_tables(text: str) -> str:
|
| 1734 |
"""
|
| 1735 |
For every <table>...</table>:
|
|
@@ -1759,8 +1837,9 @@ def normalize_html_tables(text: str) -> str:
|
|
| 1759 |
|
| 1760 |
# Suppress known junk tables (OCR artifacts from page headers)
|
| 1761 |
if _is_junk_table(grid):
|
|
|
|
| 1762 |
out.append("")
|
| 1763 |
-
last = m.end()
|
| 1764 |
continue
|
| 1765 |
|
| 1766 |
grid = _drop_truly_empty_columns(grid)
|
|
@@ -1780,6 +1859,9 @@ def normalize_html_tables(text: str) -> str:
|
|
| 1780 |
# Fix 8: split fused multi-keyword header cell (First Horizon Bank)
|
| 1781 |
grid = _split_fused_multicolumn_header(grid)
|
| 1782 |
|
|
|
|
|
|
|
|
|
|
| 1783 |
# Fix 6: merge split rows where description wraps to next line
|
| 1784 |
# (Hardin County Bank and similar monospace statement formats)
|
| 1785 |
grid = _merge_split_rows(grid)
|
|
|
|
| 1730 |
return new_grid
|
| 1731 |
|
| 1732 |
|
| 1733 |
+
def _normalize_daily_balance_table(grid):
|
| 1734 |
+
"""
|
| 1735 |
+
Fix OCR artifact in DAILY BALANCE SUMMARY tables where:
|
| 1736 |
+
- OCR produces N DATE columns followed by N BALANCE columns
|
| 1737 |
+
instead of interleaved DATE|BALANCE|DATE|BALANCE pairs
|
| 1738 |
+
- Multiple dates are stacked in one cell "01/02\n01/03\n01/04\n01/05"
|
| 1739 |
+
|
| 1740 |
+
Reorders columns and expands stacked date cells into proper rows.
|
| 1741 |
+
Only fires when ALL of these hold:
|
| 1742 |
+
- Header has exactly 2N columns where N >= 2
|
| 1743 |
+
- First N columns are all DATE, last N columns are all BALANCE
|
| 1744 |
+
- At least one data cell contains a newline (stacked dates)
|
| 1745 |
+
"""
|
| 1746 |
+
if not grid or len(grid) < 2:
|
| 1747 |
+
return grid
|
| 1748 |
+
|
| 1749 |
+
header = [str(c or "").strip().upper() for c in grid[0]]
|
| 1750 |
+
ncols = len(header)
|
| 1751 |
+
|
| 1752 |
+
if ncols < 4 or ncols % 2 != 0:
|
| 1753 |
+
return grid
|
| 1754 |
+
|
| 1755 |
+
half = ncols // 2
|
| 1756 |
+
date_half = header[:half]
|
| 1757 |
+
bal_half = header[half:]
|
| 1758 |
+
|
| 1759 |
+
if not all(h == "DATE" for h in date_half):
|
| 1760 |
+
return grid
|
| 1761 |
+
if not all(h == "BALANCE" for h in bal_half):
|
| 1762 |
+
return grid
|
| 1763 |
+
|
| 1764 |
+
# Build new interleaved header: DATE BALANCE DATE BALANCE ...
|
| 1765 |
+
new_header = []
|
| 1766 |
+
for i in range(half):
|
| 1767 |
+
new_header.append("DATE")
|
| 1768 |
+
new_header.append("BALANCE")
|
| 1769 |
+
|
| 1770 |
+
# Expand each data row
|
| 1771 |
+
new_rows = []
|
| 1772 |
+
_DATE_RE3 = re.compile(r"^\d{1,2}/\d{2}(?:/\d{2,4})?$")
|
| 1773 |
+
|
| 1774 |
+
for row in grid[1:]:
|
| 1775 |
+
cells = [str(c or "").strip() for c in row]
|
| 1776 |
+
while len(cells) < ncols:
|
| 1777 |
+
cells.append("")
|
| 1778 |
+
|
| 1779 |
+
# Dates are all stacked in cells[0]; balances follow immediately in cells[1..N]
|
| 1780 |
+
# (OCR layout: stacked_dates | bal1 | bal2 | bal3 | bal4 | empty...)
|
| 1781 |
+
# NOT: date | date | date | date | bal | bal | bal | bal (despite header order)
|
| 1782 |
+
date_cells = [cells[0]]
|
| 1783 |
+
bal_cells = cells[1:]
|
| 1784 |
+
|
| 1785 |
+
# Check if dates are stacked in first cell (newline OR space-separated)
|
| 1786 |
+
# e.g. "01/02 01/03 01/04 01/05" or "01/02\n01/03\n01/04\n01/05"
|
| 1787 |
+
first = date_cells[0]
|
| 1788 |
+
tokens = re.split(r"[\n\r\s]+", first)
|
| 1789 |
+
date_tokens = [t.strip() for t in tokens if t.strip() and _DATE_RE3.match(t.strip())]
|
| 1790 |
+
if len(date_tokens) >= 2:
|
| 1791 |
+
stacked = date_tokens
|
| 1792 |
+
else:
|
| 1793 |
+
stacked = date_cells # already one date per cell
|
| 1794 |
+
|
| 1795 |
+
# Build ONE interleaved row for this group of stacked dates
|
| 1796 |
+
new_row = [""] * ncols
|
| 1797 |
+
for i, date_val in enumerate(stacked):
|
| 1798 |
+
if not _DATE_RE3.match(date_val):
|
| 1799 |
+
continue
|
| 1800 |
+
bal_val = bal_cells[i] if i < len(bal_cells) else ""
|
| 1801 |
+
new_row[i * 2] = date_val
|
| 1802 |
+
new_row[i * 2 + 1] = bal_val
|
| 1803 |
+
new_rows.append(new_row)
|
| 1804 |
+
|
| 1805 |
+
if not new_rows:
|
| 1806 |
+
return grid
|
| 1807 |
+
|
| 1808 |
+
return [new_header] + new_rows
|
| 1809 |
+
|
| 1810 |
+
|
| 1811 |
def normalize_html_tables(text: str) -> str:
|
| 1812 |
"""
|
| 1813 |
For every <table>...</table>:
|
|
|
|
| 1837 |
|
| 1838 |
# Suppress known junk tables (OCR artifacts from page headers)
|
| 1839 |
if _is_junk_table(grid):
|
| 1840 |
+
# Append nothing (suppress the table), update last, skip rest of pipeline
|
| 1841 |
out.append("")
|
| 1842 |
+
last = m.end() # must set here before continue since line below is outside try
|
| 1843 |
continue
|
| 1844 |
|
| 1845 |
grid = _drop_truly_empty_columns(grid)
|
|
|
|
| 1859 |
# Fix 8: split fused multi-keyword header cell (First Horizon Bank)
|
| 1860 |
grid = _split_fused_multicolumn_header(grid)
|
| 1861 |
|
| 1862 |
+
# Fix 9: normalize DAILY BALANCE SUMMARY tables with stacked dates
|
| 1863 |
+
grid = _normalize_daily_balance_table(grid)
|
| 1864 |
+
|
| 1865 |
# Fix 6: merge split rows where description wraps to next line
|
| 1866 |
# (Hardin County Bank and similar monospace statement formats)
|
| 1867 |
grid = _merge_split_rows(grid)
|