From f95925b22686b2c113ba0ff29ac345b1a824639f Mon Sep 17 00:00:00 2001 From: LLLin000 <809867916@qq.com> Date: Fri, 3 Jul 2026 12:01:11 +0800 Subject: [PATCH] refactor(ocr): split legacy and vnext figure entrypoints --- paperforge/worker/ocr_figures.py | 23 ++++++++++ ...ompare_figure_inventory_legacy_vs_vnext.py | 45 +++++++++++++++++++ tests/test_ocr_figures.py | 35 +++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 scripts/dev/compare_figure_inventory_legacy_vs_vnext.py diff --git a/paperforge/worker/ocr_figures.py b/paperforge/worker/ocr_figures.py index dbc8f644..6a6ffe0f 100644 --- a/paperforge/worker/ocr_figures.py +++ b/paperforge/worker/ocr_figures.py @@ -2978,6 +2978,29 @@ def _infer_missing_main_figure_numbers( def build_figure_inventory(structured_blocks: list[dict], page_width: float = 1200, page_pdf_lines_by_page: dict[int, list[dict]] | None = None) -> dict[str, Any]: + return build_figure_inventory_legacy(structured_blocks, page_width, page_pdf_lines_by_page) + + +def build_figure_inventory_vnext(structured_blocks: list[dict], page_width: float = 1200, page_pdf_lines_by_page: dict[int, list[dict]] | None = None) -> dict[str, Any]: + return { + "pipeline_mode": "vnext", + "matched_figures": [], + "ambiguous_figures": [], + "unmatched_legends": [], + "unmatched_assets": [], + "unresolved_clusters": [], + "held_figures": [], + "rejected_legends": [], + "page_ledger": {}, + "residual_ledger": {}, + "local_pairing_hypotheses": [], + "completeness": { + "total_numbered_legends": 0, + "accounted_for": 0, + "details": [], + }, + } +def build_figure_inventory_legacy(structured_blocks: list[dict], page_width: float = 1200, page_pdf_lines_by_page: dict[int, list[dict]] | None = None) -> dict[str, Any]: legends: list[dict] = [] held_figures: list[dict] = [] rejected_legends: list[dict] = [] diff --git a/scripts/dev/compare_figure_inventory_legacy_vs_vnext.py b/scripts/dev/compare_figure_inventory_legacy_vs_vnext.py new file mode 100644 index 00000000..e03d740b --- /dev/null +++ b/scripts/dev/compare_figure_inventory_legacy_vs_vnext.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from paperforge.worker.ocr_figures import ( + build_figure_inventory_legacy, + build_figure_inventory_vnext, +) + + +def _figure_asset_ids(fig: dict[str, object]) -> list[str]: + ids = {str(x) for x in fig.get("asset_block_ids", [])} + ids.update(str(asset.get("block_id", "")) for asset in fig.get("matched_assets", [])) + return sorted(x for x in ids if x) + + +def compare_inventories(legacy: dict[str, object], vnext: dict[str, object]) -> dict[str, object]: + return { + "legacy_matched_count": len(legacy.get("matched_figures", [])), + "vnext_matched_count": len(vnext.get("matched_figures", [])), + "legacy_unresolved_count": len(legacy.get("unresolved_clusters", [])), + "vnext_unresolved_count": len(vnext.get("unresolved_clusters", [])), + "legacy_unmatched_legend_count": len(legacy.get("unmatched_legends", [])), + "vnext_unmatched_legend_count": len(vnext.get("unmatched_legends", [])), + "legacy_consumed_block_ids": sorted( + {bid for fig in legacy.get("matched_figures", []) for bid in _figure_asset_ids(fig)} + ), + "vnext_consumed_block_ids": sorted( + {bid for fig in vnext.get("matched_figures", []) for bid in _figure_asset_ids(fig)} + ), + } + + +def main(blocks_path: str) -> int: + blocks = [json.loads(line) for line in Path(blocks_path).read_text(encoding="utf-8").splitlines() if line.strip()] + legacy = build_figure_inventory_legacy(blocks) + vnext = build_figure_inventory_vnext(blocks) + print(json.dumps(compare_inventories(legacy, vnext), ensure_ascii=False, indent=2)) + return 0 + + +if __name__ == "__main__": + import sys + raise SystemExit(main(sys.argv[1])) diff --git a/tests/test_ocr_figures.py b/tests/test_ocr_figures.py index 48307bdc..3d23a6aa 100644 --- a/tests/test_ocr_figures.py +++ b/tests/test_ocr_figures.py @@ -6424,3 +6424,38 @@ def test_recover_internal_figure_number_inside_overlap_gate(): # Line that covers >15% of asset -> edge_band_score = 0 large_line = [100, 100, 500, 600] # covers large area assert _asset_edge_band_score(large_line, asset) == 0.0 + + +# --- Task 1: Legacy/vnext mechanical split --- + + +def test_build_figure_inventory_wrapper_stays_legacy_path(monkeypatch): + from paperforge.worker import ocr_figures + + called = {"legacy": 0, "vnext": 0} + + def fake_legacy(*args, **kwargs): + called["legacy"] += 1 + return {"source": "legacy"} + + def fake_vnext(*args, **kwargs): + called["vnext"] += 1 + return {"source": "vnext"} + + monkeypatch.setattr(ocr_figures, "build_figure_inventory_legacy", fake_legacy) + monkeypatch.setattr(ocr_figures, "build_figure_inventory_vnext", fake_vnext) + + result = ocr_figures.build_figure_inventory([], 1200) + + assert result == {"source": "legacy"} + assert called == {"legacy": 1, "vnext": 0} + + +def test_vnext_entrypoint_is_callable_without_cutover(): + from paperforge.worker.ocr_figures import build_figure_inventory_vnext + + result = build_figure_inventory_vnext([], 1200) + + assert isinstance(result, dict) + assert result.get("pipeline_mode") == "vnext" + assert result.get("matched_figures") == []