rehan953 commited on
Commit
147d7d7
·
verified ·
1 Parent(s): 2b61316

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -0
app.py CHANGED
@@ -133,6 +133,9 @@ Primary goals for reconcile rate:
133
  table when bbox/image markers + repeated check lines are present; skips chunks with YOUR CHECKS SEQUENCED.
134
  - Fix MD-4: YOUR CHECKS SEQUENCED wide grid (N×DATE|CHECK#|AMOUNT per row) is unfolded to one DATE|CHECK#|AMOUNT
135
  table so narrow viewers show every check, not only the first date column.
 
 
 
136
  """
137
 
138
  # Patch asyncio first (before Gradio imports it) to suppress Python 3.13 cleanup noise
@@ -4523,6 +4526,140 @@ def _dedupe_da3_by_date_amount_loose(text: str) -> str:
4523
  return text
4524
 
4525
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4526
  def _dedupe_subset_transaction_tables_html(text: str) -> str:
4527
  if not text or "<table" not in text.lower():
4528
  return text
@@ -5377,6 +5514,7 @@ def stabilize_tables_and_text(page_md: str) -> str:
5377
  stabilized = _split_ucb_deposits_electronic_sections_html(stabilized)
5378
  stabilized = _patch_da3_msft_g_ref_amounts_across_tables(stabilized)
5379
  stabilized = _dedupe_subset_transaction_tables_html(stabilized)
 
5380
  stabilized = _dedupe_da3_by_date_amount_loose(stabilized) # Fix 30
5381
  stabilized = _drop_da3_misplaced_debit_after_deposit_heading_tables(stabilized)
5382
  stabilized = _strip_credits_prefix_from_debit_da3_table(stabilized) # Fix 28
@@ -5514,6 +5652,7 @@ def run_ocr(uploaded_file):
5514
  stabilized = _split_ucb_deposits_electronic_sections_html(stabilized)
5515
  stabilized = _patch_da3_msft_g_ref_amounts_across_tables(stabilized)
5516
  stabilized = _dedupe_subset_transaction_tables_html(stabilized)
 
5517
  stabilized = _dedupe_da3_by_date_amount_loose(stabilized) # Fix 30
5518
  stabilized = _drop_da3_misplaced_debit_after_deposit_heading_tables(stabilized)
5519
  stabilized = _strip_credits_prefix_from_debit_da3_table(stabilized) # Fix 28
@@ -5596,6 +5735,7 @@ def run_ocr(uploaded_file):
5596
  merged = _split_ucb_deposits_electronic_sections_html(merged)
5597
  merged = _patch_da3_msft_g_ref_amounts_across_tables(merged)
5598
  merged = _dedupe_subset_transaction_tables_html(merged)
 
5599
  merged = _dedupe_da3_by_date_amount_loose(merged) # Fix 30
5600
  merged = _drop_da3_misplaced_debit_after_deposit_heading_tables(merged)
5601
  merged = _strip_credits_prefix_from_debit_da3_table(merged) # Fix 28
 
133
  table when bbox/image markers + repeated check lines are present; skips chunks with YOUR CHECKS SEQUENCED.
134
  - Fix MD-4: YOUR CHECKS SEQUENCED wide grid (N×DATE|CHECK#|AMOUNT per row) is unfolded to one DATE|CHECK#|AMOUNT
135
  table so narrow viewers show every check, not only the first date column.
136
+ - Fix FH-1: Drop a leading Date|Description|Amount (DA3) table when its (date, amount) multiset is a
137
+ strict subset of a later DEPOSIT/WITHDRAWAL register (e.g. First Horizon OCR duplicate before
138
+ ACCOUNT SUMMARY). Safe no-op when no such pair exists.
139
  """
140
 
141
  # Patch asyncio first (before Gradio imports it) to suppress Python 3.13 cleanup noise
 
4526
  return text
4527
 
4528
 
4529
+ def _fh_withdrawal_column_index(grid) -> Optional[int]:
4530
+ for row in grid[: min(6, len(grid))]:
4531
+ for ci, c in enumerate(row):
4532
+ if str(c or "").strip().upper() == "WITHDRAWAL":
4533
+ return ci
4534
+ return None
4535
+
4536
+
4537
+ def _fh_is_deposit_withdrawal_register(grid) -> bool:
4538
+ if not grid or len(grid) < 2:
4539
+ return False
4540
+ blob = " ".join(
4541
+ " ".join(str(c or "") for c in row) for row in grid[: min(5, len(grid))]
4542
+ ).upper()
4543
+ return "DEPOSIT" in blob and "WITHDRAWAL" in blob and "DATE" in blob
4544
+
4545
+
4546
+ def _drop_da3_duplicate_of_later_deposit_withdrawal_register(text: str) -> str:
4547
+ """
4548
+ Fix FH-1: Vision OCR may emit a spurious DA3 table (subset of withdrawals) before the real
4549
+ ACCOUNT HISTORY grid. Drop the DA3 when every (date, amount) row is covered by a later
4550
+ register's WITHDRAWAL column and that register has strictly more withdrawal rows.
4551
+ """
4552
+ if not text or "<table" not in text.lower():
4553
+ return text
4554
+ try:
4555
+ pattern = re.compile(r"<table[^>]*>.*?</table>", re.DOTALL | re.IGNORECASE)
4556
+ matches = list(pattern.finditer(text))
4557
+ if len(matches) < 2:
4558
+ return text
4559
+
4560
+ def _parse_grid(html: str):
4561
+ try:
4562
+ p = TableGridParser()
4563
+ p.feed(html)
4564
+ return _build_grid(p.rows)
4565
+ except Exception:
4566
+ return None
4567
+
4568
+ def _norm_da_key_date_amt(date_s: str, amt_s: str) -> Tuple[str, str]:
4569
+ d = re.sub(r"\s+", "", str(date_s or "").strip())
4570
+ a = re.sub(r"[\s$,]", "", str(amt_s or "").strip()).lower()
4571
+ a = a.replace("−", "-").replace("\u2013", "-")
4572
+ return (d, a)
4573
+
4574
+ def _frag_counter(g) -> Optional[Counter]:
4575
+ if not g or not _is_da3_header(g):
4576
+ return None
4577
+ c = Counter()
4578
+ for row in g[1:]:
4579
+ cells = [str(x or "").strip() for x in row]
4580
+ while len(cells) < 3:
4581
+ cells.append("")
4582
+ if not _DA3_LEADING_DATE.match(cells[0]):
4583
+ continue
4584
+ if not _DA3_STRICT_AMOUNT.match(cells[2]):
4585
+ continue
4586
+ c[_norm_da_key_date_amt(cells[0], cells[2])] += 1
4587
+ if sum(c.values()) < 2:
4588
+ return None
4589
+ return c
4590
+
4591
+ def _register_wd_counter(g) -> Optional[Counter]:
4592
+ if not _fh_is_deposit_withdrawal_register(g):
4593
+ return None
4594
+ wci = _fh_withdrawal_column_index(g)
4595
+ if wci is None:
4596
+ return None
4597
+ c = Counter()
4598
+ for row in g[1:]:
4599
+ cells = [str(x or "").strip() for x in row]
4600
+ while len(cells) <= wci:
4601
+ cells.append("")
4602
+ if not _DA3_LEADING_DATE.match(cells[0]):
4603
+ continue
4604
+ amt = cells[wci]
4605
+ if not amt.strip():
4606
+ continue
4607
+ if not _DA3_STRICT_AMOUNT.match(amt):
4608
+ continue
4609
+ c[_norm_da_key_date_amt(cells[0], amt)] += 1
4610
+ if sum(c.values()) < 3:
4611
+ return None
4612
+ return c
4613
+
4614
+ grids = [(m, _parse_grid(m.group(0))) for m in matches]
4615
+ drop_starts = set()
4616
+
4617
+ for i in range(len(grids) - 1):
4618
+ mi, gi = grids[i]
4619
+ if mi.start() in drop_starts:
4620
+ continue
4621
+ fk = _frag_counter(gi)
4622
+ if fk is None:
4623
+ continue
4624
+ n_frag = sum(fk.values())
4625
+ if n_frag > 48:
4626
+ continue
4627
+ for j in range(i + 1, len(grids)):
4628
+ mj, gj = grids[j]
4629
+ rk = _register_wd_counter(gj)
4630
+ if rk is None:
4631
+ continue
4632
+ n_reg = sum(rk.values())
4633
+ if n_reg <= n_frag:
4634
+ continue
4635
+ if not all(fk[k] <= rk.get(k, 0) for k in fk):
4636
+ continue
4637
+ drop_starts.add(mi.start())
4638
+ log.info(
4639
+ "Fix FH-1: dropped DA3 (%d rows) duplicate of later "
4640
+ "DEPOSIT/WITHDRAWAL register (%d wd rows)",
4641
+ n_frag,
4642
+ n_reg,
4643
+ )
4644
+ break
4645
+
4646
+ if not drop_starts:
4647
+ return text
4648
+
4649
+ out: List[str] = []
4650
+ last = 0
4651
+ for m in matches:
4652
+ out.append(text[last : m.start()])
4653
+ if m.start() not in drop_starts:
4654
+ out.append(m.group(0))
4655
+ last = m.end()
4656
+ out.append(text[last:])
4657
+ return "".join(out)
4658
+ except Exception as e:
4659
+ log.warning("_drop_da3_duplicate_of_later_deposit_withdrawal_register failed: %s", e)
4660
+ return text
4661
+
4662
+
4663
  def _dedupe_subset_transaction_tables_html(text: str) -> str:
4664
  if not text or "<table" not in text.lower():
4665
  return text
 
5514
  stabilized = _split_ucb_deposits_electronic_sections_html(stabilized)
5515
  stabilized = _patch_da3_msft_g_ref_amounts_across_tables(stabilized)
5516
  stabilized = _dedupe_subset_transaction_tables_html(stabilized)
5517
+ stabilized = _drop_da3_duplicate_of_later_deposit_withdrawal_register(stabilized) # Fix FH-1
5518
  stabilized = _dedupe_da3_by_date_amount_loose(stabilized) # Fix 30
5519
  stabilized = _drop_da3_misplaced_debit_after_deposit_heading_tables(stabilized)
5520
  stabilized = _strip_credits_prefix_from_debit_da3_table(stabilized) # Fix 28
 
5652
  stabilized = _split_ucb_deposits_electronic_sections_html(stabilized)
5653
  stabilized = _patch_da3_msft_g_ref_amounts_across_tables(stabilized)
5654
  stabilized = _dedupe_subset_transaction_tables_html(stabilized)
5655
+ stabilized = _drop_da3_duplicate_of_later_deposit_withdrawal_register(stabilized) # Fix FH-1
5656
  stabilized = _dedupe_da3_by_date_amount_loose(stabilized) # Fix 30
5657
  stabilized = _drop_da3_misplaced_debit_after_deposit_heading_tables(stabilized)
5658
  stabilized = _strip_credits_prefix_from_debit_da3_table(stabilized) # Fix 28
 
5735
  merged = _split_ucb_deposits_electronic_sections_html(merged)
5736
  merged = _patch_da3_msft_g_ref_amounts_across_tables(merged)
5737
  merged = _dedupe_subset_transaction_tables_html(merged)
5738
+ merged = _drop_da3_duplicate_of_later_deposit_withdrawal_register(merged) # Fix FH-1
5739
  merged = _dedupe_da3_by_date_amount_loose(merged) # Fix 30
5740
  merged = _drop_da3_misplaced_debit_after_deposit_heading_tables(merged)
5741
  merged = _strip_credits_prefix_from_debit_da3_table(merged) # Fix 28