baodan/api/insurance/poster/field_profiles.py

83 lines
3.6 KiB
Python
Raw Normal View History

主要完成内容: 修复 PPT 异步任务无法生成的问题,包括任务变量引用错误、失败状态回写、心跳缺失任务恢复。 脱敏改为保司/产品后台统一配置,生成端不再让用户选择;任务创建时保存策略快照。 保司支持独立控制 PPT、海报 Logo 显示。 PPT 核验新增吸烟状态、币种及三个条件字段。 利益演示、退保提取调整为警告,不再阻止生成。 PPT 生成完成后可以直接返回数据核验页修改。 建立不同险种、单图/长图共六套海报字段画像。 PPT“生成场景”支持后台新增、启停和删除。 保司、产品、PPT 模板、文案模板均支持安全删除。 内置模板禁止删除,只允许停用;存在关联数据时拒绝危险删除。 补充策略变更及删除审计日志。 更新 API 文档、部署文档及修复计划实施记录。 关键交付文件: [数据库迁移 migrate_027.py](D:/work/code/python/coding/baodanagent/api/insurance/db/migrate_027.py) [海报字段画像 field_profiles.py](D:/work/code/python/coding/baodanagent/api/insurance/poster/field_profiles.py) [动态场景服务 scenarios.py](D:/work/code/python/coding/baodanagent/api/insurance/ppt/scenarios.py) [新增回归测试](D:/work/code/python/coding/baodanagent/tests/ppt_poster_optimization_test.py) [优化修复计划书](D:/work/code/python/coding/baodanagent/docs/保险智能客服系统_PPT与海报优化修复计划书_20260731.md) 验证结果: 核心链路测试:37 passed,1 skipped 扩展回归测试:140 passed PPT 渲染器测试:6 passed 前端生产构建:通过 Python 编译检查:通过 完整测试集:190 passed,1 failed 唯一失败为 tests/test_chat_save.py::test_chat_logs_query 未建立 Flask application context,与本次 PPT/海报链路无关。
2026-07-31 14:10:24 +08:00
"""海报字段画像:统一约束不同险种、不同版式的内容范围。"""
from copy import deepcopy
_PROFILES = {
("savings", "single"): {
"required": ["product_name", "company_name", "currency", "annual_premium", "premium_term"],
"recommended": ["features", "benefit_milestone"],
"optional": ["basic_sum_insured", "coverage_period", "insured_age"],
"forbidden": ["full_benefit_table", "withdrawal_table"],
"maxFeatureCount": 3,
"milestoneYears": [20],
},
("savings", "long"): {
"required": ["product_name", "company_name", "currency", "annual_premium", "premium_term"],
"recommended": ["features", "total_premium", "benefit_milestones", "breakeven_hint"],
"optional": ["first_year_amount_due", "withdrawal_plan", "coverage_period"],
"forbidden": [],
"maxFeatureCount": 6,
"milestoneYears": [10, 20, 30],
},
("ci", "single"): {
"required": ["product_name", "company_name", "currency", "sum_assured"],
"recommended": ["annual_premium", "premium_term", "coverage_highlights", "waiting_period"],
"optional": ["insured_age", "gender", "smoker"],
"forbidden": ["breakeven_hint", "withdrawal_table"],
"maxFeatureCount": 3,
"milestoneYears": [],
},
("ci", "long"): {
"required": ["product_name", "company_name", "currency", "sum_assured"],
"recommended": ["coverage_highlights", "features", "waiting_period", "coverage_period"],
"optional": ["premium_waiver", "death_benefit", "insured_age", "smoker"],
"forbidden": ["breakeven_hint", "withdrawal_table"],
"maxFeatureCount": 6,
"milestoneYears": [],
},
("iul", "single"): {
"required": ["product_name", "company_name", "currency", "sum_assured"],
"recommended": ["target_premium", "premium_term", "index_account"],
"optional": ["insured_age", "gender", "smoker"],
"forbidden": ["unverified_return_promise", "withdrawal_table"],
"maxFeatureCount": 3,
"milestoneYears": [20],
},
("iul", "long"): {
"required": ["product_name", "company_name", "currency", "sum_assured"],
"recommended": ["target_premium", "minimum_premium", "index_accounts", "benefit_milestones"],
"optional": ["withdrawal_notes", "fee_notes", "first_year_amount_due"],
"forbidden": ["unverified_return_promise"],
"maxFeatureCount": 6,
"milestoneYears": [10, 20, 30],
},
("other", "single"): {
"required": ["product_name", "company_name", "coverage_highlights", "disclaimer"],
"recommended": ["features"],
"optional": ["audience"],
"forbidden": ["benefit_table", "return_metrics"],
"maxFeatureCount": 3,
"milestoneYears": [],
},
("other", "long"): {
"required": ["product_name", "company_name", "coverage_highlights", "disclaimer"],
"recommended": ["features", "audience"],
"optional": ["coverage_period"],
"forbidden": ["benefit_table", "return_metrics"],
"maxFeatureCount": 6,
"milestoneYears": [],
},
}
def get_field_profile(plan_type: str, output_mode: str) -> dict:
"""返回安全副本;未知险种和版式回退到保守画像。"""
normalized_type = str(plan_type or "other").lower()
if normalized_type not in {"savings", "ci", "iul"}:
normalized_type = "other"
normalized_mode = "long" if output_mode == "long" else "single"
profile = deepcopy(_PROFILES[(normalized_type, normalized_mode)])
profile["planType"] = normalized_type
profile["outputMode"] = normalized_mode
return profile