diff --git a/src/didactopus/pack_to_frontend.py b/src/didactopus/pack_to_frontend.py index 4d3b14b..39ccd78 100644 --- a/src/didactopus/pack_to_frontend.py +++ b/src/didactopus/pack_to_frontend.py @@ -5,12 +5,33 @@ import argparse, json, yaml def load_yaml(path: Path) -> dict: return yaml.safe_load(path.read_text(encoding="utf-8")) or {} + +def _load_json(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) if path.exists() else {} + + +def _notebook_summary(pack_dir: Path) -> dict: + bundle = _load_json(pack_dir / "groundrecall_query_bundle.json") + page = _load_json(pack_dir / "notebook_page.json") + concept = page.get("concept", {}) or bundle.get("concept", {}) or {} + return { + "available": bool(bundle or page), + "conceptId": concept.get("concept_id", ""), + "conceptTitle": concept.get("title", ""), + "claimCount": len(bundle.get("relevant_claims", []) or []), + "sourceRoleSummary": page.get("source_role_summary", {}) or bundle.get("source_role_summary", {}) or {}, + "distinctionCount": len(page.get("distinctions", []) or bundle.get("key_distinctions", []) or []), + "supportingObservationCount": (page.get("summary", {}) or {}).get("supporting_observation_count", 0), + "relatedConceptCount": (page.get("summary", {}) or {}).get("related_concept_count", 0), + } + def convert_pack(pack_dir: str | Path) -> dict: pack_dir = Path(pack_dir) pack = load_yaml(pack_dir / "pack.yaml") concepts_data = load_yaml(pack_dir / "concepts.yaml") compliance_path = pack_dir / "pack_compliance_manifest.json" compliance = json.loads(compliance_path.read_text(encoding="utf-8")) if compliance_path.exists() else {} + notebook = _notebook_summary(pack_dir) concepts = [] for item in concepts_data.get("concepts", []) or []: @@ -39,6 +60,7 @@ def convert_pack(pack_dir: str | Path) -> dict: "level": pack.get("audience_level", "novice-friendly"), "concepts": concepts, "onboarding": onboarding, + "notebook": notebook, "compliance": { "sources": len(compliance.get("derived_from_sources", []) or []), "attributionRequired": bool(compliance.get("attribution_required", False)), diff --git a/tests/test_pack_export.py b/tests/test_pack_export.py index 85670f5..2808f49 100644 --- a/tests/test_pack_export.py +++ b/tests/test_pack_export.py @@ -15,3 +15,33 @@ def test_convert_pack(tmp_path: Path): payload = convert_pack(tmp_path) assert payload["id"] == "p1" assert payload["concepts"][0]["id"] == "c1" + assert payload["notebook"]["available"] is False + + +def test_convert_pack_surfaces_notebook_summary(tmp_path: Path): + (tmp_path / "pack.yaml").write_text( + "name: p2\ndisplay_name: Pack 2\ndescription: Demo pack with notebook\n", encoding="utf-8" + ) + (tmp_path / "concepts.yaml").write_text( + "concepts:\n - id: c2\n title: Concept 2\n prerequisites: []\n", encoding="utf-8" + ) + (tmp_path / "pack_compliance_manifest.json").write_text( + '{"derived_from_sources":["s1"],"attribution_required":true,"share_alike_required":false,"noncommercial_only":false,"restrictive_flags":[]}', + encoding="utf-8" + ) + (tmp_path / "groundrecall_query_bundle.json").write_text( + '{"bundle_kind":"groundrecall_query_bundle","concept":{"concept_id":"concept::thermo","title":"Thermodynamics and Entropy"},"relevant_claims":[{"claim_id":"c1"},{"claim_id":"c2"}],"source_role_summary":{"overview":1},"key_distinctions":[{"distinction_type":"non_implication"}]}', + encoding="utf-8", + ) + (tmp_path / "notebook_page.json").write_text( + '{"concept":{"concept_id":"concept::thermo","title":"Thermodynamics and Entropy"},"summary":{"supporting_observation_count":3,"related_concept_count":2},"source_role_summary":{"overview":1},"distinctions":[{"distinction_type":"non_implication"}]}', + encoding="utf-8", + ) + + payload = convert_pack(tmp_path) + assert payload["notebook"]["available"] is True + assert payload["notebook"]["conceptTitle"] == "Thermodynamics and Entropy" + assert payload["notebook"]["claimCount"] == 2 + assert payload["notebook"]["sourceRoleSummary"]["overview"] == 1 + assert payload["notebook"]["distinctionCount"] == 1 + assert payload["notebook"]["supportingObservationCount"] == 3