54 KiB
保险智能客服系统 PPT 与海报优化修复计划书
版本:v1.1 日期:2026-07-31
范围:PPT 生成、海报生成、保司管理、产品管理、PPT 模板管理、文案模板管理
原则:先恢复可用性,再统一配置与数据口径,最后补齐管理能力;仅修改api/insurance/、frontend/、tests/和自定义迁移,不修改 BaoDan 基座业务文件。
1. 执行摘要
本轮需求应拆成四个层次推进:
- P0 恢复 PPT 生成:当前 PPT 一直生成不出来存在已确认的代码级直接根因。
generate_ppt_task()在异常捕获块之前访问未定义变量task.workspace_id,Worker 领取任务后会立即抛出NameError,任务还可能停留在running。 - P0 让所选 PPT 模板真正生效:当前 Worker 能正确解析
templateId和源模板路径,但渲染器随后删除源 PPTX 的全部幻灯片,再用统一的硬编码构建器重建页面。三个内置模板虽然分别包含 20、17、18 张完整设计页,实际成品却变成 12、10、15 张统一深蓝基础样式。 - P1 统一生成策略与字段校验:取消用户侧脱敏开关,改由保司和产品后台配置;新增 Logo 开关;补齐吸烟状态、币种和三个待确认字段;把利益演示和退保提取从阻断错误降级为非阻断警告。
- P1/P2 完善海报与后台管理:按险种和单图/长图建立字段画像;让 PPT 模板场景可扩展;为保司、产品、PPT 模板和文案模板提供安全删除能力。
本计划不建议把所有问题一次性塞进一个大版本。建议按 P0 热修、P1 数据与策略、P1 海报、P2 管理能力四个可独立验收的版本交付。
2. 审计方法与成功标准
本计划基于当前仓库代码、迁移、测试和部署配置形成,不把推测当成结论。
2.1 已执行的检查
- 对 PPT/海报前后端、后台管理、数据模型、迁移和测试进行了关键词与调用链审计。
- 对 PPT 路由、校验、归一化、渲染器、Celery 任务执行了 Python 语法检查,语法检查通过。
- 执行相关测试:
- 结果:
10 passed, 1 failed, 1 skipped。 - 唯一失败为提取器测试 mock 仍返回旧签名,实际代码现在要求
_extract_pdf_text()返回(pdf_text, page_qualities),说明测试与新接口不同步。
- 结果:
- 检查本地 SQLite:
- 迁移历史只到
migrate_016。 - 当前代码中的统一任务表和工作区字段由
migrate_022创建。 - 这不是线上环境已确认事实,但属于必须上线前核验的高风险环境差异。
- 迁移历史只到
- 检查 Docker 配置:
- Worker 已监听
insurance队列。 - API 与 Worker 已配置相同
INSURANCE_STORAGE_ROOT。 - 自定义镜像显式安装并验证
python-pptx。
- Worker 已监听
2.2 总体验收目标
- PPT 正常任务在约定时限内从
queued → running → done,失败任务必定进入failed,不存在永久running。 - PPT 和海报页面不再出现用户脱敏开关;最终是否脱敏完全由后台当前配置快照决定。
- Logo 关闭后,PPT、海报预览、导出文件和 AI Prompt 均不出现 Logo。
- 利益演示或退保提取缺失/异常不再阻止进入生成步骤。
- 单图与长图、不同险种使用各自字段规则,不因无关字段缺失阻塞。
- 自定义场景能被创建、选择、保存和用于模板生成,不被固定场景校验拒绝。
- 选择三个内置 PPT 模板中的任意一个时,输出必须保留该模板的母版、页面结构、背景、图片、图表、表格和排版特征,不得退回统一基础样式。
- 任务结果可追溯实际使用的模板资产版本和 SHA-256;“已应用模板”不能只代表模板 ID 校验通过。
- 四类后台对象均可删除或安全下架,历史记录和已生成文件仍可追溯。
3. 当前问题与结论
| 编号 | 需求 | 当前实现 | 结论 | 优先级 |
|---|---|---|---|---|
| 1 | 后台控制脱敏 | PPT、海报前台均有 useMaskedData 开关;后台只有脱敏展示名,没有启停开关 |
必须移除前台控制,并由服务端解析后台策略 | P1 |
| 2 | PPT 核验字段与非阻断板块 | 核验页缺吸烟状态和币种编辑项;后端仍把部分利益/退保问题标成 error |
新增字段并重构错误分级 | P1 |
| 3 | PPT 一直无法生成 | Worker 使用未定义的 task,且位于 try 之前 |
已确认 P0 直接根因 | P0 |
| 4 | 后台控制 Logo | 有 Logo 上传/主 Logo,但无是否启用字段 | 增加保司级 logo_enabled,服务端强制执行 |
P1 |
| 5 | 海报按险种和版式取字段 | 当前主要按字段是否存在展示,单图/长图只改变布局高度 | 需要字段画像和内容预算层 | P1 |
| 6 | PPT 生成场景可添加 | 前端固定 3 个场景;后端探测器固定 6 个代码;自定义值会被匹配校验拒绝 | 不能只做 allow-create,需拆分业务场景和计算模式 |
P1 |
| 7 | 四类后台删除 | 仅文案模板已有硬删除;保司、产品、PPT 模板没有删除接口 | 增加软删除和引用检查 | P2 |
| 8 | 三个内置 PPT 模板选中后仍使用基础样式 | 模板 ID 和文件路径已进入 Worker,但渲染器删除源模板全部页面,并由统一 SLIDE_BUILDERS/THEMES 重建 |
不是缓存或前端传参主因;必须新增“克隆并原位编辑”渲染模式 | P0 |
| 9 | 后台上传模板可能无法稳定保存或追溯版本 | source_template_asset_id 仅 VARCHAR(50),而 stored://{templateId}/{uuid}.pptx 可超过 50 字符;任务也未固化资产版本/hash |
扩容字段并建立版本化模板资产与任务快照 | P0/P1 |
4. P0:PPT 无法生成专项修复
4.1 已确认直接根因
调用链:
POST /insurance/ppt/generate/{sessionId}
→ task_service.create_task()
→ generate_ppt_task.apply_async(queue="insurance")
→ Worker _claim_task(taskId)
→ 访问 task.workspace_id
→ NameError: name 'task' is not defined
证据:
api/insurance/generation/celery_tasks.py:266使用task.workspace_id。- 同一函数此前没有查询或赋值
task。 _claim_task()的返回类型是bool,不会返回任务对象。- 该代码在
try之前执行,因此现有失败回写逻辑捕获不到异常。 git blame显示这段状态同步代码由 2026-07-30 的提交5f78598b3引入,属于明确回归点。
实际影响:
- Worker 任务本身异常退出。
- 任务数据库状态已由
_claim_task()改为running,但不会被改为failed。 - 前端可能持续轮询或仅看到“生成中”。
- 过期任务只能等待启动时的 stale recovery 才可能恢复,用户感知为“一直生成不出来”。
4.2 P0 修复方案
- 在
generate_ppt_task()成功领取任务后,先通过GenerationTask.query.get(task_id)获取任务。 - 把任务读取、工作区状态同步和
_execute_ppt_generate()全部放入同一个try。 - 若任务记录不存在,显式结束,不继续执行。
- 所有异常统一:
- 任务状态改为
failed; error_code=generate_error;- 同步
PptSession.status/error/workflow_step; - 记录带
task_id/session_id/stage的结构化日志。
- 任务状态改为
- 不使用 Celery 声明上的
max_retries伪装重试能力:- 当前函数没有调用
self.retry(); - 第一阶段直接移除误导性重试参数,或明确仅对瞬时渲染异常调用受控重试;
- 数据校验错误不得重试。
- 当前函数没有调用
- 增加一次性运维脚本或管理命令,把该回归期间超过阈值的
runningPPT 任务改为failed/stale_task,同步工作区状态。
推荐的最小修复结构:
if not _claim_task(task_id):
return
try:
task = GenerationTask.query.get(task_id)
if not task:
return
sync_workspace_to_generating(task.workspace_id)
_execute_ppt_generate(task_id)
except Exception as exc:
mark_task_and_workspace_failed(task_id, exc)
raise
4.3 必须同时核验的环境项
直接根因修复后,仍需在部署环境执行以下检查,防止第二层问题被遮住:
- 数据库迁移:
db_migration_history至少包含migrate_022~migrate_026。insurance_generation_tasks表存在。insurance_ppt_sessions包含workflow_step/latest_task_id/draft_revision等字段。
- Worker:
celery inspect registered能看到insurance.generate_ppt。- Worker 命令包含
-Q ...,insurance。
- 共享存储:
- API 和 Worker 的
INSURANCE_STORAGE_ROOT完全一致。 - Worker 可写
outputs/ppt/{userId}/{taskId}。 - API 可读 Worker 生成的同一路径。
- API 和 Worker 的
- 渲染依赖:
- Worker 容器内
import pptx成功。 fast_pptx_renderer.py存在。- 使用 Worker 实际 Python 解释器完成一次最小 DeckContract 冒烟测试。
- Worker 容器内
4.4 P0 测试
新增测试:
test_generate_ppt_task_loads_task_before_workspace_synctest_generate_ppt_task_exception_marks_task_failedtest_generate_ppt_task_exception_syncs_workspace_failedtest_generate_ppt_task_missing_task_exits_cleanlytest_generate_ppt_task_success_reaches_donetest_stale_running_generate_task_is_recovered- API/Worker/共享存储容器级冒烟测试
修复现有失败测试:
- 更新
tests/ppt_task_lifecycle_test.py中_extract_pdf_textmock,使其返回("文本", page_qualities)。 - 该失败不是当前 PPT 生成直接根因,但必须清零后才允许发布。
4.5 P0 验收
- 连续生成 20 次 PPT,无永久
queued/running。 - 人工制造渲染异常,30 秒内前端显示可理解的失败原因。
- 重启 Worker 后,旧任务不会覆盖新任务状态。
- 生成文件可由 API 下载,PPTX 可被 PowerPoint/WPS 打开。
5. 后台统一脱敏策略
5.1 当前范围说明
现有 masking.py 实际处理的是保司名称和产品名称替换,不是完整客户隐私脱敏系统。后台已有:
PptCompany.masked_display_namePptProduct.masked_display_name
前端 PPT 和海报却让用户通过 useMaskedData 决定是否执行,服务端也信任该请求参数。这会导致同一产品由不同用户生成不同口径,且用户可以绕过后台策略。
本轮建议明确:
- 保司开关只控制保司名称是否替换为保司脱敏展示名。
- 产品开关只控制产品名称是否替换为产品脱敏展示名。
- 客户姓名、证件号、手机号等个人信息脱敏属于另一套 PII 规则;若业务也要求,应另立范围,不能误以为当前名称替换已经覆盖。
5.2 数据模型
在新迁移中增加:
insurance_ppt_companies.masking_enabled BOOLEAN NOT NULL DEFAULT FALSE
insurance_ppt_companies.logo_enabled BOOLEAN NOT NULL DEFAULT TRUE
insurance_ppt_products.masking_enabled BOOLEAN NOT NULL DEFAULT FALSE
不设计保司与产品之间的复杂继承关系:
- 保司名称是否脱敏,看保司自己的开关。
- 产品名称是否脱敏,看产品自己的开关。
- 多产品 PPT 对每个产品独立处理。
这样每个后台开关的含义唯一,也符合“一键设置”的要求。
5.3 服务端策略
新增统一解析函数,例如:
resolve_generation_brand_policy(company, products)
→ companyNameMasked
→ productNameMaskedById
→ logoEnabled
→ policyVersion
规则:
- PPT/海报 API 忽略客户端传入的
useMaskedData。 - 任务创建时从数据库读取当前配置,写入任务快照。
- Worker 只使用快照,不在执行中途重新读取,避免管理员修改设置导致同一任务前后不一致。
- 历史记录保存最终策略布尔值和策略版本,不保存无必要的原始敏感文本。
- 用户自上传资料没有后台产品配置时:
- 默认不自动伪造脱敏产品名;
- 若能匹配到保司,则仍执行保司名称策略;
- 产品名脱敏需先由管理员建立映射,或在用户资料模型中另加管理员审核配置。
5.4 前后端改造
后台:
- 保司管理增加“名称脱敏”开关。
- 产品管理增加“名称脱敏”开关。
- 开启但未填写
maskedDisplayName时禁止保存,避免运行时采用不可审计的临时规则。
用户端:
- 删除
PptGenerate.vue的“数据脱敏”开关。 - 删除
PosterProductPanel.vue的“使用脱敏数据”开关。 - 删除草稿、请求类型和自动保存中的
useMaskedData。 - 可只读显示“后台策略:保司名已脱敏/产品名未脱敏”,但不能由用户修改。
兼容:
- API 在一个版本内可接收旧
useMaskedData字段但完全忽略,并记录一次弃用日志。 - 下一版本从接口文档和类型中删除。
5.5 验收矩阵
| 保司开关 | 产品开关 | 期望结果 |
|---|---|---|
| 关 | 关 | 保司名、产品名均显示真实配置名 |
| 开 | 关 | 只替换保司名 |
| 关 | 开 | 只替换产品名 |
| 开 | 开 | 两者均替换 |
以上四种组合需分别验证 PPT、海报预览、最终导出、历史快照和 AI Prompt。
6. Logo 启停策略
6.1 当前问题
系统已有 Logo 上传、排序、主 Logo 和 logoUrl,但没有启停配置。PPT 的 DeckContract 和海报产品快照会直接携带 logoUrl。
仅在前端隐藏 Logo 不足够,因为:
- Worker 仍可能把 Logo 渲染进最终文件。
- 海报 Prompt 或快照仍可能携带 Logo。
- 用户可以直接调用 API 绕过页面。
6.2 改造方案
- 保司模型新增
logo_enabled,默认TRUE,保证升级后现有行为不突变。 - 保司管理增加“生成物展示 Logo”开关。
- 统一品牌策略解析时:
logo_enabled=false→ 输出logoUrl=""、logoAssetId=null;- 不删除已上传 Logo;
- 重新开启后继续使用原主 Logo。
- PPT:
- DeckContract 中不携带 Logo。
- 模板占位符没有 Logo 时隐藏,而不是显示破图或空白边框。
- 海报:
- HTML 画布和最终导出均不渲染 Logo 图层。
- AI 图片 Prompt 继续禁止模型生成伪 Logo。
- 多保司比较:
- 每个保司分别按自身开关处理;
- 不因其中一家关闭而隐藏其他保司 Logo。
6.3 验收
- 关闭 Logo 后,新生成 PPT/海报无 Logo。
- 历史文件不被追溯修改。
- 重新开启后恢复主 Logo。
- 无主 Logo、Logo 文件已丢失、外部 URL 失效时均能无损降级。
7. PPT 核验字段与校验分级
7.1 新增关键字段
核验页“关键字段”新增:
| 中文名 | 原始字段 | 归一化字段 | 类型 | 建议口径 |
|---|---|---|---|---|
| 吸烟状态 | insured.smoker |
insured.smoker |
枚举 | yes/no/unknown |
| 币种 | policy.currency |
policy.currency |
枚举 | ISO 4217 代码 |
| 基本计划年保费 | policy.basic_plan_annual_premium |
policy.basicPlanAnnualPremium |
金额,可选 | 仅来源明确标注时填写 |
| 基本计划名义金额 | policy.basic_sum_insured |
policy.basicSumInsured |
金额,可选 | 对应 Basic Sum Assured/Notional Amount |
| 首年应缴金额 | policy.first_year_amount_due |
policy.firstYearAmountDue |
金额,可选 | 含折扣、征费或附加费后的首年实际应缴 |
注意:
- 需求中的“USB”应按“USD”处理;
USB不是货币代码,不应作为选项。 - 人民币统一保存为
CNY,页面可以显示“人民币(CNY)”;输入RMB/¥时归一化为CNY。 - 建议常用选项:
USD、HKD、CNY、SGD、EUR、GBP,并保留可搜索的 ISO 代码扩展能力。 - 当前
normalizer.py在部分路径中把缺失币种默认成USD。该行为必须移除,未知币种应为null/unknown并提示用户确认,不能伪装成已确认数据。
7.2 三个待判断字段的结论
仓库目前没有真实 PDF 样本,无法证明三个中文标签在所有保司计划书中含义一致。因此不能把它们直接设为所有险种必填。
推荐结论:
- 基本计划年保费
- 与现有
annual_premium很可能重叠。 - 只有计划书同时出现“基本计划年保费”和“总年保费/附加保障保费”时,才应保存为独立字段。
- 普通储蓄险只有一个年缴金额时,继续使用
annual_premium。
- 与现有
- 基本计划名义金额
- 可映射到已有提取结构中的
basic_sum_insured。 - 对 CI/IUL 更常见;储蓄险可能不存在或不适合作为主要销售指标。
- 建议作为条件字段,不设全局必填。
- 可映射到已有提取结构中的
- 首年应缴金额
- 不等同于年缴保费,可能包含首年折扣、保费征费、附加费。
- 当前
total_premium_with_levy只能作为候选来源,不能无条件等同。 - 建议新增独立字段,并保留
source_label/source_page。
落地前需要业务方提供至少以下脱敏样本:
- 储蓄险 3 份,至少 2 家保司;
- 重疾险 3 份,至少 2 家保司;
- IUL 3 份,至少 2 家保司;
- 至少 1 份含征费/折扣;
- 至少 1 份明确出现 Basic Sum Assured/Notional Amount。
样本确认后建立“原文标签 → 规范字段”映射表和金标测试,未确认前三个字段均按可选字段开发。
7.3 必填与非阻断规则
建议将校验分为三级:
BLOCKER:无法确定产品或生成基本事实,禁止进入生成
WARNING:可能影响部分页面准确性,允许继续
INFO:展示提示,不影响流程
全局 BLOCKER:
- 产品名称缺失;
- 险种无法确定;
- 被保人年龄缺失或非法;
- 币种缺失;
- 生成所需的核心金额全部缺失。
按险种 BLOCKER:
- 储蓄险:年缴保费、缴费年期。
- 重疾险:保额、年缴保费、缴费年期。
- IUL:保额;目标/计划保费至少有一个有效值。
吸烟状态:
- 作为关键字段展示。
yes/no已确认时正常通过。unknown时产生 WARNING,允许继续;避免计划书确实未载明时形成死锁。
以下全部改为 WARNING,不再阻断:
- 利益演示行不足;
- 利益演示年份不连续;
- 利益演示金额关系异常;
- 退保提取缺失;
- 退保提取年份不连续;
- 利益/退保来源页缺失。
生成策略:
- 数据缺失的板块不生成对应页面,或明确显示“待正式计划书确认”。
- 不得用
0、默认USD或 AI 推测值填补缺失事实。 - 用户完成生成后可以回到核验步骤修改数据,再创建新版本。
7.4 前端交互
- 关键字段显示必填标识和来源页。
- 币种使用可搜索下拉框。
- 吸烟状态使用“吸烟/不吸烟/未确认”单选或下拉。
- WARNING 使用黄色,不禁用“下一步”。
- 点击警告可定位对应字段或板块。
- 进入下一步时如仍有 WARNING,弹出一次汇总确认,不要求逐条解决。
- 结果页“返回修改”保留当前 session、模板、字段和修改日志;重新生成形成新版本,不覆盖旧版本。
7.5 后端改造点
prompts.py:补齐三个候选字段及来源要求。regex_extractor.py:补充标签映射;未知币种不默认 USD。normalizer.py:全险种统一输出吸烟、币种及三个候选字段。validator.py:按分区和险种分级;利益/退保只返回 warning。routes.py:validated只由 BLOCKER 数量决定。celery_tasks.py:生成阶段不得重新把 WARNING 当成 error 阻断。PptDataReview.vue:补字段、选项、来源和非阻断交互。fast_pptx_renderer.py:字段缺失时隐藏相关卡片,不制造默认值。
8. 海报按险种和单图/长图的字段画像
8.1 当前问题
当前海报前端主要根据 parsedFields 中是否存在字段决定展示;outputMode=single/long 主要只控制画布固定比例或自动高度。系统还没有“险种 × 版式”的字段选择层,因此会出现:
- 单图塞入过多表格和事实,信息密度过高;
- 长图仍只显示单图级摘要,信息不足;
- 储蓄险、重疾险、IUL 使用同一套卡片;
- 某险种不存在的字段被误判为缺失;
- 生成 Prompt 和 HTML 画布可能使用不同字段。
8.2 推荐架构
新增单一字段画像配置,不在多个组件中各写一套判断:
PosterFieldProfile
planType: savings | ci | iul | other
outputMode: single | long
required: []
recommended: []
optional: []
forbidden: []
maxFeatureCount
milestoneYears
流程:
产品资料/计划书
→ 统一事实模型
→ 按险种选择 FieldProfile
→ 按 single/long 裁剪内容
→ 同一 ViewModel 同时供 HTML 预览、AI Prompt、最终导出使用
8.3 字段矩阵
储蓄险
单图:
- 必需:产品名、保司名、币种、年缴保费、缴费年期。
- 推荐:2~3 个已审核产品卖点、一个可核验里程碑数据。
- 可选:基本计划名义金额、保障期、投保年龄。
- 不展示:完整利益演示表、完整退保提取表、过多年度数据。
长图:
- 必需:单图必需字段。
- 推荐:投保条件、总计划保费、10/20/30 年里程碑、保证/非保证拆分、回本提示。
- 可选:首年应缴金额、提取方案、完整产品亮点。
- 利益演示不足时隐藏图表,不阻断长图生成。
重疾险(CI)
单图:
- 必需:产品名、保司名、币种、基本保额。
- 推荐:年缴保费、缴费年期、核心疾病/保障层级、等待期。
- 条件字段:投保年龄、性别、吸烟状态。
- 不展示:储蓄险回本倍数、退保价值营销结论。
长图:
- 必需:单图必需字段。
- 推荐:保障项目列表、早期/严重疾病结构、额外赔付、等待期、保障期限。
- 可选:豁免、身故保障、特定疾病、年龄/吸烟条件。
- 保障列表为空时只允许简版长图,并标记 WARNING。
IUL
单图:
- 必需:产品名、保司名、币种、名义保额/身故保障。
- 推荐:目标保费或计划保费、缴费方式、一个指数账户核心机制。
- 条件字段:投保年龄、性别、吸烟状态。
- 不展示:未经确认的收益率承诺。
长图:
- 必需:单图必需字段。
- 推荐:目标/最低保费、指数账户、封顶率/参与率/保底机制、关键年度账户价值和退保价值。
- 可选:贷款/提取说明、费用说明、首年应缴金额。
- 所有非保证数据必须带演示口径和免责声明。
其他险种
- 使用通用保守画像:产品名、保司名、保障对象、核心保障、适用人群、免责声明。
- 未建立专属画像前,不展示收益图表或推导指标。
8.4 单图与长图的内容预算
| 项目 | 单图 | 长图 |
|---|---|---|
| 主标题 | 1 | 1 |
| 副标题/正文 | 1~2 段 | 2~4 段 |
| 核心数字卡 | 2~4 个 | 4~8 个 |
| 卖点 | 2~3 个 | 3~6 个 |
| 图表 | 0~1 个 | 0~3 个 |
| 年度数据 | 1 个里程碑 | 3~6 个里程碑 |
| 免责声明 | 必须 | 必须、可更完整 |
8.5 改造位置
- 后端新增
poster/field_profiles.py或等价配置模块。 product_source_resolver.py输出统一事实模型,不直接决定视觉字段。content_builder.py根据画像构建 Poster ViewModel。copy_generator.py和image_generator.py只消费 ViewModel。PosterHtmlCanvas.vue不再自行猜测字段别名。PosterSummaryCards.vue接收已经筛选和排序的 cards。- 长图图表只使用服务端确认允许展示的里程碑数据。
8.6 海报验收
每个险种至少使用 2 份样本,分别生成单图和长图,共至少 12 个基准输出:
- 无跨险种错误字段;
- 单图无信息溢出;
- 长图缺失板块能自动隐藏;
- 数字、币种、产品名在预览和导出中一致;
- 后台脱敏和 Logo 策略一致生效;
- 不生成虚构文字、数字或 Logo。
9. PPT 模板“生成场景”动态化
9.1 为什么不能只把下拉框改成可输入
前端目前固定三个场景:
single_savingsmulti_savings_comparisonsavings_iul_comprehensive
后端 detect_generation_scenario() 仍使用固定代码计算场景;生成接口还要求模板 scenario_tag 必须等于检测结果。因此只给 el-select 增加 allow-create 会导致:
- 后台可以保存自定义值;
- 用户选择后生成接口仍提示“模板不适用于当前计划书组合”;
- 自动选模板永远不会命中新场景。
9.2 推荐模型
新增 insurance_ppt_scenarios:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
VARCHAR(50) PK | 稳定代码 |
name |
VARCHAR(100) | 后台显示名 |
base_scenario |
VARCHAR(50) | 对应内置数据组合,可空 |
generation_mode |
VARCHAR(20) | single/compare/portfolio |
description |
TEXT | 场景说明 |
status |
SMALLINT | 启停 |
sort_order |
INT | 排序 |
is_builtin |
BOOLEAN | 是否内置 |
deleted_at |
TIMESTAMP | 软删除 |
设计边界:
- 场景负责模板分类和业务话术。
- generation_mode仍由实际上传产品组合确定,负责计算逻辑。
- 自定义场景不能修改底层金融计算模式,避免后台误配置导致错误比较。
base_scenario用于自动匹配;为空的自定义场景只能由管理员配置模板、用户手动选择。
9.3 接口
GET /insurance/admin/ppt/scenarios
POST /insurance/admin/ppt/scenarios
PUT /insurance/admin/ppt/scenarios/{code}
DELETE /insurance/admin/ppt/scenarios/{code}
PUT /insurance/admin/ppt/scenarios/{code}/status
模板接口继续保存 scenarioTag=code,但要校验场景存在且启用。
9.4 前端
- PPT 模板管理从场景接口加载选项。
- 提供“新增场景”弹窗,而不是在下拉框里随意输入不可审计文本。
- 内置场景允许改显示名和排序,不允许修改代码与计算模式。
- 已被模板使用的场景删除时返回引用冲突,引导先迁移模板。
9.5 生成匹配
- 根据实际产品计算
detectedScenario和generationMode。 - 自动推荐:
- 先匹配
base_scenario=detectedScenario; - 再按适用保司、产品和状态过滤。
- 先匹配
- 手动选择自定义场景模板:
- 不再要求
template.scenario_tag == detectedScenario; - 校验模板场景的
generation_mode与实际 mode 一致; - 继续校验适用保司和适用产品。
- 不再要求
- 任务快照同时保存:
detectedScenarioselectedScenariogenerationMode
这样既允许业务新增场景,又不破坏底层计算规则。
10. 四类后台删除能力
10.1 删除策略
这些对象会被历史记录、生成任务、产品、Logo、模板适用范围或文件引用。直接物理删除会破坏追溯,因此统一采用:
默认:软删除(deleted_at)
必要时:先停用,再删除
文件:引用计数为 0 且过保留期后异步清理
历史:永不级联删除
所有列表默认排除软删除数据,管理员可选择查看“回收站”。
10.2 保司删除
删除前检查:
- 是否存在未删除产品;
- 是否有进行中的 PPT/海报任务;
- 是否仍被模板
applicable_company_ids引用; - 是否有 Logo 文件。
规则:
- 有未删除产品时返回
409 COMPANY_HAS_PRODUCTS,不自动级联。 - 管理员先迁移或删除产品。
- 删除保司时同时软删除 Logo 元数据;物理 Logo 文件进入延迟清理。
- 历史记录保留公司 ID 和当时快照。
10.3 产品删除
删除前检查:
- 是否有进行中的任务;
- 是否被 PPT 模板适用产品列表引用;
- 是否有关联已审核产品小册子或用户资料。
规则:
- 进行中任务存在时禁止删除。
- 模板引用时返回具体模板列表。
- 已生成历史不阻止软删除。
- 小册子文件不立即删除,按数据保留策略异步处理。
10.4 PPT 模板删除
规则:
- 内置模板默认只允许停用/隐藏,不物理删除内置资产。
- 自定义模板允许软删除。
- 进行中任务使用该模板时禁止删除。
- 历史 PPT 继续保留
template_id和内容快照。 - 模板源 PPTX 只有在无其他版本引用且过保留期后才清理。
10.5 文案模板删除
当前 delete_copy_template() 是硬删除,应改为软删除:
- 新增
deleted_at。 - 海报历史必须使用生成时的文案快照,不回查已删除模板内容。
- 已被草稿引用时允许软删除,但草稿打开时提示“模板已下架,保留当前文案,重新选择后才能再次套用”。
10.6 API 与交互
新增:
DELETE /insurance/admin/ppt/companies/{id}
DELETE /insurance/admin/ppt/products/{id}
DELETE /insurance/admin/ppt/templates/{id}
DELETE /insurance/admin/ppt/copy-templates/{id} # 改软删除语义
统一响应:
{
"code": 0,
"message": "success",
"data": {
"id": "xxx",
"deleted": true,
"deletedAt": "2026-07-31T12:00:00"
}
}
引用冲突:
{
"code": 1409,
"message": "该保司仍有 3 个产品,无法删除",
"data": {
"reason": "COMPANY_HAS_PRODUCTS",
"references": []
}
}
前端要求:
- 仅
config_manage权限可见删除按钮。 - 二次确认显示对象名称和影响。
- 禁止只用无上下文的“确定删除?”。
- 删除成功后刷新当前分页,不跳回第一页。
- 409 时展示具体依赖和处理入口。
- 所有删除写入
system_operation_logs。
11. 数据库迁移计划
建议分两个迁移,降低回滚复杂度。
11.1 migrate_027:策略与软删除
新增:
insurance_ppt_companies.masking_enabled
insurance_ppt_companies.logo_enabled
insurance_ppt_companies.deleted_at
insurance_ppt_products.masking_enabled
insurance_ppt_products.deleted_at
insurance_ppt_templates.deleted_at
poster_copy_templates.deleted_at
默认值:
masking_enabled = FALSE,保持升级前默认不脱敏。logo_enabled = TRUE,保持升级前 Logo 行为。deleted_at = NULL。
增加必要索引:
- 公司、产品、模板的
(status, deleted_at)。 - 产品的
(company_id, deleted_at)。 - 文案模板的
(status, deleted_at)。
11.2 migrate_028:动态场景
- 创建
insurance_ppt_scenarios。 - 写入现有内置场景。
- 为已有模板的未知
scenario_tag补建禁用的兼容场景记录,避免数据丢失。 - 增加
base_scenario/generation_mode校验。
11.3 迁移安全
- 迁移只加列、加表、加索引,不删旧字段。
- 发布前对生产库备份。
- API 启动后健康检查返回当前迁移版本。
- 若迁移未到 028,管理后台禁用新开关和场景编辑并显示明确错误,不静默失败。
- 必须修复或补测 SQLite 兼容性;当前
migrate_022的表/列存在检查主要面向 PostgreSQL/MySQL。
12. API 改造清单
12.1 保司与产品
GET companies/products返回maskingEnabled/deletedAt。- 保司额外返回
logoEnabled。 POST/PUT支持新字段。DELETE执行软删除与引用检查。- 公共生成选项接口只返回
status=1 AND deleted_at IS NULL。
12.2 PPT 生成
请求中移除:
useMaskedData
任务快照新增:
{
"brandPolicy": {
"companyMaskingEnabled": true,
"productMaskingById": {
"product-a": true
},
"logoEnabled": false,
"policyVersion": 1
},
"detectedScenario": "single_savings",
"selectedScenario": "retirement_education",
"generationMode": "single",
"templateAsset": {
"templateId": "scenario_single_savings",
"assetId": "builtin://single_savings.pptx",
"version": 1,
"sha256": "ba7b198e92caba6316ed8db71f0ace4231d9b570aa78f8d2ad96ce6e3c31f184",
"rendererMode": "clone-edit-v2"
}
}
12.3 校验
校验响应新增:
{
"canProceed": true,
"blockerCount": 0,
"warningCount": 3,
"infoCount": 1
}
兼容旧字段:
- 一个版本内继续返回
validated = blockerCount == 0。 - 前端改用
canProceed。
12.4 海报
海报创建和文案生成请求不再接收 useMaskedData。
响应/任务快照增加:
{
"planType": "savings",
"outputMode": "single",
"fieldProfileVersion": 1,
"brandPolicy": {}
}
13. 代码修改范围
13.1 后端
| 文件/模块 | 改动 |
|---|---|
api/insurance/generation/celery_tasks.py |
P0 任务修复、校验分级、策略快照消费;固化模板资产版本/hash/渲染模式 |
api/insurance/generation/task_service.py |
失败状态同步、stale 任务恢复 |
api/insurance/ppt/routes.py |
移除用户脱敏控制、动态场景、校验响应 |
api/insurance/ppt/validator.py |
BLOCKER/WARNING/INFO 分级 |
api/insurance/ppt/normalizer.py |
新字段和未知币种处理 |
api/insurance/ppt/prompts.py |
新字段提取口径 |
api/insurance/ppt/regex_extractor.py |
标签与币种归一化 |
api/insurance/ppt/renderer.py |
Logo/脱敏策略写入 DeckContract;按 rendererMode 分流真实模板与通用构建器 |
api/insurance/ppt/scripts/fast_pptx_renderer.py |
禁止在真实模板模式删除源幻灯片;按模板清单原位替换形状、图表、表格和图片 |
api/insurance/ppt/template_asset_service.py |
模板入库校验、规范化、SHA-256、版本化和绑定清单解析 |
api/insurance/ppt/masking.py |
从请求布尔值改为实体策略 |
api/insurance/poster/product_source_resolver.py |
品牌策略和统一事实模型 |
api/insurance/poster/content_builder.py |
按画像构建 ViewModel |
api/insurance/poster/copy_generator.py |
只消费允许字段 |
api/insurance/poster/image_generator.py |
禁止伪 Logo,执行 Logo 策略 |
api/insurance/poster/field_profiles.py |
新增险种 × 版式画像 |
api/insurance/admin/ppt_admin_service.py |
新开关、动态场景、删除与引用检查 |
api/insurance/admin/ppt_admin_routes.py |
新接口 |
api/insurance/models/ppt_config.py |
新字段、场景模型;模板资产字段扩容及版本关系 |
api/insurance/models/poster_copy_template.py |
软删除 |
api/insurance/db/migrate_027.py |
策略与软删除迁移 |
api/insurance/db/migrate_028.py |
场景迁移 |
api/insurance/db/migrate_029.py |
模板资产版本、字段扩容、三个内置模板清单与 hash 回填 |
13.2 前端
| 文件/模块 | 改动 |
|---|---|
PptGenerate.vue |
删除脱敏开关、显示只读后台策略 |
PptDataReview.vue |
新字段、币种、非阻断警告、返回修改 |
PptResult.vue |
返回核验并生成新版本 |
PosterProductPanel.vue |
删除脱敏开关 |
PosterConfigRail.vue |
移除脱敏事件 |
PosterCopyPanel.vue |
请求移除 useMaskedData |
PosterHtmlCanvas.vue |
消费服务端 ViewModel |
PosterSummaryCards.vue |
消费排序后的字段卡 |
usePosterWorkspace.ts |
删除草稿脱敏字段,增加画像版本 |
PptCompaniesAdmin.vue |
脱敏、Logo、删除开关 |
PptProductsAdmin.vue |
脱敏、删除开关 |
PptTemplatesAdmin.vue |
动态场景、删除 |
CopyTemplatesAdmin.vue |
软删除交互和依赖提示 |
ppt-api.ts/poster-api.ts/ppt-admin-api.ts |
更新类型与接口 |
14. 分阶段交付计划
阶段 A:P0 热修(建议 0.5~1 人日)
- 修复未定义
task。 - 把状态同步纳入异常捕获。
- 清理 stale 任务。
- 修复失败的提取器测试 mock。
- 增加 Worker 任务回归测试。
- 在部署环境确认 022~026 迁移、队列和共享存储。
验收门槛:PPT 可稳定生成,任务状态闭环。
阶段 A2:P0 模板真实套用(建议 3~5 人日)
- 引入
clone-edit-v2,从所选源 PPTX 开始原位编辑,不再先删光源页面。 - 为三个内置模板建立逐页、逐形状的数据绑定清单。
- 修复模板图表 OOXML 兼容性并完成 PowerPoint/WPS 打开测试。
- 扩容模板资产标识,固化任务使用的版本和 SHA-256。
- 将当前“文件存在 + 页数正确”的测试升级为模板结构和视觉保真测试。
验收门槛:三个模板各生成一次,成品设计明显互异,且均保留源模板独有视觉签名、可编辑对象和正确资产 hash。
阶段 B:后台策略与 PPT 核验(建议 3~5 人日)
migrate_027。- 保司/产品脱敏开关、保司 Logo 开关。
- 移除 PPT/海报用户脱敏开关。
- 新增吸烟、币种和三个条件字段。
- 重构校验分级。
- 支持结果页返回修改和版本化重生成。
验收门槛:四种脱敏组合、Logo 开关、非阻断校验全部通过。
阶段 C:海报字段画像(建议 3~5 人日)
- 建立统一事实模型和六套核心画像。
- 单图/长图内容预算。
- 预览、Prompt、导出共用 ViewModel。
- 建立至少 12 个基准输出。
验收门槛:无跨险种字段、无虚构数字、布局不过载。
阶段 D:动态场景与删除能力(建议 3~4 人日)
migrate_028。- 场景管理接口和页面。
- 模板匹配逻辑拆分。
- 四类对象软删除、依赖检查、审计日志和回收站。
验收门槛:自定义场景可完整使用,删除不破坏历史。
阶段 E:回归与灰度(建议 2~3 人日)
- 全量后端测试、前端构建、容器冒烟。
- 真实脱敏样本 UAT。
- 灰度开启新策略。
- 观察任务失败率、平均生成时间和 stale 任务数。
总估算:约 14.5~23 人日,不包含业务方准备样本、设计稿调整和外部 AI 服务不稳定等待时间。
15. 测试计划
15.1 单元测试
- 品牌策略四种组合。
- Logo 开关。
- 币种别名归一化和未知币种。
- 三个条件字段映射。
- 各险种 BLOCKER/WARNING。
- 利益/退保异常永不成为 BLOCKER。
- 六套海报字段画像。
- 自定义场景与 generation mode 兼容性。
- 四类删除的引用检查。
15.2 API 测试
- 用户传入
useMaskedData不改变结果。 - 无权限用户不能改策略或删除。
- 删除引用冲突返回 409 和依赖详情。
- 被删除对象不出现在生成选项。
- 历史接口仍返回已删除对象的快照。
15.3 集成测试
- API 创建任务 → insurance 队列 → Worker → 文件 → 下载。
- API/Worker 共享存储。
- PostgreSQL 迁移 016 → 028。
- 空库迁移 001 → 028。
- 重复执行迁移无副作用。
- Worker 中断和恢复。
15.4 前端测试
- PPT/海报用户端无脱敏开关。
- 后台三个新开关保存后立即生效。
- 核验页 WARNING 可继续。
- 返回修改保持数据。
- 自定义场景新增、编辑、停用、删除。
- 删除成功、冲突、无权限三种状态。
15.5 视觉和文件测试
- PPTX 可打开、页数正确、无破图。
- 三个内置模板分别生成后进行逐页截图对比;不得出现三份成品共用同一深蓝基础背景的情况。
- 校验任务记录中的
templateId/assetVersion/assetSha256/rendererMode与 Worker 实际读取文件一致。 - 校验源模板独有对象仍可编辑:单储蓄模板图表与摘要表、多储蓄模板双产品对比图、储蓄险 + IUL 模板图片与组合配置页。
- 模板导入需通过 OOXML 校验及 PowerPoint/WPS 无修复提示打开;不得只依赖
python-pptx可解析。 - Logo 开/关像素级对比。
- 单图常见比例不溢出。
- 长图高度随内容增长。
- 中英文、长产品名、大金额、字段缺失。
16. 监控与可观测性
新增指标:
insurance_ppt_tasks_total{status,error_code}insurance_ppt_generation_duration_secondsinsurance_ppt_stale_tasksinsurance_ppt_render_failures_totalinsurance_poster_generation_duration_secondsinsurance_generation_policy_snapshot_totalinsurance_admin_delete_conflicts_total{entity}
日志必须包含:
task_id
workspace_id
user_id(必要时脱敏)
stage
template_id
detected_scenario
selected_scenario
generation_mode
plan_types
error_code
不得记录完整客户 PDF 文本、未脱敏姓名或完整 Prompt。
建议告警:
- 5 分钟内 PPT 失败率 > 10%;
- 任一任务
running超过 10 分钟; - Worker 未注册
insurance.generate_ppt; - API/Worker 存储探针不一致;
- 最新迁移版本低于应用要求。
17. 上线与回滚
17.1 上线顺序
- 备份数据库。
- 部署 P0 Worker 修复。
- 执行迁移 027。
- 部署兼容新旧字段的后端。
- 部署前端,移除用户脱敏开关。
- 执行迁移 028。
- 开启动态场景和删除页面。
- 完成冒烟后再开放真实用户。
必须先后端后前端,避免旧后端不识别新字段。
17.2 回滚
- 新迁移只增加字段和表,应用回滚时保留数据。
- 后端回滚后旧代码会忽略新增字段。
- Logo 默认 TRUE、脱敏默认 FALSE,回滚行为接近升级前。
- 动态场景故障时可关闭场景管理入口,保留三个内置场景。
- 删除全部为软删除,可通过回收站恢复。
- P0 Worker 修复不得回滚到含未定义变量的版本。
18. 风险与待业务确认
18.1 已确定,不需要再讨论
- PPT Worker 未定义变量必须优先修复。
- 用户侧脱敏开关必须移除,服务端不能信任旧参数。
- Logo 开关必须由服务端执行。
- 利益演示和退保提取不得阻断下一步。
USB应纠正为USD。- 删除应保护历史,不能直接级联物理删除。
18.2 开发前需要业务确认
- “脱敏”是否仅指保司/产品名称,还是还包括客户姓名、证件号、手机号和保费组合。
- 三个候选字段在真实计划书中的原文标签和业务含义。
- 吸烟状态未知时是否允许继续;本计划建议允许并警告。
- 自定义生成场景是否只用于模板分类,还是需要新增计算逻辑;本计划只开放分类和话术,不开放任意金融计算。
- 内置 PPT 模板是否允许彻底删除;本计划建议仅停用。
- 删除后的回收站保留期和源文件物理清理周期。
这些确认不会阻塞 P0 热修,可以与阶段 A 并行完成。
19. Definition of Done
本轮优化完成必须同时满足:
- P0 根因有自动化回归测试。
- 相关测试全部通过,无已知失败或跳过的核心链路。
- 生产迁移版本已核验。
- 用户端脱敏开关已移除。
- 保司/产品脱敏策略服务端强制生效。
- Logo 关闭后所有新生成物均无 Logo。
- 吸烟状态、币种和三个条件字段完成前后端贯通。
- 利益/退保问题仅为非阻断警告。
- 结果页可以返回修改并生成新版本。
- 三险种 × 两版式海报基准全部通过。
- 自定义场景可以完整创建、选择、生成和停用。
- 三个内置 PPT 模板均由
clone-edit-v2真实套用,源模板结构和视觉特征有自动化保真测试。 - 模板资产版本、SHA-256 和实际渲染模式进入不可变任务快照与结果元数据。
- 四类后台对象支持安全删除和引用提示。
- 删除、策略修改和生成均有审计记录。
- API 文档、测试用例、部署文档同步更新。
20. 建议立即执行的前三项
- 立即热修
generate_ppt_task()未定义变量,并清理 stale 任务。 - 在实际部署数据库和 Worker 中核验迁移、任务注册、队列与共享存储。
- 收集 9 份以上脱敏计划书样本,确认三个候选字段,再冻结字段规范。
在这三项完成前,不建议先做大规模界面调整;否则可能出现页面已经改完,但生成链路和字段语义仍不可靠的情况。
21. 实施记录(2026-07-31)
本次已按计划完成代码级修复:
- 修复 PPT Worker 在异常捕获前访问未定义任务变量的问题;失败任务会进入
failed并同步工作区,stale 任务统一使用stale_task。 - 新增
migrate_027.py:品牌策略、Logo 开关、软删除字段、动态场景表及六个内置场景。 - PPT/海报生成页已移除用户脱敏开关;服务端忽略旧参数,按任务创建时的后台策略快照执行。
- PPT 核验新增吸烟状态、ISO 币种及三个条件金额字段;
USB归一化为USD,RMB/¥归一化为CNY,未知币种不再默认。 - 利益演示与退保提取问题全部为非阻断警告;结果页可返回核验数据,重新生成保留历史版本。
- 海报新增三险种 × 单图/长图六套字段画像和内容预算,单图不会携带完整利益表。
- PPT 场景支持后台新增、停用、删除与模板选择;计算模式仍由实际材料组合确定。
- 保司、产品、PPT 模板、文案模板均使用软删除;保司活跃产品、内置模板和场景引用具备删除保护;策略变更和删除写入操作审计。
- API 文档和部署文档已同步。
已完成验证:
- Python 语法编译通过。
- 核心新增与现有任务/路由测试:
37 passed, 1 skipped。 - PPT 提取、校验、对比与状态同步扩展回归:
140 passed(更新旧测试口径后)。 - 使用工作区幻灯片运行环境补跑 PPT 渲染对比:
6 passed。 - 全量测试:
190 passed, 1 failed;唯一失败是既有test_chat_logs_query未创建 Flask application context,与本次 PPT/海报改动无关。 - Vue/Vite 生产构建通过。
环境限制与上线前待办:
- 尚未在生产数据库实际执行
migrate_027,也未连接真实 Celery Worker、共享存储和图片模型做端到端生成。 - 三个条件字段仍需使用脱敏真实计划书样本冻结标签语义;本实现保持可选,不将其作为全险种阻断条件。
- 三险种 × 两版式的 12 份视觉基准需要业务样本和人工验收,不能由无样本单元测试替代。
22. 三个内置 PPT 模板专项诊断与修复方案(2026-07-31 补充)
22.1 已核验事实
对源 PPTX、数据库模板记录、生成任务快照、Worker 输出和最终成品进行了逐层比对:
| 模板 | 源文件 | 源页数 | 实际成品页数 | 源模板主要视觉 | 实际成品 |
|---|---|---|---|---|---|
scenario_single_savings |
single_savings.pptx |
20 | 12 | 白底、藏蓝与金色;4 个图表、2 个摘要表 | 统一深蓝底、青绿色卡片 |
scenario_multi_savings |
multi_savings_comparison.pptx |
17 | 10 | 白底、藏蓝与金色;双产品对比图和双摘要表 | 统一深蓝底、青绿色卡片 |
scenario_savings_iul |
savings_iul_comprehensive.pptx |
18 | 15 | 米白/绿色;5 个媒体资产、家庭与顾问照片、组合配置页 | 统一深蓝底,源照片全部消失 |
数据库中三条记录的 source_template_asset_id 均正确,且都被标记为 clone_ready=true;近期任务也记录了正确的 requestedTemplateId/appliedTemplateId 和 templateFallback=false。因此这不是“用户没有选中模板”,也不是内置文件缺失。
22.2 直接根因
真实调用链为:
PptGenerate.vue 选择 templateId
→ 生成任务快照保存 templateId
→ celery_tasks.py 查询 PptTemplate
→ resolve_template_asset() 得到正确 builtin:// 文件路径
→ renderer.py 同时写入 scenarioSlides 与 templateConfig
→ fast_pptx_renderer.py 打开源 PPTX
→ while len(prs.slides): 删除全部源幻灯片
→ SLIDE_BUILDERS + THEMES 重新生成基础页面
具体有四个叠加问题:
fast_pptx_renderer.py打开源模板后立即删除全部幻灯片。源背景、图片、形状、图表、表格、字号、间距和页面编排因此全部丢失,只可能残留母版关系。renderer.py总是生成scenarioSlides;_resolve_page_types()又优先使用它,导致模板解析得到的slidesConfig和 20/17/18 页结构被覆盖。- 三个模板数据库记录的
style_preset都是broker;最终颜色由硬编码THEMES['broker']决定,所以选择不同模板仍会生成同一深蓝风格。 - 当前结果中的
appliedTemplateId只表示模板记录通过校验并进入 Worker,不表示源模板视觉被保留,属于误导性可观测状态。
前端恢复 draft_options.templateId 会让旧工作区继续显示上次选择,这可能造成“仍在用旧模板”的次级现象,但无法解释本次三个内置模板都退回基础样式;本次主因已经定位在渲染器。
22.3 模板资产本身还存在的兼容风险
- 三个源文件均没有 PowerPoint 结构化占位符,当前解析器只得到标题、页面类型和
sourceSlide,没有任何“业务字段 → shape/chart/table”绑定关系。不能靠换一个主题色实现动态填充。 single_savings.pptx的图表 XML 含barGrouping=none/standard;artifact-tool 2.8.36 无法导入,PowerPoint 自动化需要OpenAndRepair才能稳定打开。资产上线前必须规范化并重新保存,不能只以python-pptx可读取作为合格标准。source_template_asset_id当前为VARCHAR(50),而后台上传返回的stored://{templateId}/{32位uuid}.pptx在常见模板 ID 下可能超过 50 字符,造成保存失败、截断或资产记录缺失。- 任务只固化模板 ID,没有固化资产版本和 hash;管理员替换模板后,排队任务无法证明自己实际使用的是新文件还是旧文件。
22.4 推荐实现:双渲染模式
保留通用构建器作为兼容回退,但把“真实模板”与“基础主题”明确分成两条链:
clone-edit-v2
打开指定版本源 PPTX
→ 按 manifest 选择/复制源页面
→ 按 shapeId 原位改文字、表格、图表数据和图片
→ 保留母版、版式、背景、图片裁剪和可编辑对象
→ 结构/视觉 QA
generic-builder-v1
无源资产或旧式主题模板
→ SLIDE_BUILDERS + THEMES
对 clone-edit-v2 的硬性约束:
- 禁止执行“删除全部源幻灯片再重建”。
- 每个输出页都必须映射到明确的
sourceSlide。 - 三个内置模板分别维护版本化 manifest,记录页面用途、保留/可选规则和可编辑元素的 shape ID。
- 文本必须在原形状中替换;图表更新数据源;表格更新单元格;图片替换时保留原裁剪和几何信息。
- 源模板没有可用内容槽位时,必须调整模板或显式报错,不允许静默退回基础模板。
- 删除样例业务数据必须按 manifest 精确执行,禁止遍历并清空所有文本形状,以免误删品牌、页脚和设计文字。
22.5 资产版本模型
新增 insurance_ppt_template_assets,至少包含:
id, template_id, version, storage_uri, sha256, file_size,
slide_count, manifest_json, validation_status, created_at
同时:
- 将
source_template_asset_id扩容为VARCHAR(255),或改为资产表外键。 - 上传采用“先保存临时文件 → 校验/规范化 → 计算 hash → 创建新版本 → 原子切换当前版本”。
- 旧版本在仍被 queued/running 任务或历史记录引用时不得物理删除。
- Worker 只消费任务快照中的
assetId/version/sha256,读取后再次校验 hash;不在执行时回查“当前模板版本”。 - 新工作区默认使用当前版本;旧工作区若保留旧版本,界面必须明确显示版本和更新时间,并提供“切换到最新版”。
22.6 测试与验收
现有 test_renderer_can_reuse_builtin_template_master 只检查输出存在且为 2 页,实际上无法发现源模板页面被删光。应替换为以下门禁:
- 三个模板各做一套真实数据端到端测试,断言请求、任务快照、Worker 文件 hash 和结果元数据一致。
- 断言输出页面全部来自 manifest 映射,不再固定为通用构建器的 12/10/15 页。
- 断言单储蓄模板的图表/摘要表、多储蓄模板的双产品图表、储蓄险 + IUL 模板的源图片和组合页面仍存在且可编辑。
- 对三个成品计算渲染截图感知 hash;三个模板之间必须明显不同,并分别通过与各自基准图的差异阈值。
- 扫描最终 PPTX,确保示例客户、示例保费和示例公司数据已被替换或删除,没有模板样例数据泄漏。
- PowerPoint/WPS 无修复提示打开,逐页无溢出、破图、空占位符或丢失字体。
- 管理员上传新版后创建两个任务:切换前任务仍使用旧 hash,切换后任务使用新 hash,以验证版本隔离而不是缓存猜测。
完成上述门禁后,任务结果才允许写入:
{
"templateApplied": true,
"templateId": "scenario_savings_iul",
"assetVersion": 2,
"assetSha256": "...",
"rendererMode": "clone-edit-v2",
"templateFallback": false
}
23. 专项修复实施记录(2026-07-31)
本轮已完成以下修复:
- 三个内置模板改为
clone-edit-v2:按模板清单选择原始页面并保留背景、图片和版式,不再清空源幻灯片后生成统一深蓝基础模板。 - 源模板中的样例业务文本、表格和图表会在动态内容写入前清理;模板页数不足或页面映射非法时明确失败,不再静默回退。
- 模板上传后记录
assetVersion与assetSha256;创建任务时固化资产 ID、版本、SHA-256 和渲染模式,Worker 严格消费该快照并复核文件哈希。 source_template_asset_id已扩容为VARCHAR(255);旧模板文件在仍可能被排队任务引用时不再立即物理删除。- 海报工作区改为真实缩放画布并从顶部开始滚动;长图不再因垂直居中而无法查看顶部。
- 海报完成状态增加资源加载阶段:背景、图片和字体全部可用且最终合成保存成功后,任务才显示完成;加载失败会展示可重试错误。
- 前端生成按钮与后端 15 秒活动任务复用共同拦截快速重复提交,避免一次点击创建多条生成记录。
已完成验证:
- 三个内置模板均使用真实历史生成契约渲染,输出模式均为
clone-edit-v2,且页面映射与模板分别对应。 - PowerPoint 可直接打开三个生成文件,无修复提示;源模板的样例金额未出现在成品文本中。
- IUL 模板的源图片资产在成品中保留;三套模板的最终视觉不再相同。
- 迁移
migrate_029、migrate_030已在当前 BaoDan 数据库实际执行,模板资产字段、版本、SHA-256 和clone-edit-v2模式标识已回填。 - Python 编译、Vue 生产构建及 PPT/海报相关自动化回归均通过。