Files

185 lines
5.7 KiB
Python
Raw Permalink Normal View History

2026-08-03 18:42:47 +08:00
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any
from gfa_core.mas.tools.executor import ExecutionResult, ExecutionStatus, ToolExecutor
class WorkflowStatus(Enum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
@dataclass
class WorkflowStep:
step_id: str
tool_id: str
name: str
inputs: dict[str, str] = field(default_factory=dict)
parameters: dict[str, Any] = field(default_factory=dict)
dependencies: list[str] = field(default_factory=list)
result: ExecutionResult | None = None
@dataclass
class WorkflowResult:
workflow_id: str
status: WorkflowStatus
steps: list[WorkflowStep] = field(default_factory=list)
outputs: dict[str, str] = field(default_factory=dict)
started_at: datetime | None = None
completed_at: datetime | None = None
error: str | None = None
class GeneFamilyWorkflow:
def __init__(self, executor: ToolExecutor | None = None):
self.executor = executor or ToolExecutor()
def create_identification_workflow(
self,
hmm_file: str,
genome_file: str,
e_value: float = 1e-5,
species_name: str = "unknown",
) -> list[WorkflowStep]:
return [
WorkflowStep(
step_id="hmmsearch",
tool_id="hmmsearch",
name="HMM搜索鉴定",
inputs={
"query": hmm_file,
"target": genome_file,
},
parameters={
"e_value": e_value,
"cpu": 4,
},
dependencies=[],
),
]
def create_full_analysis_workflow(
self,
hmm_file: str,
genome_file: str,
protein_file: str,
e_value: float = 1e-5,
species_name: str = "unknown",
) -> list[WorkflowStep]:
return [
WorkflowStep(
step_id="hmmsearch",
tool_id="hmmsearch",
name="HMM搜索鉴定",
inputs={
"query": hmm_file,
"target": protein_file,
},
parameters={
"e_value": e_value,
"cpu": 4,
},
dependencies=[],
),
WorkflowStep(
step_id="mafft",
tool_id="mafft",
name="多序列比对",
inputs={
"sequences": "", # Will be filled from hmmsearch output
},
parameters={
"strategy": "auto",
"maxiterate": 1000,
},
dependencies=["hmmsearch"],
),
WorkflowStep(
step_id="iqtree",
tool_id="iqtree",
name="系统发育树构建",
inputs={
"alignment": "", # Will be filled from mafft output
},
parameters={
"model": "MFP",
"bootstrap": 1000,
"threads": 4,
},
dependencies=["mafft"],
),
]
async def run_workflow(
self,
steps: list[WorkflowStep],
workflow_id: str = "",
) -> WorkflowResult:
workflow_id = workflow_id or datetime.now().strftime("%Y%m%d_%H%M%S")
started_at = datetime.now()
completed_steps: dict[str, WorkflowStep] = {}
outputs: dict[str, str] = {}
for step in steps:
step_inputs = dict(step.inputs)
for dep_id in step.dependencies:
if dep_id in completed_steps:
dep_step = completed_steps[dep_id]
if dep_step.result and dep_step.result.outputs:
for output_name, output_path in dep_step.result.outputs.items():
if not step_inputs.get(output_name):
step_inputs[output_name] = output_path
result = await self.executor.aexecute(
step.tool_id,
step_inputs,
step.parameters,
work_subdir=f"{workflow_id}/{step.step_id}",
)
step.result = result
completed_steps[step.step_id] = step
if result.status == ExecutionStatus.COMPLETED:
outputs[step.step_id] = str(result.outputs)
elif result.status in [ExecutionStatus.FAILED, ExecutionStatus.TIMEOUT]:
return WorkflowResult(
workflow_id=workflow_id,
status=WorkflowStatus.FAILED,
steps=list(completed_steps.values()),
outputs=outputs,
started_at=started_at,
completed_at=datetime.now(),
error=f"Step {step.step_id} failed: {result.error or result.stderr}",
)
return WorkflowResult(
workflow_id=workflow_id,
status=WorkflowStatus.COMPLETED,
steps=list(completed_steps.values()),
outputs=outputs,
started_at=started_at,
completed_at=datetime.now(),
)
def get_available_tools(self) -> list[dict]:
tools = self.executor.list_tools()
return [
{
"id": t.id,
"name": t.name,
"category": t.category,
"executable": t.executable,
"docker_image": t.docker_image,
}
for t in tools
]