Pointf5ive commited on
Commit
85cfc31
·
1 Parent(s): bb53942

Consolidate profile into ingest and make profile tab optional

Browse files
Files changed (1) hide show
  1. smoke_signal_tab.py +41 -14
smoke_signal_tab.py CHANGED
@@ -10,8 +10,8 @@ Then add to app.py:
10
  # Add smoke_signal_tab() call inside your gr.Blocks() tabs
11
 
12
  Architecture:
13
- Step 1: INGEST — upload PDFs, register + hash
14
- Step 2: PROFILE detect text vs image pages
15
  Step 3: OCR — Surya extraction + confidence scoring
16
  Step 4: REVIEW — human correction workbench (feeds training data)
17
  Step 5: EXPORT — clean JSONL to Codex + downloadable gold set
@@ -498,6 +498,7 @@ def ingest_pdfs(files, rights_class: str, notes: str) -> tuple:
498
  log = []
499
  new_count = 0
500
  dup_count = 0
 
501
 
502
  for file in files:
503
  path = Path(file.name) if hasattr(file, "name") else Path(file)
@@ -509,11 +510,15 @@ def ingest_pdfs(files, rights_class: str, notes: str) -> tuple:
509
 
510
  # Check duplicate — update rights/notes if changed
511
  if not df.empty and file_hash in df["sha256"].values:
 
 
512
  existing_rights = df.loc[df["sha256"] == file_hash, "rights_class"].values[0]
513
  if rights_class != "unknown" and existing_rights != rights_class:
514
  df.loc[df["sha256"] == file_hash, "rights_class"] = rights_class
515
  df.loc[df["sha256"] == file_hash, "notes"] = notes
516
  log.append(log_line(f"↻ Updated rights for duplicate: {path.name} → {rights_class}"))
 
 
517
  else:
518
  log.append(log_line(f"↩ Duplicate: {path.name} (rights={existing_rights})"))
519
  dup_count += 1
@@ -548,12 +553,22 @@ def ingest_pdfs(files, rights_class: str, notes: str) -> tuple:
548
  df = pd.concat([df, new_row], ignore_index=True)
549
  log.append(log_line(f"✓ Registered {book_id} — {path.name} ({page_count or '?'} pages)"))
550
  new_count += 1
 
551
 
552
  save_manifest_df(df)
553
 
554
  summary = f"Registered {new_count} new | {dup_count} duplicates skipped"
555
  log.append(log_line(summary))
556
 
 
 
 
 
 
 
 
 
 
557
  return _ingest_status_html("done", new_count, dup_count), df, "\n".join(log)
558
 
559
 
@@ -582,20 +597,30 @@ def update_rights(book_id: str, new_rights: str) -> tuple:
582
  df = load_manifest_df()
583
  if df.empty or book_id not in df["book_id"].values:
584
  return _ingest_status_html("idle"), df, f"Book ID {book_id} not found."
 
585
  df.loc[df["book_id"] == book_id, "rights_class"] = new_rights
586
  save_manifest_df(df)
587
- return _ingest_status_html("done"), df, f"[{datetime.utcnow().strftime('%H:%M:%S')}] ✓ Updated {book_id} rights → {new_rights}"
 
 
 
 
 
588
 
589
 
590
  # ── Step 2: PROFILE ────────────────────────────────────────────────────────────
591
- def run_profile() -> tuple:
592
- """Profile all pending PDFs."""
593
  df = load_manifest_df()
594
  if df.empty:
595
  return _profile_status_html(), "No sources registered. Complete Step 1 first."
596
 
597
  pending = df[df["status"] == "pending"]
 
 
598
  if pending.empty:
 
 
599
  return _profile_status_html(), "No pending PDFs to profile."
600
 
601
  log = []
@@ -672,7 +697,7 @@ def run_profile() -> tuple:
672
  log.append(
673
  log_line(
674
  f"⚠ {skipped_rights} source(s) skipped due rights_class=unknown/excluded. "
675
- "Set rights in Step 1 and rerun Profile."
676
  )
677
  )
678
 
@@ -1257,7 +1282,9 @@ def run_ocr(progress=gr.Progress(track_tqdm=False)) -> tuple:
1257
  conf = 0.0
1258
  method = "skipped-no-surya"
1259
 
1260
- if conf >= default_cal["auto_accept"]:
 
 
1261
  conf_class = "auto-accept"
1262
  elif conf >= default_cal["review"]:
1263
  conf_class = "review-required"
@@ -1589,9 +1616,9 @@ def smoke_signal_tab():
1589
  </div>
1590
  </div>
1591
  <div class="ss-wizard">
1592
- <div class="ss-step active"><span class="ss-num">1</span>INGEST</div>
1593
  <div class="ss-connector"></div>
1594
- <div class="ss-step"><span class="ss-num">2</span>PROFILE</div>
1595
  <div class="ss-connector"></div>
1596
  <div class="ss-step"><span class="ss-num">3</span>OCR</div>
1597
  <div class="ss-connector"></div>
@@ -1609,7 +1636,7 @@ def smoke_signal_tab():
1609
  gr.HTML("""<div class="ss-panel-header" style="padding:20px 0 0 0">
1610
  <div class="ss-panel-icon">📥</div>
1611
  <div><p class="ss-panel-title">Source Registry</p>
1612
- <p class="ss-panel-sub">Upload PDFs · Register · Hash · Rights class</p></div>
1613
  </div>""")
1614
 
1615
  ingest_status = gr.HTML(_ingest_status_html("idle"))
@@ -1629,7 +1656,7 @@ def smoke_signal_tab():
1629
  value="unknown",
1630
  )
1631
  ingest_notes = gr.Textbox(label="Notes", placeholder="Source, edition, etc.", lines=2)
1632
- ingest_btn = gr.Button("Register Sources →", elem_classes=["ss-btn-run"])
1633
 
1634
  manifest_table = gr.DataFrame(
1635
  label="Source Manifest",
@@ -1665,15 +1692,15 @@ def smoke_signal_tab():
1665
  # Auto-load removed — use Refresh button instead to avoid SSR hang
1666
 
1667
  # ── STEP 2: PROFILE ───────────────────────────────────────────────
1668
- with gr.TabItem("② Profile", id="ss-profile"):
1669
  gr.HTML("""<div class="ss-panel-header" style="padding:20px 0 0 0">
1670
  <div class="ss-panel-icon">🔍</div>
1671
  <div><p class="ss-panel-title">PDF Profiler</p>
1672
- <p class="ss-panel-sub">Detect embedded text vs image pages · Route to extraction path</p></div>
1673
  </div>""")
1674
 
1675
  profile_status = gr.HTML(_profile_status_html())
1676
- profile_btn = gr.Button("Run Profiler →", elem_classes=["ss-btn-run"])
1677
  profile_log = gr.Textbox(label="Log", lines=10, interactive=False, elem_classes=["ss-log"])
1678
 
1679
  profile_btn.click(run_profile, outputs=[profile_status, profile_log])
 
10
  # Add smoke_signal_tab() call inside your gr.Blocks() tabs
11
 
12
  Architecture:
13
+ Step 1: INGEST+PROFILE — upload PDFs, register, auto-profile
14
+ Step 2: PROFILE (optional) manual re-run when needed
15
  Step 3: OCR — Surya extraction + confidence scoring
16
  Step 4: REVIEW — human correction workbench (feeds training data)
17
  Step 5: EXPORT — clean JSONL to Codex + downloadable gold set
 
498
  log = []
499
  new_count = 0
500
  dup_count = 0
501
+ auto_profile_ids = []
502
 
503
  for file in files:
504
  path = Path(file.name) if hasattr(file, "name") else Path(file)
 
510
 
511
  # Check duplicate — update rights/notes if changed
512
  if not df.empty and file_hash in df["sha256"].values:
513
+ existing_book_id = df.loc[df["sha256"] == file_hash, "book_id"].values[0]
514
+ existing_status = df.loc[df["sha256"] == file_hash, "status"].values[0]
515
  existing_rights = df.loc[df["sha256"] == file_hash, "rights_class"].values[0]
516
  if rights_class != "unknown" and existing_rights != rights_class:
517
  df.loc[df["sha256"] == file_hash, "rights_class"] = rights_class
518
  df.loc[df["sha256"] == file_hash, "notes"] = notes
519
  log.append(log_line(f"↻ Updated rights for duplicate: {path.name} → {rights_class}"))
520
+ if existing_status == "pending":
521
+ auto_profile_ids.append(existing_book_id)
522
  else:
523
  log.append(log_line(f"↩ Duplicate: {path.name} (rights={existing_rights})"))
524
  dup_count += 1
 
553
  df = pd.concat([df, new_row], ignore_index=True)
554
  log.append(log_line(f"✓ Registered {book_id} — {path.name} ({page_count or '?'} pages)"))
555
  new_count += 1
556
+ auto_profile_ids.append(book_id)
557
 
558
  save_manifest_df(df)
559
 
560
  summary = f"Registered {new_count} new | {dup_count} duplicates skipped"
561
  log.append(log_line(summary))
562
 
563
+ unique_profile_ids = sorted(set(auto_profile_ids))
564
+ if unique_profile_ids:
565
+ log.append(log_line(f"↻ Auto-profile queued for {len(unique_profile_ids)} source(s)"))
566
+ _, profile_log = run_profile(book_ids=unique_profile_ids)
567
+ for line in str(profile_log).splitlines():
568
+ if line.strip():
569
+ log.append(line)
570
+ df = load_manifest_df()
571
+
572
  return _ingest_status_html("done", new_count, dup_count), df, "\n".join(log)
573
 
574
 
 
597
  df = load_manifest_df()
598
  if df.empty or book_id not in df["book_id"].values:
599
  return _ingest_status_html("idle"), df, f"Book ID {book_id} not found."
600
+ prev_status = df.loc[df["book_id"] == book_id, "status"].values[0]
601
  df.loc[df["book_id"] == book_id, "rights_class"] = new_rights
602
  save_manifest_df(df)
603
+ msg = f"[{datetime.utcnow().strftime('%H:%M:%S')}] ✓ Updated {book_id} rights → {new_rights}"
604
+ if prev_status == "pending" and new_rights not in ("unknown", "excluded"):
605
+ _, profile_log = run_profile(book_ids=[book_id])
606
+ if profile_log:
607
+ msg = msg + "\n" + str(profile_log)
608
+ return _ingest_status_html("done"), load_manifest_df(), msg
609
 
610
 
611
  # ── Step 2: PROFILE ────────────────────────────────────────────────────────────
612
+ def run_profile(book_ids: Optional[list[str]] = None) -> tuple:
613
+ """Profile pending PDFs. If book_ids are provided, profile only those sources."""
614
  df = load_manifest_df()
615
  if df.empty:
616
  return _profile_status_html(), "No sources registered. Complete Step 1 first."
617
 
618
  pending = df[df["status"] == "pending"]
619
+ if book_ids:
620
+ pending = pending[pending["book_id"].isin(book_ids)]
621
  if pending.empty:
622
+ if book_ids:
623
+ return _profile_status_html(), "No pending PDFs to profile for selected sources."
624
  return _profile_status_html(), "No pending PDFs to profile."
625
 
626
  log = []
 
697
  log.append(
698
  log_line(
699
  f"⚠ {skipped_rights} source(s) skipped due rights_class=unknown/excluded. "
700
+ "Set rights in Step 1 to auto-profile on update, or rerun Profile manually."
701
  )
702
  )
703
 
 
1282
  conf = 0.0
1283
  method = "skipped-no-surya"
1284
 
1285
+ if method == "tesseract-noise-filtered" and not regions:
1286
+ conf_class = "auto-accept"
1287
+ elif conf >= default_cal["auto_accept"]:
1288
  conf_class = "auto-accept"
1289
  elif conf >= default_cal["review"]:
1290
  conf_class = "review-required"
 
1616
  </div>
1617
  </div>
1618
  <div class="ss-wizard">
1619
+ <div class="ss-step active"><span class="ss-num">1</span>INGEST+PROFILE</div>
1620
  <div class="ss-connector"></div>
1621
+ <div class="ss-step"><span class="ss-num">2</span>PROFILE (OPTIONAL)</div>
1622
  <div class="ss-connector"></div>
1623
  <div class="ss-step"><span class="ss-num">3</span>OCR</div>
1624
  <div class="ss-connector"></div>
 
1636
  gr.HTML("""<div class="ss-panel-header" style="padding:20px 0 0 0">
1637
  <div class="ss-panel-icon">📥</div>
1638
  <div><p class="ss-panel-title">Source Registry</p>
1639
+ <p class="ss-panel-sub">Upload PDFs · Register · Auto-profile · Rights class</p></div>
1640
  </div>""")
1641
 
1642
  ingest_status = gr.HTML(_ingest_status_html("idle"))
 
1656
  value="unknown",
1657
  )
1658
  ingest_notes = gr.Textbox(label="Notes", placeholder="Source, edition, etc.", lines=2)
1659
+ ingest_btn = gr.Button("Register + Auto-Profile →", elem_classes=["ss-btn-run"])
1660
 
1661
  manifest_table = gr.DataFrame(
1662
  label="Source Manifest",
 
1692
  # Auto-load removed — use Refresh button instead to avoid SSR hang
1693
 
1694
  # ── STEP 2: PROFILE ───────────────────────────────────────────────
1695
+ with gr.TabItem("② Profile (Optional)", id="ss-profile"):
1696
  gr.HTML("""<div class="ss-panel-header" style="padding:20px 0 0 0">
1697
  <div class="ss-panel-icon">🔍</div>
1698
  <div><p class="ss-panel-title">PDF Profiler</p>
1699
+ <p class="ss-panel-sub">Auto-runs during ingest · use here only for manual re-profile</p></div>
1700
  </div>""")
1701
 
1702
  profile_status = gr.HTML(_profile_status_html())
1703
+ profile_btn = gr.Button("Re-run Profiler (Optional) →", elem_classes=["ss-btn-run"])
1704
  profile_log = gr.Textbox(label="Log", lines=10, interactive=False, elem_classes=["ss-log"])
1705
 
1706
  profile_btn.click(run_profile, outputs=[profile_status, profile_log])