swarm repositories / source
aboutsummaryrefslogtreecommitdiff
path: root/noo-harvest/scripts/paper_prepare.py
blob: 3dc8329e9dec627acca160acee854ad2cd4deba7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = "==3.13.*"
# dependencies = []
# ///

from __future__ import annotations

import argparse
import os
import subprocess
import sys
from pathlib import Path


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Run the noo-harvest paper extraction and phase-2 verification pipeline."
    )
    parser.add_argument("input_pdf")
    parser.add_argument("output_dir")
    parser.add_argument("--model-path", help="Explicit local DeepSeek-OCR-2 snapshot for paper_ocr.py")
    parser.add_argument("--ocr-python", help="Explicit Python interpreter for the OCR runtime")
    parser.add_argument("--pages", help="Comma-separated 1-based pages and ranges, e.g. 1,3-5")
    parser.add_argument(
        "--extract-only",
        action="store_true",
        help="Run OCR extraction only; do not invoke phase-2 verification",
    )
    parser.add_argument(
        "--verify-only",
        action="store_true",
        help="Run phase-2 verification from an existing extracted/document.blocks.json",
    )
    parser.add_argument(
        "--force-extract",
        action="store_true",
        help="Rerun OCR extraction even if extracted/document.blocks.json already exists",
    )
    return parser.parse_args()


def run_step(argv: list[str], *, cwd: Path | None = None) -> None:
    subprocess.run(argv, check=True, cwd=cwd)


def discover_ocr_python(explicit: str | None) -> str:
    candidates: list[Path] = []
    if explicit:
        candidates.append(Path(explicit).expanduser())

    env_python = os.environ.get("NOO_HARVEST_OCR_PYTHON")
    if env_python:
        candidates.append(Path(env_python).expanduser())

    repo_root = Path(__file__).resolve().parents[2]
    candidates.append(repo_root / ".tmp/deepseek-ocr2/.venv/bin/python")

    for candidate in candidates:
        if candidate.is_file():
            return str(candidate)

    return sys.executable


def main() -> int:
    args = parse_args()
    if args.extract_only and args.verify_only:
        raise SystemExit("--extract-only and --verify-only are mutually exclusive")

    script_root = Path(__file__).resolve().parent
    trusted_root = script_root.parents[1]
    input_pdf = Path(args.input_pdf).expanduser().resolve()
    output_dir = Path(args.output_dir).expanduser().resolve()
    extracted_dir = output_dir / "extracted"
    verified_dir = output_dir / "verified"
    document_path = extracted_dir / "document.blocks.json"
    ocr_python = discover_ocr_python(args.ocr_python)

    if not args.verify_only and (args.force_extract or not document_path.is_file()):
        ocr_cmd = [ocr_python, str(script_root / "paper_ocr.py"), str(input_pdf), str(extracted_dir)]
        if args.model_path:
            ocr_cmd.extend(["--model-path", args.model_path])
        if args.pages:
            ocr_cmd.extend(["--pages", args.pages])
        run_step(ocr_cmd, cwd=trusted_root)
    elif not document_path.is_file():
        raise SystemExit(f"missing extracted ledger at {document_path}; rerun without --verify-only")

    if args.extract_only:
        return 0

    verify_cmd = [sys.executable, str(script_root / "paper_verify.py"), str(extracted_dir), str(verified_dir)]
    if args.pages:
        verify_cmd.extend(["--pages", args.pages])
    run_step(verify_cmd, cwd=trusted_root)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())