rehan953 commited on
Commit
224d591
·
verified ·
1 Parent(s): e6912c4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -1
app.py CHANGED
@@ -119,6 +119,11 @@ Primary goals for reconcile rate:
119
  five columns using trailing amount in the description cell and TD-specific debit/credit
120
  routing. Fires on \"continued\" in-table text and/or TD body cues (TD ZELLE, LENDINGCLUB,
121
  etc.). Safe no-op elsewhere.
 
 
 
 
 
122
  """
123
 
124
  # Patch asyncio first (before Gradio imports it) to suppress Python 3.13 cleanup noise
@@ -139,7 +144,7 @@ import os
139
  import re
140
  import html
141
  import tempfile
142
- from typing import List, Tuple
143
  from collections import Counter, defaultdict
144
  from html.parser import HTMLParser
145
 
@@ -4956,6 +4961,139 @@ def _strip_check_image_ocr_junk_html(text: str) -> str:
4956
  return text
4957
 
4958
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4959
  def _dedupe_adjacent_duplicate_html_tables(text: str) -> str:
4960
  """
4961
  Drop consecutive <table> blocks that are identical after grid parse (same header + row multiset),
@@ -5028,6 +5166,7 @@ def stabilize_tables_and_text(page_md: str) -> str:
5028
  _tl_freq_context.clear()
5029
 
5030
  page_md = normalize_money_glyphs(page_md)
 
5031
 
5032
  blocks = re.split(r"\n\s*\n", page_md.strip())
5033
  out_blocks = []
@@ -5043,6 +5182,8 @@ def stabilize_tables_and_text(page_md: str) -> str:
5043
  stabilized = _strip_standalone_continued_banner_line(stabilized)
5044
  stabilized = normalize_html_tables(stabilized)
5045
  stabilized = _dedupe_adjacent_duplicate_html_tables(stabilized)
 
 
5046
  stabilized = _trim_adjacent_da3_suffix_duplicating_next_table_prefix(stabilized)
5047
  stabilized = _split_ucb_deposits_electronic_sections_html(stabilized)
5048
  stabilized = _patch_da3_msft_g_ref_amounts_across_tables(stabilized)
@@ -5054,6 +5195,7 @@ def stabilize_tables_and_text(page_md: str) -> str:
5054
  stabilized = _strip_orphan_continued_da3_flatline(stabilized)
5055
  stabilized = _strip_standalone_continued_banner_line(stabilized)
5056
  stabilized = _strip_check_image_ocr_junk_html(stabilized) # Fix 31
 
5057
  return close_unclosed_html(stabilized)
5058
 
5059
  # --------------------------
@@ -5176,6 +5318,8 @@ def run_ocr(uploaded_file):
5176
  stabilized = _strip_standalone_continued_banner_line(stabilized)
5177
  stabilized = normalize_html_tables(stabilized)
5178
  stabilized = _dedupe_adjacent_duplicate_html_tables(stabilized)
 
 
5179
  stabilized = _trim_adjacent_da3_suffix_duplicating_next_table_prefix(stabilized)
5180
  stabilized = _split_ucb_deposits_electronic_sections_html(stabilized)
5181
  stabilized = _patch_da3_msft_g_ref_amounts_across_tables(stabilized)
@@ -5254,6 +5398,9 @@ def run_ocr(uploaded_file):
5254
  if all_pages and merged and not merged.startswith("Error") and "<table" in merged.lower():
5255
  merged = _strip_orphan_continued_da3_flatline(merged)
5256
  merged = _strip_standalone_continued_banner_line(merged)
 
 
 
5257
  merged = _trim_adjacent_da3_suffix_duplicating_next_table_prefix(merged)
5258
  merged = _split_ucb_deposits_electronic_sections_html(merged)
5259
  merged = _patch_da3_msft_g_ref_amounts_across_tables(merged)
 
119
  five columns using trailing amount in the description cell and TD-specific debit/credit
120
  routing. Fires on \"continued\" in-table text and/or TD body cues (TD ZELLE, LENDINGCLUB,
121
  etc.). Safe no-op elsewhere.
122
+ - Fix MD-1: Markdown fidelity — (a) collapse adjacent HTML tables that are the same register
123
+ (identical multiset of date + debit + credit + balance per row, descriptions may differ);
124
+ keep the table with richer description text so output matches the PDF once. (b) Common OCR
125
+ typos (e.g. CINTHIAL→CINTHIA L) and double-escaped ampersands (&amp;amp;→&amp;).
126
+ Gap between tables must not contain another <table> or page-separator.
127
  """
128
 
129
  # Patch asyncio first (before Gradio imports it) to suppress Python 3.13 cleanup noise
 
144
  import re
145
  import html
146
  import tempfile
147
+ from typing import List, Optional, Tuple
148
  from collections import Counter, defaultdict
149
  from html.parser import HTMLParser
150
 
 
4961
  return text
4962
 
4963
 
4964
+ def _fix_markdown_fidelity_typos_and_escapes(text: str) -> str:
4965
+ """Fix MD-1: common OCR name glitches and double HTML-escaped ampersands in OCR markdown."""
4966
+ if not text:
4967
+ return text
4968
+ text = re.sub(
4969
+ r"\bCINTHIAL\s+BARTON\b",
4970
+ "CINTHIA L BARTON",
4971
+ text,
4972
+ flags=re.IGNORECASE,
4973
+ )
4974
+ text = text.replace("&amp;amp;", "&amp;")
4975
+ return text
4976
+
4977
+
4978
+ def _gap_ok_loose_register_pair(ma, mb, text: str) -> bool:
4979
+ gap = text[ma.end() : mb.start()]
4980
+ if re.search(r"<table", gap, re.IGNORECASE):
4981
+ return False
4982
+ if re.search(r"page-separator", gap, re.IGNORECASE):
4983
+ return False
4984
+ return True
4985
+
4986
+
4987
+ def _norm_register_amount_key(s: str) -> str:
4988
+ t = str(s or "").strip().replace("$", "").replace(",", "").replace("−", "-")
4989
+ if not t or t in ("---", "—"):
4990
+ return ""
4991
+ try:
4992
+ return f"{float(t):.2f}"
4993
+ except ValueError:
4994
+ return t
4995
+
4996
+
4997
+ def _register_row_loose_key_cells(cells: List[str]) -> Optional[Tuple[str, str, str, str]]:
4998
+ if len(cells) < 5:
4999
+ return None
5000
+ d = str(cells[0]).strip()
5001
+ if not re.match(r"^\d{1,2}/\d{1,2}(?:/\d{2,4})?$", d):
5002
+ return None
5003
+ deb = _norm_register_amount_key(cells[2])
5004
+ cre = _norm_register_amount_key(cells[3])
5005
+ bal = _norm_register_amount_key(cells[4])
5006
+ return (d, deb, cre, bal)
5007
+
5008
+
5009
+ def _register_multiset_and_score(grid) -> Tuple[Optional[Counter], int]:
5010
+ if not grid or len(grid) < 2:
5011
+ return None, 0
5012
+ ncols = max(len(r) for r in grid if isinstance(r, list))
5013
+ if ncols < 5:
5014
+ return None, 0
5015
+ c = Counter()
5016
+ desc_score = 0
5017
+ for row in grid[1:]:
5018
+ cells = [str(x or "").strip() for x in row]
5019
+ while len(cells) < ncols:
5020
+ cells.append("")
5021
+ cells = cells[: max(5, ncols)]
5022
+ if len(cells) < 5:
5023
+ continue
5024
+ k = _register_row_loose_key_cells(cells[:5])
5025
+ if not k:
5026
+ continue
5027
+ c[k] += 1
5028
+ desc_score += len(cells[1])
5029
+ if sum(c.values()) < 3:
5030
+ return None, 0
5031
+ return c, desc_score
5032
+
5033
+
5034
+ def _dedupe_adjacent_loose_register_duplicate_tables(text: str) -> str:
5035
+ """
5036
+ Fix MD-1: Drop adjacent duplicate bank register <table> blocks when date+debit+credit+balance
5037
+ multiset matches but description text differs (OCR double-emission). Keeps the richer table.
5038
+ """
5039
+ if not text or "<table" not in text.lower():
5040
+ return text
5041
+ try:
5042
+ tbl_pat = re.compile(r"<table[^>]*>.*?</table>", re.DOTALL | re.IGNORECASE)
5043
+ matches = list(tbl_pat.finditer(text))
5044
+ if len(matches) < 2:
5045
+ return text
5046
+
5047
+ def _grid(m):
5048
+ p = TableGridParser()
5049
+ p.feed(m.group(0))
5050
+ return _build_grid(p.rows)
5051
+
5052
+ keep_indices: List[int] = [0]
5053
+ for j in range(1, len(matches)):
5054
+ prev_i = keep_indices[-1]
5055
+ prev_m, cur_m = matches[prev_i], matches[j]
5056
+ if not _gap_ok_loose_register_pair(prev_m, cur_m, text):
5057
+ keep_indices.append(j)
5058
+ continue
5059
+ ga, gb = _grid(prev_m), _grid(cur_m)
5060
+ ma, sa = _register_multiset_and_score(ga)
5061
+ mb, sb = _register_multiset_and_score(gb)
5062
+ if ma is None or mb is None or ma != mb:
5063
+ keep_indices.append(j)
5064
+ continue
5065
+ if sb >= sa:
5066
+ keep_indices[-1] = j
5067
+ log.info(
5068
+ "Fix MD-1: loose-dup register — kept table at offset %d (desc score %d vs %d)",
5069
+ cur_m.start(),
5070
+ sb,
5071
+ sa,
5072
+ )
5073
+ else:
5074
+ log.info(
5075
+ "Fix MD-1: dropped loose-dup register table at offset %d",
5076
+ cur_m.start(),
5077
+ )
5078
+
5079
+ drop = set(range(len(matches))) - set(keep_indices)
5080
+ if not drop:
5081
+ return text
5082
+
5083
+ out: List[str] = []
5084
+ pos = 0
5085
+ for i, m in enumerate(matches):
5086
+ out.append(text[pos : m.start()])
5087
+ if i not in drop:
5088
+ out.append(m.group(0))
5089
+ pos = m.end()
5090
+ out.append(text[pos:])
5091
+ return "".join(out)
5092
+ except Exception as e:
5093
+ log.warning("_dedupe_adjacent_loose_register_duplicate_tables failed: %s", e)
5094
+ return text
5095
+
5096
+
5097
  def _dedupe_adjacent_duplicate_html_tables(text: str) -> str:
5098
  """
5099
  Drop consecutive <table> blocks that are identical after grid parse (same header + row multiset),
 
5166
  _tl_freq_context.clear()
5167
 
5168
  page_md = normalize_money_glyphs(page_md)
5169
+ page_md = _fix_markdown_fidelity_typos_and_escapes(page_md)
5170
 
5171
  blocks = re.split(r"\n\s*\n", page_md.strip())
5172
  out_blocks = []
 
5182
  stabilized = _strip_standalone_continued_banner_line(stabilized)
5183
  stabilized = normalize_html_tables(stabilized)
5184
  stabilized = _dedupe_adjacent_duplicate_html_tables(stabilized)
5185
+ stabilized = _dedupe_adjacent_loose_register_duplicate_tables(stabilized)
5186
+ stabilized = _fix_markdown_fidelity_typos_and_escapes(stabilized)
5187
  stabilized = _trim_adjacent_da3_suffix_duplicating_next_table_prefix(stabilized)
5188
  stabilized = _split_ucb_deposits_electronic_sections_html(stabilized)
5189
  stabilized = _patch_da3_msft_g_ref_amounts_across_tables(stabilized)
 
5195
  stabilized = _strip_orphan_continued_da3_flatline(stabilized)
5196
  stabilized = _strip_standalone_continued_banner_line(stabilized)
5197
  stabilized = _strip_check_image_ocr_junk_html(stabilized) # Fix 31
5198
+ stabilized = _fix_markdown_fidelity_typos_and_escapes(stabilized)
5199
  return close_unclosed_html(stabilized)
5200
 
5201
  # --------------------------
 
5318
  stabilized = _strip_standalone_continued_banner_line(stabilized)
5319
  stabilized = normalize_html_tables(stabilized)
5320
  stabilized = _dedupe_adjacent_duplicate_html_tables(stabilized)
5321
+ stabilized = _dedupe_adjacent_loose_register_duplicate_tables(stabilized)
5322
+ stabilized = _fix_markdown_fidelity_typos_and_escapes(stabilized)
5323
  stabilized = _trim_adjacent_da3_suffix_duplicating_next_table_prefix(stabilized)
5324
  stabilized = _split_ucb_deposits_electronic_sections_html(stabilized)
5325
  stabilized = _patch_da3_msft_g_ref_amounts_across_tables(stabilized)
 
5398
  if all_pages and merged and not merged.startswith("Error") and "<table" in merged.lower():
5399
  merged = _strip_orphan_continued_da3_flatline(merged)
5400
  merged = _strip_standalone_continued_banner_line(merged)
5401
+ merged = _dedupe_adjacent_duplicate_html_tables(merged)
5402
+ merged = _dedupe_adjacent_loose_register_duplicate_tables(merged)
5403
+ merged = _fix_markdown_fidelity_typos_and_escapes(merged)
5404
  merged = _trim_adjacent_da3_suffix_duplicating_next_table_prefix(merged)
5405
  merged = _split_ucb_deposits_electronic_sections_html(merged)
5406
  merged = _patch_da3_msft_g_ref_amounts_across_tables(merged)