阶段 当前状态 说明 Phase 0~3 基本完成 Document IR、证据链、人工确认、不可变 PlanData Snapshot、海报/PPT 投影已实现 Phase 4 代码完成 场景、策略、模板版本,校验/发布门禁,确定性 resolver 和严格槽位合并已实现 Phase 5 代码完成 输入冻结、PPTX/海报对账、失败关闭、幂等、心跳、重试和冻结输入重放已实现 Phase 6 基础完成 质量看板、结构化告警、Golden 审批、留存清理、孤儿检查和灰度开关已实现 正式上线 未完成 缺真实样本、生产模板、业务规则签字和灰度观察 目前验证基线: PPT/海报专项测试:107 passed, 1 skipped Vue TypeScript 检查:通过 前端生产构建:通过 代码变更仍在工作区,尚未提交 仓库全量测试仍有既有失败/挂起项,暂时不能宣称全仓测试完全绿色 仍未完成的代码任务主要有: 影子解析差异流水线 目前有灰度开关,但还没有完整的“新旧解析同时运行、字段差异入库、按保司/profile 聚合”的影子比较任务。 自动视觉回归 目前实现的是 DOM 模块、溢出、尺寸、文本和数值检查;还缺基于真实模板和标准图片的像素差异、字体缺失、遮挡和裁切回归。 告警通道接入 后台已经能产生结构化质量告警,但尚未自动推送到邮件、企微或其他通知通道。 留存任务生产化 dry-run、实删服务和失败审计已经具备,但尚未接入周期性 Celery/定时任务,也没有自动重试失败清理批次。 旧链路最终下线 旧 PPT 解析器和海报紧凑解析仍保留为回滚路径。需要全量灰度稳定后才能删除或彻底关闭写入口。 全仓测试收口 需要处理现有无关失败和挂起测试,建立真正全绿的 CI 基线。 仍需外部输入和生产环境完成的事项: 至少 30 份脱敏 Golden PDF,并完成双人标注和精确率验收。 业务专家确认派生公式、缺失值、可比较性和结论策略。 上传并标注真实生产 PPTX 的语义 shape、页面类型和容量。 安装生产字体并建立视觉基准图片。 实际执行数据库迁移 038~040。 完成留存 dry-run、实删演练以及 10% → 30% → 100% 灰度。 观察期通过后开启 SCENARIO_ENGINE_V2,目前它仍默认关闭;真实清理开关也默认关闭。 完整状态记录在 [PPT与海报Phase4至6补充实施记录](D:/work/code/python/coding/baodanagent/docs/PPT与海报Phase4至6补充实施记录_20260802.md)。
86 lines
5.3 KiB
Python
86 lines
5.3 KiB
Python
"""PPT 生成会话模型。"""
|
||
from sqlalchemy import Column, String, Text, Integer, TIMESTAMP, func
|
||
from insurance.db.compat import db
|
||
|
||
|
||
class PptSession(db.Model):
|
||
"""PPT 生成会话表。"""
|
||
__tablename__ = "insurance_ppt_sessions"
|
||
|
||
id = Column(String(36), primary_key=True, comment="UUID")
|
||
user_id = Column(String(64), nullable=False, comment="用户 ID")
|
||
status = Column(String(20), default="created", nullable=False,
|
||
comment="状态: created/parsing/parsed/generating/done/error")
|
||
files_json = Column(Text, nullable=True, comment="上传文件列表 JSON")
|
||
extractions_json = Column(Text, nullable=True, comment="提取结果 JSON")
|
||
parse_progress = Column(Integer, default=0, nullable=False, comment="解析进度 0-100")
|
||
parse_message = Column(Text, nullable=True, comment="解析进度说明")
|
||
parse_error = Column(Text, nullable=True, comment="解析任务错误")
|
||
parse_started_at = Column(TIMESTAMP, nullable=True, comment="解析开始时间")
|
||
parse_finished_at = Column(TIMESTAMP, nullable=True, comment="解析完成时间")
|
||
chat_history_json = Column(Text, nullable=True, comment="对话历史 JSON")
|
||
ppt_path = Column(String(500), nullable=True, comment="生成的 PPT 路径")
|
||
markdown_path = Column(String(500), nullable=True, comment="Markdown 路径")
|
||
preview_paths_json = Column(Text, nullable=True, comment="预览图路径 JSON")
|
||
preview_pdf_path = Column(String(500), nullable=True, comment="预览 PDF 路径")
|
||
slide_count = Column(Integer, nullable=True, comment="幻灯片数量")
|
||
# 工作区字段(migrate_022)
|
||
title = Column(String(200), default="", comment="用户可识别名称")
|
||
workflow_step = Column(String(20), default="upload", comment="当前业务步骤")
|
||
draft_options_json = Column(Text, nullable=True, comment="模板、公司、脱敏等草稿设置")
|
||
draft_revision = Column(Integer, default=1, comment="当前草稿版本")
|
||
generated_revision = Column(Integer, default=0, comment="最新成品版本")
|
||
latest_task_id = Column(String(36), nullable=True, comment="最近一次任务 ID")
|
||
latest_output_path = Column(String(500), nullable=True, comment="最新成品路径")
|
||
archived_at = Column(TIMESTAMP, nullable=True, comment="归档时间")
|
||
# 预览与质量检查(migrate_025)
|
||
slides_json_path = Column(String(500), nullable=True, comment="幻灯片结构 JSON 路径")
|
||
quality_report_json = Column(Text, nullable=True, comment="质量检查报告 JSON")
|
||
preview_status = Column(String(20), default="none", nullable=False,
|
||
comment="预览状态: none/generating/ready/failed")
|
||
deck_contract_path = Column(String(500), nullable=True, comment="DeckContract 快照路径")
|
||
versions_json = Column(Text, nullable=True, comment="版本历史 JSON 数组")
|
||
snapshot_ids_json = Column(Text, nullable=True, comment="各计划书 confirmed snapshot ID 数组")
|
||
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
|
||
from insurance.utils.public_payload import strip_server_paths
|
||
|
||
files = json.loads(self.files_json) if self.files_json else []
|
||
extractions = json.loads(self.extractions_json) if self.extractions_json else []
|
||
return {
|
||
"id": self.id,
|
||
"user_id": self.user_id,
|
||
"status": self.status,
|
||
"files": strip_server_paths(files),
|
||
"extractions": strip_server_paths(extractions),
|
||
"parse_progress": self.parse_progress or 0,
|
||
"parse_message": self.parse_message,
|
||
"parse_error": self.parse_error,
|
||
"parse_started_at": str(self.parse_started_at) if self.parse_started_at else None,
|
||
"parse_finished_at": str(self.parse_finished_at) if self.parse_finished_at else None,
|
||
"chat_history": json.loads(self.chat_history_json) if self.chat_history_json else [],
|
||
"has_ppt": bool(self.ppt_path),
|
||
"has_markdown": bool(self.markdown_path),
|
||
"has_previews": bool(self.preview_paths_json),
|
||
"slide_count": self.slide_count,
|
||
"title": self.title or "",
|
||
"workflow_step": self.workflow_step or "upload",
|
||
"draft_options": json.loads(self.draft_options_json) if self.draft_options_json else None,
|
||
"draft_revision": self.draft_revision or 1,
|
||
"generated_revision": self.generated_revision or 0,
|
||
"latest_task_id": self.latest_task_id,
|
||
"has_latest_output": bool(self.latest_output_path),
|
||
"archived_at": str(self.archived_at) if self.archived_at else None,
|
||
"has_slides_json": bool(self.slides_json_path),
|
||
"quality_report": json.loads(self.quality_report_json) if self.quality_report_json else None,
|
||
"preview_status": self.preview_status or "none",
|
||
"has_deck_contract": bool(self.deck_contract_path),
|
||
"versions": json.loads(self.versions_json) if self.versions_json else [],
|
||
"snapshot_ids": json.loads(self.snapshot_ids_json) if self.snapshot_ids_json else [],
|
||
"created_at": str(self.created_at) if self.created_at else None,
|
||
"updated_at": str(self.updated_at) if self.updated_at else None,
|
||
}
|