Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -32,6 +32,10 @@ Primary goals for reconcile rate:
|
|
| 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
|
|
@@ -2926,6 +2930,28 @@ def _is_strong_ucb_electronic_debit_row(grid, r: int) -> bool:
|
|
| 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
|
|
@@ -2952,7 +2978,9 @@ def _ucb_split_grid_electronic_credits_debits_after_itm_prefix(grid):
|
|
| 2952 |
return g_ec, g_ed
|
| 2953 |
|
| 2954 |
|
| 2955 |
-
def _try_split_ucb_ec_ed_from_fused_table_html(
|
|
|
|
|
|
|
| 2956 |
try:
|
| 2957 |
p = TableGridParser()
|
| 2958 |
p.feed(table_html)
|
|
@@ -2960,22 +2988,42 @@ def _try_split_ucb_ec_ed_from_fused_table_html(lead_before_ec: str, table_html:
|
|
| 2960 |
except Exception:
|
| 2961 |
return None
|
| 2962 |
pair = _ucb_split_grid_electronic_credits_debits_after_itm_prefix(grid)
|
| 2963 |
-
if
|
| 2964 |
-
|
| 2965 |
-
|
| 2966 |
-
|
| 2967 |
-
|
| 2968 |
-
|
| 2969 |
-
parts.
|
| 2970 |
-
|
| 2971 |
-
|
| 2972 |
-
|
| 2973 |
-
|
| 2974 |
-
|
| 2975 |
-
|
| 2976 |
-
|
| 2977 |
-
|
| 2978 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2979 |
|
| 2980 |
|
| 2981 |
# Optional empty centered banner only; EC/ED title divs are re-emitted after a successful split
|
|
@@ -2983,7 +3031,7 @@ def _try_split_ucb_ec_ed_from_fused_table_html(lead_before_ec: str, table_html:
|
|
| 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 |
)
|
|
@@ -2997,8 +3045,10 @@ def _split_ucb_fused_ec_ed_headings_single_table_html(text: str) -> str:
|
|
| 2997 |
|
| 2998 |
def _repl(m):
|
| 2999 |
lead = m.group("lead")
|
|
|
|
| 3000 |
table_html = m.group("table")
|
| 3001 |
-
|
|
|
|
| 3002 |
return new if new is not None else m.group(0)
|
| 3003 |
|
| 3004 |
return _UCB_EC_ED_SINGLE_FUSED_TABLE.sub(_repl, text)
|
|
|
|
| 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 |
+
- Fix 17c: When Electronic Debits appears *before* the table but every data row is credit-side (no strong
|
| 36 |
+
debit pattern) and at least one ITM DEPOSIT row exists, OCR stacked headings wrong — emit
|
| 37 |
+
Electronic Credits, then the table, then Electronic Debits (section continues below). Safe no-op
|
| 38 |
+
if the Debits heading was not before the table or any row looks like a card/ATM debit.
|
| 39 |
- Post-pass: dedupe 3-col Date|Description|Amount tables when one is a duplicate fragment
|
| 40 |
(later subset of earlier, earlier subset of later, or identical row sets)
|
| 41 |
- Fix 18: adjacent DA3 tables — if the last K data rows of table N match the first K rows of table N+1
|
|
|
|
| 2930 |
return False
|
| 2931 |
|
| 2932 |
|
| 2933 |
+
def _ucb_has_itm_deposit_data_row(grid) -> bool:
|
| 2934 |
+
for r in range(1, len(grid)):
|
| 2935 |
+
if _is_itm_deposit_row_ucb(grid, r):
|
| 2936 |
+
return True
|
| 2937 |
+
return False
|
| 2938 |
+
|
| 2939 |
+
|
| 2940 |
+
def _ucb_table_credits_only_no_strong_debits(grid) -> bool:
|
| 2941 |
+
"""True if DA3 table has real data rows but none match strong electronic-debit patterns."""
|
| 2942 |
+
if not grid or len(grid) < 2 or not _is_da3_header(grid):
|
| 2943 |
+
return False
|
| 2944 |
+
has_data = False
|
| 2945 |
+
for r in range(1, len(grid)):
|
| 2946 |
+
row_txt = _row_text_full_ucb(grid, r).strip()
|
| 2947 |
+
if not row_txt:
|
| 2948 |
+
continue
|
| 2949 |
+
has_data = True
|
| 2950 |
+
if _is_strong_ucb_electronic_debit_row(grid, r):
|
| 2951 |
+
return False
|
| 2952 |
+
return bool(has_data and _ucb_has_itm_deposit_data_row(grid))
|
| 2953 |
+
|
| 2954 |
+
|
| 2955 |
def _ucb_split_grid_electronic_credits_debits_after_itm_prefix(grid):
|
| 2956 |
"""
|
| 2957 |
Fix 17b: one fused DA3 table under Electronic Credits (+ optional empty EC/ED headings) where OCR
|
|
|
|
| 2978 |
return g_ec, g_ed
|
| 2979 |
|
| 2980 |
|
| 2981 |
+
def _try_split_ucb_ec_ed_from_fused_table_html(
|
| 2982 |
+
lead_before_ec: str, table_html: str, had_ed_heading_immediately_before_table: bool
|
| 2983 |
+
):
|
| 2984 |
try:
|
| 2985 |
p = TableGridParser()
|
| 2986 |
p.feed(table_html)
|
|
|
|
| 2988 |
except Exception:
|
| 2989 |
return None
|
| 2990 |
pair = _ucb_split_grid_electronic_credits_debits_after_itm_prefix(grid)
|
| 2991 |
+
if pair:
|
| 2992 |
+
g_ec, g_ed = pair
|
| 2993 |
+
sect = '<div align="center">\n\n{title}\n\n</div>\n\n'
|
| 2994 |
+
parts = []
|
| 2995 |
+
if lead_before_ec and lead_before_ec.strip():
|
| 2996 |
+
parts.append(lead_before_ec.rstrip())
|
| 2997 |
+
parts.extend(
|
| 2998 |
+
[
|
| 2999 |
+
sect.format(title="Electronic Credits"),
|
| 3000 |
+
_grid_to_html(g_ec),
|
| 3001 |
+
sect.format(title="Electronic Debits"),
|
| 3002 |
+
_grid_to_html(g_ed),
|
| 3003 |
+
]
|
| 3004 |
+
)
|
| 3005 |
+
return "\n\n".join(parts)
|
| 3006 |
+
|
| 3007 |
+
# Fix 17c: headings stacked as EC / ED / table but rows are all credits (e.g. ITM-only) —
|
| 3008 |
+
# put the table under Electronic Credits, then leave Electronic Debits for continuation.
|
| 3009 |
+
if (
|
| 3010 |
+
had_ed_heading_immediately_before_table
|
| 3011 |
+
and _ucb_table_credits_only_no_strong_debits(grid)
|
| 3012 |
+
):
|
| 3013 |
+
sect = '<div align="center">\n\n{title}\n\n</div>\n\n'
|
| 3014 |
+
parts = []
|
| 3015 |
+
if lead_before_ec and lead_before_ec.strip():
|
| 3016 |
+
parts.append(lead_before_ec.rstrip())
|
| 3017 |
+
parts.extend(
|
| 3018 |
+
[
|
| 3019 |
+
sect.format(title="Electronic Credits"),
|
| 3020 |
+
_grid_to_html(grid),
|
| 3021 |
+
sect.format(title="Electronic Debits"),
|
| 3022 |
+
]
|
| 3023 |
+
)
|
| 3024 |
+
return "\n\n".join(parts)
|
| 3025 |
+
|
| 3026 |
+
return None
|
| 3027 |
|
| 3028 |
|
| 3029 |
# Optional empty centered banner only; EC/ED title divs are re-emitted after a successful split
|
|
|
|
| 3031 |
_UCB_EC_ED_SINGLE_FUSED_TABLE = re.compile(
|
| 3032 |
r'(?P<lead>(?:<div\s+align=["\']center["\']\s*>\s*</div>\s*)?)'
|
| 3033 |
r'<div\s+align=["\']center["\']\s*>\s*Electronic\s+Credits\s*</div>\s*'
|
| 3034 |
+
r'(?P<ed_before>(?:<div\s+align=["\']center["\']\s*>\s*Electronic\s+Debits\s*</div>\s*)?)'
|
| 3035 |
r'(?P<table><table[^>]*>.*?</table>)',
|
| 3036 |
re.DOTALL | re.IGNORECASE,
|
| 3037 |
)
|
|
|
|
| 3045 |
|
| 3046 |
def _repl(m):
|
| 3047 |
lead = m.group("lead")
|
| 3048 |
+
ed_before = m.group("ed_before") or ""
|
| 3049 |
table_html = m.group("table")
|
| 3050 |
+
had_ed = bool(ed_before.strip())
|
| 3051 |
+
new = _try_split_ucb_ec_ed_from_fused_table_html(lead, table_html, had_ed)
|
| 3052 |
return new if new is not None else m.group(0)
|
| 3053 |
|
| 3054 |
return _UCB_EC_ED_SINGLE_FUSED_TABLE.sub(_repl, text)
|