hookehuyr

feat(docs): 添加文档自动解析工具

- 添加 parse-docs.js 自动化脚本
- 添加 AI 提取器 (ai-extractor.js)
- 添加配置生成器 (config-generator.js)
- 添加文档说明和使用指南
- 更新 package.json 添加解析脚本命令
- 清理 admin 目录遗留文件

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 +# 文档解析工具
2 +
3 +## 📁 文件夹说明
4 +
5 +此文件夹用于存放需要解析的保险产品文档,脚本将自动读取并生成配置。
6 +
7 +## 🚀 使用方法
8 +
9 +### 1. 添加文档
10 +
11 +将客户提供的 PDF/Word 文档复制到此文件夹:
12 +```
13 +docs/to-parse/
14 +├── WIOP3E 产品说明书.pdf
15 +├── 宏挚传承保障计划.docx
16 +└── MBC PRO 保障计划.pdf
17 +```
18 +
19 +### 2. 执行解析脚本
20 +
21 +```bash
22 +# 查看待处理的文档
23 +pnpm run parse:docs:list
24 +
25 +# 解析所有文档
26 +pnpm run parse:docs
27 +
28 +# 解析指定文档
29 +pnpm run parse:docs:file -- --file="产品说明书.pdf"
30 +```
31 +
32 +### 3. 查看结果
33 +
34 +解析成功后,配置会自动添加到 `src/config/plan-templates.js`
35 +
36 +## 📋 支持的文档格式
37 +
38 +- ✅ PDF (.pdf)
39 +- ✅ Word (.doc, .docx)
40 +- ✅ 纯本文档 (.txt, .md)
41 +
42 +## 🔧 配置 AI 服务
43 +
44 +脚本使用 skill 工具调用 AI 服务,支持:
45 +- OpenAI GPT-4o Vision
46 +- Anthropic Claude 3.5 Sonnet
47 +
48 +你需要配置 API Key(首次使用时脚本会提示)
49 +
50 +## ⚠️ 注意事项
51 +
52 +1. **文档命名**:建议使用有意义的文件名,方便识别产品
53 +2. **手动审核**:生成后请检查配置是否正确
54 +3. **版本控制**:生成的配置会自动备份
...@@ -33,7 +33,10 @@ ...@@ -33,7 +33,10 @@
33 "changelog:check": "bash scripts/check-changelog.sh 7", 33 "changelog:check": "bash scripts/check-changelog.sh 7",
34 "changelog:check:30": "bash scripts/check-changelog.sh 30", 34 "changelog:check:30": "bash scripts/check-changelog.sh 30",
35 "changelog:check:all": "bash scripts/check-changelog.sh 0", 35 "changelog:check:all": "bash scripts/check-changelog.sh 0",
36 - "prepare": "husky" 36 + "prepare": "husky",
37 + "parse:docs": "node scripts/parse-docs.js",
38 + "parse:docs:list": "node scripts/parse-docs.js --list",
39 + "parse:docs:file": "node scripts/parse-docs.js --file=\"产品说明书.pdf\""
37 }, 40 },
38 "browserslist": [ 41 "browserslist": [
39 "last 3 versions", 42 "last 3 versions",
......
This diff is collapsed. Click to expand it.
...@@ -5,6 +5,21 @@ ...@@ -5,6 +5,21 @@
5 * @module config/plan-templates 5 * @module config/plan-templates
6 * @author Claude Code 6 * @author Claude Code
7 * @created 2026-02-06 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 唯一(建议使用产品英文标识 + 版本号)
8 */ 23 */
9 24
10 /** 25 /**
......
1 +/**
2 + * 计划书配置生成器
3 + *
4 + * @description 将提取的数据转换为 plan-templates.js 的配置格式
5 + * @file utils/parsers/config-generator
6 + * @author Claude Code
7 + * @created 2026-02-13
8 + */
9 +
10 +import { generateFormSn, validateConfig } from './ai-extractor'
11 +
12 +/**
13 + * 生成完整的配置代码
14 + *
15 + * @description 将提取的配置数据转换为可写入 plan-templates.js 的代码
16 + * @param {Object} extractedConfig - 从文档提取的配置
17 + * @param {Object} options - 生成选项
18 + * @param {string} options.formSn - 自定义 form_sn(可选)
19 + * @param {boolean} options.includeComment - 是否包含注释(默认 true)
20 + * @returns {Object} { formSn: string, configCode: string, insertPosition: string }
21 + *
22 + * @example
23 + * const result = generateConfigCode({
24 + * product_name: '宏挚传承保障计划',
25 + * product_type: 'savings',
26 + * ...
27 + * })
28 + * // 返回: { formSn: 'savings-xxx', configCode: '...', insertPosition: '...' }
29 + */
30 +export function generateConfigCode(extractedConfig, options = {}) {
31 + const { formSn: customFormSn, includeComment = true } = options
32 +
33 + // 验证配置
34 + const validation = validateConfig(extractedConfig)
35 + if (!validation.valid) {
36 + throw new Error(`配置验证失败:\n${validation.errors.join('\n')}`)
37 + }
38 +
39 + // 生成 form_sn
40 + const formSn = customFormSn || generateFormSn(
41 + extractedConfig.product_name,
42 + extractedConfig.product_type
43 + )
44 +
45 + // 根据产品类型生成配置
46 + let configCode = ''
47 +
48 + if (extractedConfig.is_savings) {
49 + configCode = generateSavingsConfig(extractedConfig, formSn, includeComment)
50 + } else if (extractedConfig.product_type === 'life-insurance') {
51 + configCode = generateLifeInsuranceConfig(extractedConfig, formSn, includeComment)
52 + } else if (extractedConfig.product_type === 'critical-illness') {
53 + configCode = generateCriticalIllnessConfig(extractedConfig, formSn, includeComment)
54 + } else {
55 + throw new Error(`不支持的产品类型: ${extractedConfig.product_type}`)
56 + }
57 +
58 + // 生成插入位置提示
59 + const insertPosition = generateInsertPositionHint(extractedConfig)
60 +
61 + return {
62 + formSn,
63 + configCode,
64 + insertPosition,
65 + validation
66 + }
67 +}
68 +
69 +/**
70 + * 生成储蓄型产品配置
71 + *
72 + * @private
73 + */
74 +function generateSavingsConfig(config, formSn, includeComment) {
75 + const comment = includeComment ? `
76 + // ${config.product_name}
77 + // form_sn: ${formSn}
78 + // 提取方式: ${config.withdrawal_modes.join(', ')}
79 + // 提取周期: ${config.withdrawal_periods.join(', ')}` : ''
80 +
81 + const paymentPeriodsArray = JSON.stringify(config.payment_periods, null, 2)
82 + const withdrawalModesArray = JSON.stringify(config.withdrawal_modes, null, 2)
83 + const withdrawalPeriodsArray = JSON.stringify(config.withdrawal_periods, null, 2)
84 +
85 + return ` '${formSn}': {${comment}
86 + name: '${config.product_name}',
87 + component: 'SavingsTemplate',
88 + category: 'savings',
89 + config: {
90 + currency: '${config.currency}',
91 + payment_periods: ${paymentPeriodsArray},
92 + age_range: { min: ${config.age_range.min}, max: ${config.age_range.max} },
93 + insurance_period: '${config.insurance_period}',
94 + withdrawal_plan: {
95 + enabled: true,
96 + currencies: ['HKD', 'USD', 'CNY'],
97 + default_currency: '${config.currency}',
98 + withdrawal_modes: ${withdrawalModesArray},
99 + withdrawal_periods: ${withdrawalPeriodsArray}
100 + }
101 + }
102 + }`
103 +}
104 +
105 +/**
106 + * 生成人寿保险产品配置
107 + *
108 + * @private
109 + */
110 +function generateLifeInsuranceConfig(config, formSn, includeComment) {
111 + const comment = includeComment ? `
112 + // ${config.product_name}
113 + // form_sn: ${formSn}` : ''
114 +
115 + const paymentPeriodsArray = JSON.stringify(config.payment_periods, null, 2)
116 +
117 + return ` '${formSn}': {${comment}
118 + name: '${config.product_name}',
119 + component: 'LifeInsuranceTemplate',
120 + config: {
121 + currency: '${config.currency}',
122 + payment_periods: ${paymentPeriodsArray},
123 + age_range: { min: ${config.age_range.min}, max: ${config.age_range.max} },
124 + insurance_period: '${config.insurance_period}'
125 + }
126 + }`
127 +}
128 +
129 +/**
130 + * 生成重疾保险产品配置
131 + *
132 + * @private
133 + */
134 +function generateCriticalIllnessConfig(config, formSn, includeComment) {
135 + const comment = includeComment ? `
136 + // ${config.product_name}
137 + // form_sn: ${formSn}` : ''
138 +
139 + const paymentPeriodsArray = JSON.stringify(config.payment_periods, null, 2)
140 +
141 + return ` '${formSn}': {${comment}
142 + name: '${config.product_name}',
143 + component: 'CriticalIllnessTemplate',
144 + config: {
145 + currency: '${config.currency}',
146 + payment_periods: ${paymentPeriodsArray},
147 + age_range: { min: ${config.age_range.min}, max: ${config.age_range.max} },
148 + insurance_period: '${config.insurance_period}'
149 + }
150 + }`
151 +}
152 +
153 +/**
154 + * 生成插入位置提示
155 + *
156 + * @private
157 + * @param {Object} config - 配置对象
158 + * @returns {string} 插入位置说明
159 + */
160 +function generateInsertPositionHint(config) {
161 + if (config.is_savings) {
162 + return '// 插入到 PLAN_TEMPLATES 对象中的储蓄型产品部分(搜索 "// ====== 储蓄型产品")'
163 + } else if (config.product_type === 'life-insurance') {
164 + return '// 插入到 PLAN_TEMPLATES 对象中的人寿保险部分(搜索 "// 人寿保险产品")'
165 + } else if (config.product_type === 'critical-illness') {
166 + return '// 插入到 PLAN_TEMPLATES 对象中的重疾保险部分(搜索 "// 重疾保险产品")'
167 + }
168 + return '// 插入到 PLAN_TEMPLATES 对象中'
169 +}
170 +
171 +/**
172 + * 生成完整的导出代码(包含导入和导出)
173 + *
174 + * @param {Object} config - 提取的配置
175 + * @param {Object} options - 选项
176 + * @returns {string} 完整的模块代码
177 + */
178 +export function generateFullModuleCode(config, options = {}) {
179 + const { configCode, formSn } = generateConfigCode(config, options)
180 +
181 + return `/**
182 + * 新增产品配置
183 + * @description ${config.product_name}
184 + * @created ${new Date().toISOString()}
185 + */
186 +
187 +// 在 PLAN_TEMPLATES 中添加以下配置:
188 +${configCode}
189 +
190 +/**
191 + * 如果需要导出,在文件底部的 export 中添加:
192 + */
193 +export const ${toCamelCase(formSn)} = PLAN_TEMPLATES['${formSn}']
194 +`
195 +}
196 +
197 +/**
198 + * 转换为驼峰命名
199 + *
200 + * @private
201 + */
202 +function toCamelCase(str) {
203 + return str
204 + .replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
205 + .replace(/^(.)/, (c) => c.toLowerCase())
206 +}
207 +
208 +/**
209 + * 批量生成配置代码
210 + *
211 + * @param {Array} configs - 配置数组
212 + * @returns {string} 批量配置代码
213 + */
214 +export function generateBatchConfigCode(configs) {
215 + const result = configs.map((config, index) => {
216 + const { configCode } = generateConfigCode(config, {
217 + formSn: `batch-product-${Date.now()}-${index}`,
218 + includeComment: true
219 + })
220 + return configCode
221 + })
222 +
223 + return result.join(',\n\n')
224 +}
225 +
226 +/**
227 + * 预览配置数据(用于确认)
228 + *
229 + * @param {Object} config - 配置对象
230 + * @returns {Object} 格式化的预览数据
231 + */
232 +export function previewConfig(config) {
233 + return {
234 + '产品名称': config.product_name,
235 + '产品类型': getProductTypeLabel(config.product_type),
236 + '币种': config.currency,
237 + '缴费年期': config.payment_periods?.join('、') || '-',
238 + '年龄范围': `${config.age_range?.min || 0} - ${config.age_range?.max || 75} `,
239 + '保险期间': config.insurance_period,
240 + '是否储蓄型': config.is_savings ? '是' : '否',
241 + '提取方式': config.withdrawal_modes?.join('、') || '-',
242 + '提取周期': config.withdrawal_periods?.join('、') || '-'
243 + }
244 +}
245 +
246 +/**
247 + * 获取产品类型标签
248 + *
249 + * @private
250 + */
251 +function getProductTypeLabel(type) {
252 + const labels = {
253 + 'life-insurance': '人寿保险',
254 + 'critical-illness': '重疾保险',
255 + 'savings': '储蓄型'
256 + }
257 + return labels[type] || type
258 +}
259 +
260 +/**
261 + * 生成差异对比(新配置 vs 现有配置)
262 + *
263 + * @param {Object} newConfig - 新配置
264 + * @param {Object} existingConfig - 现有配置
265 + * @returns {Object} 差异对象
266 + */
267 +export function compareConfigs(newConfig, existingConfig) {
268 + const differences = {
269 + added: [],
270 + removed: [],
271 + changed: []
272 + }
273 +
274 + // 比较缴费年期
275 + if (newConfig.payment_periods && existingConfig.payment_periods) {
276 + const newPeriods = newConfig.payment_periods.filter(p => !existingConfig.payment_periods.includes(p))
277 + const removedPeriods = existingConfig.payment_periods.filter(p => !newConfig.payment_periods.includes(p))
278 +
279 + if (newPeriods.length > 0) differences.changed.push(`新增缴费年期: ${newPeriods.join(', ')}`)
280 + if (removedPeriods.length > 0) differences.changed.push(`移除缴费年期: ${removedPeriods.join(', ')}`)
281 + }
282 +
283 + // 比较年龄范围
284 + if (newConfig.age_range && existingConfig.age_range) {
285 + if (newConfig.age_range.min !== existingConfig.age_range.min ||
286 + newConfig.age_range.max !== existingConfig.age_range.max) {
287 + differences.changed.push(`年龄范围: ${existingConfig.age_range.min}-${existingConfig.age_range.max}${newConfig.age_range.min}-${newConfig.age_range.max}`)
288 + }
289 + }
290 +
291 + return differences
292 +}