lllin000_PaperForge/paperforge/worker/ocr_artifacts.py

109 lines
3.3 KiB
Python

from __future__ import annotations
import hashlib
import json
from dataclasses import dataclass
from pathlib import Path
@dataclass(frozen=True)
class OCRArtifactPaths:
paper_root: Path
meta_json: Path
result_json: Path
compat_fulltext: Path
raw_meta: Path
source_metadata: Path
blocks_raw: Path
blocks_structured: Path
def artifact_paths_for_root(ocr_root: Path, zotero_key: str) -> OCRArtifactPaths:
paper_root = ocr_root / zotero_key
return OCRArtifactPaths(
paper_root=paper_root,
meta_json=paper_root / "meta.json",
result_json=paper_root / "json" / "result.json",
compat_fulltext=paper_root / "fulltext.md",
raw_meta=paper_root / "raw" / "raw_meta.json",
source_metadata=paper_root / "raw" / "source_metadata.json",
blocks_raw=paper_root / "canonical" / "blocks.raw.jsonl",
blocks_structured=paper_root / "structure" / "blocks.structured.jsonl",
)
def artifact_paths_for_key(vault: Path, zotero_key: str) -> OCRArtifactPaths:
from paperforge.worker._utils import pipeline_paths
paths = pipeline_paths(vault)
return artifact_paths_for_root(paths["ocr"], zotero_key)
def build_version_payload(
*,
pdf_fingerprint: str,
result_json_hash: str,
ocr_model: str,
) -> dict:
from paperforge.worker.ocr_versions import expected_derived_payload, expected_raw_payload
return {
"raw_version": {
**expected_raw_payload(ocr_model=ocr_model),
"pdf_fingerprint": pdf_fingerprint,
"result_json_hash": result_json_hash,
},
"derived_version": {
**expected_derived_payload(),
},
}
def _sha256_hexdigest(data: bytes) -> str:
return "sha256:" + hashlib.sha256(data).hexdigest()
def compute_pdf_fingerprint(pdf_path: Path) -> str:
try:
return _sha256_hexdigest(pdf_path.read_bytes())
except (FileNotFoundError, OSError):
return "unknown"
def compute_json_hash(data: list | dict) -> str:
if isinstance(data, list):
data = {"data": data}
return _sha256_hexdigest(json.dumps(data, sort_keys=True).encode("utf-8"))
def cleanup_ocr_artifact_cache(paper_root: Path, *, dry_run: bool = False) -> dict[str, Any]:
"""Remove regenerable cache artifacts while preserving canonical data.
Canonical (kept):
canonical/, structure/, metadata/, assets/, render/, health/, index/
raw/, meta.json, fulltext.md
Cache (removed):
pages/ — page render cache, always regenerable from source PDF
Returns a summary dict with paths removed per category.
"""
report: dict[str, Any] = {"pages_removed": [], "errors": []}
pages_dir = paper_root / "pages"
if pages_dir.is_dir():
for f in sorted(pages_dir.iterdir()):
if f.suffix in {".jpg", ".png", ".webp"}:
if not dry_run:
try:
f.unlink()
except OSError as e:
report["errors"].append(str(e))
report["pages_removed"].append(f.name)
if not dry_run:
try:
remaining = list(pages_dir.iterdir())
if not remaining:
pages_dir.rmdir()
except OSError:
pass
return report