baodan/api/insurance/ppt/prompts.py

243 lines
11 KiB
Python
Raw Normal View History

2026-07-28 16:45:14 +08:00
"""LLM 提取 Prompt 常量。
2026-07-23 13:10:50 +08:00
2026-07-28 16:45:14 +08:00
以下 prompt 仅在正则提取行数不足时作为 LLM 回退使用
销售分析职责已移交给 ANALYSIS_SYSTEM_PROMPT
"""
2026-07-23 09:18:16 +08:00
2026-07-28 16:45:14 +08:00
SAVINGS_PLAN_SYSTEM_PROMPT = """你是香港保险计划书数据提取专家。从PDF文本中提取结构化JSON。
2026-07-23 09:18:16 +08:00
2026-07-28 16:45:14 +08:00
输出JSON只输出JSON无markdown
2026-07-23 09:18:16 +08:00
{
2026-07-28 16:45:14 +08:00
"product_name": "产品全称",
2026-07-23 09:18:16 +08:00
"product_type": "savings",
"insured": {"name": null, "age": 数字, "gender": "male/female", "relation": null, "smoker": "yes/no/unknown"},
主要完成内容: 修复 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
"policy": {"product_name": "产品名称", "currency": "ISO 4217代码", "sum_insured": null, "basic_sum_insured": null, "basic_plan_annual_premium": 数字或null, "first_year_amount_due": 数字或null, "annual_premium": 数字, "premium_payment_period": 数字, "coverage_period": "终身"},
主要改动: PPT 表格现在按槽位解析,正确保留 —、空列和单空格表格,不再发生金额左移。[regex_extractor.py (line 470)](D:/work/code/python/coding/baodanagent/api/insurance/ppt/regex_extractor.py:470) 补齐年龄、累计保费、非保证现金价值、非保证身故赔偿等别名。 IUL 使用专属 LLM 提取结构,并加强年度、年龄、账户价值完整性校验。[extraction.py (line 741)](D:/work/code/python/coding/baodanagent/api/insurance/ppt/extraction.py:741) PPT 缓存升级到 v6,旧错误缓存会自动失效。 提领方案不再与普通退保价值混淆,相关提示语已统一。 海报摘要年度金额与收益图表改为同一数据源;缺少部分里程碑时会从原始真实年度补足,不会隐藏图表。[content_builder.py (line 51)](D:/work/code/python/coding/baodanagent/api/insurance/poster/content_builder.py:51) 系统计算、利益表派生、人工修改增加来源标记。 增加“重新解析”功能,旧海报计划书无需重新上传。[routes.py (line 266)](D:/work/code/python/coding/baodanagent/api/insurance/poster/routes.py:266) ECharts 导出增加双帧就绪检测和事件竞态保护,不再依赖不存在的 .once()。 PPT 手机端改为可编辑数据卡片,320px 操作栏自动纵向排列;补齐按钮语义、键盘焦点及 44px 触控区域。[PptDataReview.vue (line 195)](D:/work/code/python/coding/baodanagent/frontend/src/pages/components/ppt/PptDataReview.vue:195) 验证结果: 核心专项测试:145 passed 除既有聊天日志测试外的测试集:236 passed 前端生产构建:通过 Python 语法检查:通过 git diff --check:通过 Impeccable 前端检测:无发现 全量测试仅剩一个与本次无关的既有失败:test_chat_logs_query 缺少 Flask application context
2026-08-01 01:22:07 +08:00
"benefit_illustration": [{"policy_year": 数字, "age": 数字或null, "total_premium_paid": 数字, "guaranteed_cash_value": 数字, "reversionary_bonus": 数字, "terminal_dividend": 数字, "total_surrender_value": 数字, "death_benefit": 数字或null, "source_page": 数字或null}],
2026-07-28 16:45:14 +08:00
"withdrawal_illustration": [{"policy_year": 数字, "annual_withdrawal": 数字, "total_withdrawn": 数字或null, "surrender_value_before": 数字或null, "surrender_value_after": 数字或null, "source_page": 数字或null}]
2026-07-23 09:18:16 +08:00
}
2026-07-28 16:45:14 +08:00
规则
1. 扫描所有页面提取所有保单年度数据至少20行
2. 提领表只取"总额/Total"不取子列用官方"提取后"列作为surrender_value_after
3. 数值去逗号转数字无法确定填null不填0
主要改动: PPT 表格现在按槽位解析,正确保留 —、空列和单空格表格,不再发生金额左移。[regex_extractor.py (line 470)](D:/work/code/python/coding/baodanagent/api/insurance/ppt/regex_extractor.py:470) 补齐年龄、累计保费、非保证现金价值、非保证身故赔偿等别名。 IUL 使用专属 LLM 提取结构,并加强年度、年龄、账户价值完整性校验。[extraction.py (line 741)](D:/work/code/python/coding/baodanagent/api/insurance/ppt/extraction.py:741) PPT 缓存升级到 v6,旧错误缓存会自动失效。 提领方案不再与普通退保价值混淆,相关提示语已统一。 海报摘要年度金额与收益图表改为同一数据源;缺少部分里程碑时会从原始真实年度补足,不会隐藏图表。[content_builder.py (line 51)](D:/work/code/python/coding/baodanagent/api/insurance/poster/content_builder.py:51) 系统计算、利益表派生、人工修改增加来源标记。 增加“重新解析”功能,旧海报计划书无需重新上传。[routes.py (line 266)](D:/work/code/python/coding/baodanagent/api/insurance/poster/routes.py:266) ECharts 导出增加双帧就绪检测和事件竞态保护,不再依赖不存在的 .once()。 PPT 手机端改为可编辑数据卡片,320px 操作栏自动纵向排列;补齐按钮语义、键盘焦点及 44px 触控区域。[PptDataReview.vue (line 195)](D:/work/code/python/coding/baodanagent/frontend/src/pages/components/ppt/PptDataReview.vue:195) 验证结果: 核心专项测试:145 passed 除既有聊天日志测试外的测试集:236 passed 前端生产构建:通过 Python 语法检查:通过 git diff --check:通过 Impeccable 前端检测:无发现 全量测试仅剩一个与本次无关的既有失败:test_chat_logs_query 缺少 Flask application context
2026-08-01 01:22:07 +08:00
4. 年龄与保单年度是不同列年龄绝不能写入金额字段
5. 严禁编造数据"""
2026-07-23 09:18:16 +08:00
2026-07-28 16:45:14 +08:00
CI_PLAN_SYSTEM_PROMPT = """你是香港危疾保险计划书数据提取专家。从PDF文本中提取结构化JSON。
2026-07-23 09:18:16 +08:00
2026-07-28 16:45:14 +08:00
输出JSON只输出JSON无markdown
{
"product_name": "产品全称",
"product_type": "ci",
主要完成内容: 修复 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
"insured": {"name": null, "age": 数字, "gender": "male/female", "relation": null, "smoker": "yes/no/unknown"},
"policy": {"currency": "ISO 4217代码", "sum_insured": 数字, "basic_sum_insured": 数字或null, "basic_plan_annual_premium": 数字或null, "first_year_amount_due": 数字或null, "annual_premium": 数字, "premium_payment_period": 数字, "coverage_period": "终身"},
2026-07-28 16:45:14 +08:00
"coverage_items": [{"label": "保障项目", "amount": 数字, "percentage": 数字或null, "description": null}],
"benefit_illustration": [{"policy_year": 数字, "total_premium_paid": 数字, "death_benefit": 数字}]
}
2026-07-23 13:10:50 +08:00
2026-07-28 16:45:14 +08:00
规则
1. 提取所有保障项目不要遗漏
2. benefit_illustration 提取所有保单年度
3. 无法确定的字段填null"""
2026-07-23 13:10:50 +08:00
2026-07-23 09:18:16 +08:00
2026-07-28 16:45:14 +08:00
IUL_SYSTEM_PROMPT = """你是IUL指数型万用寿险计划书数据提取专家。从PDF文本中提取结构化JSON。
2026-07-23 09:18:16 +08:00
2026-07-28 16:45:14 +08:00
输出JSON只输出JSON无markdown
2026-07-23 09:18:16 +08:00
{
"product_name": "产品全称",
2026-07-28 16:45:14 +08:00
"product_type": "iul",
主要完成内容: 修复 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
"insured": {"name": null, "age": 数字, "gender": "male/female", "smoker": "yes/no/unknown"},
"policy": {"currency": "ISO 4217代码", "sum_insured": 数字, "basic_sum_insured": 数字或null, "basic_plan_annual_premium": 数字或null, "first_year_amount_due": 数字或null, "annual_premium": 数字, "premium_payment_period": 数字, "coverage_period": "终身", "target_premium": 数字或null, "minimum_premium": 数字或null},
2026-07-28 16:45:14 +08:00
"index_accounts": [{"name": "账户名称", "allocation": 数字, "current_rate": 数字或null, "guaranteed_floor": 数字或null}],
主要改动: PPT 表格现在按槽位解析,正确保留 —、空列和单空格表格,不再发生金额左移。[regex_extractor.py (line 470)](D:/work/code/python/coding/baodanagent/api/insurance/ppt/regex_extractor.py:470) 补齐年龄、累计保费、非保证现金价值、非保证身故赔偿等别名。 IUL 使用专属 LLM 提取结构,并加强年度、年龄、账户价值完整性校验。[extraction.py (line 741)](D:/work/code/python/coding/baodanagent/api/insurance/ppt/extraction.py:741) PPT 缓存升级到 v6,旧错误缓存会自动失效。 提领方案不再与普通退保价值混淆,相关提示语已统一。 海报摘要年度金额与收益图表改为同一数据源;缺少部分里程碑时会从原始真实年度补足,不会隐藏图表。[content_builder.py (line 51)](D:/work/code/python/coding/baodanagent/api/insurance/poster/content_builder.py:51) 系统计算、利益表派生、人工修改增加来源标记。 增加“重新解析”功能,旧海报计划书无需重新上传。[routes.py (line 266)](D:/work/code/python/coding/baodanagent/api/insurance/poster/routes.py:266) ECharts 导出增加双帧就绪检测和事件竞态保护,不再依赖不存在的 .once()。 PPT 手机端改为可编辑数据卡片,320px 操作栏自动纵向排列;补齐按钮语义、键盘焦点及 44px 触控区域。[PptDataReview.vue (line 195)](D:/work/code/python/coding/baodanagent/frontend/src/pages/components/ppt/PptDataReview.vue:195) 验证结果: 核心专项测试:145 passed 除既有聊天日志测试外的测试集:236 passed 前端生产构建:通过 Python 语法检查:通过 git diff --check:通过 Impeccable 前端检测:无发现 全量测试仅剩一个与本次无关的既有失败:test_chat_logs_query 缺少 Flask application context
2026-08-01 01:22:07 +08:00
"benefit_illustration": [{"policy_year": 数字, "age": 数字或null, "total_premium_paid": 数字, "guaranteed_account_value": 数字, "guaranteed_cash_value": 数字, "non_guaranteed_account_value": 数字, "non_guaranteed_cash_value": 数字, "total_surrender_value": 数字或null, "non_guaranteed_death_benefit": 数字, "cost_of_insurance": 数字或null, "source_page": 数字或null}]
2026-07-23 09:18:16 +08:00
}
2026-07-28 16:45:14 +08:00
规则
1. 优先提取非保证当前假设利率数据
2. 逐年提取所有保单年度不要只输出摘要年份
主要改动: PPT 表格现在按槽位解析,正确保留 —、空列和单空格表格,不再发生金额左移。[regex_extractor.py (line 470)](D:/work/code/python/coding/baodanagent/api/insurance/ppt/regex_extractor.py:470) 补齐年龄、累计保费、非保证现金价值、非保证身故赔偿等别名。 IUL 使用专属 LLM 提取结构,并加强年度、年龄、账户价值完整性校验。[extraction.py (line 741)](D:/work/code/python/coding/baodanagent/api/insurance/ppt/extraction.py:741) PPT 缓存升级到 v6,旧错误缓存会自动失效。 提领方案不再与普通退保价值混淆,相关提示语已统一。 海报摘要年度金额与收益图表改为同一数据源;缺少部分里程碑时会从原始真实年度补足,不会隐藏图表。[content_builder.py (line 51)](D:/work/code/python/coding/baodanagent/api/insurance/poster/content_builder.py:51) 系统计算、利益表派生、人工修改增加来源标记。 增加“重新解析”功能,旧海报计划书无需重新上传。[routes.py (line 266)](D:/work/code/python/coding/baodanagent/api/insurance/poster/routes.py:266) ECharts 导出增加双帧就绪检测和事件竞态保护,不再依赖不存在的 .once()。 PPT 手机端改为可编辑数据卡片,320px 操作栏自动纵向排列;补齐按钮语义、键盘焦点及 44px 触控区域。[PptDataReview.vue (line 195)](D:/work/code/python/coding/baodanagent/frontend/src/pages/components/ppt/PptDataReview.vue:195) 验证结果: 核心专项测试:145 passed 除既有聊天日志测试外的测试集:236 passed 前端生产构建:通过 Python 语法检查:通过 git diff --check:通过 Impeccable 前端检测:无发现 全量测试仅剩一个与本次无关的既有失败:test_chat_logs_query 缺少 Flask application context
2026-08-01 01:22:07 +08:00
3. 年龄与保单年度是不同列年龄绝不能写入账户价值现金价值或退保价值
4. 严禁编造数据无法确定填null"""
2026-07-23 13:10:50 +08:00
2026-07-28 16:45:14 +08:00
# ─── 轻量级分析 Prompt正则提取后使用──────────────────────
2026-07-23 09:18:16 +08:00
2026-07-28 16:45:14 +08:00
ANALYSIS_SYSTEM_PROMPT = """你是一位香港保险销售顾问。用户已经通过正则提取了计划书的核心数据JSON格式请你结合 PDF 关键页面原文,完成以下分析:
2026-07-23 09:18:16 +08:00
2026-07-28 16:45:14 +08:00
输入
- key_pages_text: PDF 中与产品相关的 4-5 页原文已标注页码
- structured_data: 正则提取的结构化 JSON可能有缺失字段
请输出以下 JSON只输出 JSON markdown无额外文字
2026-07-23 09:18:16 +08:00
{
2026-07-28 16:45:14 +08:00
"keyPoints": [
"关键发现1描述产品的核心亮点或关键数据",
"关键发现2...",
"关键发现3..."
2026-07-23 09:18:16 +08:00
],
2026-07-28 16:45:14 +08:00
"gaps": [
"缺失字段1具体说明哪个字段未提取到应该在哪里找",
"缺失字段2..."
2026-07-23 09:18:16 +08:00
],
2026-07-28 16:45:14 +08:00
"suggestedQuestions": [
"建议用户确认的问题1",
"建议用户确认的问题2"
]
2026-07-23 09:18:16 +08:00
}
2026-07-28 16:45:14 +08:00
规则
1. keyPoints基于提取的数据和原文总结3-5个最有价值的销售要点如IRR回本年限关键收益年度等
2. gaps只列出structured_data中确实为null且原文中能找到的字段说明在PDF第几页可以找到
3. suggestedQuestions建议用户确认的数据问题如年龄是否正确是否有附加险等
4. 输出总长度控制在300字以内
5. 不要重复已提取的数据只提供增量分析"""
def build_analysis_prompt(key_pages_text: str, structured_data: dict) -> str:
"""构建轻量级分析 prompt正则提取后调用"""
import json
data_str = json.dumps(structured_data, ensure_ascii=False, indent=2)
return (
f"## PDF 关键页面原文\n{key_pages_text}\n\n"
f"## 正则提取的结构化数据\n{data_str}"
)
# ─── 页面筛选工具 ──────────────────────────────────────────
def select_key_pages(pdf_text: str, max_pages: int = 5, max_chars: int = 8000) -> str:
"""从完整 PDF 文本中筛选与产品最相关的 N 页。
筛选优先级
1. 包含产品名称的封面页
2. 包含演示表保单年度 + 价值类关键词的页面
3. 包含保费信息的页面
4. 其余页面按顺序填充
Args:
pdf_text: 完整 PDF 文本可能含 [PAGE N] 标记
max_pages: 最多选取页数
max_chars: 总字符预算
Returns:
筛选后的页面文本含页码标记
"""
import re
# 拆分为页面(按 [PAGE N] 标记或换页符)
page_pattern = re.compile(r'\[PAGE\s+(\d+)\]')
parts = page_pattern.split(pdf_text)
pages = []
if len(parts) > 1:
# 有 [PAGE N] 标记
for i in range(1, len(parts), 2):
page_num = int(parts[i])
content = parts[i + 1] if i + 1 < len(parts) else ''
pages.append((page_num, content.strip()))
else:
# 没有页码标记,按换页符或大量换行分割
raw_pages = re.split(r'\f|\n{3,}', pdf_text)
for i, content in enumerate(raw_pages):
if content.strip():
pages.append((i + 1, content.strip()))
if not pages:
return pdf_text[:max_chars]
2026-07-31 09:00:39 +08:00
# ── 第一轮:按页面角色强制选入 ──
# 角色识别关键词
cover_kw = ['产品名称', '产品名', '保单', '受保人', '计划书', '建议书',
'Product', 'Proposal', '計劃書', '建議書', '產品名稱', '保障計劃']
customer_kw = ['受保人', '被保人', '投保人', '姓名', '年龄', '性别',
'Insured', 'Policyholder', '受保人年齡', '被保人年齡',
'Date of Birth', '出生日期']
summary_kw = ['保单摘要', '保單摘要', 'Policy Summary', '基本资料', '基本資料',
'保额', '保額', '缴费年期', '繳費年期', '保障年期']
# 利益表关键词(匹配这些的页面必须全部选入)
table_kw = [
2026-07-31 09:00:39 +08:00
'保单年度', '保單年度', '保证现金', '保證現金', '退保价值', '退保價值',
'红利', '紅利', '保费', '保費',
'benefit', 'illustration', 'policy year', 'account value',
'cash value', 'cash surrender', 'death benefit',
2026-07-31 09:00:39 +08:00
'guaranteed', 'non-guaranteed', 'non guaranteed',
]
premium_kw = [
'年缴保费', 'Annual Premium', '缴费年期', 'Premium Payment',
2026-07-31 09:00:39 +08:00
'planned premium', 'initial premium', '年繳保費',
]
2026-07-28 16:45:14 +08:00
2026-07-31 09:00:39 +08:00
# 分类每页
mandatory_pages = [] # 必选页(封面、客户信息、利益表)
optional_scored = [] # 可选页(按分数排序)
2026-07-28 16:45:14 +08:00
for page_num, content in pages:
content_lower = content.lower()
2026-07-31 09:00:39 +08:00
is_cover = any(kw.lower() in content_lower for kw in cover_kw)
is_customer = any(kw.lower() in content_lower for kw in customer_kw)
is_summary = any(kw.lower() in content_lower for kw in summary_kw)
is_table = sum(1 for kw in table_kw if kw.lower() in content_lower) >= 2
has_premium = any(kw.lower() in content_lower for kw in premium_kw)
if is_table:
# 利益表页必须全部选入
mandatory_pages.append((page_num, content, "table"))
elif is_cover and page_num <= 3:
# 封面只取前 3 页
mandatory_pages.append((page_num, content, "cover"))
elif is_customer:
mandatory_pages.append((page_num, content, "customer"))
elif is_summary:
mandatory_pages.append((page_num, content, "summary"))
else:
# 可选页:按分数排序
score = 0
if is_cover:
score += 30
if has_premium:
score += 20
num_count = len(re.findall(r'\b\d{3,}\b', content))
score += min(num_count, 20)
optional_scored.append((score, page_num, content))
# ── 第二轮:拼接必选页 + 填充可选页 ──
mandatory_pages.sort(key=lambda x: x[0]) # 按页码排序
optional_scored.sort(key=lambda x: x[0], reverse=True) # 按分数排序
selected_pages = []
selected_nums = set()
2026-07-28 16:45:14 +08:00
total_chars = 0
2026-07-31 09:00:39 +08:00
# 先加入所有必选页
for page_num, content, role in mandatory_pages:
if page_num in selected_nums:
continue
2026-07-28 16:45:14 +08:00
page_text = f"[PAGE {page_num}]\n{content}"
if total_chars + len(page_text) > max_chars:
2026-07-31 09:00:39 +08:00
# 必选页也截断,但保留页码标记
remaining = max_chars - total_chars
2026-07-31 09:00:39 +08:00
if remaining > 50:
selected_pages.append((page_num, page_text[:remaining]))
break
selected_pages.append((page_num, page_text))
selected_nums.add(page_num)
total_chars += len(page_text)
# 再填充高分可选页
for score, page_num, content in optional_scored:
if len(selected_pages) >= max_pages:
break
if page_num in selected_nums:
continue
page_text = f"[PAGE {page_num}]\n{content}"
if total_chars + len(page_text) > max_chars:
2026-07-28 16:45:14 +08:00
break
2026-07-31 09:00:39 +08:00
selected_pages.append((page_num, page_text))
selected_nums.add(page_num)
2026-07-28 16:45:14 +08:00
total_chars += len(page_text)
2026-07-31 09:00:39 +08:00
# 按页码排序输出
selected_pages.sort(key=lambda x: x[0])
return '\n\n'.join(text for _, text in selected_pages)