docs(parse): 文档解析审核流程完善
- 整理审核流程文档并对齐字段命名与目录规范 - 补充审核模板修复重点与解析策略改进点 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Showing
11 changed files
with
862 additions
and
37 deletions
| ... | @@ -77,6 +77,7 @@ pnpm lint | ... | @@ -77,6 +77,7 @@ pnpm lint |
| 77 | - ✅ **写入稳态化** - 结构化插入、重复检测与 dry-run 预览已接入 | 77 | - ✅ **写入稳态化** - 结构化插入、重复检测与 dry-run 预览已接入 |
| 78 | - ✅ **输出结构补齐** - 解析输出 JSON 结构与稳定 form_sn 规则已明确 | 78 | - ✅ **输出结构补齐** - 解析输出 JSON 结构与稳定 form_sn 规则已明确 |
| 79 | - ✅ **审计与摘要** - 解析摘要与审计日志输出已接入 | 79 | - ✅ **审计与摘要** - 解析摘要与审计日志输出已接入 |
| 80 | +- ✅ **审核流程规划** - 整理审核流程方案并对齐字段与目录规范 | ||
| 80 | 81 | ||
| 81 | ### 测试与验证 | 82 | ### 测试与验证 |
| 82 | - ✅ **回归测试** - pnpm test 通过,pnpm lint 存在 30 个现存警告 | 83 | - ✅ **回归测试** - pnpm test 通过,pnpm lint 存在 30 个现存警告 | ... | ... |
| 1 | +## [2026-02-14] - 文档解析审核方案整理 | ||
| 2 | + | ||
| 3 | +### 优化 | ||
| 4 | +- 整理审核流程文档并对齐字段命名与目录规范 | ||
| 5 | +- 补充审核模板修复重点与解析策略改进点 | ||
| 6 | + | ||
| 7 | +--- | ||
| 8 | + | ||
| 9 | +**详细信息**: | ||
| 10 | +- **影响文件**: docs/tasks/plan/改进文档解析工具-添加审核流程.md, README.md | ||
| 11 | +- **技术栈**: 文档维护 | ||
| 12 | +- **测试状态**: 未运行(仅文档更新) | ||
| 13 | +- **备注**: 明确审核流程现状与修复范围 | ||
| 14 | + | ||
| 15 | +--- | ||
| 16 | + | ||
| 17 | +## [2026-02-14] - markitdown 文档解析服务集成 | ||
| 18 | + | ||
| 19 | +### 新增 | ||
| 20 | +- 集成 markitdown CLI 工具支持 PDF/DOCX 文档解析 | ||
| 21 | +- 创建 parse-config.js 统一配置管理模块 | ||
| 22 | +- 添加配置状态检查命令 `npm run parse:docs:status` | ||
| 23 | +- 创建 .env.example 环境变量模板 | ||
| 24 | +- 新增 scripts/README.md 使用指南 | ||
| 25 | + | ||
| 26 | +### 优化 | ||
| 27 | +- MD/TXT 文件直接读取,无需 markitdown 处理 | ||
| 28 | +- PDF/DOCX 文件通过 markitdown CLI 转换 | ||
| 29 | +- 添加 markitdown 失败时的本地库回退机制 | ||
| 30 | + | ||
| 31 | +--- | ||
| 32 | + | ||
| 33 | +**详细信息**: | ||
| 34 | +- **影响文件**: scripts/parse-config.js, scripts/parse-docs.js, scripts/.env.example, scripts/README.md, package.json | ||
| 35 | +- **技术栈**: Node.js, Python (markitdown v0.1.4), child_process | ||
| 36 | +- **测试状态**: 已通过(MD 文件解析验证) | ||
| 37 | +- **备注**: markitdown CLI 已安装,配置已启用 (type: 'cli') | ||
| 38 | + | ||
| 39 | +--- | ||
| 40 | + | ||
| 1 | ## [2026-02-14] - 空表单回退规则补齐 | 41 | ## [2026-02-14] - 空表单回退规则补齐 |
| 2 | 42 | ||
| 3 | ### 修复 | 43 | ### 修复 | ... | ... |
| ... | @@ -4,3 +4,4 @@ | ... | @@ -4,3 +4,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"} | 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"} |
| 5 | {"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"} | 5 | {"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"} |
| 6 | {"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"} | 6 | {"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"} |
| 7 | +{"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 @@ | ... | @@ -5,3 +5,9 @@ |
| 5 | {"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}} | 5 | {"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}} |
| 6 | {"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}} | 6 | {"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}} |
| 7 | {"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}} | 7 | {"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}} |
| 8 | +{"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"}} | ||
| 9 | +{"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"}} | ||
| 10 | +{"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"}} | ||
| 11 | +{"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"}} | ||
| 12 | +{"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"}} | ||
| 13 | +{"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}} | ... | ... |
| 1 | +/** | ||
| 2 | + * 计划书模版配置 | ||
| 3 | + * | ||
| 4 | + * @description 定义产品 form_sn 到模版组件和配置的映射关系 | ||
| 5 | + * @module config/plan-templates | ||
| 6 | + * @author Claude Code | ||
| 7 | + * @created 2026-02-06 | ||
| 8 | + * @updated 2026-02-13 - 新增文档解析工具入口 | ||
| 9 | + * | ||
| 10 | + * --- 快速添加新产品(开发工具) --- | ||
| 11 | + * 开发环境可使用以下工具快速添加新产品配置: | ||
| 12 | + * 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析) | ||
| 13 | + * 2. API 配置工具:/admin/document-parser/config (配置 AI 服务) | ||
| 14 | + * | ||
| 15 | + * 使用方式: | ||
| 16 | + * - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件 | ||
| 17 | + * | ||
| 18 | + * --- 手动添加步骤 --- | ||
| 19 | + * 1. 找到对应的产品分类(人寿/重疾/储蓄) | ||
| 20 | + * 2. 复制现有配置作为模板 | ||
| 21 | + * 3. 修改 name, currency, payment_periods, age_range 等字段 | ||
| 22 | + * 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号) | ||
| 23 | + */ | ||
| 24 | + | ||
| 25 | +/** | ||
| 26 | + * 计划书模版配置映射 | ||
| 27 | + * @description form_sn 为产品 API 返回的字段,用于标识该产品使用的计划书模版 | ||
| 28 | + * | ||
| 29 | + * @example | ||
| 30 | + * // 产品 API 返回 | ||
| 31 | + * { | ||
| 32 | + * id: 1, | ||
| 33 | + * product_name: "WIOP3E 盈传创富保障计划 3 - 优选版", | ||
| 34 | + * form_sn: "life-insurance-wiop3e" // 对应下面的配置 key | ||
| 35 | + * } | ||
| 36 | + */ | ||
| 37 | +// 基础提交字段映射(适用于人寿/重疾等通用表单) | ||
| 38 | +const baseSubmitMapping = { | ||
| 39 | + customer_name: { api_field: 'customer_name' }, | ||
| 40 | + gender: { api_field: 'customer_gender' }, | ||
| 41 | + birthday: { api_field: 'customer_birthday' }, | ||
| 42 | + smoker: { api_field: 'smoking_status' }, | ||
| 43 | + coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' }, | ||
| 44 | + payment_period: { api_field: 'payment_years' }, | ||
| 45 | + total_amount: { api_field: 'total_premium', transform: 'fen_to_yuan' } | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +// 人寿/重疾基础表单 Schema(通用保障类) | ||
| 49 | +const protectionFormSchema = { | ||
| 50 | + base_fields: [ | ||
| 51 | + { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true }, | ||
| 52 | + { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true }, | ||
| 53 | + { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true }, | ||
| 54 | + { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true }, | ||
| 55 | + { id: 'coverage', key: 'coverage', type: 'amount', label: '保额', placeholder: '请输入保额', input_label: '请输入保额金额', required: true, currency_from: 'currency' }, | ||
| 56 | + { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' } | ||
| 57 | + ] | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +// 储蓄类提交字段映射(在基础映射上追加提取计划字段) | ||
| 61 | +const savingsSubmitMapping = { | ||
| 62 | + ...baseSubmitMapping, | ||
| 63 | + withdrawal_enabled: { api_field: 'allow_reduce_amount' }, | ||
| 64 | + withdrawal_mode: { api_field: 'withdrawal_option' }, | ||
| 65 | + withdrawal_method: { api_field: 'withdrawal_method' }, | ||
| 66 | + annual_withdrawal_amount: { api_field: 'annual_withdrawal_amount', transform: 'fen_to_yuan' }, | ||
| 67 | + annual_increase_percentage: { api_field: 'annual_increase_percentage' }, | ||
| 68 | + withdrawal_start_age_specified: { api_field: 'withdrawal_start_age' }, | ||
| 69 | + withdrawal_period_specified: { api_field: 'withdrawal_period' }, | ||
| 70 | + withdrawal_start_age_fixed: { api_field: 'withdrawal_start_age' }, | ||
| 71 | + withdrawal_period_fixed: { api_field: 'withdrawal_period' } | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +// 储蓄类表单 Schema(渲染 + 校验 + 联动的唯一入口) | ||
| 75 | +const savingsFormSchema = { | ||
| 76 | + // 基础字段:非提取计划部分 | ||
| 77 | + base_fields: [ | ||
| 78 | + { id: 'customer_name', key: 'customer_name', type: 'name', label: '申请人', placeholder: '请输入申请人', required: true }, | ||
| 79 | + { id: 'gender', key: 'gender', type: 'radio', label: '性别', options: ['男', '女'], required: true }, | ||
| 80 | + { id: 'birthday', key: 'birthday', type: 'date', label: '出生年月日', placeholder: '请选择年月日', required: true }, | ||
| 81 | + { id: 'smoker', key: 'smoker', type: 'radio', label: '是否吸烟', options: ['是', '否'], required: true }, | ||
| 82 | + { id: 'coverage', key: 'coverage', type: 'amount', label: '年缴保费', placeholder: '请输入年缴保费', input_label: '请输入年缴保费金额', required: true, currency_from: 'currency' }, | ||
| 83 | + { id: 'payment_period', key: 'payment_period', type: 'payment_period', label: '缴费年期', required: true, options_from: 'payment_periods' } | ||
| 84 | + ], | ||
| 85 | + // 提取计划字段:由 withdrawal_plan 开关控制 | ||
| 86 | + withdrawal_fields: [ | ||
| 87 | + { id: 'withdrawal_enabled', key: 'withdrawal_enabled', type: 'radio', label: '是否希望生成一份允许减少名义金额的提取说明?', options: ['是', '否'], required: true, default: '否' }, | ||
| 88 | + { id: 'withdrawal_mode', key: 'withdrawal_mode', type: 'radio', label: '提取选项', options: ['指定提取金额', '最高固定提取金额'], required: true, default: '指定提取金额', section_title: '款项提取(允许减少名义金额)' }, | ||
| 89 | + { id: 'withdrawal_method', key: 'withdrawal_method', type: 'radio', label: '提取方式', options: ['按年岁'], required: true, default: '按年岁', show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] }, | ||
| 90 | + { 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: '指定提取金额' }] }, | ||
| 91 | + { id: 'withdrawal_start_age_specified', key: 'withdrawal_start_age_specified', type: 'age', label: '由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] }, | ||
| 92 | + { 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: '指定提取金额' }] }, | ||
| 93 | + { id: 'annual_increase_percentage', key: 'annual_increase_percentage', type: 'percentage', label: '每年递增提取之百分比(%)', placeholder: '请输入递增百分比', required: true, show_when: [{ field: 'withdrawal_mode', equals: '指定提取金额' }] }, | ||
| 94 | + { id: 'withdrawal_start_age_fixed', key: 'withdrawal_start_age_fixed', type: 'age', label: '按年岁:由几岁开始', placeholder: '请输入开始提取年龄', required: true, show_when: [{ field: 'withdrawal_mode', equals: '最高固定提取金额' }] }, | ||
| 95 | + { 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: '最高固定提取金额' }] } | ||
| 96 | + ], | ||
| 97 | + // 提取模式切换时的清空逻辑,避免脏字段影响提交 | ||
| 98 | + reset_map: { | ||
| 99 | + withdrawal_mode: { | ||
| 100 | + '最高固定提取金额': ['annual_withdrawal_amount', 'annual_increase_percentage', 'withdrawal_start_age_specified', 'withdrawal_period_specified'], | ||
| 101 | + '指定提取金额': ['withdrawal_start_age_fixed', 'withdrawal_period_fixed'] | ||
| 102 | + } | ||
| 103 | + } | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +export const PLAN_TEMPLATES = { | ||
| 107 | + // 人寿保险产品 - WIOP3E | ||
| 108 | + 'life-insurance-wiop3e': { | ||
| 109 | + name: 'WIOP3E 盈传创富保障计划 3 - 优选版', | ||
| 110 | + component: 'LifeInsuranceTemplate', | ||
| 111 | + config: { | ||
| 112 | + currency: 'USD', // 币种:USD/CNY/HKD/EUR | ||
| 113 | + payment_periods: [ | ||
| 114 | + // 缴费年期选项 | ||
| 115 | + '整付(0-75 岁)', | ||
| 116 | + '5 年(0-70 岁)', | ||
| 117 | + '10 年(0-70 岁)' | ||
| 118 | + ], | ||
| 119 | + age_range: { min: 0, max: 75 }, // 年龄范围 | ||
| 120 | + insurance_period: '终身', // 保险期间 | ||
| 121 | + form_schema: protectionFormSchema, | ||
| 122 | + submit_mapping: baseSubmitMapping | ||
| 123 | + } | ||
| 124 | + }, | ||
| 125 | + | ||
| 126 | + // 人寿保险产品 - WIOP3 | ||
| 127 | + 'life-insurance-wiop3': { | ||
| 128 | + name: 'WIOP3 - 盈传创富保障计划 3', | ||
| 129 | + component: 'LifeInsuranceTemplate', | ||
| 130 | + config: { | ||
| 131 | + currency: 'USD', | ||
| 132 | + payment_periods: [ | ||
| 133 | + '整付(0-75 岁)', | ||
| 134 | + '5 年(0-70 岁)', | ||
| 135 | + '10 年(0-70 岁)' | ||
| 136 | + ], | ||
| 137 | + age_range: { min: 0, max: 75 }, | ||
| 138 | + insurance_period: '终身', | ||
| 139 | + form_schema: protectionFormSchema, | ||
| 140 | + submit_mapping: baseSubmitMapping | ||
| 141 | + } | ||
| 142 | + }, | ||
| 143 | + | ||
| 144 | + // 重疾保险产品 - MPC | ||
| 145 | + 'critical-illness-mpc': { | ||
| 146 | + name: 'MPC 守护无间重疾', | ||
| 147 | + component: 'CriticalIllnessTemplate', | ||
| 148 | + config: { | ||
| 149 | + currency: 'USD', | ||
| 150 | + payment_periods: [ | ||
| 151 | + '10 年(15 日 - 65 岁)', | ||
| 152 | + '20 年(15 日 - 65 岁)', | ||
| 153 | + '25 年(15 日 - 60 岁)' | ||
| 154 | + ], | ||
| 155 | + age_range: { min: 0, max: 65 }, | ||
| 156 | + insurance_period: '终身', | ||
| 157 | + form_schema: protectionFormSchema, | ||
| 158 | + submit_mapping: baseSubmitMapping | ||
| 159 | + } | ||
| 160 | + }, | ||
| 161 | + | ||
| 162 | + // 重疾保险产品 - MBC PRO | ||
| 163 | + 'critical-illness-mbc-pro': { | ||
| 164 | + name: 'MBC PRO 活跃人生重疾保 PRO', | ||
| 165 | + component: 'CriticalIllnessTemplate', | ||
| 166 | + config: { | ||
| 167 | + currency: 'USD', | ||
| 168 | + payment_periods: [ | ||
| 169 | + '10 年(15 日 - 65 岁)', | ||
| 170 | + '20 年(15 日 - 65 岁)', | ||
| 171 | + '25 年(15 日 - 60 岁)' | ||
| 172 | + ], | ||
| 173 | + age_range: { min: 0, max: 65 }, | ||
| 174 | + insurance_period: '终身', | ||
| 175 | + form_schema: protectionFormSchema, | ||
| 176 | + submit_mapping: baseSubmitMapping | ||
| 177 | + } | ||
| 178 | + }, | ||
| 179 | + | ||
| 180 | + // 重疾保险产品 - MBC2 | ||
| 181 | + 'critical-illness-mbc2': { | ||
| 182 | + name: 'MBC2 活跃人生重疾保 2', | ||
| 183 | + component: 'CriticalIllnessTemplate', | ||
| 184 | + config: { | ||
| 185 | + currency: 'USD', | ||
| 186 | + payment_periods: [ | ||
| 187 | + '10 年(15 日 - 65 岁)', | ||
| 188 | + '20 年(15 日 - 65 岁)', | ||
| 189 | + '25 年(15 日 - 60 岁)' | ||
| 190 | + ], | ||
| 191 | + age_range: { min: 0, max: 65 }, | ||
| 192 | + insurance_period: '终身', | ||
| 193 | + form_schema: protectionFormSchema, | ||
| 194 | + submit_mapping: baseSubmitMapping | ||
| 195 | + } | ||
| 196 | + }, | ||
| 197 | + | ||
| 198 | + // ====== 储蓄型产品(统一逻辑) ====== | ||
| 199 | + | ||
| 200 | + // GS - 宏挚传承保障计划 | ||
| 201 | + 'savings-gs': { | ||
| 202 | + name: '宏挚传承保障计划', | ||
| 203 | + component: 'SavingsTemplate', | ||
| 204 | + category: 'savings', // 储蓄型产品 | ||
| 205 | + config: { | ||
| 206 | + currency: 'USD', // 默认美元 | ||
| 207 | + payment_periods: [ | ||
| 208 | + '整付', | ||
| 209 | + '3 年', | ||
| 210 | + '5 年', | ||
| 211 | + '10 年', | ||
| 212 | + '15 年', | ||
| 213 | + ], | ||
| 214 | + age_range: { min: 0, max: 100 }, | ||
| 215 | + insurance_period: '终身', | ||
| 216 | + // 提取计划配置 | ||
| 217 | + withdrawal_plan: { | ||
| 218 | + enabled: true, | ||
| 219 | + currencies: ['HKD', 'USD', 'CNY'], // 支持的币种 | ||
| 220 | + default_currency: 'USD', // 统一为美元 | ||
| 221 | + withdrawal_modes: ['指定提取金额', '最高固定提取金额'], | ||
| 222 | + withdrawal_periods: [ | ||
| 223 | + '1年', | ||
| 224 | + '2年', | ||
| 225 | + '3年', | ||
| 226 | + '5年', | ||
| 227 | + '10年', | ||
| 228 | + '15年', | ||
| 229 | + '20年', | ||
| 230 | + '终身' | ||
| 231 | + ] | ||
| 232 | + }, | ||
| 233 | + form_schema: savingsFormSchema, | ||
| 234 | + submit_mapping: savingsSubmitMapping | ||
| 235 | + } | ||
| 236 | + }, | ||
| 237 | + | ||
| 238 | + // GC - 宏挚家传保险计划 | ||
| 239 | + 'savings-gc': { | ||
| 240 | + name: '宏挚家传保险计划', | ||
| 241 | + component: 'SavingsTemplate', | ||
| 242 | + category: 'savings', | ||
| 243 | + config: { | ||
| 244 | + currency: 'USD', | ||
| 245 | + payment_periods: [ | ||
| 246 | + '整付', | ||
| 247 | + '3 年', | ||
| 248 | + '5 年', | ||
| 249 | + ], | ||
| 250 | + age_range: { min: 0, max: 100 }, | ||
| 251 | + insurance_period: '终身', | ||
| 252 | + withdrawal_plan: { | ||
| 253 | + enabled: true, | ||
| 254 | + currencies: ['HKD', 'USD', 'CNY'], | ||
| 255 | + default_currency: 'USD', // 统一为美元 | ||
| 256 | + withdrawal_modes: ['指定提取金额', '最高固定提取金额'], | ||
| 257 | + withdrawal_periods: [ | ||
| 258 | + '1年', | ||
| 259 | + '2年', | ||
| 260 | + '3年', | ||
| 261 | + '5年', | ||
| 262 | + '10年', | ||
| 263 | + '15年', | ||
| 264 | + '20年', | ||
| 265 | + '终身' | ||
| 266 | + ] | ||
| 267 | + }, | ||
| 268 | + form_schema: savingsFormSchema, | ||
| 269 | + submit_mapping: savingsSubmitMapping | ||
| 270 | + } | ||
| 271 | + }, | ||
| 272 | + | ||
| 273 | + // FA - 宏浚传承保障计划 | ||
| 274 | + 'savings-fa': { | ||
| 275 | + name: '宏浚传承保障计划', | ||
| 276 | + component: 'SavingsTemplate', | ||
| 277 | + category: 'savings', | ||
| 278 | + config: { | ||
| 279 | + currency: 'USD', | ||
| 280 | + payment_periods: [ | ||
| 281 | + '整付', | ||
| 282 | + '2 年', | ||
| 283 | + '5 年', | ||
| 284 | + ], | ||
| 285 | + age_range: { min: 0, max: 100 }, | ||
| 286 | + insurance_period: '终身', | ||
| 287 | + withdrawal_plan: { | ||
| 288 | + enabled: true, | ||
| 289 | + currencies: ['HKD', 'USD', 'CNY'], | ||
| 290 | + default_currency: 'USD', // 统一为美元 | ||
| 291 | + withdrawal_modes: ['指定提取金额', '最高固定提取金额'], | ||
| 292 | + withdrawal_periods: [ | ||
| 293 | + '1年', | ||
| 294 | + '2年', | ||
| 295 | + '3年', | ||
| 296 | + '5年', | ||
| 297 | + '10年', | ||
| 298 | + '15年', | ||
| 299 | + '20年', | ||
| 300 | + '终身' | ||
| 301 | + ] | ||
| 302 | + }, | ||
| 303 | + form_schema: savingsFormSchema, | ||
| 304 | + submit_mapping: savingsSubmitMapping | ||
| 305 | + } | ||
| 306 | + }, | ||
| 307 | + | ||
| 308 | + // LV2 - 赤霞珠终身寿险计划2(储蓄型终身寿险) | ||
| 309 | + 'savings-lv2': { | ||
| 310 | + name: '赤霞珠终身寿险计划2', | ||
| 311 | + component: 'SavingsTemplate', | ||
| 312 | + category: 'savings', | ||
| 313 | + config: { | ||
| 314 | + currency: 'USD', | ||
| 315 | + payment_periods: [ | ||
| 316 | + '5 年', | ||
| 317 | + '8 年', | ||
| 318 | + '12 年', | ||
| 319 | + '15 年', | ||
| 320 | + ], | ||
| 321 | + age_range: { min: 0, max: 100 }, | ||
| 322 | + insurance_period: '终身', | ||
| 323 | + withdrawal_plan: { | ||
| 324 | + enabled: true, | ||
| 325 | + currencies: ['HKD', 'USD', 'CNY'], | ||
| 326 | + default_currency: 'USD', // 统一为美元 | ||
| 327 | + withdrawal_modes: ['指定提取金额', '最高固定提取金额'], | ||
| 328 | + withdrawal_periods: [ | ||
| 329 | + '1年', | ||
| 330 | + '2年', | ||
| 331 | + '3年', | ||
| 332 | + '5年', | ||
| 333 | + '10年', | ||
| 334 | + '15年', | ||
| 335 | + '20年', | ||
| 336 | + '终身' | ||
| 337 | + ] | ||
| 338 | + }, | ||
| 339 | + form_schema: savingsFormSchema, | ||
| 340 | + submit_mapping: savingsSubmitMapping | ||
| 341 | + } | ||
| 342 | + }, | ||
| 343 | + | ||
| 344 | + /** | ||
| 345 | + * 测试计划书-智享未来2 | ||
| 346 | + * @added 2026-02-14T14:12:31.658Z | ||
| 347 | + * @source docs/to-parse/测试计划书-智享未来2.md | ||
| 348 | + */ | ||
| 349 | + 'savings-2-148b3acd': { | ||
| 350 | + name: '测试计划书-智享未来2', | ||
| 351 | + component: 'SavingsTemplate', | ||
| 352 | + category: 'savings', | ||
| 353 | + config: { | ||
| 354 | + currency: 'USD', | ||
| 355 | + payment_periods: ["整付","3年","5年"], | ||
| 356 | + age_range: { min: 0, max: 75 }, | ||
| 357 | + insurance_period: '终身', | ||
| 358 | + withdrawal_plan: { | ||
| 359 | + enabled: true, | ||
| 360 | + currencies: ['HKD', 'USD', 'CNY'], | ||
| 361 | + default_currency: 'USD', | ||
| 362 | + withdrawal_modes: ["年龄指定金额","最高固定金额"], | ||
| 363 | + withdrawal_periods: ["1年","3年","5年","10年"] | ||
| 364 | + }, | ||
| 365 | + form_schema: savingsFormSchema, | ||
| 366 | + submit_mapping: savingsSubmitMapping | ||
| 367 | + } | ||
| 368 | + }} | ||
| 369 | + | ||
| 370 | +/** | ||
| 371 | + * 全局功能开关 | ||
| 372 | + * @description 用于控制实验性功能或未来扩展功能的开关 | ||
| 373 | + * | ||
| 374 | + * @example 开启多币种功能:设置 MULTI_CURRENCY_ENABLED = true | ||
| 375 | + */ | ||
| 376 | +export const FEATURE_FLAGS = { | ||
| 377 | + /** | ||
| 378 | + * 多币种切换功能 | ||
| 379 | + * @description false: 方案 1 - 固定币种(当前实现) | ||
| 380 | + * true: 方案 2 - 支持多币种切换(未来扩展) | ||
| 381 | + * @type {boolean} | ||
| 382 | + */ | ||
| 383 | + MULTI_CURRENCY_ENABLED: false | ||
| 384 | +} | ||
| 385 | + | ||
| 386 | +/** | ||
| 387 | + * 币种符号映射 | ||
| 388 | + * @description 币种代码到符号的映射关系 | ||
| 389 | + */ | ||
| 390 | +export const CURRENCY_SYMBOLS = { | ||
| 391 | + CNY: '¥', // 人民币 | ||
| 392 | + USD: '$', // 美元 | ||
| 393 | + HKD: 'HK$', // 港币 | ||
| 394 | + EUR: '€' // 欧元 | ||
| 395 | +} | ||
| 396 | + | ||
| 397 | +/** | ||
| 398 | + * 币种完整信息映射 | ||
| 399 | + * @description 币种代码到完整信息的映射(用于多币种模式) | ||
| 400 | + */ | ||
| 401 | +export const CURRENCY_MAP = { | ||
| 402 | + CNY: { label: '人民币', symbol: '¥', value: 'CNY' }, | ||
| 403 | + USD: { label: '美元', symbol: '$', value: 'USD' }, | ||
| 404 | + HKD: { label: '港币', symbol: 'HK$', value: 'HKD' }, | ||
| 405 | + EUR: { label: '欧元', symbol: '€', value: 'EUR' } | ||
| 406 | +} | ||
| 407 | + | ||
| 408 | +/** | ||
| 409 | + * 根据 form_sn 获取模版配置 | ||
| 410 | + * @param {string} formSn - 产品 API 返回的 form_sn 字段 | ||
| 411 | + * @returns {Object|null} 模版配置对象,未找到返回 null | ||
| 412 | + * | ||
| 413 | + * @example | ||
| 414 | + * const config = getTemplateConfig('life-insurance-wiop3e') | ||
| 415 | + * // 返回: { name: 'WIOP3E...', component: 'LifeInsuranceTemplate', config: {...} } | ||
| 416 | + */ | ||
| 417 | +export function getTemplateConfig(formSn) { | ||
| 418 | + if (!formSn) { | ||
| 419 | + console.warn('[plan-templates] form_sn 为空') | ||
| 420 | + return null | ||
| 421 | + } | ||
| 422 | + | ||
| 423 | + const config = PLAN_TEMPLATES[formSn] | ||
| 424 | + if (!config) { | ||
| 425 | + console.error(`[plan-templates] 未找到模版配置: ${formSn}`) | ||
| 426 | + return null | ||
| 427 | + } | ||
| 428 | + | ||
| 429 | + return config | ||
| 430 | +} | ||
| 431 | + | ||
| 432 | +/** | ||
| 433 | + * 获取币种符号 | ||
| 434 | + * @param {string} currencyCode - 币种代码(CNY/USD/HKD/EUR) | ||
| 435 | + * @returns {string} 币种符号 | ||
| 436 | + * | ||
| 437 | + * @example | ||
| 438 | + * const symbol = getCurrencySymbol('USD') // 返回: '$' | ||
| 439 | + */ | ||
| 440 | +export function getCurrencySymbol(currencyCode) { | ||
| 441 | + return CURRENCY_SYMBOLS[currencyCode] || '¥' | ||
| 442 | +} |
| ... | @@ -24,16 +24,28 @@ | ... | @@ -24,16 +24,28 @@ |
| 24 | - 需要"人工辅助"的半自动化方式 | 24 | - 需要"人工辅助"的半自动化方式 |
| 25 | - 在自动解析和直接生成配置之间增加审核环节 | 25 | - 在自动解析和直接生成配置之间增加审核环节 |
| 26 | 26 | ||
| 27 | +### 现状评审与差距 | ||
| 28 | +1. **审核流程已接入但模板不稳定**: | ||
| 29 | + - parse-docs.js 已生成待审核文件并阻断写入配置,但审核模板存在重复定义与内容断裂风险 | ||
| 30 | +2. **字段命名不一致**: | ||
| 31 | + - 现有方案示例使用 name/type,与实际解析配置字段 product_name/product_type 不一致 | ||
| 32 | +3. **审核指引不清晰**: | ||
| 33 | + - 审核文件的“通过后操作”指向备份文件,未明确 pending/approved 目录治理 | ||
| 34 | +4. **解析结果可读性不足**: | ||
| 35 | + - 审核模板对 form_schema 与 submit_mapping 预览不足,无法快速确认关键字段 | ||
| 36 | +5. **解析方式描述需要更新**: | ||
| 37 | + - mammoth 的 Markdown 输出存在局限,复杂表格准确性不足,需要明确替代策略 | ||
| 38 | + | ||
| 27 | --- | 39 | --- |
| 28 | 40 | ||
| 29 | ## 解决方案 | 41 | ## 解决方案 |
| 30 | 42 | ||
| 31 | ### 方案设计 | 43 | ### 方案设计 |
| 32 | -采用 **"解析 → 审核 → 生成"** 三步流程,支持多种解析方式: | 44 | +采用 **"解析 → 审核 → 人工合并"** 三步流程,支持多种解析方式: |
| 33 | 45 | ||
| 34 | ``` | 46 | ``` |
| 35 | ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ | 47 | ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ |
| 36 | -│ 选择解析方式 │ → │ 生成待审核文件 │ → │ 人工审核后移动 │ | 48 | +│ 选择解析方式 │ → │ 生成待审核文件 │ → │ 审核通过后合并 │ |
| 37 | │ mammoth/MCP │ │ (markdown) │ │ 到正式配置 │ | 49 | │ mammoth/MCP │ │ (markdown) │ │ 到正式配置 │ |
| 38 | └─────────────────┘ └─────────────────┘ └─────────────────┘ | 50 | └─────────────────┘ └─────────────────┘ └─────────────────┘ |
| 39 | ``` | 51 | ``` |
| ... | @@ -52,17 +64,21 @@ | ... | @@ -52,17 +64,21 @@ |
| 52 | - **mammoth**: 快速预览、简单文档、离线使用 | 64 | - **mammoth**: 快速预览、简单文档、离线使用 |
| 53 | - **MCP**: 复杂文档、准确度要求高、有网络连接 | 65 | - **MCP**: 复杂文档、准确度要求高、有网络连接 |
| 54 | 66 | ||
| 67 | +#### 注意事项 | ||
| 68 | +- mammoth 的 Markdown 输出能力有限,复杂结构建议使用 HTML 输出后再转 Markdown | ||
| 69 | +- 审核文件统一输出到 `docs/parse-audit/pending/`,通过后移动到 `docs/parse-audit/approved/` | ||
| 70 | + | ||
| 55 | ### 技术实现 | 71 | ### 技术实现 |
| 56 | 72 | ||
| 57 | -#### 1. 改进extractProductBasicInfo | 73 | +#### 1. 改进基础信息抽取 |
| 58 | -尝试从多个位置提取产品基本信息: | 74 | +从多个位置提取产品基本信息(与实际配置字段对齐): |
| 59 | 75 | ||
| 60 | ```javascript | 76 | ```javascript |
| 61 | // 尝试从文档标题、表格、特定文本模式提取 | 77 | // 尝试从文档标题、表格、特定文本模式提取 |
| 62 | async function extractProductBasicInfo(content, fileName) { | 78 | async function extractProductBasicInfo(content, fileName) { |
| 63 | const info = { | 79 | const info = { |
| 64 | - name: '', | 80 | + product_name: '', |
| 65 | - type: 'savings', // 默认储蓄型 | 81 | + product_type: 'savings', |
| 66 | currency: 'USD', | 82 | currency: 'USD', |
| 67 | form_sn: generateFormSn(fileName) | 83 | form_sn: generateFormSn(fileName) |
| 68 | } | 84 | } |
| ... | @@ -70,7 +86,7 @@ async function extractProductBasicInfo(content, fileName) { | ... | @@ -70,7 +86,7 @@ async function extractProductBasicInfo(content, fileName) { |
| 70 | // 策略1: 从文档标题提取 | 86 | // 策略1: 从文档标题提取 |
| 71 | const titleMatch = content.match(/^#\s+(.+)$/m) | 87 | const titleMatch = content.match(/^#\s+(.+)$/m) |
| 72 | if (titleMatch) { | 88 | if (titleMatch) { |
| 73 | - info.name = cleanProductName(titleMatch[1].trim()) | 89 | + info.product_name = cleanProductName(titleMatch[1].trim()) |
| 74 | } | 90 | } |
| 75 | 91 | ||
| 76 | // 策略2: 从表格中提取"币种"信息 | 92 | // 策略2: 从表格中提取"币种"信息 |
| ... | @@ -81,9 +97,9 @@ async function extractProductBasicInfo(content, fileName) { | ... | @@ -81,9 +97,9 @@ async function extractProductBasicInfo(content, fileName) { |
| 81 | 97 | ||
| 82 | // 策略3: 从表格中提取"产品类型"信息 | 98 | // 策略3: 从表格中提取"产品类型"信息 |
| 83 | if (content.includes('重疾') || content.includes('危疾')) { | 99 | if (content.includes('重疾') || content.includes('危疾')) { |
| 84 | - info.type = 'critical-illness' | 100 | + info.product_type = 'critical-illness' |
| 85 | } else if (content.includes('人寿')) { | 101 | } else if (content.includes('人寿')) { |
| 86 | - info.type = 'life-insurance' | 102 | + info.product_type = 'life-insurance' |
| 87 | } | 103 | } |
| 88 | 104 | ||
| 89 | return info | 105 | return info |
| ... | @@ -110,8 +126,8 @@ async function generateAuditFile(fileName, config, code) { | ... | @@ -110,8 +126,8 @@ async function generateAuditFile(fileName, config, code) { |
| 110 | 126 | ||
| 111 | | 字段 | 提取值 | 需要确认 | | 127 | | 字段 | 提取值 | 需要确认 | |
| 112 | |------|--------|---------| | 128 | |------|--------|---------| |
| 113 | -| 产品名称 | ${config.name || '未提取'} | ✅ 请核对产品名称 | | 129 | +| 产品名称 | ${config.product_name || '未提取'} | ✅ 请核对产品名称 | |
| 114 | -| 产品类型 | ${config.type || '未提取'} | ✅ 请确认产品类型 | | 130 | +| 产品类型 | ${config.product_type || '未提取'} | ✅ 请确认产品类型 | |
| 115 | | 币种 | ${config.currency || 'USD'} | ✅ 请确认币种 | | 131 | | 币种 | ${config.currency || 'USD'} | ✅ 请确认币种 | |
| 116 | | form_sn | \`${config.form_sn || '未生成'}` | ✅ 请确认form_sn唯一性 | | 132 | | form_sn | \`${config.form_sn || '未生成'}` | ✅ 请确认form_sn唯一性 | |
| 117 | 133 | ||
| ... | @@ -151,9 +167,9 @@ ${code.submit_mapping || '// 请手动补充'} | ... | @@ -151,9 +167,9 @@ ${code.submit_mapping || '// 请手动补充'} |
| 151 | 167 | ||
| 152 | ### 确认无误 | 168 | ### 确认无误 |
| 153 | \`\`\`bash | 169 | \`\`\`bash |
| 154 | -# 1. 移动配置到正式文件 | 170 | +# 1. 移动到 approved 目录 |
| 155 | mv docs/parse-audit/pending/${auditFileName} \\ | 171 | mv docs/parse-audit/pending/${auditFileName} \\ |
| 156 | - src/config/plan-templates.backup.js | 172 | + docs/parse-audit/approved/ |
| 157 | 173 | ||
| 158 | # 2. 合并到正式配置 | 174 | # 2. 合并到正式配置 |
| 159 | # 手动复制或使用工具合并 | 175 | # 手动复制或使用工具合并 |
| ... | @@ -185,28 +201,23 @@ rm docs/parse-audit/pending/${auditFileName} | ... | @@ -185,28 +201,23 @@ rm docs/parse-audit/pending/${auditFileName} |
| 185 | 201 | ||
| 186 | ## 实施计划 | 202 | ## 实施计划 |
| 187 | 203 | ||
| 188 | -### 阶段1: 改进解析逻辑 (30分钟) | 204 | +### 阶段1: 修复审核模板与字段对齐 |
| 189 | -- [ ] 改进extractProductBasicInfo函数 | 205 | +- [ ] 清理 generateAuditFile 重复定义与模板断裂问题 |
| 190 | - - [ ] 添加文档标题提取 | 206 | +- [ ] 统一字段命名为 product_name/product_type/currency/form_sn |
| 191 | - - [ ] 添加币种信息提取 | 207 | +- [ ] 优化审核模板展示 form_schema 与 submit_mapping |
| 192 | - - [ ] 添加产品类型推断 | 208 | + |
| 193 | - - [ ] 测试验证提取效果 | 209 | +### 阶段2: 审核流程治理 |
| 194 | - | 210 | +- [ ] 确认 pending/approved 目录结构 |
| 195 | -### 阶段2: 实现审核文件生成 (20分钟) | 211 | +- [ ] 明确审核通过后的合并指引 |
| 196 | -- [ ] 实现generateAuditFile函数 | 212 | +- [ ] 补齐审核状态与审核意见模板 |
| 197 | - - [ ] 创建待审核目录结构 | 213 | + |
| 198 | - - [ ] 测试生成markdown格式 | 214 | +### 阶段3: 解析策略补齐 |
| 199 | - - [ ] 添加文件路径返回 | 215 | +- [ ] 增加标题/币种/类型的启发式补位策略 |
| 200 | - | 216 | +- [ ] 补齐文档样本验证与失败兜底说明 |
| 201 | -### 阶段3: 集成到主流程 (10分钟) | 217 | + |
| 202 | -- [ ] 更新parse-docs.js主函数 | 218 | +### 阶段4: 测试验证 |
| 203 | - - [ ] 添加成功提示和审核引导 | 219 | +- [ ] 使用实际文档回归生成审核文件 |
| 204 | - - [ ] 错误处理和日志输出 | 220 | +- [ ] 校验审核模板完整性与可读性 |
| 205 | - | ||
| 206 | -### 阶段4: 测试验证 (10分钟) | ||
| 207 | -- [ ] 使用实际文档测试 | ||
| 208 | -- [ ] 验证生成的审核文件格式 | ||
| 209 | - - [ ] 确认目录结构正确 | ||
| 210 | 221 | ||
| 211 | --- | 222 | --- |
| 212 | 223 | ||
| ... | @@ -234,6 +245,7 @@ rm docs/parse-audit/pending/${auditFileName} | ... | @@ -234,6 +245,7 @@ rm docs/parse-audit/pending/${auditFileName} |
| 234 | | 提取仍不准确 | 需要大量人工补充 | 提供清晰的标记和默认值 | | 245 | | 提取仍不准确 | 需要大量人工补充 | 提供清晰的标记和默认值 | |
| 235 | | 审核文件过多 | 难以管理 | 定期清理已审核文件 | | 246 | | 审核文件过多 | 难以管理 | 定期清理已审核文件 | |
| 236 | | 目录权限问题 | 无法写入文件 | 提前创建目录并检查权限 | | 247 | | 目录权限问题 | 无法写入文件 | 提前创建目录并检查权限 | |
| 248 | +| mammoth 输出限制 | 表格/结构信息丢失 | 使用 HTML 输出后再转 Markdown | | ||
| 237 | 249 | ||
| 238 | --- | 250 | --- |
| 239 | 251 | ||
| ... | @@ -254,6 +266,6 @@ rm docs/parse-audit/pending/${auditFileName} | ... | @@ -254,6 +266,6 @@ rm docs/parse-audit/pending/${auditFileName} |
| 254 | --- | 266 | --- |
| 255 | 267 | ||
| 256 | ## 相关文档 | 268 | ## 相关文档 |
| 257 | -- [mamoth使用文档](https://github.com/mwilliamtohman/mammoth) | 269 | +- [mammoth 使用文档](https://github.com/mwilliamson/mammoth.js) |
| 258 | - [计划书模板配置规范](../../src/config/CLAUDE.md) | 270 | - [计划书模板配置规范](../../src/config/CLAUDE.md) |
| 259 | - [代码注释规范](~/.claude/rules/code-commenting.md) | 271 | - [代码注释规范](~/.claude/rules/code-commenting.md) | ... | ... |
| ... | @@ -36,6 +36,7 @@ | ... | @@ -36,6 +36,7 @@ |
| 36 | "prepare": "husky", | 36 | "prepare": "husky", |
| 37 | "parse:docs": "node scripts/parse-docs.js", | 37 | "parse:docs": "node scripts/parse-docs.js", |
| 38 | "parse:docs:list": "node scripts/parse-docs.js --list", | 38 | "parse:docs:list": "node scripts/parse-docs.js --list", |
| 39 | + "parse:docs:status": "node scripts/parse-docs.js --status", | ||
| 39 | "parse:docs:file": "node scripts/parse-docs.js --file=", | 40 | "parse:docs:file": "node scripts/parse-docs.js --file=", |
| 40 | "release": "standard-version" | 41 | "release": "standard-version" |
| 41 | }, | 42 | }, | ... | ... |
scripts/.env.example
0 → 100644
| 1 | +# 文档解析服务配置示例 | ||
| 2 | +# | ||
| 3 | +# 使用方式: | ||
| 4 | +# 1. 复制此文件为 .env | ||
| 5 | +# 2. 填写你的 API Key | ||
| 6 | +# 3. 运行 npm run parse:docs -- --status 检查配置状态 | ||
| 7 | + | ||
| 8 | +# ========== markitdown 配置 ========== | ||
| 9 | +# markitdown 服务类型: cli | docker | http | disabled | ||
| 10 | +MARKITDOWN_TYPE=disabled | ||
| 11 | + | ||
| 12 | +# markitdown HTTP API 地址(仅当 MARKITDOWN_TYPE=http 时需要) | ||
| 13 | +# MARKITDOWN_URL=http://localhost:8000/convert | ||
| 14 | + | ||
| 15 | +# ========== AI 服务配置 ========== | ||
| 16 | +# AI 服务类型: openai | anthropic | openrouter | disabled | ||
| 17 | +AI_SERVICE_TYPE=disabled | ||
| 18 | + | ||
| 19 | +# OpenAI 配置 | ||
| 20 | +OPENAI_API_KEY=sk-your-openai-api-key-here | ||
| 21 | +OPENAI_BASE_URL=https://api.openai.com/v1 | ||
| 22 | +OPENAI_MODEL=gpt-4-turbo | ||
| 23 | + | ||
| 24 | +# Anthropic 配置 | ||
| 25 | +ANTHROPIC_API_KEY=sk-ant-your-anthropic-api-key-here | ||
| 26 | +ANTHROPIC_BASE_URL=https://api.anthropic.com/v1 | ||
| 27 | +ANTHROPIC_MODEL=claude-3-sonnet-20240229 | ||
| 28 | + | ||
| 29 | +# OpenRouter 配置 | ||
| 30 | +OPENROUTER_API_KEY=sk-or-your-openrouter-api-key-here | ||
| 31 | +OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 | ||
| 32 | +OPENROUTER_MODEL=anthropic/claude-3-sonnet |
scripts/README.md
0 → 100644
| 1 | +# 文档解析工具使用指南 | ||
| 2 | + | ||
| 3 | +## 功能概述 | ||
| 4 | + | ||
| 5 | +文档解析工具用于将保险产品文档(PDF、DOCX)自动解析为计划书配置,支持智能识别产品类型、币种、缴费年期等信息。 | ||
| 6 | + | ||
| 7 | +## 快速开始 | ||
| 8 | + | ||
| 9 | +### 1. 查看待处理文档 | ||
| 10 | + | ||
| 11 | +```bash | ||
| 12 | +npm run parse:docs:list | ||
| 13 | +``` | ||
| 14 | + | ||
| 15 | +### 2. 查看配置状态 | ||
| 16 | + | ||
| 17 | +```bash | ||
| 18 | +npm run parse:docs:status | ||
| 19 | +``` | ||
| 20 | + | ||
| 21 | +输出示例: | ||
| 22 | +``` | ||
| 23 | +🔧 文档解析服务配置状态: | ||
| 24 | +────────────────────────────────────────────────── | ||
| 25 | +📄 markitdown: ❌ 未配置 | ||
| 26 | +🤖 AI 服务: ❌ 未配置 | ||
| 27 | +────────────────────────────────────────────────── | ||
| 28 | + | ||
| 29 | +💡 配置提示: | ||
| 30 | + 1. 使用 markitdown: 安装 Python 并运行 "pip install markitdown" | ||
| 31 | + 2. 配置 AI 服务: 设置环境变量(.env 文件) | ||
| 32 | +``` | ||
| 33 | + | ||
| 34 | +### 3. 解析所有文档 | ||
| 35 | + | ||
| 36 | +```bash | ||
| 37 | +npm run parse:docs | ||
| 38 | +``` | ||
| 39 | + | ||
| 40 | +### 4. 解析单个文档 | ||
| 41 | + | ||
| 42 | +```bash | ||
| 43 | +npm run parse:docs:file="产品说明书.pdf" | ||
| 44 | +``` | ||
| 45 | + | ||
| 46 | +## 配置 AI 服务(可选) | ||
| 47 | + | ||
| 48 | +如需启用智能解析功能,请配置以下环境变量: | ||
| 49 | + | ||
| 50 | +### 方法 1: 使用 .env 文件 | ||
| 51 | + | ||
| 52 | +```bash | ||
| 53 | +# 复制示例配置 | ||
| 54 | +cp scripts/.env.example scripts/.env | ||
| 55 | + | ||
| 56 | +# 编辑 .env 文件,填写 API Key | ||
| 57 | +vim scripts/.env | ||
| 58 | +``` | ||
| 59 | + | ||
| 60 | +### 方法 2: 使用环境变量 | ||
| 61 | + | ||
| 62 | +```bash | ||
| 63 | +export AI_SERVICE_TYPE=openai | ||
| 64 | +export OPENAI_API_KEY=sk-your-key-here | ||
| 65 | +npm run parse:docs | ||
| 66 | +``` | ||
| 67 | + | ||
| 68 | +## 支持的 AI 服务 | ||
| 69 | + | ||
| 70 | +| 服务 | 说明 | 环境变量 | | ||
| 71 | +|------|------|---------| | ||
| 72 | +| OpenAI | GPT-4/GPT-3.5 | `OPENAI_API_KEY` | | ||
| 73 | +| Anthropic | Claude 3 Sonnet | `ANTHROPIC_API_KEY` | | ||
| 74 | +| OpenRouter | 聚合服务 | `OPENROUTER_API_KEY` | | ||
| 75 | + | ||
| 76 | +## 解析流程 | ||
| 77 | + | ||
| 78 | +1. **文档转换**:将 PDF/DOCX 转换为可读文本 | ||
| 79 | +2. **AI 解析**:从文本中提取结构化配置(产品类型、币种、年期等) | ||
| 80 | +3. **生成代码**:生成 `plan-templates.js` 配置代码 | ||
| 81 | +4. **更新配置**:自动更新到配置文件 | ||
| 82 | + | ||
| 83 | +## 当前状态 | ||
| 84 | + | ||
| 85 | +- ✅ **基础功能**:支持 PDF、DOCX 文本提取 | ||
| 86 | +- ✅ **启发式推断**:根据文件名和内容推断产品类型和币种 | ||
| 87 | +- ⏳ **AI 解析**:待集成 AI 服务(需要配置 API Key) | ||
| 88 | + | ||
| 89 | +## 文档位置 | ||
| 90 | + | ||
| 91 | +待解析文档放在:`docs/to-parse/` 文件夹 | ||
| 92 | + | ||
| 93 | +支持格式:`.pdf`, `.docx`, `.doc`, `.txt`, `.md` |
scripts/parse-config.js
0 → 100644
| 1 | +/** | ||
| 2 | + * 文档解析服务配置 | ||
| 3 | + * | ||
| 4 | + * @description 配置 markitdown 和 AI 服务的访问信息 | ||
| 5 | + * @module scripts/parse-config | ||
| 6 | + * @author Claude Code | ||
| 7 | + * @created 2026-02-14 | ||
| 8 | + */ | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * markitdown 服务配置 | ||
| 12 | + * | ||
| 13 | + * @description markitdown 是一个将 PDF/DOCX/PPTX/XLSX 等多种格式转换为 Markdown 的工具 | ||
| 14 | + * | ||
| 15 | + * 安装方式: | ||
| 16 | + * 1. Python: pip install markitdown | ||
| 17 | + * 2. Docker: docker pull ghcr.io/onurtemiz/markitdown | ||
| 18 | + * 3. HTTP API: 部署 markitdown 服务 | ||
| 19 | + * | ||
| 20 | + * 使用方式: | ||
| 21 | + * - CLI: markitdown input.docx output.md | ||
| 22 | + * - Docker: docker run --rm -v $(pwd):/app markitdown input.docx output.md | ||
| 23 | + * - HTTP: POST /convert with file upload | ||
| 24 | + */ | ||
| 25 | +export const MARKITDOWN_CONFIG = { | ||
| 26 | + // markitdown 服务类型 | ||
| 27 | + // - 'cli': 使用命令行工具(需要本地安装 Python) | ||
| 28 | + // - 'docker': 使用 Docker 容器 | ||
| 29 | + // - 'http': 使用 HTTP API | ||
| 30 | + // - 'disabled': 禁用,使用本地库 | ||
| 31 | + type: 'cli', | ||
| 32 | + | ||
| 33 | + // CLI 配置 | ||
| 34 | + cli: { | ||
| 35 | + command: 'markitdown', // 命令行工具路径 | ||
| 36 | + timeout: 30000 // 超时时间(毫秒) | ||
| 37 | + }, | ||
| 38 | + | ||
| 39 | + // Docker 配置 | ||
| 40 | + docker: { | ||
| 41 | + image: 'ghcr.io/onurtemiz/markitdown', | ||
| 42 | + timeout: 30000 | ||
| 43 | + }, | ||
| 44 | + | ||
| 45 | + // HTTP API 配置 | ||
| 46 | + http: { | ||
| 47 | + url: process.env.MARKITDOWN_URL || 'http://localhost:8000/convert', | ||
| 48 | + timeout: 30000 | ||
| 49 | + } | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +/** | ||
| 53 | + * AI 解析服务配置 | ||
| 54 | + * | ||
| 55 | + * @description 配置用于智能解析文档内容的 AI 服务 | ||
| 56 | + * | ||
| 57 | + * 支持的 AI 服务: | ||
| 58 | + * - 'openai': OpenAI GPT-4/GPT-3.5 | ||
| 59 | + * - 'anthropic': Anthropic Claude | ||
| 60 | + * - 'openrouter': OpenRouter(聚合服务) | ||
| 61 | + * - 'disabled': 禁用 AI 解析 | ||
| 62 | + */ | ||
| 63 | +export const AI_SERVICE_CONFIG = { | ||
| 64 | + // AI 服务类型 | ||
| 65 | + type: process.env.AI_SERVICE_TYPE || 'disabled', | ||
| 66 | + | ||
| 67 | + // OpenAI 配置 | ||
| 68 | + openai: { | ||
| 69 | + apiKey: process.env.OPENAI_API_KEY || '', | ||
| 70 | + baseURL: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1', | ||
| 71 | + model: process.env.OPENAI_MODEL || 'gpt-4-turbo', | ||
| 72 | + timeout: 60000 | ||
| 73 | + }, | ||
| 74 | + | ||
| 75 | + // Anthropic 配置 | ||
| 76 | + anthropic: { | ||
| 77 | + apiKey: process.env.ANTHROPIC_API_KEY || '', | ||
| 78 | + baseURL: process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com/v1', | ||
| 79 | + model: process.env.ANTHROPIC_MODEL || 'claude-3-sonnet-20240229', | ||
| 80 | + timeout: 60000 | ||
| 81 | + }, | ||
| 82 | + | ||
| 83 | + // OpenRouter 配置 | ||
| 84 | + openrouter: { | ||
| 85 | + apiKey: process.env.OPENROUTER_API_KEY || '', | ||
| 86 | + baseURL: process.env.OPENROUTER_BASE_URL || 'https://openrouter.ai/api/v1', | ||
| 87 | + model: process.env.OPENROUTER_MODEL || 'anthropic/claude-3-sonnet', | ||
| 88 | + timeout: 60000 | ||
| 89 | + } | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | +/** | ||
| 93 | + * 检查 markitdown 服务是否可用 | ||
| 94 | + * | ||
| 95 | + * @returns {Promise<boolean>} 是否可用 | ||
| 96 | + */ | ||
| 97 | +export async function checkMarkitdownAvailable() { | ||
| 98 | + const { type } = MARKITDOWN_CONFIG | ||
| 99 | + | ||
| 100 | + if (type === 'disabled') { | ||
| 101 | + return false | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + if (type === 'cli') { | ||
| 105 | + // 检查命令是否存在 | ||
| 106 | + const { exec } = require('child_process') | ||
| 107 | + return new Promise((resolve) => { | ||
| 108 | + exec(`${MARKITDOWN_CONFIG.cli.command} --version`, (error) => { | ||
| 109 | + resolve(!error) | ||
| 110 | + }) | ||
| 111 | + }) | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + if (type === 'http') { | ||
| 115 | + // 检查 HTTP 服务是否可访问 | ||
| 116 | + try { | ||
| 117 | + const response = await fetch(MARKITDOWN_CONFIG.http.url, { | ||
| 118 | + method: 'HEAD', | ||
| 119 | + timeout: 5000 | ||
| 120 | + }) | ||
| 121 | + return response.ok | ||
| 122 | + } catch { | ||
| 123 | + return false | ||
| 124 | + } | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + if (type === 'docker') { | ||
| 128 | + // 检查 Docker 是否可用 | ||
| 129 | + const { exec } = require('child_process') | ||
| 130 | + return new Promise((resolve) => { | ||
| 131 | + exec('docker --version', (error) => { | ||
| 132 | + resolve(!error) | ||
| 133 | + }) | ||
| 134 | + }) | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + return false | ||
| 138 | +} | ||
| 139 | + | ||
| 140 | +/** | ||
| 141 | + * 检查 AI 服务是否配置 | ||
| 142 | + * | ||
| 143 | + * @returns {boolean} 是否已配置 API Key | ||
| 144 | + */ | ||
| 145 | +export function checkAIServiceConfigured() { | ||
| 146 | + const { type } = AI_SERVICE_CONFIG | ||
| 147 | + | ||
| 148 | + if (type === 'disabled') { | ||
| 149 | + return false | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + if (type === 'openai') { | ||
| 153 | + return !!AI_SERVICE_CONFIG.openai.apiKey | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + if (type === 'anthropic') { | ||
| 157 | + return !!AI_SERVICE_CONFIG.anthropic.apiKey | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + if (type === 'openrouter') { | ||
| 161 | + return !!AI_SERVICE_CONFIG.openrouter.apiKey | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + return false | ||
| 165 | +} | ||
| 166 | + | ||
| 167 | +/** | ||
| 168 | + * 打印配置状态 | ||
| 169 | + */ | ||
| 170 | +export function printConfigStatus() { | ||
| 171 | + console.log('\n🔧 文档解析服务配置状态:') | ||
| 172 | + console.log('─'.repeat(50)) | ||
| 173 | + | ||
| 174 | + // markitdown 状态 | ||
| 175 | + const markitdownType = MARKITDOWN_CONFIG.type | ||
| 176 | + console.log(`📄 markitdown: ${markitdownType === 'disabled' ? '❌ 未配置' : '✅ ' + markitdownType}`) | ||
| 177 | + | ||
| 178 | + // AI 服务状态 | ||
| 179 | + const aiType = AI_SERVICE_CONFIG.type | ||
| 180 | + console.log(`🤖 AI 服务: ${aiType === 'disabled' ? '❌ 未配置' : '✅ ' + aiType}`) | ||
| 181 | + | ||
| 182 | + if (aiType !== 'disabled') { | ||
| 183 | + const configured = checkAIServiceConfigured() | ||
| 184 | + console.log(` API Key: ${configured ? '✅ 已配置' : '❌ 未配置'}`) | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + console.log('─'.repeat(50)) | ||
| 188 | + console.log('') | ||
| 189 | + console.log('💡 配置提示:') | ||
| 190 | + console.log(' 1. 使用 markitdown: 安装 Python 并运行 "pip install markitdown"') | ||
| 191 | + console.log(' 2. 配置 AI 服务: 设置环境变量(.env 文件)') | ||
| 192 | + console.log(' - OPENAI_API_KEY: OpenAI API Key') | ||
| 193 | + console.log(' - ANTHROPIC_API_KEY: Anthropic API Key') | ||
| 194 | + console.log(' - AI_SERVICE_TYPE: openai | anthropic | openrouter') | ||
| 195 | + console.log('') | ||
| 196 | +} |
| ... | @@ -365,7 +365,8 @@ export const PLAN_TEMPLATES = { | ... | @@ -365,7 +365,8 @@ export const PLAN_TEMPLATES = { |
| 365 | form_schema: savingsFormSchema, | 365 | form_schema: savingsFormSchema, |
| 366 | submit_mapping: savingsSubmitMapping | 366 | submit_mapping: savingsSubmitMapping |
| 367 | } | 367 | } |
| 368 | - }} | 368 | + }, |
| 369 | +} | ||
| 369 | 370 | ||
| 370 | /** | 371 | /** |
| 371 | * 全局功能开关 | 372 | * 全局功能开关 | ... | ... |
-
Please register or login to post a comment