Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -25,7 +25,8 @@ Primary goals for reconcile rate:
|
|
| 25 |
Beginning/Ending balance rows (UCB-style wide summary tables)
|
| 26 |
- Fix 14: UCB 3-col tables where Beginning Balance amount is fused into description and Amount is $0.00
|
| 27 |
- Fix 15: UCB (and similar) 3-col rows where Date+Description are fused in col1 and Amount sits in col2 with col3 empty
|
| 28 |
-
- Post-pass:
|
|
|
|
| 29 |
"""
|
| 30 |
|
| 31 |
# Patch asyncio first (before Gradio imports it) to suppress Python 3.13 cleanup noise
|
|
@@ -3108,9 +3109,11 @@ def normalize_html_tables(text: str) -> str:
|
|
| 3108 |
|
| 3109 |
def _dedupe_subset_transaction_tables_html(text: str) -> str:
|
| 3110 |
"""
|
| 3111 |
-
Remove duplicate 3-column
|
| 3112 |
-
|
| 3113 |
-
|
|
|
|
|
|
|
| 3114 |
"""
|
| 3115 |
if not text or "<table" not in text.lower():
|
| 3116 |
return text
|
|
@@ -3132,42 +3135,55 @@ def _dedupe_subset_transaction_tables_html(text: str) -> str:
|
|
| 3132 |
for m in matches:
|
| 3133 |
grids.append((m, _parse_grid(m.group(0))))
|
| 3134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3135 |
drop_idx = set()
|
| 3136 |
for i in range(1, len(grids)):
|
|
|
|
|
|
|
| 3137 |
mi, gi = grids[i]
|
| 3138 |
-
if not
|
| 3139 |
continue
|
| 3140 |
-
|
| 3141 |
-
if
|
| 3142 |
continue
|
| 3143 |
|
| 3144 |
for j in range(i):
|
| 3145 |
if j in drop_idx:
|
| 3146 |
continue
|
| 3147 |
mj, gj = grids[j]
|
| 3148 |
-
if not
|
| 3149 |
continue
|
| 3150 |
-
|
| 3151 |
-
if
|
| 3152 |
continue
|
| 3153 |
|
| 3154 |
-
|
| 3155 |
-
|
| 3156 |
-
|
| 3157 |
-
if not any(str(c or "").strip() for c in r):
|
| 3158 |
-
continue
|
| 3159 |
-
t = tuple(str(c or "").strip() for c in r)
|
| 3160 |
-
if any(t):
|
| 3161 |
-
s.add(t)
|
| 3162 |
-
return s
|
| 3163 |
-
|
| 3164 |
-
si, sj = _row_set(gi), _row_set(gj)
|
| 3165 |
-
if not si or not sj:
|
| 3166 |
continue
|
| 3167 |
-
|
|
|
|
| 3168 |
if si <= sj and len(si) < len(sj):
|
| 3169 |
drop_idx.add(i)
|
| 3170 |
break
|
|
|
|
| 3171 |
if si == sj:
|
| 3172 |
drop_idx.add(i)
|
| 3173 |
break
|
|
|
|
| 25 |
Beginning/Ending balance rows (UCB-style wide summary tables)
|
| 26 |
- Fix 14: UCB 3-col tables where Beginning Balance amount is fused into description and Amount is $0.00
|
| 27 |
- Fix 15: UCB (and similar) 3-col rows where Date+Description are fused in col1 and Amount sits in col2 with col3 empty
|
| 28 |
+
- Post-pass: dedupe 3-col Date|Description|Amount tables when one is a duplicate fragment
|
| 29 |
+
(later subset of earlier, earlier subset of later, or identical row sets)
|
| 30 |
"""
|
| 31 |
|
| 32 |
# Patch asyncio first (before Gradio imports it) to suppress Python 3.13 cleanup noise
|
|
|
|
| 3109 |
|
| 3110 |
def _dedupe_subset_transaction_tables_html(text: str) -> str:
|
| 3111 |
"""
|
| 3112 |
+
Remove duplicate 3-column Date|Description|Amount tables:
|
| 3113 |
+
- Later table is a strict subset of an earlier one → drop later (original).
|
| 3114 |
+
- Earlier table is a strict subset of a later one → drop earlier (UCB-style
|
| 3115 |
+
"Deposits (continued)" preview before the same rows appear in the full list).
|
| 3116 |
+
- Identical row sets → drop the later table.
|
| 3117 |
"""
|
| 3118 |
if not text or "<table" not in text.lower():
|
| 3119 |
return text
|
|
|
|
| 3135 |
for m in matches:
|
| 3136 |
grids.append((m, _parse_grid(m.group(0))))
|
| 3137 |
|
| 3138 |
+
hdr_ok = ["date", "description", "amount"]
|
| 3139 |
+
|
| 3140 |
+
def _header_matches(grid):
|
| 3141 |
+
if not grid or len(grid) < 2:
|
| 3142 |
+
return False
|
| 3143 |
+
h = [re.sub(r"\s+", " ", str(c or "").strip().lower()) for c in grid[0]]
|
| 3144 |
+
return len(h) == 3 and h == hdr_ok
|
| 3145 |
+
|
| 3146 |
+
def _row_set(g):
|
| 3147 |
+
s = set()
|
| 3148 |
+
for r in g[1:]:
|
| 3149 |
+
if not any(str(c or "").strip() for c in r):
|
| 3150 |
+
continue
|
| 3151 |
+
t = tuple(str(c or "").strip() for c in r)
|
| 3152 |
+
if any(t):
|
| 3153 |
+
s.add(t)
|
| 3154 |
+
return s
|
| 3155 |
+
|
| 3156 |
drop_idx = set()
|
| 3157 |
for i in range(1, len(grids)):
|
| 3158 |
+
if i in drop_idx:
|
| 3159 |
+
continue
|
| 3160 |
mi, gi = grids[i]
|
| 3161 |
+
if not _header_matches(gi):
|
| 3162 |
continue
|
| 3163 |
+
si = _row_set(gi)
|
| 3164 |
+
if not si:
|
| 3165 |
continue
|
| 3166 |
|
| 3167 |
for j in range(i):
|
| 3168 |
if j in drop_idx:
|
| 3169 |
continue
|
| 3170 |
mj, gj = grids[j]
|
| 3171 |
+
if not _header_matches(gj):
|
| 3172 |
continue
|
| 3173 |
+
sj = _row_set(gj)
|
| 3174 |
+
if not sj:
|
| 3175 |
continue
|
| 3176 |
|
| 3177 |
+
# Earlier j fully contained in later i (preview + full list) → drop earlier.
|
| 3178 |
+
if sj <= si and len(sj) < len(si):
|
| 3179 |
+
drop_idx.add(j)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3180 |
continue
|
| 3181 |
+
|
| 3182 |
+
# Later i strict subset of earlier j → drop later.
|
| 3183 |
if si <= sj and len(si) < len(sj):
|
| 3184 |
drop_idx.add(i)
|
| 3185 |
break
|
| 3186 |
+
|
| 3187 |
if si == sj:
|
| 3188 |
drop_idx.add(i)
|
| 3189 |
break
|