diff --git a/paperforge/commands/ocr.py b/paperforge/commands/ocr.py index 086a0aca..03c292fe 100644 --- a/paperforge/commands/ocr.py +++ b/paperforge/commands/ocr.py @@ -90,7 +90,7 @@ def _diagnose(vault: Path, live: bool = False, json_output: bool = False) -> int passed = result.get("passed", False) # Collect OCR version state - _version_state_summary: dict = {"total_papers": 0, "derived_stale": [], "raw_upgradable": []} + _version_state_summary: dict = {"total_papers": 0, "derived_stale": [], "raw_upgradable": [], "legacy_backfilled": []} try: from paperforge.config import load_vault_config cfg = load_vault_config(vault) @@ -104,12 +104,16 @@ def _diagnose(vault: Path, live: bool = False, json_output: bool = False) -> int meta_path = paper_dir / "meta.json" if meta_path.exists(): meta = read_json(meta_path) - if "raw_version" in meta or "derived_version" in meta: + has_state = "raw_version" in meta or "derived_version" in meta + if has_state: _version_papers.append(meta) + elif meta.get("ocr_status") == "done" and meta.get("is_backfilled"): + _version_papers.append({**meta, "is_legacy": True}) _version_state_summary = { "total_papers": len(_version_papers), "derived_stale": [m.get("zotero_key", "?") for m in _version_papers if m.get("derived_stale")], "raw_upgradable": [m.get("zotero_key", "?") for m in _version_papers if m.get("raw_upgradable")], + "legacy_backfilled": [m.get("zotero_key", "?") for m in _version_papers if m.get("is_legacy")], } except Exception: pass @@ -185,6 +189,10 @@ def _diagnose(vault: Path, live: bool = False, json_output: bool = False) -> int print(f" raw_upgradable: {len(_version_state_summary['raw_upgradable'])} paper(s)") for k in _version_state_summary["raw_upgradable"]: print(f" - {k}") + if _version_state_summary.get("legacy_backfilled"): + print(f" legacy_backfilled: {len(_version_state_summary['legacy_backfilled'])} paper(s)") + for k in _version_state_summary["legacy_backfilled"]: + print(f" - {k}") return 0 if passed else 1 diff --git a/paperforge/worker/status.py b/paperforge/worker/status.py index 9cdcc135..23b67bb8 100644 --- a/paperforge/worker/status.py +++ b/paperforge/worker/status.py @@ -1118,6 +1118,7 @@ def run_status(vault: Path, verbose: bool = False, json_output: bool = False) -> _ocr_version_state = { "total_papers": 0, "derived_stale_count": 0, "raw_upgradable_count": 0, "derived_stale_keys": [], "raw_upgrade_keys": [], + "legacy_backfilled_count": 0, "legacy_backfilled_keys": [], } try: ocr_root = vault / cfg["system_dir"] / "PaperForge" / "ocr" @@ -1129,8 +1130,11 @@ def run_status(vault: Path, verbose: bool = False, json_output: bool = False) -> meta_path = paper_dir / "meta.json" if meta_path.exists(): meta = read_json(meta_path) - if "raw_version" in meta or "derived_version" in meta: + has_state = "raw_version" in meta or "derived_version" in meta + if has_state: papers.append(meta) + elif meta.get("ocr_status") == "done" and meta.get("is_backfilled"): + papers.append({**meta, "is_legacy_backfilled": True}) if papers: _ocr_version_state["total_papers"] = len(papers) _ocr_version_state["derived_stale_count"] = sum(1 for m in papers if m.get("derived_stale")) @@ -1141,6 +1145,10 @@ def run_status(vault: Path, verbose: bool = False, json_output: bool = False) -> _ocr_version_state["raw_upgrade_keys"] = [ m.get("zotero_key", "?") for m in papers if m.get("raw_upgradable") ] + _ocr_version_state["legacy_backfilled_count"] = sum(1 for m in papers if m.get("is_legacy_backfilled")) + _ocr_version_state["legacy_backfilled_keys"] = [ + m.get("zotero_key", "?") for m in papers if m.get("is_legacy_backfilled") + ] except Exception: pass @@ -1226,6 +1234,9 @@ def run_status(vault: Path, verbose: bool = False, json_output: bool = False) -> print(f" derived_stale: {stale} (auto-rebuilt on sync)") if upgradable: print(f" raw_upgradable: {upgradable} (run `paperforge ocr redo` to upgrade)") + legacy_count = _ocr_version_state.get("legacy_backfilled_count", 0) + if legacy_count: + print(f" legacy_backfilled: {legacy_count} (backfilled from old OCR result)") print(f"- path_errors: {path_error_count}") if path_error_count > 0: print(" Tip: Run `paperforge repair --fix-paths` to attempt resolution") diff --git a/tests/test_ocr_doctor.py b/tests/test_ocr_doctor.py index 1649494f..a788edc4 100644 --- a/tests/test_ocr_doctor.py +++ b/tests/test_ocr_doctor.py @@ -229,6 +229,32 @@ def test_doctor_reads_structured_ocr_health(tmp_path: Path, capsys) -> None: # --------------------------------------------------------------------------- # OCR version state in _diagnose() # --------------------------------------------------------------------------- +def test_doctor_shows_legacy_backfilled_papers(tmp_path: Path, capsys) -> None: + """_diagnose() output includes legacy_backfilled for papers with is_backfilled flag.""" + vault = tmp_path / "vault" + vault.mkdir() + (vault / "paperforge.json").write_text( + json.dumps({"vault_config": {"system_dir": "System", "resources_dir": "Resources"}}), + encoding="utf-8", + ) + ocr_dir = vault / "System" / "PaperForge" / "ocr" / "LEGACY001" + ocr_dir.mkdir(parents=True) + (ocr_dir / "meta.json").write_text( + json.dumps({"zotero_key": "LEGACY001", "ocr_status": "done", "is_backfilled": True}), + encoding="utf-8", + ) + + from paperforge.commands.ocr import _diagnose + + with patch("paperforge.ocr_diagnostics.ocr_doctor", + return_value={"level": 3, "passed": True, "message": "All good"}): + exit_code = _diagnose(vault, live=False) + + captured = capsys.readouterr().out + assert exit_code == 0 + assert "legacy_backfilled" in captured + + def test_doctor_mentions_version_state(tmp_path: Path, capsys) -> None: """_diagnose() output includes OCR version state.""" vault = tmp_path / "vault" diff --git a/tests/test_status.py b/tests/test_status.py index f5be7445..c5e4fefa 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -358,3 +358,26 @@ def test_status_text_ocr_version_state(tmp_path: Path, capsys) -> None: assert code == 0 captured = capsys.readouterr().out assert "ocr_version_state" in captured + + +def test_status_text_legacy_backfilled(tmp_path: Path, capsys) -> None: + """Text output includes legacy_backfilled count when papers are backfilled.""" + from paperforge.worker.status import run_status + + vault = _minimal_vault(tmp_path) + _ensure_domain_config(vault) + _ensure_exports(vault) + + ocr_dir = vault / "99_System" / "PaperForge" / "ocr" / "LEGACY001" + ocr_dir.mkdir(parents=True) + meta = { + "zotero_key": "LEGACY001", + "ocr_status": "done", + "is_backfilled": True, + } + (ocr_dir / "meta.json").write_text(json.dumps(meta), encoding="utf-8") + + code = run_status(vault) + assert code == 0 + captured = capsys.readouterr().out + assert "legacy_backfilled" in captured