fix: emit figure/table objects on page completion, include all legends in matching

This commit is contained in:
Research Assistant 2026-06-05 15:22:04 +08:00
parent cff4107633
commit 7d20aba6ba
3 changed files with 28 additions and 13 deletions

View file

@ -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 = []

View file

@ -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}

View file

@ -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"<!-- page {p} -->")
@ -129,6 +137,7 @@ def render_fulltext_markdown(
current_page = block_page
lines.append(f"<!-- page {block_page} -->")
lines.append("")
emitted_pages.add(block_page)
if role == "section_heading":
if text.strip().lower() in FRONTMATTER_NOISE: