""" Pydantic schemas for External API. Defines request and response models for external system integration. """ from datetime import datetime from typing import Optional, List, Any from enum import Enum from pydantic import BaseModel, Field, field_validator import re # ============== 枚举定义 ============== class TaskType(str, Enum): """支持的任务类型""" TEXT_CLASSIFICATION = "text_classification" IMAGE_CLASSIFICATION = "image_classification" OBJECT_DETECTION = "object_detection" NER = "ner" POLYGON = "polygon" # 多边形标注 class ExternalExportFormat(str, Enum): """支持的导出格式""" JSON = "json" CSV = "csv" SHAREGPT = "sharegpt" YOLO = "yolo" COCO = "coco" ALPACA = "alpaca" PASCAL_VOC = "pascal_voc" # PascalVOC XML 格式 # ============== 项目初始化相关 ============== class TagItem(BaseModel): """标签项,用于外部系统传入标注标签""" tag: str = Field(..., min_length=1, description="标签名称") color: Optional[str] = Field(None, description="标签颜色,格式: #RRGGBB,不传则自动生成") @field_validator('color') @classmethod def validate_color(cls, v): """验证颜色格式""" if v is not None: if not re.match(r'^#[0-9A-Fa-f]{6}$', v): raise ValueError('颜色格式无效,应为 #RRGGBB 格式') return v class Config: json_schema_extra = { "example": { "tag": "猫猫", "color": "#FF5733" } } class TaskDataItem(BaseModel): """单个任务数据项""" id: Optional[str] = Field(None, description="外部系统的数据ID,用于关联") content: str = Field(..., description="数据内容(文本或图像URL)") metadata: Optional[dict] = Field(None, description="额外元数据") class Config: json_schema_extra = { "example": { "id": "ext_001", "content": "https://example.com/image.jpg", "metadata": {"batch": "001", "source": "商品库"} } } class ProjectInitRequest(BaseModel): """项目初始化请求""" name: str = Field(..., min_length=1, description="项目名称") description: Optional[str] = Field("", description="项目描述") task_type: TaskType = Field(..., description="任务类型") data: List[TaskDataItem] = Field(..., min_length=1, description="任务数据列表") external_id: Optional[str] = Field(None, description="外部系统的项目ID,用于关联查询") tags: Optional[List[TagItem]] = Field(None, description="标签列表,包含标签名称和可选颜色") class Config: json_schema_extra = { "example": { "name": "图像分类项目-批次001", "description": "对商品图片进行分类标注", "task_type": "image_classification", "data": [ { "id": "ext_001", "content": "https://example.com/images/product1.jpg", "metadata": {"batch": "001"} } ], "external_id": "sample_center_proj_001", "tags": [ {"tag": "猫猫", "color": "#FF5733"}, {"tag": "狗狗"} ] } } class ProjectInitResponse(BaseModel): """项目初始化响应""" project_id: str = Field(..., description="标注平台项目ID,样本中心需保存用于后续回调") project_name: str = Field(..., description="项目名称") task_count: int = Field(..., description="创建的任务数量") status: str = Field(..., description="项目状态") created_at: datetime = Field(..., description="创建时间") config: str = Field(..., description="实际使用的XML配置模板") external_id: Optional[str] = Field(None, description="外部系统的项目ID") class Config: json_schema_extra = { "example": { "project_id": "proj_abc123def456", "project_name": "图像分类项目-批次001", "task_count": 100, "status": "draft", "created_at": "2026-02-03T10:30:00Z", "config": "...", "external_id": "sample_center_proj_001" } } # ============== 进度查询相关 ============== class AnnotatorProgress(BaseModel): """标注人员进度""" user_id: str = Field(..., description="用户ID") username: str = Field(..., description="用户名") assigned_count: int = Field(..., description="分配的任务数") completed_count: int = Field(..., description="已完成数") in_progress_count: int = Field(..., description="进行中数") completion_rate: float = Field(..., description="个人完成率(0-100)") class ProgressResponse(BaseModel): """项目进度响应""" project_id: str = Field(..., description="项目ID") project_name: str = Field(..., description="项目名称") status: str = Field(..., description="项目状态") total_tasks: int = Field(..., description="总任务数") completed_tasks: int = Field(..., description="已完成任务数") in_progress_tasks: int = Field(..., description="进行中任务数") pending_tasks: int = Field(..., description="待处理任务数") completion_percentage: float = Field(..., description="完成百分比(0-100)") annotators: List[AnnotatorProgress] = Field(default_factory=list, description="标注人员列表") last_updated: Optional[datetime] = Field(None, description="最后更新时间") class Config: json_schema_extra = { "example": { "project_id": "proj_abc123def456", "project_name": "图像分类项目-批次001", "status": "in_progress", "total_tasks": 100, "completed_tasks": 45, "in_progress_tasks": 30, "pending_tasks": 25, "completion_percentage": 45.0, "annotators": [ { "user_id": "user_001", "username": "annotator1", "assigned_count": 50, "completed_count": 25, "in_progress_count": 15, "completion_rate": 50.0 } ], "last_updated": "2026-02-03T15:30:00Z" } } # ============== 数据导出相关 ============== class ExternalExportRequest(BaseModel): """导出请求""" format: ExternalExportFormat = Field( default=ExternalExportFormat.JSON, description="导出格式" ) completed_only: bool = Field( default=True, description="是否只导出已完成的任务" ) callback_url: Optional[str] = Field( None, description="回调URL,导出完成后通知样本中心" ) class Config: json_schema_extra = { "example": { "format": "sharegpt", "completed_only": True, "callback_url": "https://sample-center.example.com/api/callback/export" } } class ExportedTaskData(BaseModel): """导出的任务数据""" task_id: str = Field(..., description="任务ID") external_id: Optional[str] = Field(None, description="外部系统的数据ID") original_data: dict = Field(..., description="原始数据") annotations: List[dict] = Field(default_factory=list, description="标注结果列表") status: str = Field(..., description="任务状态") annotator: Optional[str] = Field(None, description="标注人员") completed_at: Optional[datetime] = Field(None, description="完成时间") class ExternalExportResponse(BaseModel): """导出响应""" project_id: str = Field(..., description="项目ID") format: str = Field(..., description="导出格式") total_exported: int = Field(..., description="导出的任务数量") file_url: str = Field(..., description="文件下载URL") file_name: str = Field(..., description="文件名") file_size: Optional[int] = Field(None, description="文件大小(字节)") expires_at: Optional[datetime] = Field(None, description="下载链接过期时间") class Config: json_schema_extra = { "example": { "project_id": "proj_abc123def456", "format": "sharegpt", "total_exported": 45, "file_url": "/api/exports/export_789/download", "file_name": "export_proj_abc123_sharegpt_20260203.json", "file_size": 1024000, "expires_at": "2026-02-10T10:30:00Z" } } class ExportCallbackPayload(BaseModel): """导出完成回调载荷""" project_id: str = Field(..., description="项目ID") export_id: str = Field(..., description="导出任务ID") status: str = Field(..., description="状态:completed 或 failed") format: str = Field(..., description="导出格式") total_exported: int = Field(..., description="导出的任务数量") file_url: str = Field(..., description="文件下载URL(完整URL)") file_name: str = Field(..., description="文件名") file_size: int = Field(..., description="文件大小(字节)") error_message: Optional[str] = Field(None, description="错误信息(失败时)") # ============== 错误响应 ============== class ErrorResponse(BaseModel): """统一错误响应格式""" error_code: str = Field(..., description="错误码") message: str = Field(..., description="错误信息") details: Optional[dict] = Field(None, description="详细信息") class Config: json_schema_extra = { "example": { "error_code": "PROJECT_NOT_FOUND", "message": "项目不存在", "details": {"project_id": "proj_invalid"} } }