Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -48,6 +48,10 @@ Primary goals for reconcile rate:
|
|
| 48 |
(≤6 rows), append those rows to the preceding debits table and drop the extra ED heading.
|
| 49 |
Skip the *first* Electronic Debits + table on the page when the previous table is ITM/credits-only
|
| 50 |
and the fragment is clearly debits (so we do not treat the main debits section as a duplicate).
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
- Fix 17e: Repeat EC/ED fused + 17d passes until stable (handles chained replacements). Centered section titles
|
| 52 |
match both align=\"center\" and style=\"text-align:center\" on divs.
|
| 53 |
- Post-pass: dedupe 3-col Date|Description|Amount tables when one is a duplicate fragment
|
|
@@ -2944,6 +2948,17 @@ def _is_strong_ucb_electronic_debit_row(grid, r: int) -> bool:
|
|
| 2944 |
return False
|
| 2945 |
|
| 2946 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2947 |
def _ucb_has_itm_deposit_data_row(grid) -> bool:
|
| 2948 |
for r in range(1, len(grid)):
|
| 2949 |
if _is_itm_deposit_row_ucb(grid, r):
|
|
@@ -3088,6 +3103,111 @@ _UCB_EC_ED_SINGLE_FUSED_TABLE = re.compile(
|
|
| 3088 |
re.DOTALL | re.IGNORECASE,
|
| 3089 |
)
|
| 3090 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3091 |
|
| 3092 |
def _ucb_strip_redundant_ed_subset_fragment(text: str) -> str:
|
| 3093 |
"""
|
|
@@ -3247,14 +3367,6 @@ def _ucb_prune_ed_fragment_dupes_vs_prev_table(text: str) -> str:
|
|
| 3247 |
return None
|
| 3248 |
return k
|
| 3249 |
|
| 3250 |
-
def _grid_has_strong_debit_row(grid):
|
| 3251 |
-
for r in range(1, len(grid)):
|
| 3252 |
-
if not any(str(c or "").strip() for c in grid[r]):
|
| 3253 |
-
continue
|
| 3254 |
-
if _is_strong_ucb_electronic_debit_row(grid, r):
|
| 3255 |
-
return True
|
| 3256 |
-
return False
|
| 3257 |
-
|
| 3258 |
for _ in range(12):
|
| 3259 |
changed = False
|
| 3260 |
for m in ed_block.finditer(text):
|
|
@@ -3274,7 +3386,7 @@ def _ucb_prune_ed_fragment_dupes_vs_prev_table(text: str) -> str:
|
|
| 3274 |
):
|
| 3275 |
continue
|
| 3276 |
# Legitimate layout: ITM/credits-only table, then Electronic Debits + main debits — do not prune.
|
| 3277 |
-
if not
|
| 3278 |
continue
|
| 3279 |
cb = Counter()
|
| 3280 |
for r in range(1, len(g_big)):
|
|
@@ -3389,9 +3501,10 @@ def _split_ucb_deposits_electronic_sections_html(text: str) -> str:
|
|
| 3389 |
return new
|
| 3390 |
|
| 3391 |
text = _UCB_DEP_CONT_BLOCK.sub(_repl, text)
|
| 3392 |
-
# Fix 17e: repeat fused EC/ED fix + duplicate ED strip + 17g prune until stable.
|
| 3393 |
for _ in range(16):
|
| 3394 |
-
nxt =
|
|
|
|
| 3395 |
nxt = _ucb_strip_redundant_ed_subset_fragment(nxt)
|
| 3396 |
nxt = _ucb_prune_ed_fragment_dupes_vs_prev_table(nxt)
|
| 3397 |
if nxt == text:
|
|
|
|
| 48 |
(≤6 rows), append those rows to the preceding debits table and drop the extra ED heading.
|
| 49 |
Skip the *first* Electronic Debits + table on the page when the previous table is ITM/credits-only
|
| 50 |
and the fragment is clearly debits (so we do not treat the main debits section as a duplicate).
|
| 51 |
+
- Fix 17h: UCB sometimes OCRs Electronic Credits (+ optional Electronic Debits) then the *main debits* DA3
|
| 52 |
+
table first and a *second* DA3 table whose rows start with ITM DEPOSIT (credits) plus duplicate
|
| 53 |
+
debits. Reorder to Credits + ITM table + Electronic Debits + merged debits; drop dup keys from the
|
| 54 |
+
second table against the first; merge remaining orphan rows into the debits table.
|
| 55 |
- Fix 17e: Repeat EC/ED fused + 17d passes until stable (handles chained replacements). Centered section titles
|
| 56 |
match both align=\"center\" and style=\"text-align:center\" on divs.
|
| 57 |
- Post-pass: dedupe 3-col Date|Description|Amount tables when one is a duplicate fragment
|
|
|
|
| 2948 |
return False
|
| 2949 |
|
| 2950 |
|
| 2951 |
+
def _ucb_grid_has_any_strong_debit_row(grid) -> bool:
|
| 2952 |
+
if not grid:
|
| 2953 |
+
return False
|
| 2954 |
+
for r in range(1, len(grid)):
|
| 2955 |
+
if not any(str(c or "").strip() for c in grid[r]):
|
| 2956 |
+
continue
|
| 2957 |
+
if _is_strong_ucb_electronic_debit_row(grid, r):
|
| 2958 |
+
return True
|
| 2959 |
+
return False
|
| 2960 |
+
|
| 2961 |
+
|
| 2962 |
def _ucb_has_itm_deposit_data_row(grid) -> bool:
|
| 2963 |
for r in range(1, len(grid)):
|
| 2964 |
if _is_itm_deposit_row_ucb(grid, r):
|
|
|
|
| 3103 |
re.DOTALL | re.IGNORECASE,
|
| 3104 |
)
|
| 3105 |
|
| 3106 |
+
# Fix 17h: debits DA3 first, then DA3 with leading ITM + duplicate tail (no ED between tables).
|
| 3107 |
+
_UCB_EC_TWO_DA3_DEBITS_BEFORE_ITM_TAIL = re.compile(
|
| 3108 |
+
r'(?P<lead>(?:' + _UCB_CENTER_DIV + r'\s*</div>\s*)?)'
|
| 3109 |
+
r'(?P<ecdiv>' + _UCB_CENTER_DIV + r'\s*Electronic\s+Credits\s*</div>\s*)'
|
| 3110 |
+
r'(?P<ed_maybe>(?:' + _UCB_CENTER_DIV + r'\s*Electronic\s+Debits\s*</div>\s*)?)'
|
| 3111 |
+
r'(?P<t1><table[^>]*>.*?</table>)'
|
| 3112 |
+
r'\s*'
|
| 3113 |
+
r'(?P<t2><table[^>]*>.*?</table>)',
|
| 3114 |
+
re.DOTALL | re.IGNORECASE,
|
| 3115 |
+
)
|
| 3116 |
+
|
| 3117 |
+
|
| 3118 |
+
def _ucb_reorder_ec_ed_debits_first_itm_tail_html(text: str) -> str:
|
| 3119 |
+
"""
|
| 3120 |
+
Fix 17h: Electronic Credits (+ optional Electronic Debits) then two DA3 tables where the first is
|
| 3121 |
+
clearly debits and the second begins with a run of ITM DEPOSIT rows (mis-ordered vs credits section).
|
| 3122 |
+
"""
|
| 3123 |
+
if not text or "electronic credits" not in text.lower():
|
| 3124 |
+
return text
|
| 3125 |
+
low = text.lower()
|
| 3126 |
+
if "itm deposit" not in low and "itm dep" not in low:
|
| 3127 |
+
return text
|
| 3128 |
+
pat = _UCB_EC_TWO_DA3_DEBITS_BEFORE_ITM_TAIL
|
| 3129 |
+
_dn = re.compile(r"^\d{1,2}[/\-]\d{2}")
|
| 3130 |
+
_amt_ok = re.compile(r"^-?\d+(?:\.\d{1,4})?-?$")
|
| 3131 |
+
|
| 3132 |
+
def _key_cells(cells):
|
| 3133 |
+
parts = [str(c or "").strip() for c in cells[:3]]
|
| 3134 |
+
while len(parts) < 3:
|
| 3135 |
+
parts.append("")
|
| 3136 |
+
date_s = re.sub(r"\s+", "", parts[0])
|
| 3137 |
+
amt_s = re.sub(r"[\s$,]", "", parts[2]).lower()
|
| 3138 |
+
amt_s = amt_s.replace("\u2212", "-").replace("\u2013", "-")
|
| 3139 |
+
return date_s, amt_s
|
| 3140 |
+
|
| 3141 |
+
def _row_loose_key(grid, r):
|
| 3142 |
+
cells = [str(c or "").strip() for c in grid[r]]
|
| 3143 |
+
if not any(cells):
|
| 3144 |
+
return None
|
| 3145 |
+
k = _key_cells(cells)
|
| 3146 |
+
if not (_dn.match(k[0]) and _amt_ok.match(k[1]) and "." in k[1]):
|
| 3147 |
+
return None
|
| 3148 |
+
return k
|
| 3149 |
+
|
| 3150 |
+
m = pat.search(text)
|
| 3151 |
+
if not m:
|
| 3152 |
+
return text
|
| 3153 |
+
g1 = _parse_da3_grid_from_table_html(m.group("t1"))
|
| 3154 |
+
g2 = _parse_da3_grid_from_table_html(m.group("t2"))
|
| 3155 |
+
if not g1 or not g2 or not _is_da3_header(g1) or not _is_da3_header(g2):
|
| 3156 |
+
return text
|
| 3157 |
+
if not _ucb_grid_has_any_strong_debit_row(g1):
|
| 3158 |
+
return text
|
| 3159 |
+
itm_end = 1
|
| 3160 |
+
while itm_end < len(g2):
|
| 3161 |
+
if not any(str(c or "").strip() for c in g2[itm_end]):
|
| 3162 |
+
itm_end += 1
|
| 3163 |
+
continue
|
| 3164 |
+
if not _is_itm_deposit_row_ucb(g2, itm_end):
|
| 3165 |
+
break
|
| 3166 |
+
itm_end += 1
|
| 3167 |
+
if itm_end <= 1:
|
| 3168 |
+
return text
|
| 3169 |
+
cb = Counter()
|
| 3170 |
+
for r in range(1, len(g1)):
|
| 3171 |
+
kk = _row_loose_key(g1, r)
|
| 3172 |
+
if kk:
|
| 3173 |
+
cb[kk] += 1
|
| 3174 |
+
new_tail = []
|
| 3175 |
+
for r in range(itm_end, len(g2)):
|
| 3176 |
+
if not any(str(c or "").strip() for c in g2[r]):
|
| 3177 |
+
continue
|
| 3178 |
+
if _is_itm_deposit_row_ucb(g2, r):
|
| 3179 |
+
continue
|
| 3180 |
+
kk = _row_loose_key(g2, r)
|
| 3181 |
+
if kk and cb.get(kk, 0) > 0:
|
| 3182 |
+
cb[kk] -= 1
|
| 3183 |
+
continue
|
| 3184 |
+
new_tail.append(list(g2[r]))
|
| 3185 |
+
g_itm = [list(g2[0])] + [list(g2[r]) for r in range(1, itm_end)]
|
| 3186 |
+
merged = [list(g1[0])] + [list(r) for r in g1[1:]] + new_tail
|
| 3187 |
+
max_c = max(len(r) for r in merged)
|
| 3188 |
+
for row in merged:
|
| 3189 |
+
while len(row) < max_c:
|
| 3190 |
+
row.append("")
|
| 3191 |
+
max_ci = max(len(r) for r in g_itm)
|
| 3192 |
+
for row in g_itm:
|
| 3193 |
+
while len(row) < max_ci:
|
| 3194 |
+
row.append("")
|
| 3195 |
+
sect = '<div align="center">\n\n{title}\n\n</div>\n\n'
|
| 3196 |
+
parts = []
|
| 3197 |
+
lead = m.group("lead") or ""
|
| 3198 |
+
if lead.strip():
|
| 3199 |
+
parts.append(lead.rstrip())
|
| 3200 |
+
parts.extend(
|
| 3201 |
+
[
|
| 3202 |
+
sect.format(title="Electronic Credits"),
|
| 3203 |
+
_grid_to_html(g_itm),
|
| 3204 |
+
sect.format(title="Electronic Debits"),
|
| 3205 |
+
_grid_to_html(merged),
|
| 3206 |
+
]
|
| 3207 |
+
)
|
| 3208 |
+
new_blob = "\n\n".join(parts)
|
| 3209 |
+
return text[: m.start()] + new_blob + text[m.end() :]
|
| 3210 |
+
|
| 3211 |
|
| 3212 |
def _ucb_strip_redundant_ed_subset_fragment(text: str) -> str:
|
| 3213 |
"""
|
|
|
|
| 3367 |
return None
|
| 3368 |
return k
|
| 3369 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3370 |
for _ in range(12):
|
| 3371 |
changed = False
|
| 3372 |
for m in ed_block.finditer(text):
|
|
|
|
| 3386 |
):
|
| 3387 |
continue
|
| 3388 |
# Legitimate layout: ITM/credits-only table, then Electronic Debits + main debits — do not prune.
|
| 3389 |
+
if not _ucb_grid_has_any_strong_debit_row(g_big) and _ucb_grid_has_any_strong_debit_row(g_frag):
|
| 3390 |
continue
|
| 3391 |
cb = Counter()
|
| 3392 |
for r in range(1, len(g_big)):
|
|
|
|
| 3501 |
return new
|
| 3502 |
|
| 3503 |
text = _UCB_DEP_CONT_BLOCK.sub(_repl, text)
|
| 3504 |
+
# Fix 17e: repeat 17h reorder + fused EC/ED fix + duplicate ED strip + 17g prune until stable.
|
| 3505 |
for _ in range(16):
|
| 3506 |
+
nxt = _ucb_reorder_ec_ed_debits_first_itm_tail_html(text)
|
| 3507 |
+
nxt = _split_ucb_fused_ec_ed_headings_single_table_html(nxt)
|
| 3508 |
nxt = _ucb_strip_redundant_ed_subset_fragment(nxt)
|
| 3509 |
nxt = _ucb_prune_ed_fragment_dupes_vs_prev_table(nxt)
|
| 3510 |
if nxt == text:
|