From 2bf7e73fffefeb98a5bdb800d59aded19e694e9e Mon Sep 17 00:00:00 2001 From: Research Assistant Date: Sat, 6 Jun 2026 13:10:56 +0800 Subject: [PATCH] feat: persist document_structure.json, thread structure truth through downstream pipeline --- paperforge/worker/ocr.py | 5 ++++- paperforge/worker/ocr_blocks.py | 31 ++++++++++++++++++++++++++++++- paperforge/worker/ocr_rebuild.py | 5 ++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/paperforge/worker/ocr.py b/paperforge/worker/ocr.py index 596f83dc..2e4a8e6d 100644 --- a/paperforge/worker/ocr.py +++ b/paperforge/worker/ocr.py @@ -1760,7 +1760,10 @@ def postprocess_ocr_result(vault: Path, key: str, all_results: list[dict]) -> tu backfill_span_metadata_from_pdf(all_raw_blocks, source_pdf_path) write_raw_blocks_jsonl(artifacts.blocks_raw, all_raw_blocks) - structured = build_structured_blocks(all_raw_blocks) + structured = build_structured_blocks( + all_raw_blocks, + structure_output_dir=artifacts.blocks_structured.parent, + ) write_structured_blocks_jsonl(artifacts.blocks_structured, structured) # Write role-level span profiles from paperforge.worker.ocr_profiles import write_role_span_profiles diff --git a/paperforge/worker/ocr_blocks.py b/paperforge/worker/ocr_blocks.py index fb6b42ba..e2f25ec3 100644 --- a/paperforge/worker/ocr_blocks.py +++ b/paperforge/worker/ocr_blocks.py @@ -7,7 +7,10 @@ from typing import Any from paperforge.worker.ocr_roles import assign_block_role -def build_structured_blocks(raw_blocks: list[dict]) -> list[dict]: +def build_structured_blocks( + raw_blocks: list[dict], + structure_output_dir: str | Path | None = None, +) -> list[dict]: # Group raw blocks by page so assign_block_role can see page-local context by_page: dict[int, list[dict]] = {} for block in raw_blocks: @@ -86,9 +89,35 @@ def build_structured_blocks(raw_blocks: list[dict]) -> list[dict]: rows = rescue_roles_with_document_context(rows, paper_context["role_profiles"], doc_structure) + # Persist document structure artifact for downstream debugging + if doc_structure and structure_output_dir: + _write_document_structure_json(doc_structure, structure_output_dir) + return rows +def _write_document_structure_json(doc_structure, output_dir: str | Path) -> None: + """Serialize DocumentStructure to JSON for downstream inspection.""" + import dataclasses + + if hasattr(doc_structure, "_asdict"): + data = doc_structure._asdict() + elif dataclasses.is_dataclass(doc_structure): + data = dataclasses.asdict(doc_structure) + else: + return + # Convert non-serializable types + for k, v in data.items(): + if hasattr(v, "_asdict"): + data[k] = v._asdict() + elif dataclasses.is_dataclass(v): + data[k] = dataclasses.asdict(v) + output_path = Path(output_dir) / "document_structure.json" + output_path.parent.mkdir(parents=True, exist_ok=True) + with open(output_path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + def write_structured_blocks_jsonl(path: Path, rows: list[dict]) -> None: path.parent.mkdir(parents=True, exist_ok=True) with path.open("w", encoding="utf-8") as f: diff --git a/paperforge/worker/ocr_rebuild.py b/paperforge/worker/ocr_rebuild.py index 49122ad3..72aea882 100644 --- a/paperforge/worker/ocr_rebuild.py +++ b/paperforge/worker/ocr_rebuild.py @@ -67,7 +67,10 @@ def run_derived_rebuild_for_keys(vault: Path, keys: list[str]) -> dict: # Rebuild structured blocks from paperforge.worker.ocr_blocks import build_structured_blocks, write_structured_blocks_jsonl - structured = build_structured_blocks(all_raw_blocks) + structured = build_structured_blocks( + all_raw_blocks, + structure_output_dir=artifacts.blocks_structured.parent, + ) write_structured_blocks_jsonl(artifacts.blocks_structured, structured) # Write role-level span profiles from paperforge.worker.ocr_profiles import write_role_span_profiles