From 3a83fbb2cfdba8dc0e61cf88383083984f209fc5 Mon Sep 17 00:00:00 2001 From: welsberr Date: Tue, 28 Apr 2026 00:22:39 -0400 Subject: [PATCH] Add direct GroundRecall-backed Notebook export --- src/didactopus/main.py | 17 +++++++++++++++++ src/didactopus/notebook_page.py | 26 ++++++++++++++++++++++++++ tests/test_main_cli.py | 30 ++++++++++++++++++++++++++++++ tests/test_notebook_page.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+) diff --git a/src/didactopus/main.py b/src/didactopus/main.py index 3295326..f4e607f 100644 --- a/src/didactopus/main.py +++ b/src/didactopus/main.py @@ -8,6 +8,7 @@ from .config import load_config from .doclift_bundle_demo import run_doclift_bundle_demo from .groundrecall_pack_bridge import run_doclift_bundle_with_groundrecall from .notebook_page import export_notebook_page_from_groundrecall_bundle +from .notebook_page import export_notebook_page_from_groundrecall_store from .review_loader import load_draft_pack from .review_schema import ReviewSession, ReviewAction from .review_actions import apply_action @@ -56,6 +57,14 @@ def build_parser() -> argparse.ArgumentParser: ) notebook_parser.add_argument("groundrecall_query_bundle") notebook_parser.add_argument("output_path") + + notebook_gr_parser = subparsers.add_parser( + "notebook-page-groundrecall", + help="Build a Notebook page and query bundle directly from a GroundRecall concept", + ) + notebook_gr_parser.add_argument("groundrecall_store_dir") + notebook_gr_parser.add_argument("groundrecall_concept_ref") + notebook_gr_parser.add_argument("output_dir") return parser @@ -135,4 +144,12 @@ def main() -> None: ) print(summary) return + if args.command == "notebook-page-groundrecall": + summary = export_notebook_page_from_groundrecall_store( + args.groundrecall_store_dir, + args.groundrecall_concept_ref, + args.output_dir, + ) + print(summary) + return build_parser().print_help() diff --git a/src/didactopus/notebook_page.py b/src/didactopus/notebook_page.py index bd6bfde..93ec964 100644 --- a/src/didactopus/notebook_page.py +++ b/src/didactopus/notebook_page.py @@ -2,6 +2,7 @@ from __future__ import annotations import json from pathlib import Path +import sys from typing import Any @@ -203,3 +204,28 @@ def export_notebook_page_from_groundrecall_bundle(bundle_path: str | Path, out_p target.parent.mkdir(parents=True, exist_ok=True) target.write_text(json.dumps(page, indent=2), encoding="utf-8") return {"page_path": str(target), "page": page} + + +def export_notebook_page_from_groundrecall_store( + store_dir: str | Path, + concept_ref: str, + out_dir: str | Path, +) -> dict[str, Any]: + export_groundrecall_query_bundle = _load_groundrecall_export() + target = Path(out_dir) + target.mkdir(parents=True, exist_ok=True) + exported = export_groundrecall_query_bundle(store_dir, concept_ref, target) + page_path = target / "notebook_page.json" + page_result = export_notebook_page_from_groundrecall_bundle(exported["bundle_path"], page_path) + page_result["groundrecall_query_bundle_path"] = exported["bundle_path"] + page_result["concept_ref"] = concept_ref + return page_result + + +def _load_groundrecall_export(): + groundrecall_src = Path("/home/netuser/bin/GroundRecall/src") + if groundrecall_src.exists(): + sys.path.insert(0, str(groundrecall_src)) + from groundrecall.export import export_groundrecall_query_bundle # type: ignore + + return export_groundrecall_query_bundle diff --git a/tests/test_main_cli.py b/tests/test_main_cli.py index 8c9ed3f..bd78c60 100755 --- a/tests/test_main_cli.py +++ b/tests/test_main_cli.py @@ -124,3 +124,33 @@ def test_main_notebook_page_subcommand(monkeypatch, capsys, tmp_path: Path) -> N assert captured["bundle_path"].endswith("groundrecall_query_bundle.json") assert captured["out_path"].endswith("notebook_page.json") assert "page_path" in out + + +def test_main_notebook_page_groundrecall_subcommand(monkeypatch, capsys, tmp_path: Path) -> None: + captured: dict = {} + + def _fake_export_notebook_page_from_groundrecall_store(store_dir, concept_ref, out_dir): + captured["store_dir"] = str(store_dir) + captured["concept_ref"] = concept_ref + captured["out_dir"] = str(out_dir) + return {"page_path": str(Path(out_dir) / "notebook_page.json")} + + monkeypatch.setattr(main_module, "export_notebook_page_from_groundrecall_store", _fake_export_notebook_page_from_groundrecall_store) + monkeypatch.setattr( + main_module.sys, + "argv", + [ + "didactopus", + "notebook-page-groundrecall", + str(tmp_path / "store"), + "natural-selection", + str(tmp_path / "out"), + ], + ) + + main_module.main() + out = capsys.readouterr().out + + assert captured["concept_ref"] == "natural-selection" + assert captured["out_dir"].endswith("out") + assert "page_path" in out diff --git a/tests/test_notebook_page.py b/tests/test_notebook_page.py index d00289a..46a4e75 100644 --- a/tests/test_notebook_page.py +++ b/tests/test_notebook_page.py @@ -6,6 +6,7 @@ from pathlib import Path from didactopus.notebook_page import ( build_notebook_page_from_groundrecall_bundle, export_notebook_page_from_groundrecall_bundle, + export_notebook_page_from_groundrecall_store, ) @@ -111,3 +112,31 @@ def test_export_notebook_page_writes_json(tmp_path: Path) -> None: assert payload["page_path"].endswith("notebook_page.json") written = json.loads(out_path.read_text(encoding="utf-8")) assert written["concept"]["concept_id"] == "concept::natural-selection" + + +def test_export_notebook_page_from_groundrecall_store_writes_bundle_and_page(monkeypatch, tmp_path: Path) -> None: + captured: dict = {} + + def _fake_export(store_dir, concept_ref, out_dir): + out_dir = Path(out_dir) + out_dir.mkdir(parents=True, exist_ok=True) + bundle_path = out_dir / "groundrecall_query_bundle.json" + bundle_path.write_text(json.dumps(_sample_bundle()), encoding="utf-8") + captured["store_dir"] = str(store_dir) + captured["concept_ref"] = concept_ref + captured["out_dir"] = str(out_dir) + return {"bundle_path": str(bundle_path), "bundle": _sample_bundle()} + + monkeypatch.setattr("didactopus.notebook_page._load_groundrecall_export", lambda: _fake_export) + + payload = export_notebook_page_from_groundrecall_store( + tmp_path / "store", + "natural-selection", + tmp_path / "out", + ) + + assert captured["concept_ref"] == "natural-selection" + assert (tmp_path / "out" / "groundrecall_query_bundle.json").exists() + assert (tmp_path / "out" / "notebook_page.json").exists() + assert payload["concept_ref"] == "natural-selection" + assert payload["groundrecall_query_bundle_path"].endswith("groundrecall_query_bundle.json")