diff --git a/custom_nodes/research/action_route.py b/custom_nodes/research/action_route.py new file mode 100644 index 000000000..dcf7ba95e --- /dev/null +++ b/custom_nodes/research/action_route.py @@ -0,0 +1,114 @@ +"""ActionRoute node - route gaps to appropriate response actions.""" +import json +from typing_extensions import override +from comfy_api.latest import ComfyNode, io + + +class ActionRoute(io.ComfyNode): + """Route review gaps to Respond/Revise/Citation/Experiment actions.""" + + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="ActionRoute", + display_name="Route Actions", + category="Research", + inputs=[ + io.String.Input( + "gap_report", + display_name="Gap Report (JSON)", + default="{}", + multiline=True, + ), + ], + outputs=[ + io.String.Output(display_name="Action Routes (JSON)"), + ], + ) + + @classmethod + def execute(cls, gap_report: str) -> io.NodeOutput: + try: + data = json.loads(gap_report) if gap_report else {} + gaps = data.get("gaps", []) + except json.JSONDecodeError: + gaps = [] + + routes = [] + + for gap in gaps: + gap_type = gap.get("gap_type", "") + suggested_action = gap.get("suggested_action", "") + + # Determine route + if gap_type == "missing_experiment" or suggested_action == "add_experiment": + route = { + "gap_id": gap.get("gap_id"), + "action_type": "experiment", + "route": "perform_experiment", + "description": f"Perform additional experiments to address: {gap.get('description', '')[:80]}...", + "estimated_effort": "high", + "action_items": [ + "Design experiment protocol", + "Run experiments", + "Analyze results", + "Update manuscript", + ], + } + elif gap_type == "missing_citation" or suggested_action == "add_citation": + route = { + "gap_id": gap.get("gap_id"), + "action_type": "citation", + "route": "add_citation", + "description": f"Add citation to address: {gap.get('description', '')[:80]}...", + "estimated_effort": "low", + "action_items": [ + "Search for relevant papers", + "Add citations", + "Update reference list", + ], + } + elif suggested_action == "strengthen_argument": + route = { + "gap_id": gap.get("gap_id"), + "action_type": "revise", + "route": "revise_manuscript", + "description": f"Revise manuscript to address: {gap.get('description', '')[:80]}...", + "estimated_effort": "medium", + "action_items": [ + "Rewrite affected sections", + "Add supporting discussion", + "Verify consistency", + ], + } + else: + route = { + "gap_id": gap.get("gap_id"), + "action_type": "respond", + "route": "write_response", + "description": f"Write response to: {gap.get('description', '')[:80]}...", + "estimated_effort": "medium", + "action_items": [ + "Draft response text", + "Gather supporting evidence", + "Finalize response", + ], + } + + routes.append(route) + + # Group by action type + by_action = { + "experiment": [r for r in routes if r["action_type"] == "experiment"], + "citation": [r for r in routes if r["action_type"] == "citation"], + "revise": [r for r in routes if r["action_type"] == "revise"], + "respond": [r for r in routes if r["action_type"] == "respond"], + } + + result = { + "total_routes": len(routes), + "by_action": {k: len(v) for k, v in by_action.items()}, + "routes": routes, + } + + return io.NodeOutput(action_routes=json.dumps(result, indent=2)) \ No newline at end of file diff --git a/custom_nodes/research/evidence_gap_detect.py b/custom_nodes/research/evidence_gap_detect.py new file mode 100644 index 000000000..5ae8d89c4 --- /dev/null +++ b/custom_nodes/research/evidence_gap_detect.py @@ -0,0 +1,97 @@ +"""EvidenceGapDetect node - detect evidence gaps from review mappings.""" +import json +from typing_extensions import override +from comfy_api.latest import ComfyNode, io + + +class EvidenceGapDetect(io.ComfyNode): + """Detect evidence gaps based on review item mappings.""" + + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="EvidenceGapDetect", + display_name="Detect Evidence Gaps", + category="Research", + inputs=[ + io.String.Input( + "item_mappings", + display_name="Item Mappings (JSON)", + default="{}", + multiline=True, + ), + ], + outputs=[ + io.String.Output(display_name="Gap Report (JSON)"), + ], + ) + + @classmethod + def execute(cls, item_mappings: str) -> io.NodeOutput: + try: + data = json.loads(item_mappings) if item_mappings else {} + mappings = data.get("mappings", []) + except json.JSONDecodeError: + mappings = [] + + gaps = [] + gap_id = 1 + + for mapping in mappings: + severity = mapping.get("severity", 1) + requires_experiment = mapping.get("requires_experiment", False) + requires_citation = mapping.get("requires_citation", False) + related_claims = mapping.get("related_claims", []) + + # Check for evidence gaps + if requires_experiment and len(related_claims) == 0: + gaps.append({ + "gap_id": f"gap_{gap_id}", + "item_id": mapping.get("item_id"), + "gap_type": "missing_experiment", + "severity": severity, + "description": f"Review concern requires experimental evidence: {mapping.get('item_text', '')[:100]}...", + "suggested_action": "add_experiment", + "priority": "high" if severity >= 3 else "medium", + }) + gap_id += 1 + + if requires_citation and len(related_claims) == 0: + gaps.append({ + "gap_id": f"gap_{gap_id}", + "item_id": mapping.get("item_id"), + "gap_type": "missing_citation", + "severity": severity, + "description": f"Review concern requires citation to supporting work: {mapping.get('item_text', '')[:100]}...", + "suggested_action": "add_citation", + "priority": "medium", + }) + gap_id += 1 + + # Check for unsupported high-severity claims + if severity >= 3 and len(related_claims) == 0: + gaps.append({ + "gap_id": f"gap_{gap_id}", + "item_id": mapping.get("item_id"), + "gap_type": "unsupported_major_claim", + "severity": severity, + "description": f"Major concern lacks supporting evidence: {mapping.get('item_text', '')[:100]}...", + "suggested_action": "strengthen_argument", + "priority": "high", + }) + gap_id += 1 + + # Summarize + gap_summary = { + "total_gaps": len(gaps), + "high_priority": len([g for g in gaps if g["priority"] == "high"]), + "medium_priority": len([g for g in gaps if g["priority"] == "medium"]), + "by_type": { + "missing_experiment": len([g for g in gaps if g["gap_type"] == "missing_experiment"]), + "missing_citation": len([g for g in gaps if g["gap_type"] == "missing_citation"]), + "unsupported_major_claim": len([g for g in gaps if g["gap_type"] == "unsupported_major_claim"]), + }, + "gaps": gaps, + } + + return io.NodeOutput(gap_report=json.dumps(gap_summary, indent=2)) \ No newline at end of file