rehan953 commited on
Commit
2ef9e77
·
verified ·
1 Parent(s): 6ef815a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -0
app.py CHANGED
@@ -2422,6 +2422,148 @@ def _parse_nfcu_page(pdf_path: str, page_num: int) -> str:
2422
  log.warning("_parse_nfcu_page failed (page %d): %s", page_num, e)
2423
  return ""
2424
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2425
  def normalize_html_tables(text: str) -> str:
2426
  """
2427
  For every <table>...</table>:
@@ -2638,6 +2780,12 @@ def run_ocr(uploaded_file):
2638
  # Safe no-op for scanned PDFs (no text layer) and non-transaction pages.
2639
  if is_pdf and not is_nfcu_doc:
2640
  stabilized = _patch_ocr_with_textlayer(stabilized, path, page_num)
 
 
 
 
 
 
2641
 
2642
  # Fix 4D: append Johnson Bank Checks + Daily Account Balance
2643
  # Run OUTSIDE _patch_ocr_with_textlayer so exceptions there don't block it.
 
2422
  log.warning("_parse_nfcu_page failed (page %d): %s", page_num, e)
2423
  return ""
2424
 
2425
+ def _extract_nfcu_summary_table(pdf_path: str, page_num: int) -> str:
2426
+ """
2427
+ Navy-only additive helper:
2428
+ Rebuild "Summary of your deposit accounts" from PDF text-layer words.
2429
+ Returns a clean HTML table or "" (safe no-op on any miss/error).
2430
+ """
2431
+ try:
2432
+ import pymupdf as fitz
2433
+
2434
+ doc = fitz.open(pdf_path)
2435
+ if page_num >= len(doc):
2436
+ doc.close()
2437
+ return ""
2438
+ page = doc[page_num]
2439
+ words = page.get_text("words") or []
2440
+ doc.close()
2441
+ if not words:
2442
+ return ""
2443
+
2444
+ # Group tokens into reading lines by y-position.
2445
+ buckets = []
2446
+ for w in words:
2447
+ if len(w) < 5:
2448
+ continue
2449
+ x0 = float(w[0])
2450
+ y0 = float(w[1])
2451
+ tok = str(w[4] or "").strip()
2452
+ if not tok:
2453
+ continue
2454
+ bi = None
2455
+ for i, (yb, _) in enumerate(buckets):
2456
+ if abs(yb - y0) <= 2.0:
2457
+ bi = i
2458
+ break
2459
+ if bi is None:
2460
+ buckets.append((y0, [(x0, tok)]))
2461
+ else:
2462
+ buckets[bi][1].append((x0, tok))
2463
+
2464
+ lines = []
2465
+ for y, arr in sorted(buckets, key=lambda x: x[0]):
2466
+ arr2 = sorted(arr, key=lambda x: x[0])
2467
+ toks = [t for _, t in arr2]
2468
+ text = " ".join(toks).strip()
2469
+ lines.append((y, toks, text))
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
2490
+ if stop_re.search(text):
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 ""
2526
+
2527
+ def _replace_nfcu_summary_table(page_md: str, summary_table_html: str) -> str:
2528
+ """
2529
+ Replace the first table after 'Summary of your deposit accounts' with
2530
+ a rebuilt NFCU summary table. Safe no-op if anchor/table is missing.
2531
+ """
2532
+ if not page_md or not summary_table_html:
2533
+ return page_md
2534
+ table_pattern = re.compile(r"<table[^>]*>.*?</table>", re.DOTALL | re.IGNORECASE)
2535
+ lower = page_md.lower()
2536
+ anchor = lower.find("summary of your deposit accounts")
2537
+
2538
+ # Primary path: replace first table after the summary heading.
2539
+ if anchor >= 0:
2540
+ for m in table_pattern.finditer(page_md):
2541
+ if m.start() > anchor:
2542
+ old_table = m.group(0)
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()
2551
+ p.feed(tbl)
2552
+ g = _build_grid(p.rows)
2553
+ if not g:
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>:
 
2780
  # Safe no-op for scanned PDFs (no text layer) and non-transaction pages.
2781
  if is_pdf and not is_nfcu_doc:
2782
  stabilized = _patch_ocr_with_textlayer(stabilized, path, page_num)
2783
+ elif is_pdf and is_nfcu_doc:
2784
+ # Navy-only additive fix: rebuild malformed summary table from
2785
+ # text layer, but keep GLM body/header/footer unchanged.
2786
+ nfcu_summary = _extract_nfcu_summary_table(path, page_num)
2787
+ if nfcu_summary:
2788
+ stabilized = _replace_nfcu_summary_table(stabilized, nfcu_summary)
2789
 
2790
  # Fix 4D: append Johnson Bank Checks + Daily Account Balance
2791
  # Run OUTSIDE _patch_ocr_with_textlayer so exceptions there don't block it.