Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -28,6 +28,10 @@ Primary goals for reconcile rate:
|
|
| 28 |
- Fix 16: remove duplicate rows within a single Date|Description|Amount table; multi-pass table dedupe + final doc-wide dedupe
|
| 29 |
- Fix 17: UCB page 3 — split one merged table under Deposits (continued) into Deposits + Electronic Credits +
|
| 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
|
|
@@ -2898,6 +2902,108 @@ def _ucb_split_grid_deposits_ec_ed(grid):
|
|
| 2898 |
return g_dep, g_ec, g_ed
|
| 2899 |
|
| 2900 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2901 |
def _try_split_ucb_deposits_table_to_three(prefix: str, table_html: str):
|
| 2902 |
try:
|
| 2903 |
p = TableGridParser()
|
|
@@ -2926,18 +3032,20 @@ _UCB_DEP_CONT_BLOCK = re.compile(
|
|
| 2926 |
|
| 2927 |
|
| 2928 |
def _split_ucb_deposits_electronic_sections_html(text: str) -> str:
|
| 2929 |
-
if not text
|
| 2930 |
return text
|
| 2931 |
-
|
| 2932 |
-
|
| 2933 |
-
|
| 2934 |
-
|
| 2935 |
-
|
| 2936 |
-
|
| 2937 |
-
|
| 2938 |
-
|
| 2939 |
-
|
| 2940 |
-
|
|
|
|
|
|
|
| 2941 |
|
| 2942 |
|
| 2943 |
def _parse_da3_grid_from_table_html(table_html: str):
|
|
|
|
| 28 |
- Fix 16: remove duplicate rows within a single Date|Description|Amount table; multi-pass table dedupe + final doc-wide dedupe
|
| 29 |
- Fix 17: UCB page 3 — split one merged table under Deposits (continued) into Deposits + Electronic Credits +
|
| 30 |
Electronic Debits (PDF text order); drop orphan empty EC/ED headers that followed the merged table
|
| 31 |
+
- Fix 17b: When markdown has Electronic Credits (+ optional Electronic Debits headings) but a single fused
|
| 32 |
+
Date|Description|Amount table (common when Deposits (continued) is absent from OCR), split after a
|
| 33 |
+
leading ITM DEPOSIT run when the next row matches generic electronic-debit patterns (POS purchase,
|
| 34 |
+
ATM withdrawal, etc.). Safe no-op when there is no ITM prefix or no such debit row after it.
|
| 35 |
- Post-pass: dedupe 3-col Date|Description|Amount tables when one is a duplicate fragment
|
| 36 |
(later subset of earlier, earlier subset of later, or identical row sets)
|
| 37 |
- Fix 18: adjacent DA3 tables — if the last K data rows of table N match the first K rows of table N+1
|
|
|
|
| 2902 |
return g_dep, g_ec, g_ed
|
| 2903 |
|
| 2904 |
|
| 2905 |
+
def _is_strong_ucb_electronic_debit_row(grid, r: int) -> bool:
|
| 2906 |
+
"""True for typical card/ACH debit lines (generic; avoids splitting on POS return / transfer-in credits)."""
|
| 2907 |
+
t = _row_text_full_ucb(grid, r)
|
| 2908 |
+
if not t or not t.strip():
|
| 2909 |
+
return False
|
| 2910 |
+
if "pos return" in t:
|
| 2911 |
+
return False
|
| 2912 |
+
if "dda transfer" in t and "in" in t:
|
| 2913 |
+
return False
|
| 2914 |
+
if "pos purchase" in t:
|
| 2915 |
+
return True
|
| 2916 |
+
if "atm withdrawal" in t:
|
| 2917 |
+
return True
|
| 2918 |
+
if "atm" in t and "withdrawal" in t:
|
| 2919 |
+
return True
|
| 2920 |
+
if "debit card" in t:
|
| 2921 |
+
return True
|
| 2922 |
+
if "point of sale" in t:
|
| 2923 |
+
return True
|
| 2924 |
+
if "recur payment" in t:
|
| 2925 |
+
return True
|
| 2926 |
+
return False
|
| 2927 |
+
|
| 2928 |
+
|
| 2929 |
+
def _ucb_split_grid_electronic_credits_debits_after_itm_prefix(grid):
|
| 2930 |
+
"""
|
| 2931 |
+
Fix 17b: one fused DA3 table under Electronic Credits (+ optional empty EC/ED headings) where OCR
|
| 2932 |
+
merged ITM deposits with POS/ATM debits. Split after the leading ITM DEPOSIT run when the next
|
| 2933 |
+
data row is a strong electronic-debit pattern.
|
| 2934 |
+
"""
|
| 2935 |
+
if not grid or len(grid) < 3:
|
| 2936 |
+
return None
|
| 2937 |
+
if not _is_da3_header(grid):
|
| 2938 |
+
return None
|
| 2939 |
+
data_start = 1
|
| 2940 |
+
i = data_start
|
| 2941 |
+
while i < len(grid) and _is_itm_deposit_row_ucb(grid, i):
|
| 2942 |
+
i += 1
|
| 2943 |
+
if i == data_start or i >= len(grid):
|
| 2944 |
+
return None
|
| 2945 |
+
if not _is_strong_ucb_electronic_debit_row(grid, i):
|
| 2946 |
+
return None
|
| 2947 |
+
hdr = [list(grid[0])]
|
| 2948 |
+
g_ec = hdr + grid[data_start:i]
|
| 2949 |
+
g_ed = hdr + grid[i:]
|
| 2950 |
+
if len(g_ec) <= 1 or len(g_ed) <= 1:
|
| 2951 |
+
return None
|
| 2952 |
+
return g_ec, g_ed
|
| 2953 |
+
|
| 2954 |
+
|
| 2955 |
+
def _try_split_ucb_ec_ed_from_fused_table_html(lead_before_ec: str, table_html: str):
|
| 2956 |
+
try:
|
| 2957 |
+
p = TableGridParser()
|
| 2958 |
+
p.feed(table_html)
|
| 2959 |
+
grid = _build_grid(p.rows)
|
| 2960 |
+
except Exception:
|
| 2961 |
+
return None
|
| 2962 |
+
pair = _ucb_split_grid_electronic_credits_debits_after_itm_prefix(grid)
|
| 2963 |
+
if not pair:
|
| 2964 |
+
return None
|
| 2965 |
+
g_ec, g_ed = pair
|
| 2966 |
+
sect = '<div align="center">\n\n{title}\n\n</div>\n\n'
|
| 2967 |
+
parts = []
|
| 2968 |
+
if lead_before_ec and lead_before_ec.strip():
|
| 2969 |
+
parts.append(lead_before_ec.rstrip())
|
| 2970 |
+
parts.extend(
|
| 2971 |
+
[
|
| 2972 |
+
sect.format(title="Electronic Credits"),
|
| 2973 |
+
_grid_to_html(g_ec),
|
| 2974 |
+
sect.format(title="Electronic Debits"),
|
| 2975 |
+
_grid_to_html(g_ed),
|
| 2976 |
+
]
|
| 2977 |
+
)
|
| 2978 |
+
return "\n\n".join(parts)
|
| 2979 |
+
|
| 2980 |
+
|
| 2981 |
+
# Optional empty centered banner only; EC/ED title divs are re-emitted after a successful split
|
| 2982 |
+
# so we do not duplicate headings.
|
| 2983 |
+
_UCB_EC_ED_SINGLE_FUSED_TABLE = re.compile(
|
| 2984 |
+
r'(?P<lead>(?:<div\s+align=["\']center["\']\s*>\s*</div>\s*)?)'
|
| 2985 |
+
r'<div\s+align=["\']center["\']\s*>\s*Electronic\s+Credits\s*</div>\s*'
|
| 2986 |
+
r'(?:<div\s+align=["\']center["\']\s*>\s*Electronic\s+Debits\s*</div>\s*)?'
|
| 2987 |
+
r'(?P<table><table[^>]*>.*?</table>)',
|
| 2988 |
+
re.DOTALL | re.IGNORECASE,
|
| 2989 |
+
)
|
| 2990 |
+
|
| 2991 |
+
|
| 2992 |
+
def _split_ucb_fused_ec_ed_headings_single_table_html(text: str) -> str:
|
| 2993 |
+
if not text or "electronic credits" not in text.lower():
|
| 2994 |
+
return text
|
| 2995 |
+
if "<table" not in text.lower():
|
| 2996 |
+
return text
|
| 2997 |
+
|
| 2998 |
+
def _repl(m):
|
| 2999 |
+
lead = m.group("lead")
|
| 3000 |
+
table_html = m.group("table")
|
| 3001 |
+
new = _try_split_ucb_ec_ed_from_fused_table_html(lead, table_html)
|
| 3002 |
+
return new if new is not None else m.group(0)
|
| 3003 |
+
|
| 3004 |
+
return _UCB_EC_ED_SINGLE_FUSED_TABLE.sub(_repl, text)
|
| 3005 |
+
|
| 3006 |
+
|
| 3007 |
def _try_split_ucb_deposits_table_to_three(prefix: str, table_html: str):
|
| 3008 |
try:
|
| 3009 |
p = TableGridParser()
|
|
|
|
| 3032 |
|
| 3033 |
|
| 3034 |
def _split_ucb_deposits_electronic_sections_html(text: str) -> str:
|
| 3035 |
+
if not text:
|
| 3036 |
return text
|
| 3037 |
+
if "deposits (continued)" in text.lower():
|
| 3038 |
+
|
| 3039 |
+
def _repl(m):
|
| 3040 |
+
prefix = m.group(1)
|
| 3041 |
+
table_html = m.group(2)
|
| 3042 |
+
new = _try_split_ucb_deposits_table_to_three(prefix, table_html)
|
| 3043 |
+
if new is None:
|
| 3044 |
+
return m.group(0)
|
| 3045 |
+
return new
|
| 3046 |
+
|
| 3047 |
+
text = _UCB_DEP_CONT_BLOCK.sub(_repl, text)
|
| 3048 |
+
return _split_ucb_fused_ec_ed_headings_single_table_html(text)
|
| 3049 |
|
| 3050 |
|
| 3051 |
def _parse_da3_grid_from_table_html(table_html: str):
|