feat: surface legacy OCR backfill state in doctor and status diagnostics

This commit is contained in:
Research Assistant 2026-06-05 13:00:35 +08:00
parent 7f19dd99ae
commit 491f09854d
4 changed files with 71 additions and 3 deletions

View file

@ -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

View file

@ -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")

View file

@ -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"

View file

@ -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