225 lines
8.0 KiB
Python
225 lines
8.0 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from app.schemas.steward import (
|
||
|
|
StewardActionStatus,
|
||
|
|
StewardActionStep,
|
||
|
|
StewardPlanResponse,
|
||
|
|
StewardTask,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class StewardActionPlanBuilder:
|
||
|
|
"""把小财管家任务转换为确定性的白名单动作步骤。"""
|
||
|
|
|
||
|
|
def attach_action_steps(self, plan: StewardPlanResponse) -> StewardPlanResponse:
|
||
|
|
if not plan.tasks:
|
||
|
|
return plan.model_copy(update={"action_steps": []})
|
||
|
|
|
||
|
|
tasks: list[StewardTask] = []
|
||
|
|
plan_steps = [self._build_detect_intent_step(plan)]
|
||
|
|
for task in plan.tasks:
|
||
|
|
task_steps = self.build_task_action_steps(task)
|
||
|
|
tasks.append(task.model_copy(update={"action_steps": task_steps}))
|
||
|
|
plan_steps.extend(task_steps)
|
||
|
|
return plan.model_copy(update={"tasks": tasks, "action_steps": plan_steps})
|
||
|
|
|
||
|
|
def build_task_action_steps(self, task: StewardTask) -> list[StewardActionStep]:
|
||
|
|
if task.task_type == "expense_application":
|
||
|
|
return self._build_application_steps(task)
|
||
|
|
return self._build_reimbursement_steps(task)
|
||
|
|
|
||
|
|
def _build_application_steps(self, task: StewardTask) -> list[StewardActionStep]:
|
||
|
|
steps = [
|
||
|
|
self._build_task_step(
|
||
|
|
task,
|
||
|
|
1,
|
||
|
|
"fill_application_fields",
|
||
|
|
"填充申请字段",
|
||
|
|
payload=self._field_payload(task),
|
||
|
|
),
|
||
|
|
self._build_task_step(
|
||
|
|
task,
|
||
|
|
2,
|
||
|
|
"build_application_preview",
|
||
|
|
"展示申请核对表",
|
||
|
|
depends_on_index=1,
|
||
|
|
payload={"task_id": task.task_id, "ontology_fields": task.ontology_fields},
|
||
|
|
),
|
||
|
|
self._build_task_step(
|
||
|
|
task,
|
||
|
|
3,
|
||
|
|
"validate_required_fields",
|
||
|
|
"校验申请必填字段",
|
||
|
|
status=self._validation_status(task),
|
||
|
|
depends_on_index=2,
|
||
|
|
payload={"missing_fields": task.missing_fields},
|
||
|
|
),
|
||
|
|
]
|
||
|
|
if task.requested_action == "save_draft":
|
||
|
|
steps.append(
|
||
|
|
self._build_task_step(
|
||
|
|
task,
|
||
|
|
4,
|
||
|
|
"save_application_draft",
|
||
|
|
"保存申请草稿",
|
||
|
|
status=self._side_effect_status(task, requires_confirmation=False),
|
||
|
|
depends_on_index=3,
|
||
|
|
payload={
|
||
|
|
"task_id": task.task_id,
|
||
|
|
"requested_action": task.requested_action,
|
||
|
|
"ontology_fields": task.ontology_fields,
|
||
|
|
},
|
||
|
|
)
|
||
|
|
)
|
||
|
|
elif task.requested_action == "submit":
|
||
|
|
steps.append(
|
||
|
|
self._build_task_step(
|
||
|
|
task,
|
||
|
|
4,
|
||
|
|
"run_duplicate_precheck",
|
||
|
|
"检查重复或冲突申请",
|
||
|
|
status=self._side_effect_status(task, requires_confirmation=False),
|
||
|
|
depends_on_index=3,
|
||
|
|
payload={"task_id": task.task_id, "precheck_type": "travel_overlap"},
|
||
|
|
)
|
||
|
|
)
|
||
|
|
steps.append(
|
||
|
|
self._build_task_step(
|
||
|
|
task,
|
||
|
|
5,
|
||
|
|
"submit_application",
|
||
|
|
"提交申请审批",
|
||
|
|
status=self._side_effect_status(task, requires_confirmation=True),
|
||
|
|
requires_confirmation=not bool(task.missing_fields),
|
||
|
|
depends_on_index=4,
|
||
|
|
payload={
|
||
|
|
"task_id": task.task_id,
|
||
|
|
"requested_action": task.requested_action,
|
||
|
|
"confirmation_required": True,
|
||
|
|
},
|
||
|
|
)
|
||
|
|
)
|
||
|
|
return steps
|
||
|
|
|
||
|
|
def _build_reimbursement_steps(self, task: StewardTask) -> list[StewardActionStep]:
|
||
|
|
steps = [
|
||
|
|
self._build_task_step(
|
||
|
|
task,
|
||
|
|
1,
|
||
|
|
"fill_reimbursement_fields",
|
||
|
|
"填充报销字段",
|
||
|
|
payload=self._field_payload(task),
|
||
|
|
),
|
||
|
|
self._build_task_step(
|
||
|
|
task,
|
||
|
|
2,
|
||
|
|
"build_reimbursement_preview",
|
||
|
|
"展示报销核对表",
|
||
|
|
depends_on_index=1,
|
||
|
|
payload={"task_id": task.task_id, "ontology_fields": task.ontology_fields},
|
||
|
|
),
|
||
|
|
self._build_task_step(
|
||
|
|
task,
|
||
|
|
3,
|
||
|
|
"validate_required_fields",
|
||
|
|
"校验报销必填字段",
|
||
|
|
status=self._validation_status(task),
|
||
|
|
depends_on_index=2,
|
||
|
|
payload={"missing_fields": task.missing_fields},
|
||
|
|
),
|
||
|
|
self._build_task_step(
|
||
|
|
task,
|
||
|
|
4,
|
||
|
|
"create_reimbursement_draft",
|
||
|
|
"创建报销草稿",
|
||
|
|
status=self._side_effect_status(task, requires_confirmation=False),
|
||
|
|
depends_on_index=3,
|
||
|
|
payload={
|
||
|
|
"task_id": task.task_id,
|
||
|
|
"requested_action": task.requested_action,
|
||
|
|
"ontology_fields": task.ontology_fields,
|
||
|
|
},
|
||
|
|
),
|
||
|
|
]
|
||
|
|
if task.ontology_fields.get("attachments"):
|
||
|
|
steps.append(
|
||
|
|
self._build_task_step(
|
||
|
|
task,
|
||
|
|
5,
|
||
|
|
"associate_attachments",
|
||
|
|
"关联报销附件",
|
||
|
|
status=self._side_effect_status(task, requires_confirmation=False),
|
||
|
|
depends_on_index=4,
|
||
|
|
payload={"task_id": task.task_id, "attachments": task.ontology_fields["attachments"]},
|
||
|
|
)
|
||
|
|
)
|
||
|
|
return steps
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def _build_detect_intent_step(plan: StewardPlanResponse) -> StewardActionStep:
|
||
|
|
return StewardActionStep(
|
||
|
|
step_id="plan:00:detect_intent",
|
||
|
|
action_type="detect_intent",
|
||
|
|
label="识别业务意图",
|
||
|
|
status="completed",
|
||
|
|
payload={
|
||
|
|
"planning_source": plan.planning_source,
|
||
|
|
"plan_status": plan.plan_status,
|
||
|
|
},
|
||
|
|
)
|
||
|
|
|
||
|
|
def _build_task_step(
|
||
|
|
self,
|
||
|
|
task: StewardTask,
|
||
|
|
index: int,
|
||
|
|
action_type: str,
|
||
|
|
label: str,
|
||
|
|
*,
|
||
|
|
status: StewardActionStatus = "planned",
|
||
|
|
requires_confirmation: bool = False,
|
||
|
|
depends_on_index: int | None = None,
|
||
|
|
payload: dict[str, Any] | None = None,
|
||
|
|
) -> StewardActionStep:
|
||
|
|
step_id = self._step_id(task, index)
|
||
|
|
depends_on = [self._step_id(task, depends_on_index)] if depends_on_index is not None else []
|
||
|
|
return StewardActionStep(
|
||
|
|
step_id=step_id,
|
||
|
|
action_type=action_type, # type: ignore[arg-type]
|
||
|
|
label=label,
|
||
|
|
target_task_id=task.task_id,
|
||
|
|
status=status,
|
||
|
|
requires_confirmation=requires_confirmation,
|
||
|
|
depends_on=depends_on,
|
||
|
|
payload=payload or {},
|
||
|
|
)
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def _field_payload(task: StewardTask) -> dict[str, Any]:
|
||
|
|
return {
|
||
|
|
"task_id": task.task_id,
|
||
|
|
"task_type": task.task_type,
|
||
|
|
"requested_action": task.requested_action,
|
||
|
|
"ontology_fields": task.ontology_fields,
|
||
|
|
"missing_fields": task.missing_fields,
|
||
|
|
}
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def _validation_status(task: StewardTask) -> StewardActionStatus:
|
||
|
|
return "blocked" if task.missing_fields else "planned"
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def _side_effect_status(
|
||
|
|
task: StewardTask,
|
||
|
|
*,
|
||
|
|
requires_confirmation: bool,
|
||
|
|
) -> StewardActionStatus:
|
||
|
|
if task.missing_fields:
|
||
|
|
return "blocked"
|
||
|
|
return "pending_confirmation" if requires_confirmation else "planned"
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def _step_id(task: StewardTask, index: int) -> str:
|
||
|
|
return f"{task.task_id}:{index:02d}"
|