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. 添加文档
将客户提供的 PDF/Word 文档复制到此文件夹:
```
docs/to-parse/
├── WIOP3E 产品说明书.pdf
├── 宏挚传承保障计划.docx
└── MBC PRO 保障计划.pdf
```
### 2. 执行解析脚本
```bash
# 查看待处理的文档
pnpm run parse:docs:list
# 解析所有文档
pnpm run parse:docs
# 解析指定文档
pnpm run parse:docs:file -- --file="产品说明书.pdf"
```
### 3. 查看结果
解析成功后,配置会自动添加到 `src/config/plan-templates.js`
## 📋 支持的文档格式
- ✅ PDF (.pdf)
- ✅ Word (.doc, .docx)
- ✅ 纯本文档 (.txt, .md)
## 🔧 配置 AI 服务
脚本使用 skill 工具调用 AI 服务,支持:
- OpenAI GPT-4o Vision
- Anthropic Claude 3.5 Sonnet
你需要配置 API Key(首次使用时脚本会提示)
## ⚠️ 注意事项
1. **文档命名**:建议使用有意义的文件名,方便识别产品
2. **手动审核**:生成后请检查配置是否正确
3. **版本控制**:生成的配置会自动备份
......@@ -33,7 +33,10 @@
"changelog:check": "bash scripts/check-changelog.sh 7",
"changelog:check:30": "bash scripts/check-changelog.sh 30",
"changelog:check:all": "bash scripts/check-changelog.sh 0",
"prepare": "husky"
"prepare": "husky",
"parse:docs": "node scripts/parse-docs.js",
"parse:docs:list": "node scripts/parse-docs.js --list",
"parse:docs:file": "node scripts/parse-docs.js --file=\"产品说明书.pdf\""
},
"browserslist": [
"last 3 versions",
......
This diff is collapsed. Click to expand it.
......@@ -5,6 +5,21 @@
* @module config/plan-templates
* @author Claude Code
* @created 2026-02-06
* @updated 2026-02-13 - 新增文档解析工具入口
*
* --- 快速添加新产品(开发工具) ---
* 开发环境可使用以下工具快速添加新产品配置:
* 1. 文档解析工具:/admin/document-parser/index (上传 PDF/Word,AI 自动解析)
* 2. API 配置工具:/admin/document-parser/config (配置 AI 服务)
*
* 使用方式:
* - 上传产品文档 → AI 自动提取配置 → 生成配置代码 → 复制到此文件
*
* --- 手动添加步骤 ---
* 1. 找到对应的产品分类(人寿/重疾/储蓄)
* 2. 复制现有配置作为模板
* 3. 修改 name, currency, payment_periods, age_range 等字段
* 4. 确保 form_sn 唯一(建议使用产品英文标识 + 版本号)
*/
/**
......
/**
* 计划书配置生成器
*
* @description 将提取的数据转换为 plan-templates.js 的配置格式
* @file utils/parsers/config-generator
* @author Claude Code
* @created 2026-02-13
*/
import { generateFormSn, validateConfig } from './ai-extractor'
/**
* 生成完整的配置代码
*
* @description 将提取的配置数据转换为可写入 plan-templates.js 的代码
* @param {Object} extractedConfig - 从文档提取的配置
* @param {Object} options - 生成选项
* @param {string} options.formSn - 自定义 form_sn(可选)
* @param {boolean} options.includeComment - 是否包含注释(默认 true)
* @returns {Object} { formSn: string, configCode: string, insertPosition: string }
*
* @example
* const result = generateConfigCode({
* product_name: '宏挚传承保障计划',
* product_type: 'savings',
* ...
* })
* // 返回: { formSn: 'savings-xxx', configCode: '...', insertPosition: '...' }
*/
export function generateConfigCode(extractedConfig, options = {}) {
const { formSn: customFormSn, includeComment = true } = options
// 验证配置
const validation = validateConfig(extractedConfig)
if (!validation.valid) {
throw new Error(`配置验证失败:\n${validation.errors.join('\n')}`)
}
// 生成 form_sn
const formSn = customFormSn || generateFormSn(
extractedConfig.product_name,
extractedConfig.product_type
)
// 根据产品类型生成配置
let configCode = ''
if (extractedConfig.is_savings) {
configCode = generateSavingsConfig(extractedConfig, formSn, includeComment)
} else if (extractedConfig.product_type === 'life-insurance') {
configCode = generateLifeInsuranceConfig(extractedConfig, formSn, includeComment)
} else if (extractedConfig.product_type === 'critical-illness') {
configCode = generateCriticalIllnessConfig(extractedConfig, formSn, includeComment)
} else {
throw new Error(`不支持的产品类型: ${extractedConfig.product_type}`)
}
// 生成插入位置提示
const insertPosition = generateInsertPositionHint(extractedConfig)
return {
formSn,
configCode,
insertPosition,
validation
}
}
/**
* 生成储蓄型产品配置
*
* @private
*/
function generateSavingsConfig(config, formSn, includeComment) {
const comment = includeComment ? `
// ${config.product_name}
// form_sn: ${formSn}
// 提取方式: ${config.withdrawal_modes.join(', ')}
// 提取周期: ${config.withdrawal_periods.join(', ')}` : ''
const paymentPeriodsArray = JSON.stringify(config.payment_periods, null, 2)
const withdrawalModesArray = JSON.stringify(config.withdrawal_modes, null, 2)
const withdrawalPeriodsArray = JSON.stringify(config.withdrawal_periods, null, 2)
return ` '${formSn}': {${comment}
name: '${config.product_name}',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: '${config.currency}',
payment_periods: ${paymentPeriodsArray},
age_range: { min: ${config.age_range.min}, max: ${config.age_range.max} },
insurance_period: '${config.insurance_period}',
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: '${config.currency}',
withdrawal_modes: ${withdrawalModesArray},
withdrawal_periods: ${withdrawalPeriodsArray}
}
}
}`
}
/**
* 生成人寿保险产品配置
*
* @private
*/
function generateLifeInsuranceConfig(config, formSn, includeComment) {
const comment = includeComment ? `
// ${config.product_name}
// form_sn: ${formSn}` : ''
const paymentPeriodsArray = JSON.stringify(config.payment_periods, null, 2)
return ` '${formSn}': {${comment}
name: '${config.product_name}',
component: 'LifeInsuranceTemplate',
config: {
currency: '${config.currency}',
payment_periods: ${paymentPeriodsArray},
age_range: { min: ${config.age_range.min}, max: ${config.age_range.max} },
insurance_period: '${config.insurance_period}'
}
}`
}
/**
* 生成重疾保险产品配置
*
* @private
*/
function generateCriticalIllnessConfig(config, formSn, includeComment) {
const comment = includeComment ? `
// ${config.product_name}
// form_sn: ${formSn}` : ''
const paymentPeriodsArray = JSON.stringify(config.payment_periods, null, 2)
return ` '${formSn}': {${comment}
name: '${config.product_name}',
component: 'CriticalIllnessTemplate',
config: {
currency: '${config.currency}',
payment_periods: ${paymentPeriodsArray},
age_range: { min: ${config.age_range.min}, max: ${config.age_range.max} },
insurance_period: '${config.insurance_period}'
}
}`
}
/**
* 生成插入位置提示
*
* @private
* @param {Object} config - 配置对象
* @returns {string} 插入位置说明
*/
function generateInsertPositionHint(config) {
if (config.is_savings) {
return '// 插入到 PLAN_TEMPLATES 对象中的储蓄型产品部分(搜索 "// ====== 储蓄型产品")'
} else if (config.product_type === 'life-insurance') {
return '// 插入到 PLAN_TEMPLATES 对象中的人寿保险部分(搜索 "// 人寿保险产品")'
} else if (config.product_type === 'critical-illness') {
return '// 插入到 PLAN_TEMPLATES 对象中的重疾保险部分(搜索 "// 重疾保险产品")'
}
return '// 插入到 PLAN_TEMPLATES 对象中'
}
/**
* 生成完整的导出代码(包含导入和导出)
*
* @param {Object} config - 提取的配置
* @param {Object} options - 选项
* @returns {string} 完整的模块代码
*/
export function generateFullModuleCode(config, options = {}) {
const { configCode, formSn } = generateConfigCode(config, options)
return `/**
* 新增产品配置
* @description ${config.product_name}
* @created ${new Date().toISOString()}
*/
// 在 PLAN_TEMPLATES 中添加以下配置:
${configCode}
/**
* 如果需要导出,在文件底部的 export 中添加:
*/
export const ${toCamelCase(formSn)} = PLAN_TEMPLATES['${formSn}']
`
}
/**
* 转换为驼峰命名
*
* @private
*/
function toCamelCase(str) {
return str
.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
.replace(/^(.)/, (c) => c.toLowerCase())
}
/**
* 批量生成配置代码
*
* @param {Array} configs - 配置数组
* @returns {string} 批量配置代码
*/
export function generateBatchConfigCode(configs) {
const result = configs.map((config, index) => {
const { configCode } = generateConfigCode(config, {
formSn: `batch-product-${Date.now()}-${index}`,
includeComment: true
})
return configCode
})
return result.join(',\n\n')
}
/**
* 预览配置数据(用于确认)
*
* @param {Object} config - 配置对象
* @returns {Object} 格式化的预览数据
*/
export function previewConfig(config) {
return {
'产品名称': config.product_name,
'产品类型': getProductTypeLabel(config.product_type),
'币种': config.currency,
'缴费年期': config.payment_periods?.join('、') || '-',
'年龄范围': `${config.age_range?.min || 0} - ${config.age_range?.max || 75} `,
'保险期间': config.insurance_period,
'是否储蓄型': config.is_savings ? '是' : '否',
'提取方式': config.withdrawal_modes?.join('、') || '-',
'提取周期': config.withdrawal_periods?.join('、') || '-'
}
}
/**
* 获取产品类型标签
*
* @private
*/
function getProductTypeLabel(type) {
const labels = {
'life-insurance': '人寿保险',
'critical-illness': '重疾保险',
'savings': '储蓄型'
}
return labels[type] || type
}
/**
* 生成差异对比(新配置 vs 现有配置)
*
* @param {Object} newConfig - 新配置
* @param {Object} existingConfig - 现有配置
* @returns {Object} 差异对象
*/
export function compareConfigs(newConfig, existingConfig) {
const differences = {
added: [],
removed: [],
changed: []
}
// 比较缴费年期
if (newConfig.payment_periods && existingConfig.payment_periods) {
const newPeriods = newConfig.payment_periods.filter(p => !existingConfig.payment_periods.includes(p))
const removedPeriods = existingConfig.payment_periods.filter(p => !newConfig.payment_periods.includes(p))
if (newPeriods.length > 0) differences.changed.push(`新增缴费年期: ${newPeriods.join(', ')}`)
if (removedPeriods.length > 0) differences.changed.push(`移除缴费年期: ${removedPeriods.join(', ')}`)
}
// 比较年龄范围
if (newConfig.age_range && existingConfig.age_range) {
if (newConfig.age_range.min !== existingConfig.age_range.min ||
newConfig.age_range.max !== existingConfig.age_range.max) {
differences.changed.push(`年龄范围: ${existingConfig.age_range.min}-${existingConfig.age_range.max}${newConfig.age_range.min}-${newConfig.age_range.max}`)
}
}
return differences
}