fix: inline <table> HTML blocks incorrectly assigned to media_asset

Three-part fix:
1. ocr_roles.py: add inline <table> check before raw_label=table → media_asset
   fallback. Blocks starting with <table> now get table_html directly regardless
   of raw_label.
2. ocr_structural_gate.py: add table_html verifier — inline <table> HTML is
   self-identifying, accepted without structural verification.
3. ocr_document.py: remove table_html→table_html_candidate conversion step.
   This role was never handled by any downstream pipeline, causing blocks to
   be downgraded to unknown_structural.

Validated on AH6Q7DLC (worst case, 30 blocks): 29/30 now correctly table_html,
1 remaining is reference_item (bibliography table, different classification).
Full corpus data pending rebuild.

585 figure/table/role tests pass.
This commit is contained in:
LLLin000 2026-07-03 01:33:29 +08:00
parent 59708cd961
commit 41e35239f2
3 changed files with 20 additions and 6 deletions

View file

@ -5989,12 +5989,6 @@ def normalize_document_structure(
for block in blocks:
if block.get("seed_role") == "figure_caption" and not _should_keep_formal_caption_seed(block):
block["role"] = "figure_caption_candidate"
if not gate_context.accepted_table_block_ids:
for block in blocks:
if block.get("seed_role") == "table_caption" and not _should_keep_formal_caption_seed(block):
block["role"] = "table_caption_candidate"
if block.get("seed_role") == "table_html":
block["role"] = "table_html_candidate"
decisions = []
for block in blocks:

View file

@ -1318,6 +1318,15 @@ def assign_block_role(
evidence=[f"textual table (bullet list) not media_asset: {text[:60]}"],
)
# Inline table HTML check: applies to all raw_labels, not just "text"
if text.strip().lower().startswith("<table"):
return RoleAssignment(
role="table_html",
confidence=0.95,
evidence=["inline table HTML"],
)
if raw_label in {"image", "chart", "table"}:
return RoleAssignment(
role="media_asset",

View file

@ -26,6 +26,7 @@ _SAFE_PRESERVED_ROLES = {
"backmatter_body",
"body_paragraph",
"ocr_raw_error",
"table_html", # inline <table> HTML is self-identifying, no verification needed
}
@ -358,6 +359,16 @@ def resolve_verified_role(block: dict, context: RoleGateContext) -> VerifiedRole
render_default=False,
)
if proposal == "table_html":
# Inline <table> HTML is self-identifying — no structural verifier needed.
# The seed_role is reliable when the block text starts with <table>.
if str(block.get("text", "") or "").strip().lower().startswith("<table"):
return accept_role(
"table_html", seed_role, "inline_table_html",
["inline <table> HTML — self-identifying, accepted without verification"],
)
return hold_role(seed_role, "table_html seed without inline <table> text")
if proposal in VERIFY_REQUIRED or seed_role in VERIFY_REQUIRED:
return hold_role(seed_role, f"{proposal} requires structural verifier")
source = "non_structural_seed" if current_role in {"", "unassigned"} else "non_structural_normalized_role"