"""通用 Pydantic 模型""" from __future__ import annotations from typing import Any, List, Optional from pydantic import AliasChoices, BaseModel, ConfigDict, Field, model_validator class TaskBreakdownBusinessObject(BaseModel): """结构化任务拆解业务对象""" model_config = ConfigDict(extra="ignore", populate_by_name=True) task_key: str = Field(validation_alias=AliasChoices("task_key", "key")) title: str = Field(validation_alias=AliasChoices("title", "task_name")) description: Optional[str] = None type: str = Field(validation_alias=AliasChoices("type", "task_type")) estimated_hours: float required_skill_names: List[str] = Field(default_factory=list) required_skill_tag_ids: List[int] = Field(default_factory=list) depends_on_task_keys: List[str] = Field(default_factory=list) risk_level: str = "medium" risk_notes: Optional[str] = None confidence: float = 0.0 evidence_refs: List[str] = Field(default_factory=list) priority: str = "medium" @model_validator(mode="after") def _fill_task_key(self) -> "TaskBreakdownBusinessObject": key = str(self.task_key or "").strip() if not key: title = str(self.title or "").strip() key = title.lower().replace(" ", "-") if title else "task" self.task_key = key return self class TaskItem(TaskBreakdownBusinessObject): """兼容旧调用的任务项别名""" pass class TaskBreakdownPayload(BaseModel): """AI 任务拆解结构化载荷""" model_config = ConfigDict(extra="ignore") tasks: List[TaskBreakdownBusinessObject] detected_skills: List[str] = Field(default_factory=list) source_context: Optional[dict[str, Any]] = None class TaskBreakdownRequest(BaseModel): """任务拆解请求""" description: str project_id: Optional[int] = None charter_id: Optional[int] = None sync_to_plane: bool = False class TaskBreakdownResponse(BaseModel): """任务拆解响应""" tasks: List[TaskBreakdownBusinessObject] sync_result: Optional[dict] = None