hookehuyr

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

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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
...@@ -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 },
......
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
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`
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 * 全局功能开关
......