From cc1f696ded96564ea7f3b5e93dfdea9802d873a2 Mon Sep 17 00:00:00 2001 From: Research Assistant Date: Tue, 28 Apr 2026 00:15:48 +0800 Subject: [PATCH] feat: add fixed sub-headings to figure/table callout blocks in skeleton --- .../skills/literature-qa/scripts/ld_deep.py | 61 +++++++--------- tests/test_ld_deep_skel.py | 73 +++++++++++++++++++ 2 files changed, 99 insertions(+), 35 deletions(-) create mode 100644 tests/test_ld_deep_skel.py diff --git a/paperforge/skills/literature-qa/scripts/ld_deep.py b/paperforge/skills/literature-qa/scripts/ld_deep.py index 99ec7215..2144190b 100644 --- a/paperforge/skills/literature-qa/scripts/ld_deep.py +++ b/paperforge/skills/literature-qa/scripts/ld_deep.py @@ -47,6 +47,24 @@ STUDY_HEADER = "## 🔍 精读" FIGURE_SECTION_HEADER = "#### Figure-by-Figure 解析" TABLE_SECTION_HEADER = "#### Table-by-Table 解析" +FIGURE_SUBHEADINGS = [ + "图像定位与核心问题", + "方法与结果", + "图表质量审查", + "作者解释", + "我的理解", + "疑点 / 局限", +] + +TABLE_SUBHEADINGS = [ + "这张表在回答什么问题", + "关键字段 / 分组", + "主要结果", + "我的理解", + "在全文中的作用", + "疑点 / 局限", +] + @dataclass class FigureEntry: @@ -65,6 +83,7 @@ class TableEntry: number: str image_link: str page: int | None + image_id: str = "" def validate_extraction_completeness( @@ -836,56 +855,28 @@ def render_study_scaffold(figures: Iterable[FigureEntry], tables: Iterable[Table def render_figure_block(figure: FigureEntry) -> str: - page_suffix = f"(第 {figure.page} 页)" if figure.page else "" lines = [ f"> [!note]- Figure {figure.number}:{figure.title}", f"> ![[{figure.image_link}]]", ] - # Add additional images for multi-image figures for add_img in figure.additional_images: lines.append(f"> ![[{add_img['link']}]]") - lines.extend( - [ - ">", - "> **图像定位与核心问题**", - f"> - 页码:{page_suffix or '待补充'}", - "> - 这张图要回答什么:", - "> - (待补充)", - ">", - "> **方法与结果**", - "> - 方法:", - "> - 结果:", - ">", - "> **作者解释**", - "> - (待补充)", - ">", - "> **我的理解**", - "> - (待补充)", - ">", - "> **在全文中的作用**", - "> - (待补充)", - ">", - "> **疑点 / 局限**", - "> - (待补充)", - ] - ) + lines.append(">") + for heading in FIGURE_SUBHEADINGS: + lines.append(f"> **{heading}**") + lines.append(">") return "\n".join(lines) + "\n\n" def render_table_block(table: TableEntry) -> str: - page_suffix = f"第 {table.page} 页" if table.page else "待补充" lines = [ f"> [!note]- Table {table.number}", f"> ![[{table.image_link}]]", ">", - f"> - 图像定位:{page_suffix}", - "> - 这张表在回答什么问题:", - "> - 关键字段 / 分组:", - "> - 主要结果:", - "> - 我的理解:", - "> - 在全文中的作用:", - "> - 疑点 / 局限:", ] + for heading in TABLE_SUBHEADINGS: + lines.append(f"> **{heading}**") + lines.append(">") return "\n".join(lines) + "\n\n" diff --git a/tests/test_ld_deep_skel.py b/tests/test_ld_deep_skel.py new file mode 100644 index 00000000..d70e003a --- /dev/null +++ b/tests/test_ld_deep_skel.py @@ -0,0 +1,73 @@ +"""Tests for skeleton rendering in ld_deep.py.""" + +import sys +from importlib.util import module_from_spec, spec_from_file_location +from pathlib import Path + +_REPO_ROOT = Path(__file__).parent.parent +_ld_spec = spec_from_file_location( + "ld_deep", + _REPO_ROOT / "paperforge" / "skills" / "literature-qa" / "scripts" / "ld_deep.py", +) +_ld_mod = module_from_spec(_ld_spec) +sys.modules["ld_deep"] = _ld_mod +_ld_spec.loader.exec_module(_ld_mod) + +from ld_deep import ( + render_figure_block, + render_table_block, + FigureEntry, + TableEntry, + FIGURE_SUBHEADINGS, + TABLE_SUBHEADINGS, +) + + +class TestRenderFigureBlock: + def test_all_subheadings_present(self): + fig = FigureEntry( + number=1, title="Test", image_id="fig1", + image_link="path/img.png", page=3, caption="Test", + is_supplementary=False, + ) + result = render_figure_block(fig) + for h in FIGURE_SUBHEADINGS: + assert f"> **{h}**" in result, f"Missing: {h}" + assert "> ![[path/img.png]]" in result + assert "> [!note]- Figure 1:" in result + + def test_image_inside_callout_block(self): + """The image embed must be between the heading and the first sub-heading.""" + fig = FigureEntry( + number=2, title="Test2", image_id="fig2", + image_link="img/test.png", page=5, caption="Test2", + is_supplementary=False, + ) + result = render_figure_block(fig) + heading_end = result.index("> ![[img/test.png]]") + first_sub = result.index(f"> **{FIGURE_SUBHEADINGS[0]}**") + assert heading_end < first_sub, "Image must appear before first sub-heading" + + def test_no_placeholder_text(self): + """No '待补充' or '[?]' text in the rendered block.""" + fig = FigureEntry( + number=3, title="Test3", image_id="fig3", + image_link="img/fig3.png", page=0, caption="Test3", + is_supplementary=False, + ) + result = render_figure_block(fig) + assert "[?]" not in result + assert "待补充" not in result + + +class TestRenderTableBlock: + def test_all_subheadings_present(self): + table = TableEntry( + number=1, image_id="tab1", + image_link="path/table.png", page=4, + ) + result = render_table_block(table) + for h in TABLE_SUBHEADINGS: + assert f"> **{h}**" in result, f"Missing: {h}" + assert "> ![[path/table.png]]" in result + assert "> [!note]- Table 1" in result