baodan/api/insurance/models/poster_record.py
wsb1224 cb09ed4803 已按计划书完成本轮核心 P0/P1 修复,覆盖用户反馈的三个主要问题。
主要结果:
PPT:
已完成步骤可自由返回,刷新后保留当前步骤。
结果页改为固定三栏视口,缩略图和质量面板独立滚动。
修复画布宽度计算和宽高自适应缩放。
返回配置页时恢复上次模板。
Worker 禁止静默替换模板,不兼容时明确失败。
结果页显示实际应用的模板。

海报解析与合规:
修复 insured/policy 嵌套字段映射。
增加 partial/failed 解析质量判断和缺失字段提示。
传入险种、产品、保司和别名上下文。
修复性别、币种归一化和错误字段计数。
新增服务端字符级合规接口与生成前复检。
不合规文字可高亮、点击定位并选中对应文字。

海报编辑:
AI 图片现在作为背景资产,不再替换整个海报。
单图、长图生成后仍可编辑标题、正文、CTA、数据和卖点。
背景生成失败不会清空当前编辑内容。
下载时合成背景、文字、数据、图表和免责声明。
增加可编辑文档快照、最终 PNG 保存和历史继续编辑。
新增数据库迁移:[migrate_028.py](/D:/work/code/python/coding/baodanagent/api/insurance/db/migrate_028.py)

验证结果:
专项及相关后端测试:26 passed, 1 skipped
Python 全模块编译检查:通过
前端生产构建:通过
git diff --check:通过
全量测试收集受本机缺少 python-pptx 依赖影响,报错为 ModuleNotFoundError: pptx,不是本次修改产生的测试失败。
2026-07-31 15:20:37 +08:00

106 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""海报生成记录模型。"""
from sqlalchemy import Column, String, Text, BigInteger, Integer, TIMESTAMP, func
from insurance.db.compat import db
class PosterRecord(db.Model):
"""海报生成记录表。"""
__tablename__ = "poster_records"
id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(String(50), nullable=False, comment="用户 ID")
product_id = Column(String(50), nullable=True, comment="产品 ID")
product_source_type = Column(String(20), nullable=True, comment="library_product/user_material")
product_source_id = Column(String(64), nullable=True, comment="产品来源 ID")
product_snapshot_json = Column(Text, nullable=True, comment="提交生成时的产品快照 JSON")
case_upload_id = Column(BigInteger, nullable=True, comment="关联计划书上传 ID")
template_id = Column(BigInteger, nullable=True, comment="海报模板 ID")
copy_mode = Column(String(20), nullable=True, comment="文案模式: template/ai")
copy_content = Column(Text, nullable=True, comment="最终文案 JSON")
ai_raw_content = Column(Text, nullable=True, comment="AI 原始文案 JSON合规留痕")
export_url = Column(String(500), nullable=True, comment="导出文件地址")
export_format = Column(String(10), nullable=True, comment="导出格式: png/jpg")
export_size = Column(String(20), nullable=True, comment="导出尺寸")
reference_image_used = Column(String(500), nullable=True, comment="使用的参考图(合规留痕)")
prompt_used = Column(Text, nullable=True, comment="完整 prompt合规留痕")
generation_mode = Column(String(20), nullable=True, comment="生成方式: ai/fallback")
image_provider = Column(String(50), nullable=True, comment="图片供应商")
image_model = Column(String(100), nullable=True, comment="图片模型名称")
task_status = Column(String(20), default="pending", comment="任务状态: pending/queued/generating/done/failed")
task_progress = Column(db.Integer, default=0, comment="任务进度 0-100")
task_error = Column(Text, nullable=True, comment="任务错误信息")
extra_data = Column(Text, nullable=True, comment="扩展数据 JSON如品牌策略、输出模式")
document_json = Column(Text, nullable=True, comment="可编辑海报文档 JSON")
document_revision = Column(Integer, default=1, comment="海报文档版本")
background_file_url = Column(String(500), nullable=True, comment="AI 背景图文件地址")
compliance_json = Column(Text, nullable=True, comment="服务端合规检查结果 JSON")
compliance_revision = Column(String(64), nullable=True, comment="合规文案哈希")
# 工作区字段migrate_022
title = Column(String(200), default="", comment="工作区名称")
workflow_step = Column(String(20), default="product", comment="当前业务步骤")
draft_status = Column(String(20), default="active", comment="active/archived")
draft_revision = Column(Integer, default=1, comment="当前草稿版本")
generated_revision = Column(Integer, default=0, comment="最新成品版本")
latest_task_id = Column(String(36), nullable=True, comment="最近任务 ID")
archived_at = Column(TIMESTAMP, nullable=True, comment="归档时间")
started_at = Column(TIMESTAMP, nullable=True, comment="任务开始时间")
finished_at = Column(TIMESTAMP, nullable=True, comment="任务完成时间")
created_at = Column(TIMESTAMP, server_default=func.now())
def to_dict(self):
import json
def _safe_json(text):
if not text:
return None
try:
return json.loads(text)
except (json.JSONDecodeError, TypeError):
return None
return {
"id": self.id,
"userId": self.user_id,
"productId": self.product_id,
"productSource": {
"type": self.product_source_type or "library_product",
"id": self.product_source_id or self.product_id,
},
"productSnapshot": _safe_json(self.product_snapshot_json),
"caseUploadId": self.case_upload_id,
"templateId": self.template_id,
"copyMode": self.copy_mode,
"copyContent": _safe_json(self.copy_content),
"aiRawContent": _safe_json(self.ai_raw_content),
"exportUrl": self.export_url,
"exportFormat": self.export_format,
"exportSize": self.export_size,
"referenceImageUsed": self.reference_image_used,
"promptUsed": self.prompt_used,
"generationMode": self.generation_mode,
"imageProvider": self.image_provider,
"imageModel": self.image_model,
"taskStatus": self.task_status,
"taskProgress": self.task_progress,
"taskError": self.task_error,
"extraData": _safe_json(self.extra_data),
"document": _safe_json(self.document_json),
"documentRevision": self.document_revision or 1,
"backgroundUrl": (
f"/insurance/poster/records/{self.id}/background"
if self.background_file_url else None
),
"compliance": _safe_json(self.compliance_json),
"complianceRevision": self.compliance_revision or "",
"title": self.title or "",
"workflowStep": self.workflow_step or "product",
"draftStatus": self.draft_status or "active",
"draftRevision": self.draft_revision or 1,
"generatedRevision": self.generated_revision or 0,
"latestTaskId": self.latest_task_id,
"archivedAt": self.archived_at.isoformat() if self.archived_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,
"createdAt": self.created_at.isoformat() if self.created_at else None,
}