rehan953 commited on
Commit
67f4b6d
·
verified ·
1 Parent(s): 2ef9e77

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +99 -30
app.py CHANGED
@@ -2470,20 +2470,29 @@ def _extract_nfcu_summary_table(pdf_path: str, page_num: int) -> str:
2470
 
2471
  anchor_idx = None
2472
  for i, (_, _, text) in enumerate(lines):
2473
- if "summary of your deposit accounts" in text.lower():
 
2474
  anchor_idx = i
2475
  break
2476
  if anchor_idx is None:
2477
  return ""
2478
 
2479
- money_re = re.compile(r"^[\d,]+\.\d{2}-?$")
2480
- date_re = re.compile(r"^\d{2}-\d{2}$")
2481
  stop_re = re.compile(
2482
  r"(business checking\s*-|mbr business savings\s*-|date\s+transaction\s+detail|items paid|checking|savings)",
2483
  re.IGNORECASE,
2484
  )
2485
 
 
 
2486
  rows = []
 
 
 
 
 
 
2487
  for _, toks, text in lines[anchor_idx + 1:]:
2488
  if not text:
2489
  continue
@@ -2491,35 +2500,51 @@ def _extract_nfcu_summary_table(pdf_path: str, page_num: int) -> str:
2491
  break
2492
  if not toks:
2493
  continue
2494
- if not date_re.match(toks[0]):
 
 
 
 
 
 
 
 
 
 
2495
  continue
2496
 
2497
- date = toks[0]
2498
- rest = toks[1:]
2499
- mvals = [t for t in rest if money_re.match(t)]
2500
- desc_tokens = [t for t in rest if not money_re.match(t)]
2501
- desc = " ".join(desc_tokens).strip()
2502
- if not desc:
2503
  continue
2504
 
2505
- amount = ""
2506
- balance = ""
2507
- if len(mvals) >= 2:
2508
- amount = mvals[0]
2509
- balance = mvals[-1]
2510
- elif len(mvals) == 1:
2511
- # Beginning/ending balance rows usually carry the number in Balance.
2512
- if re.search(r"\b(beginning|ending)\s+balance\b", desc, re.IGNORECASE):
2513
- balance = mvals[0]
2514
- else:
2515
- amount = mvals[0]
2516
 
2517
- rows.append([date, desc, "", amount, balance])
 
 
 
 
 
 
 
 
 
 
 
2518
 
2519
  if not rows:
2520
  return ""
2521
 
2522
- hdr = ["Date", "Transaction", "Detail", "Amount($)", "Balance($)"]
 
 
 
 
 
 
 
2523
  return _grid_to_html([hdr] + rows)
2524
  except Exception:
2525
  return ""
@@ -2543,8 +2568,7 @@ def _replace_nfcu_summary_table(page_md: str, summary_table_html: str) -> str:
2543
  return page_md.replace(old_table, summary_table_html, 1)
2544
 
2545
  # Fallback path: heading may be malformed/absent in OCR text.
2546
- # For NFCU, summary table is the 5-column table with
2547
- # Date + Transaction + Detail + Amount + Balance headers.
2548
  for m in table_pattern.finditer(page_md):
2549
  tbl = m.group(0)
2550
  p = TableGridParser()
@@ -2554,16 +2578,58 @@ def _replace_nfcu_summary_table(page_md: str, summary_table_html: str) -> str:
2554
  continue
2555
  h = " ".join(str(c or "").strip().lower() for c in g[0])
2556
  if (
2557
- "date" in h
2558
- and "transaction" in h
2559
- and "detail" in h
2560
- and "amount" in h
2561
- and "balance" in h
2562
  ):
2563
  return page_md.replace(tbl, summary_table_html, 1)
2564
 
2565
  return page_md
2566
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2567
  def normalize_html_tables(text: str) -> str:
2568
  """
2569
  For every <table>...</table>:
@@ -2612,6 +2678,9 @@ def normalize_html_tables(text: str) -> str:
2612
  # Fix 2: misplaced header row promotion (keyword-score guard — safe on clean PDFs)
2613
  grid = _promote_misplaced_header_row(grid)
2614
 
 
 
 
2615
  # Fix 8: split fused multi-keyword header cell (First Horizon Bank)
2616
  grid = _split_fused_multicolumn_header(grid)
2617
 
 
2470
 
2471
  anchor_idx = None
2472
  for i, (_, _, text) in enumerate(lines):
2473
+ tl = text.lower()
2474
+ if "summary of your deposit accounts" in tl or ("summary" in tl and "deposit accounts" in tl):
2475
  anchor_idx = i
2476
  break
2477
  if anchor_idx is None:
2478
  return ""
2479
 
2480
+ money_re = re.compile(r"^\$?[\d,]+\.\d{2}-?$")
2481
+ acct_no_re = re.compile(r"^\d{8,12}$")
2482
  stop_re = re.compile(
2483
  r"(business checking\s*-|mbr business savings\s*-|date\s+transaction\s+detail|items paid|checking|savings)",
2484
  re.IGNORECASE,
2485
  )
2486
 
2487
+ # NFCU summary table real columns from PDF:
2488
+ # Account | Previous Balance | Deposits/Credits | Withdrawals/Debits | Ending Balance | YTD Dividends
2489
  rows = []
2490
+ pending_account = ""
2491
+ account_name_re = re.compile(
2492
+ r"^(business checking|mbr business savings|checking|savings|money market|certificate).*$",
2493
+ re.IGNORECASE,
2494
+ )
2495
+
2496
  for _, toks, text in lines[anchor_idx + 1:]:
2497
  if not text:
2498
  continue
 
2500
  break
2501
  if not toks:
2502
  continue
2503
+ t0 = toks[0].lower()
2504
+ tl = text.lower()
2505
+
2506
+ # Ignore split header lines ("Previous ...", "Balance Credits ...")
2507
+ if (
2508
+ "previous" in tl
2509
+ or "deposits" in tl
2510
+ or "withdrawals" in tl
2511
+ or "ending" in tl
2512
+ or "dividends" in tl
2513
+ ) and not any(money_re.match(t) for t in toks):
2514
  continue
2515
 
2516
+ # Account name can appear on its own line, followed by acct# + numeric columns.
2517
+ if account_name_re.match(text.strip()) and not any(money_re.match(t) for t in toks):
2518
+ pending_account = text.strip()
 
 
 
2519
  continue
2520
 
2521
+ mvals = [t.replace("$", "") for t in toks if money_re.match(t)]
2522
+ acct_tokens = [t for t in toks if acct_no_re.match(t)]
 
 
 
 
 
 
 
 
 
2523
 
2524
+ # Totals row: "Totals <five amounts>"
2525
+ if t0 == "totals" and len(mvals) >= 5:
2526
+ rows.append(["Totals", mvals[0], mvals[1], mvals[2], mvals[3], mvals[4]])
2527
+ continue
2528
+
2529
+ # Account numeric row: "<acctno> <five amounts>"
2530
+ if acct_tokens and len(mvals) >= 5:
2531
+ acct = acct_tokens[0]
2532
+ acct_label = (pending_account + " - " + acct).strip(" -") if pending_account else acct
2533
+ rows.append([acct_label, mvals[0], mvals[1], mvals[2], mvals[3], mvals[4]])
2534
+ pending_account = ""
2535
+ continue
2536
 
2537
  if not rows:
2538
  return ""
2539
 
2540
+ hdr = [
2541
+ "Account",
2542
+ "Previous Balance",
2543
+ "Deposits/Credits",
2544
+ "Withdrawals/Debits",
2545
+ "Ending Balance",
2546
+ "YTD Dividends",
2547
+ ]
2548
  return _grid_to_html([hdr] + rows)
2549
  except Exception:
2550
  return ""
 
2568
  return page_md.replace(old_table, summary_table_html, 1)
2569
 
2570
  # Fallback path: heading may be malformed/absent in OCR text.
2571
+ # For NFCU summary, header should contain balance/deposit/withdrawal/ytd signals.
 
2572
  for m in table_pattern.finditer(page_md):
2573
  tbl = m.group(0)
2574
  p = TableGridParser()
 
2578
  continue
2579
  h = " ".join(str(c or "").strip().lower() for c in g[0])
2580
  if (
2581
+ "previous" in h
2582
+ and "deposits" in h
2583
+ and "withdrawals" in h
2584
+ and "ending" in h
2585
+ and "dividends" in h
2586
  ):
2587
  return page_md.replace(tbl, summary_table_html, 1)
2588
 
2589
  return page_md
2590
 
2591
+ def _split_fused_date_transaction_header(grid):
2592
+ """
2593
+ Fix fused header "Date Transaction Detail" -> separate Date + Transaction Detail.
2594
+ Strict guard: only when header has that exact fused phrase and also has
2595
+ Amount/Balance columns, so unrelated tables are untouched.
2596
+ """
2597
+ if not grid or len(grid) < 2:
2598
+ return grid
2599
+ ncols = max(len(r) for r in grid if isinstance(r, list))
2600
+ if ncols < 3:
2601
+ return grid
2602
+
2603
+ header = [str(c or "").strip() for c in (grid[0] + [""] * (ncols - len(grid[0])))]
2604
+ first_h = re.sub(r"\s+", " ", header[0]).strip().lower()
2605
+ if first_h != "date transaction detail":
2606
+ return grid
2607
+
2608
+ hnorm = [re.sub(r"\s+", " ", str(c or "").strip().lower()) for c in header]
2609
+ has_amount = any("amount" in h for h in hnorm)
2610
+ has_balance = any("balance" in h for h in hnorm)
2611
+ if not (has_amount and has_balance):
2612
+ return grid
2613
+
2614
+ date_tok = re.compile(r"^\d{1,2}(?:[-/])\d{2}(?:[-/]\d{2,4})?$")
2615
+
2616
+ new_grid = []
2617
+ new_header = ["Date", "Transaction Detail"] + header[1:]
2618
+ new_grid.append(new_header)
2619
+
2620
+ for row in grid[1:]:
2621
+ cells = [str(c or "").strip() for c in (row + [""] * (ncols - len(row)))]
2622
+ first = cells[0]
2623
+ dval = ""
2624
+ tval = first
2625
+ if first:
2626
+ parts = first.split()
2627
+ if parts and date_tok.match(parts[0]):
2628
+ dval = parts[0]
2629
+ tval = " ".join(parts[1:]).strip()
2630
+ new_grid.append([dval, tval] + cells[1:])
2631
+ return new_grid
2632
+
2633
  def normalize_html_tables(text: str) -> str:
2634
  """
2635
  For every <table>...</table>:
 
2678
  # Fix 2: misplaced header row promotion (keyword-score guard — safe on clean PDFs)
2679
  grid = _promote_misplaced_header_row(grid)
2680
 
2681
+ # Fix NF-2: split fused "Date Transaction Detail" header/cell.
2682
+ grid = _split_fused_date_transaction_header(grid)
2683
+
2684
  # Fix 8: split fused multi-keyword header cell (First Horizon Bank)
2685
  grid = _split_fused_multicolumn_header(grid)
2686