From c3ebe716dfa25c833d899bf80159de4868667485 Mon Sep 17 00:00:00 2001 From: welsberr Date: Fri, 8 May 2026 14:05:24 -0400 Subject: [PATCH] Expose Notebook source roles and distinctions --- src/didactopus/notebook_page.py | 18 +++++++++++++++++ tests/test_notebook_page.py | 35 +++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/didactopus/notebook_page.py b/src/didactopus/notebook_page.py index 45d3837..6b82b5f 100644 --- a/src/didactopus/notebook_page.py +++ b/src/didactopus/notebook_page.py @@ -82,10 +82,21 @@ def _review_context(bundle: dict[str, Any]) -> dict[str, Any]: qualification_candidates = 0 constraint_candidates = 0 quote_candidates = 0 + distinction_candidates = [] for claim in claims: metadata = claim.get("metadata", {}) or {} text = str(claim.get("claim_text", "")).strip() lowered = text.lower() + distinction = claim.get("distinction") + if isinstance(distinction, dict): + distinction_candidates.append( + { + "claim_id": distinction.get("claim_id", claim.get("claim_id", "")), + "distinction_type": distinction.get("distinction_type", ""), + "cue": distinction.get("cue", ""), + "text": distinction.get("text", text), + } + ) if metadata.get("definition_candidate") or any(token in lowered for token in (" defined as ", " refers to ", " means ", " describes ")): definition_candidates += 1 if metadata.get("qualification_candidate") or any(token in lowered for token in ("however", "although", "unless", "only if", "may not", "does not always")): @@ -104,6 +115,8 @@ def _review_context(bundle: dict[str, Any]) -> dict[str, Any]: "constraint_candidates": constraint_candidates, "quote_candidates": quote_candidates, }, + "source_role_summary": bundle.get("source_role_summary", {}) or {}, + "key_distinctions": distinction_candidates[:6] or (bundle.get("key_distinctions", []) or [])[:6], "public_output_policy": { "quotes_require_attribution": True, "public_prose_should_be_paraphrastic": True, @@ -130,6 +143,7 @@ def _supporting_sources(bundle: dict[str, Any]) -> list[dict[str, Any]]: "title": artifact.get("title", ""), "path": path, "artifact_kind": artifact.get("artifact_kind", ""), + "source_role": artifact.get("source_role", ""), "supporting_observation_count": by_origin.get(path, 0), } ) @@ -214,8 +228,12 @@ def build_notebook_page_from_groundrecall_bundle(bundle: dict[str, Any]) -> dict "claim_count": len(bundle.get("relevant_claims", []) or []), "supporting_observation_count": len(supporting_observations), "related_concept_count": len(bundle.get("related_concepts", []) or []), + "source_role_count": len(bundle.get("source_role_summary", {}) or {}), + "distinction_count": len(bundle.get("key_distinctions", []) or []), }, "graph_navigation": navigation, + "source_role_summary": bundle.get("source_role_summary", {}) or {}, + "distinctions": (bundle.get("key_distinctions", []) or [])[:6], "supporting_sources": _supporting_sources(bundle), "supporting_excerpts": supporting_excerpts, "review_context": _review_context(bundle), diff --git a/tests/test_notebook_page.py b/tests/test_notebook_page.py index 46a4e75..76f089b 100644 --- a/tests/test_notebook_page.py +++ b/tests/test_notebook_page.py @@ -20,8 +20,22 @@ def _sample_bundle() -> dict: "aliases": ["selection"], }, "relevant_claims": [ - {"claim_id": "clm_001", "claim_text": "Selection can change trait frequencies."}, - {"claim_id": "clm_002", "claim_text": "Selection depends on heritable variation."}, + { + "claim_id": "clm_001", + "claim_text": "Selection can change trait frequencies.", + "source_roles": ["overview"], + }, + { + "claim_id": "clm_002", + "claim_text": "Selection does not imply adaptation.", + "source_roles": ["overview"], + "distinction": { + "claim_id": "clm_002", + "distinction_type": "non_implication", + "cue": "does not imply", + "text": "Selection does not imply adaptation.", + }, + }, ], "relations": [ { @@ -74,6 +88,16 @@ def _sample_bundle() -> dict: "artifact_kind": "compiled_page", "title": "Evolutionary Biology Chapter 1", "path": "texts/futuyma/ch1.md", + "source_role": "overview", + } + ], + "source_role_summary": {"overview": 1}, + "key_distinctions": [ + { + "claim_id": "clm_002", + "distinction_type": "non_implication", + "cue": "does not imply", + "text": "Selection does not imply adaptation.", } ], "review_candidates": [ @@ -97,7 +121,14 @@ def test_build_notebook_page_buckets_graph_navigation() -> None: assert page["graph_navigation"]["derivative_concepts"][0]["title"] == "Adaptation" assert page["graph_navigation"]["closer_concepts"][0]["title"] == "Common Descent" assert page["supporting_sources"][0]["supporting_observation_count"] == 1 + assert page["supporting_sources"][0]["source_role"] == "overview" + assert page["summary"]["source_role_count"] == 1 + assert page["summary"]["distinction_count"] == 1 + assert page["source_role_summary"]["overview"] == 1 + assert page["distinctions"][0]["distinction_type"] == "non_implication" assert page["review_context"]["graph_codes"] == ["bridge_concept"] + assert page["review_context"]["source_role_summary"]["overview"] == 1 + assert page["review_context"]["key_distinctions"][0]["distinction_type"] == "non_implication" assert page["illustration_opportunities"]