mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-04-15 13:02:35 +08:00
feat: add Phase 4 Chunk 5 review nodes (CoverageCheck, RevisionPlan)
This commit is contained in:
parent
3473712bf9
commit
66d6fcd2ae
@ -27,7 +27,9 @@ class ResearchExtension(ComfyExtension):
|
|||||||
from custom_nodes.research.evidence_pack_assemble import EvidencePackAssemble
|
from custom_nodes.research.evidence_pack_assemble import EvidencePackAssemble
|
||||||
from custom_nodes.research.response_draft import ResponseDraft
|
from custom_nodes.research.response_draft import ResponseDraft
|
||||||
from custom_nodes.research.tone_control import ToneControl
|
from custom_nodes.research.tone_control import ToneControl
|
||||||
return [PaperSearch, PaperClaimExtract, ClaimEvidenceAssemble, StyleProfileExtract, ReferencePaperSelect, SectionPlan, AbstractDraft, IntroductionDraft, MethodsDraft, ConsistencyCheck, ExportManuscript, ReviewImport, ReviewAtomize, ReviewClassify, ReviewMap, EvidenceGapDetect, ActionRoute, EvidencePackAssemble, ResponseDraft, ToneControl]
|
from custom_nodes.research.coverage_check import CoverageCheck
|
||||||
|
from custom_nodes.research.revision_plan import RevisionPlan
|
||||||
|
return [PaperSearch, PaperClaimExtract, ClaimEvidenceAssemble, StyleProfileExtract, ReferencePaperSelect, SectionPlan, AbstractDraft, IntroductionDraft, MethodsDraft, ConsistencyCheck, ExportManuscript, ReviewImport, ReviewAtomize, ReviewClassify, ReviewMap, EvidenceGapDetect, ActionRoute, EvidencePackAssemble, ResponseDraft, ToneControl, CoverageCheck, RevisionPlan]
|
||||||
|
|
||||||
|
|
||||||
async def comfy_entrypoint() -> ComfyExtension:
|
async def comfy_entrypoint() -> ComfyExtension:
|
||||||
|
|||||||
90
custom_nodes/research/coverage_check.py
Normal file
90
custom_nodes/research/coverage_check.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
"""CoverageCheck node - check response coverage of review items."""
|
||||||
|
import json
|
||||||
|
from typing_extensions import override
|
||||||
|
from comfy_api.latest import ComfyNode, io
|
||||||
|
|
||||||
|
|
||||||
|
class CoverageCheck(io.ComfyNode):
|
||||||
|
"""Check if responses adequately cover all review items."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def define_schema(cls) -> io.Schema:
|
||||||
|
return io.Schema(
|
||||||
|
node_id="CoverageCheck",
|
||||||
|
display_name="Coverage Check",
|
||||||
|
category="Research",
|
||||||
|
inputs=[
|
||||||
|
io.String.Input(
|
||||||
|
"finalized_responses",
|
||||||
|
display_name="Finalized Responses (JSON)",
|
||||||
|
default="{}",
|
||||||
|
multiline=True,
|
||||||
|
),
|
||||||
|
io.String.Input(
|
||||||
|
"review_items",
|
||||||
|
display_name="Review Items (JSON)",
|
||||||
|
default="[]",
|
||||||
|
multiline=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
outputs=[
|
||||||
|
io.String.Output(display_name="Coverage Report (JSON)"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def execute(cls, finalized_responses: str, review_items: str) -> io.NodeOutput:
|
||||||
|
try:
|
||||||
|
responses_data = json.loads(finalized_responses) if finalized_responses else {}
|
||||||
|
items = json.loads(review_items) if review_items else []
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
responses_data = {}
|
||||||
|
items = []
|
||||||
|
|
||||||
|
responses = responses_data.get("responses", [])
|
||||||
|
|
||||||
|
coverage = []
|
||||||
|
covered_ids = set()
|
||||||
|
uncovered_items = []
|
||||||
|
|
||||||
|
for resp in responses:
|
||||||
|
gap_id = resp.get("gap_id", "")
|
||||||
|
response_text = resp.get("response_text", "")
|
||||||
|
action_type = resp.get("action_type", "")
|
||||||
|
|
||||||
|
coverage.append({
|
||||||
|
"gap_id": gap_id,
|
||||||
|
"covered": True,
|
||||||
|
"response_length": len(response_text),
|
||||||
|
"action_type": action_type,
|
||||||
|
"assessment": "adequate" if len(response_text) > 50 else "too_brief",
|
||||||
|
})
|
||||||
|
covered_ids.add(gap_id)
|
||||||
|
|
||||||
|
# Check for uncovered items
|
||||||
|
for item in items:
|
||||||
|
item_id = item.get("item_id", f"item_{len(coverage)}")
|
||||||
|
if item_id not in covered_ids:
|
||||||
|
uncovered_items.append({
|
||||||
|
"item_id": item_id,
|
||||||
|
"issue": item.get("item_text", "")[:100],
|
||||||
|
"severity": item.get("severity", 1),
|
||||||
|
"status": "not_addressed",
|
||||||
|
})
|
||||||
|
|
||||||
|
# Determine overall coverage
|
||||||
|
total = len(coverage) + len(uncovered_items)
|
||||||
|
covered_count = len(coverage)
|
||||||
|
coverage_pct = (covered_count / total * 100) if total > 0 else 100
|
||||||
|
|
||||||
|
report = {
|
||||||
|
"total_items": total,
|
||||||
|
"covered_items": covered_count,
|
||||||
|
"uncovered_items": len(uncovered_items),
|
||||||
|
"coverage_percentage": round(coverage_pct, 1),
|
||||||
|
"overall_status": "complete" if coverage_pct >= 90 else "partial" if coverage_pct >= 50 else "incomplete",
|
||||||
|
"item_coverage": coverage,
|
||||||
|
"uncovered": uncovered_items,
|
||||||
|
}
|
||||||
|
|
||||||
|
return io.NodeOutput(coverage_report=json.dumps(report, indent=2))
|
||||||
173
custom_nodes/research/revision_plan.py
Normal file
173
custom_nodes/research/revision_plan.py
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
"""RevisionPlan node - create revision task plan from coverage report."""
|
||||||
|
import json
|
||||||
|
from typing_extensions import override
|
||||||
|
from comfy_api.latest import ComfyNode, io
|
||||||
|
|
||||||
|
|
||||||
|
class RevisionPlan(io.ComfyNode):
|
||||||
|
"""Create a structured revision task plan from coverage report."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def define_schema(cls) -> io.Schema:
|
||||||
|
return io.Schema(
|
||||||
|
node_id="RevisionPlan",
|
||||||
|
display_name="Revision Plan",
|
||||||
|
category="Research",
|
||||||
|
inputs=[
|
||||||
|
io.String.Input(
|
||||||
|
"coverage_report",
|
||||||
|
display_name="Coverage Report (JSON)",
|
||||||
|
default="{}",
|
||||||
|
multiline=True,
|
||||||
|
),
|
||||||
|
io.String.Input(
|
||||||
|
"action_routes",
|
||||||
|
display_name="Action Routes (JSON)",
|
||||||
|
default="{}",
|
||||||
|
multiline=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
outputs=[
|
||||||
|
io.String.Output(display_name="Revision Plan (JSON)"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def execute(cls, coverage_report: str, action_routes: str) -> io.NodeOutput:
|
||||||
|
try:
|
||||||
|
coverage = json.loads(coverage_report) if coverage_report else {}
|
||||||
|
routes_data = json.loads(action_routes) if action_routes else {}
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
coverage = {}
|
||||||
|
routes_data = {}
|
||||||
|
|
||||||
|
uncovered = coverage.get("uncovered_items", [])
|
||||||
|
routes = routes_data.get("routes", [])
|
||||||
|
item_coverage = coverage.get("item_coverage", [])
|
||||||
|
|
||||||
|
# Group by action type
|
||||||
|
by_action = {"experiment": [], "citation": [], "revise": [], "respond": []}
|
||||||
|
for route in routes:
|
||||||
|
action_type = route.get("action_type", "respond")
|
||||||
|
if action_type in by_action:
|
||||||
|
by_action[action_type].append(route)
|
||||||
|
|
||||||
|
# Create revision tasks
|
||||||
|
tasks = []
|
||||||
|
task_id = 1
|
||||||
|
|
||||||
|
# Priority 1: High severity uncovered
|
||||||
|
for item in uncovered:
|
||||||
|
if item.get("severity", 1) >= 3:
|
||||||
|
tasks.append({
|
||||||
|
"task_id": f"task_{task_id}",
|
||||||
|
"priority": "high",
|
||||||
|
"action": "address_immediately",
|
||||||
|
"description": f"Uncovered high-severity item: {item.get('issue', '')[:80]}",
|
||||||
|
"estimated_time": "2-4 hours",
|
||||||
|
})
|
||||||
|
task_id += 1
|
||||||
|
|
||||||
|
# Priority 2: Experiment tasks
|
||||||
|
for route in by_action.get("experiment", []):
|
||||||
|
tasks.append({
|
||||||
|
"task_id": f"task_{task_id}",
|
||||||
|
"priority": "high",
|
||||||
|
"action": "run_experiment",
|
||||||
|
"description": route.get("description", ""),
|
||||||
|
"effort": route.get("estimated_effort", "high"),
|
||||||
|
"action_items": route.get("action_items", []),
|
||||||
|
})
|
||||||
|
task_id += 1
|
||||||
|
|
||||||
|
# Priority 3: Citation tasks
|
||||||
|
for route in by_action.get("citation", []):
|
||||||
|
tasks.append({
|
||||||
|
"task_id": f"task_{task_id}",
|
||||||
|
"priority": "medium",
|
||||||
|
"action": "add_citations",
|
||||||
|
"description": route.get("description", ""),
|
||||||
|
"effort": route.get("estimated_effort", "low"),
|
||||||
|
"action_items": route.get("action_items", []),
|
||||||
|
})
|
||||||
|
task_id += 1
|
||||||
|
|
||||||
|
# Priority 4: Revise tasks
|
||||||
|
for route in by_action.get("revise", []):
|
||||||
|
tasks.append({
|
||||||
|
"task_id": f"task_{task_id}",
|
||||||
|
"priority": "medium",
|
||||||
|
"action": "revise_manuscript",
|
||||||
|
"description": route.get("description", ""),
|
||||||
|
"effort": route.get("estimated_effort", "medium"),
|
||||||
|
"action_items": route.get("action_items", []),
|
||||||
|
})
|
||||||
|
task_id += 1
|
||||||
|
|
||||||
|
# Priority 5: Response tasks
|
||||||
|
for route in by_action.get("respond", []):
|
||||||
|
tasks.append({
|
||||||
|
"task_id": f"task_{task_id}",
|
||||||
|
"priority": "low",
|
||||||
|
"action": "write_response",
|
||||||
|
"description": route.get("description", ""),
|
||||||
|
"effort": route.get("estimated_effort", "medium"),
|
||||||
|
"action_items": route.get("action_items", []),
|
||||||
|
})
|
||||||
|
task_id += 1
|
||||||
|
|
||||||
|
# Add brief uncovered items
|
||||||
|
for item in uncovered:
|
||||||
|
if item.get("severity", 1) < 3:
|
||||||
|
tasks.append({
|
||||||
|
"task_id": f"task_{task_id}",
|
||||||
|
"priority": "low",
|
||||||
|
"action": "review_uncovered",
|
||||||
|
"description": f"Review item: {item.get('issue', '')[:80]}",
|
||||||
|
"estimated_time": "30 minutes",
|
||||||
|
})
|
||||||
|
task_id += 1
|
||||||
|
|
||||||
|
# Summarize
|
||||||
|
summary = {
|
||||||
|
"total_tasks": len(tasks),
|
||||||
|
"by_priority": {
|
||||||
|
"high": len([t for t in tasks if t.get("priority") == "high"]),
|
||||||
|
"medium": len([t for t in tasks if t.get("priority") == "medium"]),
|
||||||
|
"low": len([t for t in tasks if t.get("priority") == "low"]),
|
||||||
|
},
|
||||||
|
"by_action": {
|
||||||
|
"run_experiment": len(by_action.get("experiment", [])),
|
||||||
|
"add_citations": len(by_action.get("citation", [])),
|
||||||
|
"revise_manuscript": len(by_action.get("revise", [])),
|
||||||
|
"write_response": len(by_action.get("respond", [])),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
plan = {
|
||||||
|
"summary": summary,
|
||||||
|
"tasks": tasks,
|
||||||
|
"estimated_total_time": _estimate_time(tasks),
|
||||||
|
}
|
||||||
|
|
||||||
|
return io.NodeOutput(revision_plan=json.dumps(plan, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
def _estimate_time(tasks: list) -> str:
|
||||||
|
"""Estimate total time for all tasks."""
|
||||||
|
hours = 0
|
||||||
|
for task in tasks:
|
||||||
|
time_str = task.get("estimated_time", "1 hour")
|
||||||
|
if "hour" in time_str:
|
||||||
|
try:
|
||||||
|
hours += float(time_str.split()[0])
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
hours += 1
|
||||||
|
elif "minute" in time_str:
|
||||||
|
try:
|
||||||
|
hours += float(time_str.split()[0]) / 60
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
if hours < 1:
|
||||||
|
return f"{int(hours * 60)} minutes"
|
||||||
|
return f"{int(hours)}-{int(hours + 2)} hours"
|
||||||
Loading…
Reference in New Issue
Block a user