mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
fix: gate weak boundary signals through backmatter_boundary_candidate
This commit is contained in:
parent
bf8c1be53a
commit
3e748dcb5d
4 changed files with 70 additions and 28 deletions
|
|
@ -9,7 +9,6 @@ from paperforge.worker.ocr_roles import assign_block_role
|
|||
_CANDIDATE_ROLES = frozenset({
|
||||
"figure_caption_candidate",
|
||||
"backmatter_heading_candidate",
|
||||
"backmatter_boundary_candidate",
|
||||
})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1704,27 +1704,7 @@ def _resolve_ambiguous_candidates(
|
|||
|
||||
block["role"] = "backmatter_heading"
|
||||
|
||||
# ---- 2.2 Resolve backmatter_boundary_candidate ----
|
||||
if role == "backmatter_boundary_candidate":
|
||||
is_in_tail = (body_end_page is not None and page >= body_end_page) or (
|
||||
doc_structure.spread_start is not None and page >= doc_structure.spread_start
|
||||
)
|
||||
if not is_in_tail:
|
||||
block["role"] = "section_heading"
|
||||
continue
|
||||
|
||||
layout = page_layouts.get(page)
|
||||
boundaries = layout.column_boundaries if layout else []
|
||||
x_center = (bbox[0] + bbox[2]) / 2
|
||||
col = _get_column_index_by_boundaries(x_center, boundaries)
|
||||
|
||||
child_count = _child_heading_count(i, page, col, boundaries)
|
||||
if child_count >= 2.0:
|
||||
block["role"] = "backmatter_boundary_heading"
|
||||
else:
|
||||
block["role"] = "section_heading"
|
||||
|
||||
# ---- 2.3 Resolve figure_caption_candidate ----
|
||||
# ---- 2.2 Resolve figure_caption_candidate ----
|
||||
if role == "figure_caption_candidate":
|
||||
text = block.get("text", "") or ""
|
||||
page_blocks = [b for b in blocks if b.get("page") == page]
|
||||
|
|
|
|||
|
|
@ -459,10 +459,28 @@ def assign_block_role(
|
|||
_page_num = block.get("page", 1) or 1
|
||||
_total_pages = max((b.get("page", 1) or 1) for b in page_blocks) if page_blocks else _page_num
|
||||
if _is_backmatter_boundary_heading(block, _page_num, _total_pages):
|
||||
role_name = "backmatter_boundary_heading"
|
||||
confidence = 0.7
|
||||
lower_txt = text.lower()
|
||||
has_container_words = any(
|
||||
w in lower_txt
|
||||
for w in ["additional information", "declaration", "supplementary"]
|
||||
)
|
||||
span_meta = block.get("span_metadata", {}) or {}
|
||||
is_bold = False
|
||||
if isinstance(span_meta, dict):
|
||||
is_bold = "bold" in (span_meta.get("flags", "") or "").lower()
|
||||
elif isinstance(span_meta, list):
|
||||
is_bold = any(s.get("flags", 0) & 16 for s in span_meta)
|
||||
if not (has_container_words and is_bold):
|
||||
role_name = "backmatter_boundary_candidate"
|
||||
confidence = 0.5
|
||||
return RoleAssignment(
|
||||
role="backmatter_boundary_heading",
|
||||
confidence=0.7,
|
||||
evidence=[f"backmatter boundary heading: {text[:60]}"],
|
||||
role=role_name,
|
||||
confidence=confidence,
|
||||
evidence=[
|
||||
f"backmatter {'boundary heading' if role_name == 'backmatter_boundary_heading' else 'boundary candidate'}: {text[:60]}"
|
||||
],
|
||||
)
|
||||
if _has_heading_numbering(text):
|
||||
depth = _heading_number_depth(text)
|
||||
|
|
@ -732,10 +750,28 @@ def assign_block_role(
|
|||
_pn2 = block.get("page", 1) or 1
|
||||
_tp2 = max((b.get("page", 1) or 1) for b in page_blocks) if page_blocks else _pn2
|
||||
if _is_backmatter_boundary_heading(block, _pn2, _tp2):
|
||||
role_name = "backmatter_boundary_heading"
|
||||
confidence = 0.7
|
||||
lower_txt = text.lower()
|
||||
has_container_words = any(
|
||||
w in lower_txt
|
||||
for w in ["additional information", "declaration", "supplementary"]
|
||||
)
|
||||
span_meta = block.get("span_metadata", {}) or {}
|
||||
is_bold = False
|
||||
if isinstance(span_meta, dict):
|
||||
is_bold = "bold" in (span_meta.get("flags", "") or "").lower()
|
||||
elif isinstance(span_meta, list):
|
||||
is_bold = any(s.get("flags", 0) & 16 for s in span_meta)
|
||||
if not (has_container_words and is_bold):
|
||||
role_name = "backmatter_boundary_candidate"
|
||||
confidence = 0.5
|
||||
return RoleAssignment(
|
||||
role="backmatter_boundary_heading",
|
||||
confidence=0.7,
|
||||
evidence=[f"backmatter boundary heading from text block: {text[:60]}"],
|
||||
role=role_name,
|
||||
confidence=confidence,
|
||||
evidence=[
|
||||
f"backmatter {'boundary heading' if role_name == 'backmatter_boundary_heading' else 'boundary candidate'} from text block: {text[:60]}"
|
||||
],
|
||||
)
|
||||
|
||||
if len(text) < 20:
|
||||
|
|
|
|||
|
|
@ -463,6 +463,33 @@ def test_formal_figure_caption_still_direct() -> None:
|
|||
)
|
||||
|
||||
|
||||
def test_weak_backmatter_boundary_signal_emits_candidate():
|
||||
"""A paragraph with backmatter boundary text but no bold span_metadata
|
||||
should emit backmatter_boundary_candidate, not backmatter_boundary_heading."""
|
||||
from paperforge.worker.ocr_roles import assign_block_role
|
||||
block = {
|
||||
"block_label": "paragraph_title",
|
||||
"block_content": "ADDITIONAL INFORMATION",
|
||||
"block_bbox": [100, 1200, 500, 1240],
|
||||
"page": 8,
|
||||
}
|
||||
page_blocks = [block] + [
|
||||
{
|
||||
"block_label": "text",
|
||||
"block_content": f"Some body text {i}",
|
||||
"block_bbox": [100, 200 + i * 100, 500, 260 + i * 100],
|
||||
"page": 8,
|
||||
}
|
||||
for i in range(5)
|
||||
]
|
||||
result = assign_block_role(
|
||||
block, page_blocks=page_blocks, page_width=600, page_height=1600
|
||||
)
|
||||
assert result.role == "backmatter_boundary_candidate", (
|
||||
f"Expected backmatter_boundary_candidate, got {result.role}"
|
||||
)
|
||||
|
||||
|
||||
def test_backmatter_boundary_detects_on_early_page() -> None:
|
||||
"""Backmatter boundary should be detectable on papers with fewer
|
||||
than 8 pages, without a hard page gate."""
|
||||
|
|
|
|||
Loading…
Reference in a new issue