rehan953 commited on
Commit
9b21413
·
verified ·
1 Parent(s): 9f728f5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +53 -33
app.py CHANGED
@@ -21,9 +21,9 @@ Explicitly omitted vs the heavy Space build:
21
  Included (data-driven, no institution names):
22
  - HTML tables: modal logical width from rowspan-free rows (colspan-aware);
23
  pad short rows; trim trailing empty cells; expand each row to a logical
24
- grid, then if the rightmost non-empty slot is a solitary amount and only
25
- blanks follow to the edge, slide that amount into the rightmost slot (works
26
- with merged cells, not only uniform single-span rows).
27
 
28
  Configure GLMOCR_API_KEY (environment variable). Optional: glmocr + gradio +
29
  pymupdf + pillow installed.
@@ -344,14 +344,15 @@ def _is_whole_cell_currency(text: str) -> bool:
344
  True iff the cell is nothing but a currency-looking amount (optional $, commas, 2 decimals).
345
  Excludes dates (slashes) and arbitrary prose — not keyed to column headers.
346
  """
347
- if not text or "/" in text:
 
348
  return False
349
  return bool(
350
  re.fullmatch(
351
  r"-?(?:\$|€|£)?\s*\d{1,3}(?:,\d{3})*\.\d{2}\s*",
352
- text,
353
  )
354
- or re.fullmatch(r"-?(?:\$|€|£)?\s*\d+\.\d{2}\s*", text)
355
  )
356
 
357
 
@@ -385,28 +386,31 @@ def _logical_plain_texts_from_entries(entries: List[Tuple[str, int]]) -> List[st
385
  return out
386
 
387
 
388
- def _shift_lone_amount_past_trailing_blanks(log: List[str]) -> List[str]:
389
  """
390
- If the rightmost non-blank slot is only a currency token and every slot to
391
- its right is blank, move that token into the rightmost slot (any column count).
 
 
392
  """
393
  w = len(log)
394
  if w < 2:
395
  return log
396
- L = w - 1
397
- while L >= 0 and not (log[L] or "").strip():
398
- L -= 1
399
- if L < 0:
400
- return log
401
- token = (log[L] or "").strip()
402
- if not _is_whole_cell_currency(token):
403
- return log
404
- if L == w - 1:
405
- return log
406
- if any((log[i] or "").strip() for i in range(L + 1, w)):
407
  return log
408
  new_log = list(log)
409
- new_log[L] = ""
 
410
  new_log[w - 1] = token
411
  return new_log
412
 
@@ -430,29 +434,32 @@ def _materialize_cells_from_logical(
430
 
431
 
432
  def _apply_row_amount_tail_shift(cells: List[str], spans: List[int]) -> List[str]:
433
- """Colspan-aware tail shift; returns possibly unchanged cell HTML list."""
434
  if not cells or len(cells) != len(spans):
435
  return cells
436
- entries = list(zip(cells, spans))
437
- old_log = _logical_plain_texts_from_entries(entries)
438
- if len(old_log) < 2:
439
- return cells
440
- new_log = _shift_lone_amount_past_trailing_blanks(old_log)
441
- if new_log == old_log:
442
- return cells
443
- return _materialize_cells_from_logical(entries, new_log)
 
 
444
 
445
 
446
  def _infer_modal_logical_width(tr_inners: List[str]) -> int:
447
  """
448
  Modal logical column count across rows (colspan sums). On frequency ties,
449
  prefer the larger width so a rare short row is padded to the majority grid.
450
- Returns -1 if the table uses rowspan (skip) or has no measurable rows.
 
451
  """
452
  widths: List[int] = []
453
  for inner in tr_inners:
454
  if re.search(r"rowspan\s*=", inner, flags=re.IGNORECASE):
455
- return -1
456
  w = _logical_row_width(_cell_entries(inner))
457
  if w > 0:
458
  widths.append(w)
@@ -557,6 +564,17 @@ def normalize_html_table_row_widths(md: str) -> str:
557
  )
558
 
559
 
 
 
 
 
 
 
 
 
 
 
 
560
  def looks_like_markdown_table(block: str) -> bool:
561
  lines = [ln.rstrip() for ln in block.strip().splitlines() if ln.strip()]
562
  if len(lines) < 2:
@@ -626,7 +644,7 @@ def light_stabilize_markdown(page_md: str) -> str:
626
  else:
627
  out_blocks.append(b)
628
  merged = close_unclosed_html("\n\n".join(out_blocks))
629
- return normalize_html_table_row_widths(merged)
630
 
631
 
632
  def render_pdf_pages_to_images(pdf_path: str) -> Tuple[List[str], List[int]]:
@@ -764,6 +782,8 @@ def run_ocr(uploaded_file):
764
  all_pages.append("\n\n".join(parts))
765
 
766
  merged = "\n\n---page-separator---\n\n".join(all_pages) if all_pages else "(No content)"
 
 
767
  return merged
768
 
769
  except Exception as e:
 
21
  Included (data-driven, no institution names):
22
  - HTML tables: modal logical width from rowspan-free rows (colspan-aware);
23
  pad short rows; trim trailing empty cells; expand each row to a logical
24
+ grid, then slide a solitary amount token past trailing blank logical slots
25
+ into the rightmost slot (colspan-aware). Rowspan rows are skipped for edits
26
+ but do not disable an entire table. Stabilization runs in multiple passes.
27
 
28
  Configure GLMOCR_API_KEY (environment variable). Optional: glmocr + gradio +
29
  pymupdf + pillow installed.
 
344
  True iff the cell is nothing but a currency-looking amount (optional $, commas, 2 decimals).
345
  Excludes dates (slashes) and arbitrary prose — not keyed to column headers.
346
  """
347
+ t = (text or "").strip().strip("* \t\u00a0")
348
+ if not t or "/" in t:
349
  return False
350
  return bool(
351
  re.fullmatch(
352
  r"-?(?:\$|€|£)?\s*\d{1,3}(?:,\d{3})*\.\d{2}\s*",
353
+ t,
354
  )
355
+ or re.fullmatch(r"-?(?:\$|€|£)?\s*\d+\.\d{2}\s*", t)
356
  )
357
 
358
 
 
386
  return out
387
 
388
 
389
+ def _shift_rightmost_currency_with_blank_suffix(log: List[str]) -> List[str]:
390
  """
391
+ Find the rightmost logical slot that is currency-only and has only blank
392
+ slots to the end; move that amount into the rightmost slot. Handles cases
393
+ where non-currency text sits further right than the amount (no move), and
394
+ cases where the amount is left of one or more trailing blanks (move once).
395
  """
396
  w = len(log)
397
  if w < 2:
398
  return log
399
+ j = -1
400
+ for i in range(w - 1, -1, -1):
401
+ t = (log[i] or "").strip()
402
+ if not t:
403
+ continue
404
+ if not _is_whole_cell_currency(t):
405
+ continue
406
+ if all(not (log[k] or "").strip() for k in range(i + 1, w)):
407
+ j = i
408
+ break
409
+ if j < 0 or j == w - 1:
410
  return log
411
  new_log = list(log)
412
+ token = (new_log[j] or "").strip()
413
+ new_log[j] = ""
414
  new_log[w - 1] = token
415
  return new_log
416
 
 
434
 
435
 
436
  def _apply_row_amount_tail_shift(cells: List[str], spans: List[int]) -> List[str]:
437
+ """Colspan-aware tail shift; repeat until stable (handles chained blanks)."""
438
  if not cells or len(cells) != len(spans):
439
  return cells
440
+ for _ in range(24):
441
+ entries = list(zip(cells, spans))
442
+ old_log = _logical_plain_texts_from_entries(entries)
443
+ if len(old_log) < 2:
444
+ break
445
+ new_log = _shift_rightmost_currency_with_blank_suffix(old_log)
446
+ if new_log == old_log:
447
+ break
448
+ cells = _materialize_cells_from_logical(entries, new_log)
449
+ return cells
450
 
451
 
452
  def _infer_modal_logical_width(tr_inners: List[str]) -> int:
453
  """
454
  Modal logical column count across rows (colspan sums). On frequency ties,
455
  prefer the larger width so a rare short row is padded to the majority grid.
456
+ Rows that use rowspan are ignored for width statistics only (they do not
457
+ disable the whole table).
458
  """
459
  widths: List[int] = []
460
  for inner in tr_inners:
461
  if re.search(r"rowspan\s*=", inner, flags=re.IGNORECASE):
462
+ continue
463
  w = _logical_row_width(_cell_entries(inner))
464
  if w > 0:
465
  widths.append(w)
 
564
  )
565
 
566
 
567
+ def stabilize_table_markup(md: str, rounds: int = 4) -> str:
568
+ """Apply table row normalization repeatedly until stable or rounds exhausted."""
569
+ cur = md
570
+ for _ in range(max(1, rounds)):
571
+ nxt = normalize_html_table_row_widths(cur)
572
+ if nxt == cur:
573
+ break
574
+ cur = nxt
575
+ return cur
576
+
577
+
578
  def looks_like_markdown_table(block: str) -> bool:
579
  lines = [ln.rstrip() for ln in block.strip().splitlines() if ln.strip()]
580
  if len(lines) < 2:
 
644
  else:
645
  out_blocks.append(b)
646
  merged = close_unclosed_html("\n\n".join(out_blocks))
647
+ return stabilize_table_markup(merged, rounds=4)
648
 
649
 
650
  def render_pdf_pages_to_images(pdf_path: str) -> Tuple[List[str], List[int]]:
 
782
  all_pages.append("\n\n".join(parts))
783
 
784
  merged = "\n\n---page-separator---\n\n".join(all_pages) if all_pages else "(No content)"
785
+ if merged and merged != "(No content)" and not merged.lstrip().startswith("Error:"):
786
+ merged = stabilize_table_markup(merged, rounds=4)
787
  return merged
788
 
789
  except Exception as e: