hookehuyr

docs(parse): 文档解析审核流程完善

- 整理审核流程文档并对齐字段命名与目录规范
- 补充审核模板修复重点与解析策略改进点

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
......@@ -77,6 +77,7 @@ pnpm lint
-**写入稳态化** - 结构化插入、重复检测与 dry-run 预览已接入
-**输出结构补齐** - 解析输出 JSON 结构与稳定 form_sn 规则已明确
-**审计与摘要** - 解析摘要与审计日志输出已接入
-**审核流程规划** - 整理审核流程方案并对齐字段与目录规范
### 测试与验证
-**回归测试** - pnpm test 通过,pnpm lint 存在 30 个现存警告
......
## [2026-02-14] - 文档解析审核方案整理
### 优化
- 整理审核流程文档并对齐字段命名与目录规范
- 补充审核模板修复重点与解析策略改进点
---
**详细信息**
- **影响文件**: docs/tasks/plan/改进文档解析工具-添加审核流程.md, README.md
- **技术栈**: 文档维护
- **测试状态**: 未运行(仅文档更新)
- **备注**: 明确审核流程现状与修复范围
---
## [2026-02-14] - markitdown 文档解析服务集成
### 新增
- 集成 markitdown CLI 工具支持 PDF/DOCX 文档解析
- 创建 parse-config.js 统一配置管理模块
- 添加配置状态检查命令 `npm run parse:docs:status`
- 创建 .env.example 环境变量模板
- 新增 scripts/README.md 使用指南
### 优化
- MD/TXT 文件直接读取,无需 markitdown 处理
- PDF/DOCX 文件通过 markitdown CLI 转换
- 添加 markitdown 失败时的本地库回退机制
---
**详细信息**
- **影响文件**: scripts/parse-config.js, scripts/parse-docs.js, scripts/.env.example, scripts/README.md, package.json
- **技术栈**: Node.js, Python (markitdown v0.1.4), child_process
- **测试状态**: 已通过(MD 文件解析验证)
- **备注**: markitdown CLI 已安装,配置已启用 (type: 'cli')
---
## [2026-02-14] - 空表单回退规则补齐
### 修复
......
......@@ -4,3 +4,4 @@
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771077989896.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-readme-a4296d1f"],"at":"2026-02-14T14:06:29.896Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771078080604.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-readme-a4296d1f"],"at":"2026-02-14T14:08:00.605Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771078351660.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-2-148b3acd"],"at":"2026-02-14T14:12:31.660Z"}
{"action":"update","backup_file":"/Users/huyirui/program/itomix/git/manulife-weapp/docs/parsed-backup/plan-templates.backup.1771080130974.js","target_file":"/Users/huyirui/program/itomix/git/manulife-weapp/src/config/plan-templates.js","form_sn_list":["savings-2-55bcffc2"],"at":"2026-02-14T14:42:10.974Z"}
......
......@@ -5,3 +5,9 @@
{"at":"2026-02-14T14:06:29.897Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-readme-a4296d1f","product_name":"README","file":"README.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-readme-a4296d1f"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T14:08:00.605Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-readme-a4296d1f","product_name":"README","file":"README.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-readme-a4296d1f"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T14:12:31.661Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":1,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-2-148b3acd"],"conflicts":[],"reason":null}}
{"at":"2026-02-14T14:34:05.582Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":["savings-2-148b3acd"],"reason":"conflict"}}
{"at":"2026-02-14T14:34:22.438Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":["savings-2-148b3acd"],"reason":"conflict"}}
{"at":"2026-02-14T14:34:50.292Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":1153,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":["savings-2-148b3acd"],"reason":"conflict"}}
{"at":"2026-02-14T14:35:12.489Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":6,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":["savings-2-148b3acd"],"reason":"conflict"}}
{"at":"2026-02-14T14:35:32.726Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":2,"success_list":[{"form_sn":"savings-2-148b3acd","product_name":"测试计划书-智享未来2","file":"测试计划书-智享未来2.md"}],"failed_list":[]},"change_summary":{"ok":false,"dry_run":false,"updated_count":0,"form_sn_list":[],"conflicts":["savings-2-148b3acd"],"reason":"conflict"}}
{"at":"2026-02-14T14:42:10.975Z","mode":"single","options":{"dry_run":false},"summary":{"total":1,"success":1,"failed":0,"duration_ms":28,"success_list":[{"form_sn":"savings-2-55bcffc2","product_name":"计划书模版2","file":"计划书模版2.docx"}],"failed_list":[]},"change_summary":{"ok":true,"dry_run":false,"updated_count":1,"form_sn_list":["savings-2-55bcffc2"],"conflicts":[],"reason":null}}
......
/**
* 计划书模版配置
*
* @description 定义产品 form_sn 到模版组件和配置的映射关系
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
* 计划书模版配置映射
* @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版
*
* @example
* // 产品 API 返回
* {
* id: 1,
* product_name: "WIOP3E 盈传创富保障计划 3 - 优选版",
* form_sn: "life-insurance-wiop3e" // 对应下面的配置 key
* }
*/
// 基础提交字段映射(适用于人寿/重疾等通用表单)
const baseSubmitMapping = {
customer_name: { api_field: 'customer_name' },
gender: { api_field: 'customer_gender' },
birthday: { api_field: 'customer_birthday' },
smoker: { api_field: 'smoking_status' },
coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' },
payment_period: { api_field: 'payment_years' },
total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' }
}
// 人寿/重疾基础表单 Schema(通用保障类)
const protectionFormSchema = {
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
]
}
// 储蓄类提交字段映射(在基础映射上追加提取计划字段)
const savingsSubmitMapping = {
...baseSubmitMapping,
withdrawal_enabled: { api_field: 'allow_reduce_amount' },
withdrawal_mode: { api_field: 'withdrawal_option' },
withdrawal_method: { api_field: 'withdrawal_method' },
annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' },
annual_increase_percentage: { api_field: 'annual_increase_percentage' },
withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' },
withdrawal_period_specified: { api_field: 'withdrawal_period' },
withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' },
withdrawal_period_fixed: { api_field: 'withdrawal_period' }
}
// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口)
const savingsFormSchema = {
// 基础字段:非提取计划部分
base_fields: [
{ id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true },
{ id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true },
{ id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true },
{ id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true },
{ id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' },
{ id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' }
],
// 提取计划字段:由 withdrawal_plan 开关控制
withdrawal_fields: [
{ id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' },
{ id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' },
{ id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_withdrawal_amount', key: 'annual_withdrawal_amount', type: 'amount', label: '每年提取金额', placeholder: '请输入每年提取金额', input_label: '请输入每年提取金额', required: true, currency_from: 'withdrawal_plan.default_currency', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_period_specified', key: 'withdrawal_period_specified', type: 'select', label: '提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] },
{ id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] },
{ id: 'withdrawal_period_fixed', key: 'withdrawal_period_fixed', type: 'select', label: '按年岁:提取期(年)', placeholder: '请选择提取期', required: true, options_from: 'withdrawal_plan.withdrawal_periods', show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }
],
// 提取模式切换时的清空逻辑,避免脏字段影响提交
reset_map: {
withdrawal_mode: {
'最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'],
'指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed']
}
}
}
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD', // 币种:USD/CNY/HKD/EUR
payment_periods: [
// 缴费年期选项
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 }, // 年龄范围
insurance_period: '终身', // 保险期间
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 人寿保险产品 - WIOP3
'life-insurance-wiop3': {
name: 'WIOP3 - 盈传创富保障计划 3',
component: 'LifeInsuranceTemplate',
config: {
currency: 'USD',
payment_periods: [
'整付(0-75 岁)',
'5 年(0-70 岁)',
'10 年(0-70 岁)'
],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MPC
'critical-illness-mpc': {
name: 'MPC 守护无间重疾',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC PRO
'critical-illness-mbc-pro': {
name: 'MBC PRO 活跃人生重疾保 PRO',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// 重疾保险产品 - MBC2
'critical-illness-mbc2': {
name: 'MBC2 活跃人生重疾保 2',
component: 'CriticalIllnessTemplate',
config: {
currency: 'USD',
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)'
],
age_range: { min: 0, max: 65 },
insurance_period: '终身',
form_schema: protectionFormSchema,
submit_mapping: baseSubmitMapping
}
},
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
category: 'savings', // 储蓄型产品
config: {
currency: 'USD', // 默认美元
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 提取计划配置
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'], // 支持的币种
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// GC - 宏挚家传保险计划
'savings-gc': {
name: '宏挚家传保险计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// FA - 宏浚传承保障计划
'savings-fa': {
name: '宏浚传承保障计划',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'2 年',
'5 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
// LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险)
'savings-lv2': {
name: '赤霞珠终身寿险计划2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'5 年',
'8 年',
'12 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD', // 统一为美元
withdrawal_modes: ['指定提取金额', '最高固定提取金额'],
withdrawal_periods: [
'1年',
'2年',
'3年',
'5年',
'10年',
'15年',
'20年',
'终身'
]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
/**
* 测试计划书-智享未来2
* @added 2026-02-14T14:12:31.658Z
* @source docs/to-parse/测试计划书-智享未来2.md
*/
'savings-2-148b3acd': {
name: '测试计划书-智享未来2',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: ["整付","3年","5年"],
age_range: { min: 0, max: 75 },
insurance_period: '终身',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ["年龄指定金额","最高固定金额"],
withdrawal_periods: ["1年","3年","5年","10年"]
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
}}
/**
* 全局功能开关
* @description 用于控制实验性功能或未来扩展功能的开关
*
* @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true
*/
export const FEATURE_FLAGS = {
/**
* 多币种切换功能
* @description false: 方案 1 - 固定币种(当前实现)
* true: 方案 2 - 支持多币种切换(未来扩展)
* @type {boolean}
*/
MULTI_CURRENCY_ENABLED: false
}
/**
* 币种符号映射
* @description 币种代码到符号的映射关系
*/
export const CURRENCY_SYMBOLS = {
CNY: '¥', // 人民币
USD: '$', // 美元
HKD: 'HK$', // 港币
EUR: '€' // 欧元
}
/**
* 币种完整信息映射
* @description 币种代码到完整信息的映射(用于多币种模式)
*/
export const CURRENCY_MAP = {
CNY: { label: '人民币', symbol: '¥', value: 'CNY' },
USD: { label: '美元', symbol: '$', value: 'USD' },
HKD: { label: '港币', symbol: 'HK$', value: 'HKD' },
EUR: { label: '欧元', symbol: '€', value: 'EUR' }
}
/**
* 根据 form_sn 获取模版配置
* @param {string} formSn - 产品 API 返回的 form_sn 字段
* @returns {Object|null} 模版配置对象,未找到返回 null
*
* @example
* const config = getTemplateConfig('life-insurance-wiop3e')
* // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} }
*/
export function getTemplateConfig(formSn) {
if (!formSn) {
console.warn('[plan-templates] form_sn 为空')
return null
}
const config = PLAN_TEMPLATES[formSn]
if (!config) {
console.error(`[plan-templates] 未找到模版配置: ${formSn}`)
return null
}
return config
}
/**
* 获取币种符号
* @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR)
* @returns {string} 币种符号
*
* @example
* const symbol = getCurrencySymbol('USD') // 返回: '$'
*/
export function getCurrencySymbol(currencyCode) {
return CURRENCY_SYMBOLS[currencyCode] || '¥'
}
......@@ -24,16 +24,28 @@
- 需要"人工辅助"的半自动化方式
- 在自动解析和直接生成配置之间增加审核环节
### 现状评审与差距
1. **审核流程已接入但模板不稳定**
- parse-docs.js 已生成待审核文件并阻断写入配置,但审核模板存在重复定义与内容断裂风险
2. **字段命名不一致**
- 现有方案示例使用 name/type,与实际解析配置字段 product_name/product_type 不一致
3. **审核指引不清晰**
- 审核文件的“通过后操作”指向备份文件,未明确 pending/approved 目录治理
4. **解析结果可读性不足**
- 审核模板对 form_schema 与 submit_mapping 预览不足,无法快速确认关键字段
5. **解析方式描述需要更新**
- mammoth 的 Markdown 输出存在局限,复杂表格准确性不足,需要明确替代策略
---
## 解决方案
### 方案设计
采用 **"解析 → 审核 → 生成"** 三步流程,支持多种解析方式:
采用 **"解析 → 审核 → 人工合并"** 三步流程,支持多种解析方式:
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 选择解析方式 │ → │ 生成待审核文件 │ → │ 人工审核后移动
│ 选择解析方式 │ → │ 生成待审核文件 │ → │ 审核通过后合并
│ mammoth/MCP │ │ (markdown) │ │ 到正式配置 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
......@@ -52,17 +64,21 @@
- **mammoth**: 快速预览、简单文档、离线使用
- **MCP**: 复杂文档、准确度要求高、有网络连接
#### 注意事项
- mammoth 的 Markdown 输出能力有限,复杂结构建议使用 HTML 输出后再转 Markdown
- 审核文件统一输出到 `docs/parse-audit/pending/`,通过后移动到 `docs/parse-audit/approved/`
### 技术实现
#### 1. 改进extractProductBasicInfo
尝试从多个位置提取产品基本信息
#### 1. 改进基础信息抽取
从多个位置提取产品基本信息(与实际配置字段对齐)
```javascript
// 尝试从文档标题、表格、特定文本模式提取
async function extractProductBasicInfo(content, fileName) {
const info = {
name: '',
type: 'savings', // 默认储蓄型
product_name: '',
product_type: 'savings',
currency: 'USD',
form_sn: generateFormSn(fileName)
}
......@@ -70,7 +86,7 @@ async function extractProductBasicInfo(content, fileName) {
// 策略1: 从文档标题提取
const titleMatch = content.match(/^#\s+(.+)$/m)
if (titleMatch) {
info.name = cleanProductName(titleMatch[1].trim())
info.product_name = cleanProductName(titleMatch[1].trim())
}
// 策略2: 从表格中提取"币种"信息
......@@ -81,9 +97,9 @@ async function extractProductBasicInfo(content, fileName) {
// 策略3: 从表格中提取"产品类型"信息
if (content.includes('重疾') || content.includes('危疾')) {
info.type = 'critical-illness'
info.product_type = 'critical-illness'
} else if (content.includes('人寿')) {
info.type = 'life-insurance'
info.product_type = 'life-insurance'
}
return info
......@@ -110,8 +126,8 @@ async function generateAuditFile(fileName, config, code) {
| 字段 | 提取值 | 需要确认 |
|------|--------|---------|
| 产品名称 | ${config.name || '未提取'} | ✅ 请核对产品名称 |
| 产品类型 | ${config.type || '未提取'} | ✅ 请确认产品类型 |
| 产品名称 | ${config.product_name || '未提取'} | ✅ 请核对产品名称 |
| 产品类型 | ${config.product_type || '未提取'} | ✅ 请确认产品类型 |
| 币种 | ${config.currency || 'USD'} | ✅ 请确认币种 |
| form_sn | \`${config.form_sn || '未生成'}` | 请确认form_sn唯一性 |
......@@ -151,9 +167,9 @@ ${code.submit_mapping || '// 请手动补充'}
### 确认无误
\`\`\`bash
# 1. 移动配置到正式文件
# 1. 移动到 approved 目录
mv docs/parse-audit/pending/${auditFileName} \\
src/config/plan-templates.backup.js
docs/parse-audit/approved/
# 2. 合并到正式配置
# 手动复制或使用工具合并
......@@ -185,28 +201,23 @@ rm docs/parse-audit/pending/${auditFileName}
## 实施计划
### 阶段1: 改进解析逻辑 (30分钟)
- [ ] 改进extractProductBasicInfo函数
- [ ] 添加文档标题提取
- [ ] 添加币种信息提取
- [ ] 添加产品类型推断
- [ ] 测试验证提取效果
### 阶段2: 实现审核文件生成 (20分钟)
- [ ] 实现generateAuditFile函数
- [ ] 创建待审核目录结构
- [ ] 测试生成markdown格式
- [ ] 添加文件路径返回
### 阶段3: 集成到主流程 (10分钟)
- [ ] 更新parse-docs.js主函数
- [ ] 添加成功提示和审核引导
- [ ] 错误处理和日志输出
### 阶段4: 测试验证 (10分钟)
- [ ] 使用实际文档测试
- [ ] 验证生成的审核文件格式
- [ ] 确认目录结构正确
### 阶段1: 修复审核模板与字段对齐
- [ ] 清理 generateAuditFile 重复定义与模板断裂问题
- [ ] 统一字段命名为 product_name/product_type/currency/form_sn
- [ ] 优化审核模板展示 form_schema 与 submit_mapping
### 阶段2: 审核流程治理
- [ ] 确认 pending/approved 目录结构
- [ ] 明确审核通过后的合并指引
- [ ] 补齐审核状态与审核意见模板
### 阶段3: 解析策略补齐
- [ ] 增加标题/币种/类型的启发式补位策略
- [ ] 补齐文档样本验证与失败兜底说明
### 阶段4: 测试验证
- [ ] 使用实际文档回归生成审核文件
- [ ] 校验审核模板完整性与可读性
---
......@@ -234,6 +245,7 @@ rm docs/parse-audit/pending/${auditFileName}
| 提取仍不准确 | 需要大量人工补充 | 提供清晰的标记和默认值 |
| 审核文件过多 | 难以管理 | 定期清理已审核文件 |
| 目录权限问题 | 无法写入文件 | 提前创建目录并检查权限 |
| mammoth 输出限制 | 表格/结构信息丢失 | 使用 HTML 输出后再转 Markdown |
---
......@@ -254,6 +266,6 @@ rm docs/parse-audit/pending/${auditFileName}
---
## 相关文档
- [mamoth使用文档](https://github.com/mwilliamtohman/mammoth)
- [mammoth 使用文档](https://github.com/mwilliamson/mammoth.js)
- [计划书模板配置规范](../../src/config/CLAUDE.md)
- [代码注释规范](~/.claude/rules/code-commenting.md)
......
......@@ -36,6 +36,7 @@
"prepare": "husky",
"parse:docs": "node scripts/parse-docs.js",
"parse:docs:list": "node scripts/parse-docs.js --list",
"parse:docs:status": "node scripts/parse-docs.js --status",
"parse:docs:file": "node scripts/parse-docs.js --file=",
"release": "standard-version"
},
......
# 文档解析服务配置示例
#
# 使用方式:
# 1. 复制此文件为 .env
# 2. 填写你的 API Key
# 3. 运行 npm run parse:docs -- --status 检查配置状态
# ========== markitdown 配置 ==========
# markitdown 服务类型: cli | docker | http | disabled
MARKITDOWN_TYPE=disabled
# markitdown HTTP API 地址(仅当 MARKITDOWN_TYPE=http 时需要)
# MARKITDOWN_URL=http://localhost:8000/convert
# ========== AI 服务配置 ==========
# AI 服务类型: openai | anthropic | openrouter | disabled
AI_SERVICE_TYPE=disabled
# OpenAI 配置
OPENAI_API_KEY=sk-your-openai-api-key-here
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4-turbo
# Anthropic 配置
ANTHROPIC_API_KEY=sk-ant-your-anthropic-api-key-here
ANTHROPIC_BASE_URL=https://api.anthropic.com/v1
ANTHROPIC_MODEL=claude-3-sonnet-20240229
# OpenRouter 配置
OPENROUTER_API_KEY=sk-or-your-openrouter-api-key-here
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
OPENROUTER_MODEL=anthropic/claude-3-sonnet
# 文档解析工具使用指南
## 功能概述
文档解析工具用于将保险产品文档(PDF、DOCX)自动解析为计划书配置,支持智能识别产品类型、币种、缴费年期等信息。
## 快速开始
### 1. 查看待处理文档
```bash
npm run parse:docs:list
```
### 2. 查看配置状态
```bash
npm run parse:docs:status
```
输出示例:
```
🔧 文档解析服务配置状态:
──────────────────────────────────────────────────
📄 markitdown: ❌ 未配置
🤖 AI 服务: ❌ 未配置
──────────────────────────────────────────────────
💡 配置提示:
1. 使用 markitdown: 安装 Python 并运行 "pip install markitdown"
2. 配置 AI 服务: 设置环境变量(.env 文件)
```
### 3. 解析所有文档
```bash
npm run parse:docs
```
### 4. 解析单个文档
```bash
npm run parse:docs:file="产品说明书.pdf"
```
## 配置 AI 服务(可选)
如需启用智能解析功能,请配置以下环境变量:
### 方法 1: 使用 .env 文件
```bash
# 复制示例配置
cp scripts/.env.example scripts/.env
# 编辑 .env 文件,填写 API Key
vim scripts/.env
```
### 方法 2: 使用环境变量
```bash
export AI_SERVICE_TYPE=openai
export OPENAI_API_KEY=sk-your-key-here
npm run parse:docs
```
## 支持的 AI 服务
| 服务 | 说明 | 环境变量 |
|------|------|---------|
| OpenAI | GPT-4/GPT-3.5 | `OPENAI_API_KEY` |
| Anthropic | Claude 3 Sonnet | `ANTHROPIC_API_KEY` |
| OpenRouter | 聚合服务 | `OPENROUTER_API_KEY` |
## 解析流程
1. **文档转换**:将 PDF/DOCX 转换为可读文本
2. **AI 解析**:从文本中提取结构化配置(产品类型、币种、年期等)
3. **生成代码**:生成 `plan-templates.js` 配置代码
4. **更新配置**:自动更新到配置文件
## 当前状态
-**基础功能**:支持 PDF、DOCX 文本提取
-**启发式推断**:根据文件名和内容推断产品类型和币种
-**AI 解析**:待集成 AI 服务(需要配置 API Key)
## 文档位置
待解析文档放在:`docs/to-parse/` 文件夹
支持格式:`.pdf`, `.docx`, `.doc`, `.txt`, `.md`
/**
* 文档解析服务配置
*
* @description 配置 markitdown 和 AI 服务的访问信息
* @module scripts/parse-config
* @author Claude Code
* @created 2026-02-14
*/
/**
* markitdown 服务配置
*
* @description markitdown 是一个将 PDF/DOCX/PPTX/XLSX 等多种格式转换为 Markdown 的工具
*
* 安装方式:
* 1. Python: pip install markitdown
* 2. Docker: docker pull ghcr.io/onurtemiz/markitdown
* 3. HTTP API: 部署 markitdown 服务
*
* 使用方式:
* - CLI: markitdown input.docx output.md
* - Docker: docker run --rm -v $(pwd):/app markitdown input.docx output.md
* - HTTP: POST /convert with file upload
*/
export const MARKITDOWN_CONFIG = {
// markitdown 服务类型
// - 'cli': 使用命令行工具(需要本地安装 Python)
// - 'docker': 使用 Docker 容器
// - 'http': 使用 HTTP API
// - 'disabled': 禁用,使用本地库
type: 'cli',
// CLI 配置
cli: {
command: 'markitdown', // 命令行工具路径
timeout: 30000 // 超时时间(毫秒)
},
// Docker 配置
docker: {
image: 'ghcr.io/onurtemiz/markitdown',
timeout: 30000
},
// HTTP API 配置
http: {
url: process.env.MARKITDOWN_URL || 'http://localhost:8000/convert',
timeout: 30000
}
}
/**
* AI 解析服务配置
*
* @description 配置用于智能解析文档内容的 AI 服务
*
* 支持的 AI 服务:
* - 'openai': OpenAI GPT-4/GPT-3.5
* - 'anthropic': Anthropic Claude
* - 'openrouter': OpenRouter(聚合服务)
* - 'disabled': 禁用 AI 解析
*/
export const AI_SERVICE_CONFIG = {
// AI 服务类型
type: process.env.AI_SERVICE_TYPE || 'disabled',
// OpenAI 配置
openai: {
apiKey: process.env.OPENAI_API_KEY || '',
baseURL: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1',
model: process.env.OPENAI_MODEL || 'gpt-4-turbo',
timeout: 60000
},
// Anthropic 配置
anthropic: {
apiKey: process.env.ANTHROPIC_API_KEY || '',
baseURL: process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com/v1',
model: process.env.ANTHROPIC_MODEL || 'claude-3-sonnet-20240229',
timeout: 60000
},
// OpenRouter 配置
openrouter: {
apiKey: process.env.OPENROUTER_API_KEY || '',
baseURL: process.env.OPENROUTER_BASE_URL || 'https://openrouter.ai/api/v1',
model: process.env.OPENROUTER_MODEL || 'anthropic/claude-3-sonnet',
timeout: 60000
}
}
/**
* 检查 markitdown 服务是否可用
*
* @returns {Promise<boolean>} 是否可用
*/
export async function checkMarkitdownAvailable() {
const { type } = MARKITDOWN_CONFIG
if (type === 'disabled') {
return false
}
if (type === 'cli') {
// 检查命令是否存在
const { exec } = require('child_process')
return new Promise((resolve) => {
exec(`${MARKITDOWN_CONFIG.cli.command} --version`, (error) => {
resolve(!error)
})
})
}
if (type === 'http') {
// 检查 HTTP 服务是否可访问
try {
const response = await fetch(MARKITDOWN_CONFIG.http.url, {
method: 'HEAD',
timeout: 5000
})
return response.ok
} catch {
return false
}
}
if (type === 'docker') {
// 检查 Docker 是否可用
const { exec } = require('child_process')
return new Promise((resolve) => {
exec('docker --version', (error) => {
resolve(!error)
})
})
}
return false
}
/**
* 检查 AI 服务是否配置
*
* @returns {boolean} 是否已配置 API Key
*/
export function checkAIServiceConfigured() {
const { type } = AI_SERVICE_CONFIG
if (type === 'disabled') {
return false
}
if (type === 'openai') {
return !!AI_SERVICE_CONFIG.openai.apiKey
}
if (type === 'anthropic') {
return !!AI_SERVICE_CONFIG.anthropic.apiKey
}
if (type === 'openrouter') {
return !!AI_SERVICE_CONFIG.openrouter.apiKey
}
return false
}
/**
* 打印配置状态
*/
export function printConfigStatus() {
console.log('\n🔧 文档解析服务配置状态:')
console.log('─'.repeat(50))
// markitdown 状态
const markitdownType = MARKITDOWN_CONFIG.type
console.log(`📄 markitdown: ${markitdownType === 'disabled' ? '❌ 未配置' : '✅ ' + markitdownType}`)
// AI 服务状态
const aiType = AI_SERVICE_CONFIG.type
console.log(`🤖 AI 服务: ${aiType === 'disabled' ? '❌ 未配置' : '✅ ' + aiType}`)
if (aiType !== 'disabled') {
const configured = checkAIServiceConfigured()
console.log(` API Key: ${configured ? '✅ 已配置' : '❌ 未配置'}`)
}
console.log('─'.repeat(50))
console.log('')
console.log('💡 配置提示:')
console.log(' 1. 使用 markitdown: 安装 Python 并运行 "pip install markitdown"')
console.log(' 2. 配置 AI 服务: 设置环境变量(.env 文件)')
console.log(' - OPENAI_API_KEY: OpenAI API Key')
console.log(' - ANTHROPIC_API_KEY: Anthropic API Key')
console.log(' - AI_SERVICE_TYPE: openai | anthropic | openrouter')
console.log('')
}
......@@ -365,7 +365,8 @@ export const PLAN_TEMPLATES = {
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
}}
},
}
/**
* 全局功能开关
......