mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
test: add OCR-v2 spec-contract test suite
This commit is contained in:
parent
2eb6c1439b
commit
c8891a338a
1 changed files with 182 additions and 0 deletions
182
tests/test_ocr_spec_contracts.py
Normal file
182
tests/test_ocr_spec_contracts.py
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestOcrRoleContracts:
|
||||
"""Architecture contract tests for OCR-v2 role assignment invariants."""
|
||||
|
||||
def test_contract_zone_is_not_role(self) -> None:
|
||||
"""Being in the body_zone does not make a block body_paragraph by itself."""
|
||||
from paperforge.worker.ocr_roles import assign_block_role
|
||||
|
||||
block = {
|
||||
"block_label": "text",
|
||||
"block_content": "Check for updates",
|
||||
"block_bbox": [900, 50, 1100, 72],
|
||||
}
|
||||
role = assign_block_role(
|
||||
block, page_blocks=[block], page_width=1200, page_height=1600,
|
||||
)
|
||||
assert role.role not in ("section_heading", "subsection_heading"), (
|
||||
f"zone-only text must not become heading: {role.role}"
|
||||
)
|
||||
|
||||
def test_contract_reference_tail_first(self) -> None:
|
||||
"""Reference-like text without reference_zone/numbering support must not become reference_item."""
|
||||
from paperforge.worker.ocr_roles import assign_block_role
|
||||
|
||||
for ordinal_word in (
|
||||
"First,",
|
||||
"Fourth,",
|
||||
"Sixth,",
|
||||
"Additionally,",
|
||||
"Historically,",
|
||||
"Metabolically,",
|
||||
):
|
||||
block = {
|
||||
"block_label": "text",
|
||||
"block_content": f"{ordinal_word} some body prose that looks like a reference but is not one.",
|
||||
"block_bbox": [100, 200, 800, 260],
|
||||
}
|
||||
role = assign_block_role(
|
||||
block, page_blocks=[block], page_width=1200, page_height=1600,
|
||||
)
|
||||
assert role.role != "reference_item", (
|
||||
f"Ordinal body opening '{ordinal_word}' must not become reference_item, got {role.role}"
|
||||
)
|
||||
|
||||
|
||||
class TestOcrRenderContracts:
|
||||
"""Architecture contract tests for OCR-v2 renderer invariants."""
|
||||
|
||||
def test_contract_renderer_is_not_semantic_rescue(self) -> None:
|
||||
"""render_fulltext_markdown must not promote blocks based on raw text alone."""
|
||||
from paperforge.worker.ocr_render import render_fulltext_markdown
|
||||
|
||||
blocks = [
|
||||
{
|
||||
"block_id": "p1_b1",
|
||||
"page": 1,
|
||||
"role": "body_paragraph",
|
||||
"raw_label": "text",
|
||||
"text": "Fig. 1 This looks like a figure caption but has no accepted figure role.",
|
||||
"bbox": [100, 100, 800, 160],
|
||||
"page_width": 1200,
|
||||
"page_height": 1600,
|
||||
},
|
||||
]
|
||||
markdown = render_fulltext_markdown(
|
||||
structured_blocks=blocks,
|
||||
resolved_metadata={},
|
||||
figure_inventory={"matched_figures": []},
|
||||
table_inventory={"tables": []},
|
||||
page_count=1,
|
||||
document_structure=None,
|
||||
reader_payload={"reader_figures": [], "consumed_caption_block_ids": []},
|
||||
)
|
||||
assert "Fig. 1" in markdown, "readable text must not be silently dropped"
|
||||
assert "![[assets/figures/" not in markdown, (
|
||||
"renderer must not invent figure embedding from raw text"
|
||||
)
|
||||
|
||||
def test_contract_object_ownership_is_exclusive(self) -> None:
|
||||
"""A block consumed by a figure object must not also appear as loose body text."""
|
||||
from paperforge.worker.ocr_render import render_fulltext_markdown
|
||||
|
||||
blocks = [
|
||||
{
|
||||
"block_id": "p1_b1",
|
||||
"page": 1,
|
||||
"role": "figure_caption",
|
||||
"raw_label": "figure_title",
|
||||
"text": "Figure 1. A sample figure.",
|
||||
"bbox": [100, 100, 800, 160],
|
||||
"page_width": 1200,
|
||||
"page_height": 1600,
|
||||
},
|
||||
]
|
||||
reader_payload = {
|
||||
"reader_figures": [
|
||||
{
|
||||
"reader_figure_id": "figure_001_reader",
|
||||
"reader_status": "EXACT_MATCH",
|
||||
"caption_block_id": "p1_b1",
|
||||
"consumed_caption_block_ids": ["p1_b1"],
|
||||
},
|
||||
],
|
||||
"consumed_caption_block_ids": ["p1_b1"],
|
||||
}
|
||||
markdown = render_fulltext_markdown(
|
||||
structured_blocks=blocks,
|
||||
resolved_metadata={},
|
||||
figure_inventory={
|
||||
"matched_figures": [
|
||||
{
|
||||
"figure_id": "figure_001",
|
||||
"text": "Figure 1. A sample figure.",
|
||||
"page": 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
table_inventory={"tables": []},
|
||||
page_count=1,
|
||||
document_structure=None,
|
||||
reader_payload=reader_payload,
|
||||
)
|
||||
occurrences = markdown.count("Figure 1.")
|
||||
assert occurrences == 1, (
|
||||
f"Consumed caption must appear exactly once, found {occurrences} occurrences"
|
||||
)
|
||||
|
||||
def test_contract_reading_segments_are_authoritative(self) -> None:
|
||||
"""Conclusion must appear before References in final markdown, regardless of page geometry."""
|
||||
from paperforge.worker.ocr_render import render_fulltext_markdown
|
||||
|
||||
blocks = [
|
||||
{
|
||||
"block_id": "p1_b2",
|
||||
"page": 1,
|
||||
"role": "reference_heading",
|
||||
"raw_label": "paragraph_title",
|
||||
"text": "References",
|
||||
"bbox": [100, 600, 400, 640],
|
||||
"page_width": 1200,
|
||||
"page_height": 1600,
|
||||
},
|
||||
{
|
||||
"block_id": "p1_b3",
|
||||
"page": 1,
|
||||
"role": "reference_item",
|
||||
"raw_label": "text",
|
||||
"text": "[1] Example reference.",
|
||||
"bbox": [100, 660, 800, 700],
|
||||
"page_width": 1200,
|
||||
"page_height": 1600,
|
||||
},
|
||||
{
|
||||
"block_id": "p1_b1",
|
||||
"page": 1,
|
||||
"role": "subsection_heading",
|
||||
"raw_label": "paragraph_title",
|
||||
"text": "Conclusion",
|
||||
"bbox": [100, 800, 400, 840],
|
||||
"page_width": 1200,
|
||||
"page_height": 1600,
|
||||
},
|
||||
]
|
||||
markdown = render_fulltext_markdown(
|
||||
structured_blocks=blocks,
|
||||
resolved_metadata={},
|
||||
figure_inventory={"matched_figures": []},
|
||||
table_inventory={"tables": []},
|
||||
page_count=1,
|
||||
document_structure=None,
|
||||
reader_payload={"reader_figures": [], "consumed_caption_block_ids": []},
|
||||
)
|
||||
conclusion_pos = markdown.find("Conclusion")
|
||||
references_pos = markdown.find("References")
|
||||
assert conclusion_pos < references_pos, (
|
||||
f"Conclusion must appear before References "
|
||||
f"(conclusion at {conclusion_pos}, refs at {references_pos})"
|
||||
)
|
||||
Loading…
Reference in a new issue