baodan/api/insurance/models/generation_task.py

101 lines
5.1 KiB
Python
Raw Normal View History

"""统一生成任务模型。"""
import uuid
from sqlalchemy import Column, String, Text, Integer, TIMESTAMP, func
from insurance.db.compat import db
class GenerationTask(db.Model):
"""统一生成任务表PPT + 海报)。
职责分离
- 工作区PptSession/PosterRecord保存可编辑数据
- 任务表保存一次执行的不可变快照状态和结果
"""
__tablename__ = "insurance_generation_tasks"
id = Column(String(36), primary_key=True, default=lambda: uuid.uuid4().hex)
user_id = Column(String(64), nullable=False, comment="用户 ID")
artifact_type = Column(String(10), nullable=False, comment="ppt/poster")
operation = Column(String(10), nullable=False, comment="parse/generate")
workspace_id = Column(String(36), nullable=False, comment="PptSession.id 或 PosterRecord.id")
title_snapshot = Column(String(200), default="", comment="提交时任务名称")
status = Column(String(20), nullable=False, default="queued",
comment="queued/running/done/failed/cancelled")
stage = Column(String(30), default="", comment="当前执行阶段")
progress = Column(Integer, default=0, comment="进度 0-100")
message = Column(String(500), default="", comment="用户可读进度")
error_code = Column(String(50), default="", comment="结构化错误代码")
error_message = Column(Text, nullable=True, comment="用户可读错误")
input_revision = Column(Integer, default=1, comment="本次生成使用的草稿版本")
input_snapshot_json = Column(Text, nullable=True, comment="不可变输入快照")
output_json = Column(Text, nullable=True, comment="输出路径、页数、预览等")
idempotency_key = Column(String(100), nullable=True, comment="防止重复提交")
celery_task_id = Column(String(200), nullable=True, comment="Celery ID")
attempt_count = Column(Integer, default=0, comment="重试次数")
heartbeat_at = Column(TIMESTAMP, nullable=True, comment="Worker 心跳")
started_at = Column(TIMESTAMP, nullable=True, comment="开始时间")
finished_at = Column(TIMESTAMP, nullable=True, comment="完成时间")
viewed_at = Column(TIMESTAMP, nullable=True, comment="用户查看时间")
dock_hidden_at = Column(TIMESTAMP, nullable=True, comment="从任务坞隐藏时间")
created_at = Column(TIMESTAMP, server_default=func.now())
updated_at = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now())
def to_dict(self):
import json
return {
"id": self.id,
"userId": self.user_id,
"artifactType": self.artifact_type,
"operation": self.operation,
"workspaceId": self.workspace_id,
"titleSnapshot": self.title_snapshot,
"status": self.status,
"stage": self.stage,
"progress": self.progress,
"message": self.message,
"errorCode": self.error_code,
"errorMessage": self.error_message,
"inputRevision": self.input_revision,
"inputSnapshot": json.loads(self.input_snapshot_json) if self.input_snapshot_json else None,
"output": json.loads(self.output_json) if self.output_json else None,
"idempotencyKey": self.idempotency_key,
"celeryTaskId": self.celery_task_id,
"attemptCount": self.attempt_count,
"heartbeatAt": self.heartbeat_at.isoformat() if self.heartbeat_at else None,
"startedAt": self.started_at.isoformat() if self.started_at else None,
"finishedAt": self.finished_at.isoformat() if self.finished_at else None,
"viewedAt": self.viewed_at.isoformat() if self.viewed_at else None,
"dockHiddenAt": self.dock_hidden_at.isoformat() if self.dock_hidden_at else None,
"createdAt": self.created_at.isoformat() if self.created_at else None,
"updatedAt": self.updated_at.isoformat() if self.updated_at else None,
}
@staticmethod
def create_task(user_id: str, artifact_type: str, operation: str,
workspace_id: str, title: str = "",
input_revision 正确记录 ✅ task_service.py — create_task() 新增 input_revision 参数,存入 GenerationTask.input_revision generation_task.py — 模型的静态 create_task 方法同步新增参数 ppt/routes.py — 三处调用(parse/generate/regenerate)均传入 session.draft_revision poster/service.py — 海报生成传入 record.draft_revision 现在每个任务快照都记录了创建时的草稿版本号,不再一直是默认值 1 2. 海报任务进度实时同步 ✅ celery_tasks.py — 在海报生成的每个阶段(preparing_data→20%、building_prompt→40%、requesting_image→60%、saving→85%)调用 sync_poster_progress() 同步到 PosterRecord.task_status/task_progress 任务领取时立即将 PosterRecord.task_status 设为 running 解决了"页面轮询海报记录时一直显示排队中"的问题 3. 后端测试覆盖 ✅ test_task_state_sync.py — 新增 13 个测试用例: sync_workspace_status 同步 cancelled/failed 到 PPT session(4 个) sync_workspace_status 同步 cancelled/failed 到 PosterRecord(2 个) 旧任务跳过同步的安全检查(1 个) cancel_task 状态更新和非 queued 拒绝(2 个) mark_task_viewed 正确标记和权限校验(2 个) input_revision 正确记录和默认值(2 个) list_active_tasks 排除终态任务(1 个) 所有 13 个新测试 + 原有 10 个测试全部通过 4. useAutoSave TypeScript 类型修复 ✅ useAutoSave.ts — getEndpoint 变量显式标注 () => string 类型,消除 TS2345 编译错误
2026-07-30 13:54:44 +08:00
input_snapshot: dict = None, idempotency_key: str = None,
input_revision: int = None):
"""创建新任务(幂等检查)。"""
import json
if idempotency_key:
existing = GenerationTask.query.filter_by(
user_id=user_id,
idempotency_key=idempotency_key,
).filter(GenerationTask.status.in_(["queued", "running", "done"])).first()
if existing:
return existing
task = GenerationTask(
user_id=user_id,
artifact_type=artifact_type,
operation=operation,
workspace_id=workspace_id,
title_snapshot=title,
input_snapshot_json=json.dumps(input_snapshot, ensure_ascii=False) if input_snapshot else None,
input_revision 正确记录 ✅ task_service.py — create_task() 新增 input_revision 参数,存入 GenerationTask.input_revision generation_task.py — 模型的静态 create_task 方法同步新增参数 ppt/routes.py — 三处调用(parse/generate/regenerate)均传入 session.draft_revision poster/service.py — 海报生成传入 record.draft_revision 现在每个任务快照都记录了创建时的草稿版本号,不再一直是默认值 1 2. 海报任务进度实时同步 ✅ celery_tasks.py — 在海报生成的每个阶段(preparing_data→20%、building_prompt→40%、requesting_image→60%、saving→85%)调用 sync_poster_progress() 同步到 PosterRecord.task_status/task_progress 任务领取时立即将 PosterRecord.task_status 设为 running 解决了"页面轮询海报记录时一直显示排队中"的问题 3. 后端测试覆盖 ✅ test_task_state_sync.py — 新增 13 个测试用例: sync_workspace_status 同步 cancelled/failed 到 PPT session(4 个) sync_workspace_status 同步 cancelled/failed 到 PosterRecord(2 个) 旧任务跳过同步的安全检查(1 个) cancel_task 状态更新和非 queued 拒绝(2 个) mark_task_viewed 正确标记和权限校验(2 个) input_revision 正确记录和默认值(2 个) list_active_tasks 排除终态任务(1 个) 所有 13 个新测试 + 原有 10 个测试全部通过 4. useAutoSave TypeScript 类型修复 ✅ useAutoSave.ts — getEndpoint 变量显式标注 () => string 类型,消除 TS2345 编译错误
2026-07-30 13:54:44 +08:00
input_revision=input_revision or 1,
idempotency_key=idempotency_key,
)
db.session.add(task)
db.session.commit()
return task