rehan953 commited on
Commit
5a7f070
·
verified ·
1 Parent(s): 3a9cbb4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +86 -6
app.py CHANGED
@@ -733,6 +733,88 @@ def strip_degenerate_html_tables(md: str) -> str:
733
  return re.sub(r"\n{3,}", "\n\n", out)
734
 
735
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
736
  # Match long digit runs even when OCR glues trailing letters (e.g. 9257403599CCD).
737
  _LONG_NUM_TOKEN = re.compile(r"(?<!\d)\d{7,}(?!\d)")
738
 
@@ -1042,9 +1124,9 @@ def light_stabilize_markdown(page_md: str) -> str:
1042
  else:
1043
  out_blocks.append(coalesce_loose_ledger_lines(b))
1044
  merged = close_unclosed_html("\n\n".join(out_blocks))
 
 
1045
  merged = mask_non_monetary_long_numbers(merged)
1046
- merged = directionalize_amount_headers(merged)
1047
- merged = directionalize_amount_headers_by_nearby_context(merged)
1048
  merged = repair_thead_cell_semantics(merged)
1049
  merged = stabilize_table_markup(merged, rounds=4)
1050
  merged = strip_degenerate_html_tables(merged)
@@ -1789,11 +1871,9 @@ def run_ocr(uploaded_file):
1789
 
1790
  merged = "\n\n---page-separator---\n\n".join(all_pages) if all_pages else "(No content)"
1791
  if merged and merged != "(No content)" and not merged.lstrip().startswith("Error:"):
 
 
1792
  merged = mask_non_monetary_long_numbers(merged)
1793
- merged = directionalize_amount_headers(merged)
1794
- merged = directionalize_amount_headers_by_nearby_context(merged)
1795
- merged = normalize_ambiguous_amount_balance_tables(merged)
1796
- merged = normalize_amount_only_transaction_tables(merged)
1797
  merged = stabilize_table_markup(merged, rounds=4)
1798
  merged = enrich_transaction_tables_with_daily_balances(merged)
1799
  # Keep transaction amounts untouched; add explicit snapshot for metadata extraction.
 
733
  return re.sub(r"\n{3,}", "\n\n", out)
734
 
735
 
736
+ # OCR sometimes emits a malformed leading pseudo-header row like:
737
+ # Date05/09/25 | TypeDeposit | Amount2,270.00 | ...
738
+ _GLUED_HEADER_ROW_RE = re.compile(r"date\d{1,2}/\d{1,2}|typedeposit|amount\d", re.IGNORECASE)
739
+
740
+
741
+ def strip_malformed_leading_table_rows(md: str) -> str:
742
+ """
743
+ Remove malformed leading rows in HTML tables when they are clearly bogus
744
+ header artifacts and the table already contains a proper header row below.
745
+ """
746
+ if not md or "<table" not in md.lower():
747
+ return md
748
+
749
+ def repl_table(m: re.Match) -> str:
750
+ full = m.group(0)
751
+ trs = list(re.finditer(r"<tr\b[^>]*>.*?</tr>", full, flags=re.IGNORECASE | re.DOTALL))
752
+ if len(trs) < 2:
753
+ return full
754
+
755
+ first = trs[0].group(0)
756
+ second = trs[1].group(0)
757
+ first_plain = _cell_plain_text(first).lower()
758
+ second_plain = _cell_plain_text(second).lower()
759
+
760
+ first_is_malformed = bool(_GLUED_HEADER_ROW_RE.search(first_plain))
761
+ second_looks_header = (
762
+ ("date" in second_plain and "description" in second_plain)
763
+ or ("posting date" in second_plain and "description" in second_plain)
764
+ or ("date" in second_plain and "balance" in second_plain)
765
+ )
766
+ if not (first_is_malformed and second_looks_header):
767
+ return full
768
+
769
+ return full.replace(first, "", 1)
770
+
771
+ return re.sub(
772
+ r"<table\b[^>]*>.*?</table>",
773
+ repl_table,
774
+ md,
775
+ flags=re.IGNORECASE | re.DOTALL,
776
+ )
777
+
778
+
779
+ def strip_subtotal_rows_from_transaction_tables(md: str) -> str:
780
+ """
781
+ Remove subtotal/total line-item rows from transaction tables so aggregate
782
+ amounts are not mistaken as individual transactions.
783
+ """
784
+ if not md or "<table" not in md.lower():
785
+ return md
786
+
787
+ def repl_table(m: re.Match) -> str:
788
+ full = m.group(0)
789
+ rows = list(re.finditer(r"<tr\b[^>]*>.*?</tr>", full, flags=re.IGNORECASE | re.DOTALL))
790
+ if len(rows) < 2:
791
+ return full
792
+ # Some tables start with a title row, then the actual header appears in row 2/3.
793
+ sample_headers = [_cell_plain_text(r.group(0)).lower() for r in rows[:4]]
794
+ is_txn_like = any(
795
+ ("date" in hp or "posting date" in hp)
796
+ and ("description" in hp)
797
+ and ("amount" in hp or "debit" in hp or "credit" in hp)
798
+ for hp in sample_headers
799
+ )
800
+ if not is_txn_like:
801
+ return full
802
+
803
+ out = full
804
+ for r in rows[1:]:
805
+ rp = _cell_plain_text(r.group(0)).lower()
806
+ if "subtotal" in rp or rp.startswith("total "):
807
+ out = out.replace(r.group(0), "", 1)
808
+ return out
809
+
810
+ return re.sub(
811
+ r"<table\b[^>]*>.*?</table>",
812
+ repl_table,
813
+ md,
814
+ flags=re.IGNORECASE | re.DOTALL,
815
+ )
816
+
817
+
818
  # Match long digit runs even when OCR glues trailing letters (e.g. 9257403599CCD).
819
  _LONG_NUM_TOKEN = re.compile(r"(?<!\d)\d{7,}(?!\d)")
820
 
 
1124
  else:
1125
  out_blocks.append(coalesce_loose_ledger_lines(b))
1126
  merged = close_unclosed_html("\n\n".join(out_blocks))
1127
+ merged = strip_malformed_leading_table_rows(merged)
1128
+ merged = strip_subtotal_rows_from_transaction_tables(merged)
1129
  merged = mask_non_monetary_long_numbers(merged)
 
 
1130
  merged = repair_thead_cell_semantics(merged)
1131
  merged = stabilize_table_markup(merged, rounds=4)
1132
  merged = strip_degenerate_html_tables(merged)
 
1871
 
1872
  merged = "\n\n---page-separator---\n\n".join(all_pages) if all_pages else "(No content)"
1873
  if merged and merged != "(No content)" and not merged.lstrip().startswith("Error:"):
1874
+ merged = strip_malformed_leading_table_rows(merged)
1875
+ merged = strip_subtotal_rows_from_transaction_tables(merged)
1876
  merged = mask_non_monetary_long_numbers(merged)
 
 
 
 
1877
  merged = stabilize_table_markup(merged, rounds=4)
1878
  merged = enrich_transaction_tables_with_daily_balances(merged)
1879
  # Keep transaction amounts untouched; add explicit snapshot for metadata extraction.