diff --git a/paperforge/worker/ocr_figures.py b/paperforge/worker/ocr_figures.py index 91157340..24ae187d 100644 --- a/paperforge/worker/ocr_figures.py +++ b/paperforge/worker/ocr_figures.py @@ -88,30 +88,31 @@ def build_figure_inventory(structured_blocks: list[dict]) -> dict[str, Any]: if raw_label in {"image", "chart", "figure_title", "figure"} or not raw_label: assets.append(block) - # --- Step 1: Split legends into formal and candidate paths --- - formal_legends: list[dict] = [] - candidate_legends: list[dict] = [] - for leg in legends: + # --- Step 1: Classify each legend with confidence level --- + # All legends are included; the classification affects confidence scoring + legend_classification: dict[int, str] = {} # legend index -> "formal" | "candidate" | "fallback" + for i, leg in enumerate(legends): text = leg.get("text", "") bbox = leg.get("bbox", [0, 0, 0, 0]) if _extract_figure_number(text) is not None: - formal_legends.append(leg) - elif _compute_text_confidence(text, bbox, formal_legends): - candidate_legends.append(leg) - - all_legends = formal_legends + candidate_legends + legend_classification[i] = "formal" + elif _compute_text_confidence(text, bbox, [legends[j] for j in range(i) if legend_classification.get(j) == "formal"]): + legend_classification[i] = "candidate" + else: + legend_classification[i] = "fallback" # --- Step 2: Multi-signal matching --- used_asset_indices: set[int] = set() matched_figures: list[dict] = [] unmatched_legends: list[dict] = [] - for legend in all_legends: + for i_leg, legend in enumerate(legends): legend_page = legend.get("page", 0) legend_bbox = legend.get("bbox", [0, 0, 0, 0]) legend_text = legend.get("text", "") fig_num = _extract_figure_number(legend_text) - is_formal = legend in formal_legends + cls = legend_classification.get(i_leg, "fallback") + is_formal = cls == "formal" candidate_pages = [legend_page, legend_page + 1, legend_page - 1] matched_assets = [] diff --git a/paperforge/worker/ocr_rebuild.py b/paperforge/worker/ocr_rebuild.py index c43e1ce7..f5a0b967 100644 --- a/paperforge/worker/ocr_rebuild.py +++ b/paperforge/worker/ocr_rebuild.py @@ -236,6 +236,11 @@ def backfill_from_result(vault: Path, key: str) -> dict: meta["is_backfilled"] = True meta["backfilled_at"] = __import__("datetime").datetime.now().isoformat() meta["ocr_status"] = "done" + # Fix assets_path from legacy images/ to structured assets/ + if meta.get("assets_path", "").endswith("/images"): + meta["assets_path"] = meta["assets_path"].replace("/images", "/assets") + elif meta.get("assets_path", "").endswith("\\images"): + meta["assets_path"] = meta["assets_path"].replace("\\images", "\\assets") write_json(meta_path, meta) return {"backfill_status": "done", "paper_key": key} diff --git a/paperforge/worker/ocr_render.py b/paperforge/worker/ocr_render.py index 307170f6..c629db8e 100644 --- a/paperforge/worker/ocr_render.py +++ b/paperforge/worker/ocr_render.py @@ -113,8 +113,16 @@ def render_fulltext_markdown( block_page = block.get("page") if block_page is not None and block_page != current_page: - # Fill in page markers for any pages that had no renderable blocks - # (e.g. page 1 with only frontmatter_noise blocks) + # Emit objects for the page we just finished rendering + if current_page is not None: + for fig_id in figures_by_page.get(current_page, []): + lines.append(f"![[render/figures/{fig_id}.md]]") + lines.append("") + for tbl_id in tables_by_page.get(current_page, []): + lines.append(f"![[render/tables/{tbl_id}.md]]") + lines.append("") + emitted_pages.add(current_page) + # Fill in page markers for skipped pages (no renderable blocks) first_new_page = (current_page or 0) + 1 for p in range(first_new_page, block_page): lines.append(f"") @@ -129,6 +137,7 @@ def render_fulltext_markdown( current_page = block_page lines.append(f"") lines.append("") + emitted_pages.add(block_page) if role == "section_heading": if text.strip().lower() in FRONTMATTER_NOISE: