mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
feat: surface OCR version runtime state in status and doctor
This commit is contained in:
parent
5efb4435b4
commit
0c3ecb2bc3
4 changed files with 169 additions and 0 deletions
|
|
@ -89,6 +89,31 @@ def _diagnose(vault: Path, live: bool = False, json_output: bool = False) -> int
|
|||
level = result.get("level", 0)
|
||||
passed = result.get("passed", False)
|
||||
|
||||
# Collect OCR version state
|
||||
_version_state_summary: dict = {"total_papers": 0, "derived_stale": [], "raw_upgradable": []}
|
||||
try:
|
||||
from paperforge.config import load_vault_config
|
||||
cfg = load_vault_config(vault)
|
||||
ocr_root = vault / cfg["system_dir"] / "PaperForge" / "ocr"
|
||||
if ocr_root.exists():
|
||||
from paperforge.worker._utils import read_json
|
||||
_version_papers = []
|
||||
for paper_dir in ocr_root.iterdir():
|
||||
if not paper_dir.is_dir():
|
||||
continue
|
||||
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:
|
||||
_version_papers.append(meta)
|
||||
_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")],
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if json_output:
|
||||
queue_data = _collect_ocr_queue_data(vault)
|
||||
structured_health = _collect_ocr_health_summary(vault)
|
||||
|
|
@ -118,6 +143,7 @@ def _diagnose(vault: Path, live: bool = False, json_output: bool = False) -> int
|
|||
"message": result.get("message", result.get("error", "")),
|
||||
},
|
||||
"structured_health": structured_health,
|
||||
"ocr_version_state": _version_state_summary,
|
||||
**queue_data,
|
||||
},
|
||||
error=pf_error,
|
||||
|
|
@ -147,6 +173,19 @@ def _diagnose(vault: Path, live: bool = False, json_output: bool = False) -> int
|
|||
f"{entry['figure_count']} figures, {entry['table_count']} tables"
|
||||
)
|
||||
|
||||
if _version_state_summary["total_papers"] > 0:
|
||||
print()
|
||||
print("--- OCR Version State ---")
|
||||
print(f" ocr_version_state: {_version_state_summary['total_papers']} paper(s)")
|
||||
if _version_state_summary["derived_stale"]:
|
||||
print(f" derived_stale: {len(_version_state_summary['derived_stale'])} paper(s)")
|
||||
for k in _version_state_summary["derived_stale"]:
|
||||
print(f" - {k}")
|
||||
if _version_state_summary["raw_upgradable"]:
|
||||
print(f" raw_upgradable: {len(_version_state_summary['raw_upgradable'])} paper(s)")
|
||||
for k in _version_state_summary["raw_upgradable"]:
|
||||
print(f" - {k}")
|
||||
|
||||
return 0 if passed else 1
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1114,6 +1114,36 @@ def run_status(vault: Path, verbose: bool = False, json_output: bool = False) ->
|
|||
from paperforge.commands.ocr import _collect_ocr_health_summary as _collect_health
|
||||
_structured_health = _collect_health(vault)
|
||||
|
||||
# OCR version state
|
||||
_ocr_version_state = {
|
||||
"total_papers": 0, "derived_stale_count": 0, "raw_upgradable_count": 0,
|
||||
"derived_stale_keys": [], "raw_upgrade_keys": [],
|
||||
}
|
||||
try:
|
||||
ocr_root = vault / cfg["system_dir"] / "PaperForge" / "ocr"
|
||||
if ocr_root.exists():
|
||||
papers = []
|
||||
for paper_dir in ocr_root.iterdir():
|
||||
if not paper_dir.is_dir():
|
||||
continue
|
||||
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:
|
||||
papers.append(meta)
|
||||
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"))
|
||||
_ocr_version_state["raw_upgradable_count"] = sum(1 for m in papers if m.get("raw_upgradable"))
|
||||
_ocr_version_state["derived_stale_keys"] = [
|
||||
m.get("zotero_key", "?") for m in papers if m.get("derived_stale")
|
||||
]
|
||||
_ocr_version_state["raw_upgrade_keys"] = [
|
||||
m.get("zotero_key", "?") for m in papers if m.get("raw_upgradable")
|
||||
]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if json_output:
|
||||
payload = {
|
||||
"version": __import__("paperforge").__version__,
|
||||
|
|
@ -1133,6 +1163,7 @@ def run_status(vault: Path, verbose: bool = False, json_output: bool = False) ->
|
|||
"failed": ocr_failed,
|
||||
},
|
||||
"structured_ocr_health": _structured_health,
|
||||
"ocr_version_state": _ocr_version_state,
|
||||
"path_errors": path_error_count,
|
||||
"env_configured": len(env_found) > 0,
|
||||
# Phase 25: lifecycle/health/maturity from canonical index ({} when unavailable)
|
||||
|
|
@ -1187,6 +1218,14 @@ def run_status(vault: Path, verbose: bool = False, json_output: bool = False) ->
|
|||
f"{entry['page_count']} pages, {entry['blocks_count']} blocks, "
|
||||
f"{entry['figure_count']} figures, {entry['table_count']} tables"
|
||||
)
|
||||
if _ocr_version_state["total_papers"] > 0:
|
||||
stale = _ocr_version_state["derived_stale_count"]
|
||||
upgradable = _ocr_version_state["raw_upgradable_count"]
|
||||
print(f"- ocr_version_state: {_ocr_version_state['total_papers']} paper(s)")
|
||||
if stale:
|
||||
print(f" derived_stale: {stale} (auto-rebuilt on sync)")
|
||||
if upgradable:
|
||||
print(f" raw_upgradable: {upgradable} (run `paperforge ocr redo` to upgrade)")
|
||||
print(f"- path_errors: {path_error_count}")
|
||||
if path_error_count > 0:
|
||||
print(" Tip: Run `paperforge repair --fix-paths` to attempt resolution")
|
||||
|
|
|
|||
|
|
@ -224,3 +224,36 @@ def test_doctor_reads_structured_ocr_health(tmp_path: Path, capsys) -> None:
|
|||
assert "HLTH001" in captured.out
|
||||
assert "3 figures" in captured.out
|
||||
assert "2 tables" in captured.out
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OCR version state in _diagnose()
|
||||
# ---------------------------------------------------------------------------
|
||||
def test_doctor_mentions_version_state(tmp_path: Path, capsys) -> None:
|
||||
"""_diagnose() output includes OCR version state."""
|
||||
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" / "DOC001"
|
||||
ocr_dir.mkdir(parents=True)
|
||||
meta = {
|
||||
"zotero_key": "DOC001",
|
||||
"raw_version": {"ocr_model": "PaddleOCR-VL-1.5", "ocr_raw_schema_version": "1.0.0"},
|
||||
"derived_version": {"renderer_version": "1.0.0-compat"},
|
||||
"raw_upgradable": True,
|
||||
"derived_stale": True,
|
||||
}
|
||||
(ocr_dir / "meta.json").write_text(json.dumps(meta), 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()
|
||||
assert exit_code == 0
|
||||
assert "raw_upgradable" in captured.out or "version" in captured.out or "stale" in captured.out
|
||||
|
|
|
|||
|
|
@ -300,3 +300,61 @@ def test_status_text_structured_ocr_health(tmp_path: Path, capsys) -> None:
|
|||
assert "structured_ocr_health" in captured
|
||||
assert "HLTH001" in captured
|
||||
assert "yellow" in captured
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OCR version runtime state in status
|
||||
# ---------------------------------------------------------------------------
|
||||
def test_status_json_includes_ocr_version_state(tmp_path: Path, capsys) -> None:
|
||||
"""JSON output includes ocr_version_state with version info."""
|
||||
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" / "VST001"
|
||||
ocr_dir.mkdir(parents=True)
|
||||
meta = {
|
||||
"zotero_key": "VST001",
|
||||
"raw_version": {"ocr_model": "PaddleOCR-VL-1.6", "ocr_raw_schema_version": "1.0.0"},
|
||||
"derived_version": {"renderer_version": "2.0.0"},
|
||||
"raw_upgradable": False,
|
||||
"derived_stale": False,
|
||||
}
|
||||
(ocr_dir / "meta.json").write_text(json.dumps(meta), encoding="utf-8")
|
||||
|
||||
code = run_status(vault, json_output=True)
|
||||
assert code == 0
|
||||
captured = capsys.readouterr().out
|
||||
envelope = json.loads(captured)
|
||||
payload = envelope["data"]
|
||||
assert "ocr_version_state" in payload
|
||||
assert payload["ocr_version_state"]["total_papers"] == 1
|
||||
assert payload["ocr_version_state"]["derived_stale_count"] == 0
|
||||
assert payload["ocr_version_state"]["raw_upgradable_count"] == 0
|
||||
|
||||
|
||||
def test_status_text_ocr_version_state(tmp_path: Path, capsys) -> None:
|
||||
"""Text output includes ocr_version_state line."""
|
||||
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" / "VST001"
|
||||
ocr_dir.mkdir(parents=True)
|
||||
meta = {
|
||||
"zotero_key": "VST001",
|
||||
"raw_version": {"ocr_model": "PaddleOCR-VL-1.5", "ocr_raw_schema_version": "1.0.0"},
|
||||
"derived_version": {"renderer_version": "1.0.0-compat"},
|
||||
"raw_upgradable": True,
|
||||
"derived_stale": 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 "ocr_version_state" in captured
|
||||
|
|
|
|||
Loading…
Reference in a new issue