rehan953 commited on
Commit
5798892
Β·
verified Β·
1 Parent(s): 748c414

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +81 -3
app.py CHANGED
@@ -30,6 +30,10 @@ Primary goals for reconcile rate:
30
  Electronic Debits (PDF text order); drop orphan empty EC/ED headers that followed the merged table
31
  - Post-pass: dedupe 3-col Date|Description|Amount tables when one is a duplicate fragment
32
  (later subset of earlier, earlier subset of later, or identical row sets)
 
 
 
 
33
  """
34
 
35
  # Patch asyncio first (before Gradio imports it) to suppress Python 3.13 cleanup noise
@@ -3133,7 +3137,9 @@ def _is_da3_header(grid):
3133
  h = [re.sub(r"\s+", " ", str(c or "").strip().lower()) for c in grid[0]]
3134
  if len(h) != 3:
3135
  return False
3136
- return h[0] == "date" and "description" in h[1] and h[2] == "amount"
 
 
3137
 
3138
 
3139
  def _dedupe_duplicate_rows_in_da3_table(grid):
@@ -3266,6 +3272,77 @@ def _split_ucb_deposits_electronic_sections_html(text: str) -> str:
3266
  return _UCB_DEP_CONT_BLOCK.sub(_repl, text)
3267
 
3268
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3269
  def _dedupe_subset_transaction_tables_html(text: str) -> str:
3270
  """
3271
  Remove duplicate 3-column Date|Description|Amount tables:
@@ -3275,7 +3352,6 @@ def _dedupe_subset_transaction_tables_html(text: str) -> str:
3275
  - Identical row sets β†’ drop the later table.
3276
  - Fuzzy overlap (β‰₯92% of later rows match earlier) β†’ drop later (Electronic Debits vs main).
3277
  - Runs multiple passes until stable.
3278
-
3279
  Row matching uses normalized keys so OCR variants (e.g. "0 27" vs "027", backticks)
3280
  do not prevent duplicate tables from being detected.
3281
  """
@@ -3397,6 +3473,7 @@ def stabilize_tables_and_text(page_md: str) -> str:
3397
  stabilized = "\n\n".join(out_blocks)
3398
  stabilized = convert_plaintext_bank_sections(stabilized)
3399
  stabilized = normalize_html_tables(stabilized)
 
3400
  stabilized = _split_ucb_deposits_electronic_sections_html(stabilized)
3401
  stabilized = _dedupe_subset_transaction_tables_html(stabilized)
3402
  return close_unclosed_html(stabilized)
@@ -3525,6 +3602,7 @@ def run_ocr(uploaded_file):
3525
  # _patch_ocr_with_textlayer may append Case B rows from the PDF text layer
3526
  # with fused UCB-style cells; run the same HTML table pass again (Fix 13/14).
3527
  stabilized = normalize_html_tables(stabilized)
 
3528
  stabilized = _split_ucb_deposits_electronic_sections_html(stabilized)
3529
  stabilized = _dedupe_subset_transaction_tables_html(stabilized)
3530
  elif is_pdf and is_nfcu_doc:
@@ -3607,6 +3685,7 @@ def run_ocr(uploaded_file):
3607
  merged = "\n\n---page-separator---\n\n".join(all_pages) if all_pages else "(No content)"
3608
  # Cross-page: UCB section split + same transaction tables dedupe once on full doc.
3609
  if all_pages and merged and not merged.startswith("Error") and "<table" in merged.lower():
 
3610
  merged = _split_ucb_deposits_electronic_sections_html(merged)
3611
  merged = _dedupe_subset_transaction_tables_html(merged)
3612
  return merged
@@ -3633,4 +3712,3 @@ with gr.Blocks(title="GLM-OCR") as demo:
3633
 
3634
  if __name__ == "__main__":
3635
  demo.launch()
3636
-
 
30
  Electronic Debits (PDF text order); drop orphan empty EC/ED headers that followed the merged table
31
  - Post-pass: dedupe 3-col Date|Description|Amount tables when one is a duplicate fragment
32
  (later subset of earlier, earlier subset of later, or identical row sets)
33
+ - Fix 18: adjacent DA3 tables β€” if the last K data rows of table N match the first K rows of table N+1
34
+ (same normalized Date|Description|Amount keys), strip that suffix from table N. Fixes
35
+ formats (e.g. Truist) where deposit rows are glued onto the withdrawals table and repeated
36
+ under the proper deposits heading (K>=2; first table must keep >=1 data row).
37
  """
38
 
39
  # Patch asyncio first (before Gradio imports it) to suppress Python 3.13 cleanup noise
 
3137
  h = [re.sub(r"\s+", " ", str(c or "").strip().lower()) for c in grid[0]]
3138
  if len(h) != 3:
3139
  return False
3140
+ return h[0] == "date" and "description" in h[1] and (
3141
+ h[2] == "amount" or h[2].startswith("amount")
3142
+ )
3143
 
3144
 
3145
  def _dedupe_duplicate_rows_in_da3_table(grid):
 
3272
  return _UCB_DEP_CONT_BLOCK.sub(_repl, text)
3273
 
3274
 
3275
+ def _parse_da3_grid_from_table_html(table_html: str):
3276
+ """Return grid if table is Date|Description|Amount; else None."""
3277
+ try:
3278
+ p = TableGridParser()
3279
+ p.feed(table_html)
3280
+ grid = _build_grid(p.rows)
3281
+ if grid and _is_da3_header(grid):
3282
+ return grid
3283
+ except Exception:
3284
+ pass
3285
+ return None
3286
+
3287
+
3288
+ def _trim_adjacent_da3_suffix_duplicating_next_table_prefix(text: str) -> str:
3289
+ """
3290
+ Remove trailing data rows from a DA3 table when they are repeated as the opening
3291
+ rows of the immediately following DA3 table (OCR/LLM glued the next section onto
3292
+ the prior table). Requires overlap length >= 2 and leaves at least one data row
3293
+ in the first table. Safe no-op when patterns do not match.
3294
+ """
3295
+ if not text or "<table" not in text.lower():
3296
+ return text
3297
+
3298
+ pattern = re.compile(r"<table[^>]*>.*?</table>", re.DOTALL | re.IGNORECASE)
3299
+
3300
+ for _ in range(48):
3301
+ matches = list(pattern.finditer(text))
3302
+ if len(matches) < 2:
3303
+ break
3304
+ changed = False
3305
+ for i in range(len(matches) - 1):
3306
+ g1 = _parse_da3_grid_from_table_html(matches[i].group(0))
3307
+ g2 = _parse_da3_grid_from_table_html(matches[i + 1].group(0))
3308
+ if not g1 or not g2 or len(g1) < 3 or len(g2) < 2:
3309
+ continue
3310
+ d1 = g1[1:]
3311
+ d2 = g2[1:]
3312
+ if len(d1) < 2 or len(d2) < 2:
3313
+ continue
3314
+ max_k = min(len(d1), len(d2))
3315
+ best_k = 0
3316
+ for k in range(max_k, 1, -1):
3317
+ ok = True
3318
+ for j in range(k):
3319
+ c1 = [str(x or "").strip() for x in d1[-k + j][:3]]
3320
+ c2 = [str(x or "").strip() for x in d2[j][:3]]
3321
+ while len(c1) < 3:
3322
+ c1.append("")
3323
+ while len(c2) < 3:
3324
+ c2.append("")
3325
+ if _transaction_row_dedupe_key(c1) != _transaction_row_dedupe_key(c2):
3326
+ ok = False
3327
+ break
3328
+ if ok:
3329
+ best_k = k
3330
+ break
3331
+ if best_k < 2:
3332
+ continue
3333
+ if len(d1) - best_k < 1:
3334
+ continue
3335
+ new_grid = [g1[0]] + d1[:-best_k]
3336
+ new_html = _grid_to_html(new_grid)
3337
+ m = matches[i]
3338
+ text = text[: m.start()] + new_html + text[m.end() :]
3339
+ changed = True
3340
+ break
3341
+ if not changed:
3342
+ break
3343
+ return text
3344
+
3345
+
3346
  def _dedupe_subset_transaction_tables_html(text: str) -> str:
3347
  """
3348
  Remove duplicate 3-column Date|Description|Amount tables:
 
3352
  - Identical row sets β†’ drop the later table.
3353
  - Fuzzy overlap (β‰₯92% of later rows match earlier) β†’ drop later (Electronic Debits vs main).
3354
  - Runs multiple passes until stable.
 
3355
  Row matching uses normalized keys so OCR variants (e.g. "0 27" vs "027", backticks)
3356
  do not prevent duplicate tables from being detected.
3357
  """
 
3473
  stabilized = "\n\n".join(out_blocks)
3474
  stabilized = convert_plaintext_bank_sections(stabilized)
3475
  stabilized = normalize_html_tables(stabilized)
3476
+ stabilized = _trim_adjacent_da3_suffix_duplicating_next_table_prefix(stabilized)
3477
  stabilized = _split_ucb_deposits_electronic_sections_html(stabilized)
3478
  stabilized = _dedupe_subset_transaction_tables_html(stabilized)
3479
  return close_unclosed_html(stabilized)
 
3602
  # _patch_ocr_with_textlayer may append Case B rows from the PDF text layer
3603
  # with fused UCB-style cells; run the same HTML table pass again (Fix 13/14).
3604
  stabilized = normalize_html_tables(stabilized)
3605
+ stabilized = _trim_adjacent_da3_suffix_duplicating_next_table_prefix(stabilized)
3606
  stabilized = _split_ucb_deposits_electronic_sections_html(stabilized)
3607
  stabilized = _dedupe_subset_transaction_tables_html(stabilized)
3608
  elif is_pdf and is_nfcu_doc:
 
3685
  merged = "\n\n---page-separator---\n\n".join(all_pages) if all_pages else "(No content)"
3686
  # Cross-page: UCB section split + same transaction tables dedupe once on full doc.
3687
  if all_pages and merged and not merged.startswith("Error") and "<table" in merged.lower():
3688
+ merged = _trim_adjacent_da3_suffix_duplicating_next_table_prefix(merged)
3689
  merged = _split_ucb_deposits_electronic_sections_html(merged)
3690
  merged = _dedupe_subset_transaction_tables_html(merged)
3691
  return merged
 
3712
 
3713
  if __name__ == "__main__":
3714
  demo.launch()