mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
feat: add tail boundary score to DocumentStructure and health
This commit is contained in:
parent
46937f2a0a
commit
c8b4846998
4 changed files with 61 additions and 0 deletions
|
|
@ -83,6 +83,7 @@ class DocumentStructure:
|
|||
reference_zones: list[dict] | None = None
|
||||
span_coverage: dict | None = None
|
||||
layout_audit: dict | None = None
|
||||
tail_boundary_score: dict = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -2794,6 +2795,15 @@ def normalize_document_structure(blocks: list[dict]) -> tuple[DocumentStructure,
|
|||
layout_audit = _run_layout_audit(blocks)
|
||||
doc_structure.layout_audit = layout_audit
|
||||
|
||||
# Compute tail boundary confidence score
|
||||
from paperforge.worker.ocr_scores import score_tail_boundary
|
||||
|
||||
doc_structure.tail_boundary_score = score_tail_boundary(
|
||||
forward_body_end=doc_structure.body_end_page,
|
||||
backward_backmatter_start=doc_structure.backmatter_start.page if doc_structure.backmatter_start else None,
|
||||
references_start={"page": doc_structure.references_start.page} if doc_structure.references_start else None,
|
||||
)
|
||||
|
||||
# Rebuild tail reading order after role normalization and block reassignment.
|
||||
# Without this, reading segment block_indices reference stale positions
|
||||
# from before _promote_tail_body_candidates reassigned the blocks list.
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ def build_ocr_health(
|
|||
spine = _detect_body_spine(structured_blocks, doc=doc_structure)
|
||||
layout = _run_layout_audit(structured_blocks)
|
||||
|
||||
tail_score = {}
|
||||
if doc_structure is not None and hasattr(doc_structure, "tail_boundary_score"):
|
||||
tail_score = doc_structure.tail_boundary_score or {}
|
||||
|
||||
# Collect decision log summaries
|
||||
from paperforge.worker.ocr_decisions import collect_decisions, summarize_decisions
|
||||
|
||||
|
|
@ -99,6 +103,7 @@ def build_ocr_health(
|
|||
"layout_audit_status": layout.get("status", "unknown"),
|
||||
"layout_anomaly_pages": layout.get("anomaly_pages", []),
|
||||
"layout_anomaly_count": layout.get("anomaly_count", 0),
|
||||
"tail_boundary_confidence": tail_score.get("score", 0.0),
|
||||
}
|
||||
report.update(decision_summary)
|
||||
return report
|
||||
|
|
|
|||
|
|
@ -1982,6 +1982,22 @@ def test_document_structure_json_serialization() -> None:
|
|||
assert parsed["body_end_page"] == 70
|
||||
assert parsed["page_layouts"]["71"]["column_count"] == 2
|
||||
assert parsed["page_layouts"]["71"]["layout_type"] == "two_column"
|
||||
assert "tail_boundary_score" in parsed
|
||||
|
||||
|
||||
def test_document_structure_has_tail_boundary_score() -> None:
|
||||
from paperforge.worker.ocr_document import normalize_document_structure
|
||||
|
||||
blocks = [
|
||||
{"page": 1, "role": "body_paragraph", "text": "Body text.", "bbox": [100, 100, 500, 130]},
|
||||
{"page": 2, "role": "section_heading", "text": "Discussion", "bbox": [100, 100, 500, 130]},
|
||||
{"page": 2, "role": "body_paragraph", "text": "More body.", "bbox": [100, 140, 500, 170]},
|
||||
{"page": 10, "role": "reference_heading", "text": "References", "bbox": [100, 100, 500, 130]},
|
||||
{"page": 10, "role": "reference_item", "text": "1. Author. Title. Journal.", "bbox": [100, 140, 500, 170]},
|
||||
]
|
||||
doc, _ = normalize_document_structure(blocks)
|
||||
assert doc.layout_audit is not None
|
||||
assert doc.tail_boundary_score["score"] >= 0.0
|
||||
|
||||
|
||||
def test_figure_caption_candidate_demoted_in_body() -> None:
|
||||
|
|
|
|||
|
|
@ -152,3 +152,33 @@ def test_ocr_health_includes_decision_counts() -> None:
|
|||
report = build_ocr_health(page_count=1, raw_blocks_count=1, structured_blocks=blocks, figure_inventory={}, table_inventory={})
|
||||
assert report["role_mutation_count"] == 1
|
||||
assert report["role_rescue_count"] == 1
|
||||
|
||||
|
||||
def test_ocr_health_includes_tail_boundary_confidence() -> None:
|
||||
from paperforge.worker.ocr_health import build_ocr_health
|
||||
|
||||
structured_blocks = [
|
||||
{"role": "section_heading", "text": "1 Introduction"},
|
||||
{"role": "body_paragraph", "text": "Body"},
|
||||
{"role": "reference_heading", "text": "References"},
|
||||
{"role": "reference_item", "text": "1. Author."},
|
||||
]
|
||||
figure_inventory = {
|
||||
"matched_figures": [],
|
||||
"unmatched_legends": [],
|
||||
"unmatched_assets": [],
|
||||
}
|
||||
table_inventory = {
|
||||
"tables": [],
|
||||
"unmatched_captions": [],
|
||||
"unmatched_assets": [],
|
||||
}
|
||||
report = build_ocr_health(
|
||||
page_count=3,
|
||||
raw_blocks_count=20,
|
||||
structured_blocks=structured_blocks,
|
||||
figure_inventory=figure_inventory,
|
||||
table_inventory=table_inventory,
|
||||
)
|
||||
assert "tail_boundary_confidence" in report
|
||||
assert isinstance(report["tail_boundary_confidence"], (int, float))
|
||||
|
|
|
|||
Loading…
Reference in a new issue