From fc33a5ad8798740f23995b6a87cc057d7064e0bd Mon Sep 17 00:00:00 2001 From: welsberr Date: Mon, 27 Apr 2026 11:42:28 -0400 Subject: [PATCH] Preserve review candidate rationale in promotion --- src/groundrecall/promotion.py | 22 ++++++++++++++++++++-- tests/test_groundrecall_promotion.py | 27 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/groundrecall/promotion.py b/src/groundrecall/promotion.py index ab51bf7..59e0df4 100644 --- a/src/groundrecall/promotion.py +++ b/src/groundrecall/promotion.py @@ -60,6 +60,22 @@ def _provenance_from_payload(payload: dict[str, Any]) -> ProvenanceRecord: ) +def _review_candidate_rationale(item: dict[str, Any]) -> str: + parts: list[str] = [] + title = str(item.get("title", "")).strip() + if title: + parts.append(title) + parts.append(f"lane={item.get('triage_lane', 'knowledge_capture')}") + parts.append(f"priority={int(item.get('priority', 50))}") + finding_codes = [str(code) for code in item.get("finding_codes", []) if str(code).strip()] + graph_codes = [str(code) for code in item.get("graph_codes", []) if str(code).strip()] + if finding_codes: + parts.append(f"findings={','.join(finding_codes)}") + if graph_codes: + parts.append(f"graph={','.join(graph_codes)}") + return " | ".join(parts) + + def promote_import_to_store( import_dir: str | Path, store_dir: str | Path, @@ -181,6 +197,8 @@ def promote_import_to_store( promoted_relation_ids.append(record.relation_id) for item in queue_payload.get("items", []): + graph_codes = [str(code) for code in item.get("graph_codes", []) if str(code).strip()] + finding_codes = [str(code) for code in item.get("finding_codes", []) if str(code).strip()] store.save_review_candidate( ReviewCandidateRecord( review_candidate_id=item["queue_id"], @@ -188,8 +206,8 @@ def promote_import_to_store( candidate_id=item["candidate_id"], triage_lane=item.get("triage_lane", "knowledge_capture"), priority=int(item.get("priority", 50)), - finding_codes=list(item.get("finding_codes", [])), - rationale=item.get("title", ""), + finding_codes=sorted(set(finding_codes + graph_codes)), + rationale=_review_candidate_rationale(item), current_status="reviewed" if item["candidate_id"] in set(promoted_claim_ids + promoted_concept_ids + promoted_relation_ids) else "triaged", ) ) diff --git a/tests/test_groundrecall_promotion.py b/tests/test_groundrecall_promotion.py index 4e05a00..f86fe38 100644 --- a/tests/test_groundrecall_promotion.py +++ b/tests/test_groundrecall_promotion.py @@ -94,3 +94,30 @@ def test_groundrecall_promotion_preserves_contradiction_and_supersession_links(t claims = {item.claim_id: item for item in store.list_claims()} assert claims["clm_revised"].supersedes_claim_ids == ["clm_base"] assert claims["clm_dissent"].contradicts_claim_ids == ["clm_revised"] + + +def test_groundrecall_promotion_preserves_queue_graph_rationale(tmp_path: Path) -> None: + root = tmp_path / "llmwiki" + (root / "wiki").mkdir(parents=True) + (root / "wiki" / "a.md").write_text("# A\n\nSee also [[B]].\n", encoding="utf-8") + (root / "wiki" / "b.md").write_text("# B\n\nSee also [[C]].\n", encoding="utf-8") + (root / "wiki" / "c.md").write_text("# C\n", encoding="utf-8") + + result = run_groundrecall_import(root, mode="quick", import_id="promotion-queue-test") + review_path = result.out_dir / "review_session.json" + review_payload = json.loads(review_path.read_text(encoding="utf-8")) + for concept in review_payload["draft_pack"]["concepts"]: + concept["status"] = "trusted" + review_path.write_text(json.dumps(review_payload, indent=2), encoding="utf-8") + + store_dir = tmp_path / "groundrecall-store" + promote_import_to_store(result.out_dir, store_dir, reviewer="R") + + store = GroundRecallStore(store_dir) + review_candidates = {item.candidate_id: item for item in store.list_review_candidates()} + bridge_candidate = review_candidates["concept::b"] + + assert bridge_candidate.triage_lane == "conflict_resolution" + assert "bridge_concept" in bridge_candidate.finding_codes + assert "lane=conflict_resolution" in bridge_candidate.rationale + assert "graph=bridge_concept" in bridge_candidate.rationale