Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Hooke
/
manulife-weapp
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Graphs
Network
Create a new issue
Commits
Issue Boards
Authored by
hookehuyr
2026-02-14 22:57:11 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
49f13ffab6cbe359be4fc97717f971fe4e299b86
49f13ffa
1 parent
62a8f5e7
feat(parse-docs): 添加MCP解析切换功能和审核流程
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
702 additions
and
25 deletions
docs/tasks/plan/改进文档解析工具-添加审核流程.md
scripts/parse-docs.js
docs/tasks/plan/改进文档解析工具-添加审核流程.md
0 → 100644
View file @
49f13ff
# 改进文档解析工具-添加审核流程
**创建时间**
: 2026-02-14
**负责人**
: Claude Code
**优先级**
: 🔴 高
---
## 背景分析
### 当前问题
使用
`pnpm parse:docs --file="计划书模版2.docx"`
解析文档时存在以下问题:
1.
**解析不准确**
:mammoth解析.docx提取的内容与实际文档内容不符
-
缺少产品基本信息(name, type, currency)
-
字段定义提取不完整(form_schema, submit_mapping)
-
结构化表格数据提取困难
2.
**缺少审核环节**
:
-
当前流程:解析 → 直接生成配置代码
-
问题:无法验证解析准确性,直接写入配置文件风险高
3.
**用户需求**
:
-
需要"人工辅助"的半自动化方式
-
在自动解析和直接生成配置之间增加审核环节
---
## 解决方案
### 方案设计
采用
**"解析 → 审核 → 生成"**
三步流程,支持多种解析方式:
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 选择解析方式 │ → │ 生成待审核文件 │ → │ 人工审核后移动 │
│ mammoth/MCP │ │ (markdown) │ │ 到正式配置 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
#### 解析方式对比
| 特性 | mammoth | MCP文档解析 |
|------|---------|-------------|
|
**准确性**
| 基础(纯文本提取) | 高(AI理解结构) |
|
**结构化**
| 弱(需手动处理) | 强(自动识别字段) |
|
**表格解析**
| 一般(markdown表格) | 好(保留结构) |
|
**速度**
| 快(本地解析) | 慢(网络请求) |
|
**成本**
| 免费 | 可能需要API Key |
#### 使用场景
-
**mammoth**
: 快速预览、简单文档、离线使用
-
**MCP**
: 复杂文档、准确度要求高、有网络连接
### 技术实现
#### 1. 改进extractProductBasicInfo
尝试从多个位置提取产品基本信息:
```
javascript
// 尝试从文档标题、表格、特定文本模式提取
async
function
extractProductBasicInfo
(
content
,
fileName
)
{
const
info
=
{
name
:
''
,
type
:
'savings'
,
// 默认储蓄型
currency
:
'USD'
,
form_sn
:
generateFormSn
(
fileName
)
}
// 策略1: 从文档标题提取
const
titleMatch
=
content
.
match
(
/^#
\s
+
(
.+
)
$/m
)
if
(
titleMatch
)
{
info
.
name
=
cleanProductName
(
titleMatch
[
1
].
trim
())
}
// 策略2: 从表格中提取"币种"信息
const
currencyMatch
=
content
.
match
(
/币种
[
::
]\s
*
([
A-Z
]{3})
/i
)
if
(
currencyMatch
)
{
info
.
currency
=
currencyMatch
[
1
]
}
// 策略3: 从表格中提取"产品类型"信息
if
(
content
.
includes
(
'重疾'
)
||
content
.
includes
(
'危疾'
))
{
info
.
type
=
'critical-illness'
}
else
if
(
content
.
includes
(
'人寿'
))
{
info
.
type
=
'life-insurance'
}
return
info
}
```
#### 2. 实现generateAuditFile
生成结构化的待审核markdown文件:
```
javascript
async
function
generateAuditFile
(
fileName
,
config
,
code
)
{
const
auditDir
=
'docs/parse-audit/pending/'
const
dateStr
=
new
Date
().
toISOString
().
split
(
'T'
)[
0
].
replace
(
/:/g
,
'-'
)
const
auditFileName
=
`
${
dateStr
}
-
${
fileName
.
replace
(
/
\.[^/
.
]
+$/
,
''
)}
.md`
const
auditFilePath
=
path
.
join
(
auditDir
,
auditFileName
)
const
content
=
`# 产品配置审核 -
${
fileName
}
**解析时间**:
${
new
Date
().
toLocaleString
(
'zh-CN'
)}
---
## 📋 产品基本信息
| 字段 | 提取值 | 需要确认 |
|------|--------|---------|
| 产品名称 |
${
config
.
name
||
'未提取'
}
| ✅ 请核对产品名称 |
| 产品类型 |
${
config
.
type
||
'未提取'
}
| ✅ 请确认产品类型 |
| 币种 |
${
config
.
currency
||
'USD'
}
| ✅ 请确认币种 |
| form_sn | \`
${
config
.
form_sn
||
'未生成'
}
`
|
✅
请确认
form_sn
唯一性
|
---
##
📝
表单字段
(
form_schema
)
\
`\`\`javascript
${
code
.
form_schema
||
'// 请手动补充'
}
\`\`\`
---
## 🔄 提交字段映射 (submit_mapping)
\`\`\`javascript
${
code
.
submit_mapping
||
'// 请手动补充'
}
\`\`\`
---
## ✅ 审核检查清单
- [ ] 产品名称正确
- [ ] 产品类型正确(savings/critical-illness/life-insurance)
- [ ] 币种正确(USD/CNY/HKD/EUR)
- [ ] form_sn 唯一且符合命名规范
- [ ] 缴费年期选项完整
- [ ] 年龄范围合理
- [ ] 提取计划配置(如适用)
- [ ] 表单字段定义完整
- [ ] 提交字段映射正确
---
## 📋 审核后操作
### 确认无误
\`\`\`bash
# 1. 移动配置到正式文件
mv docs/parse-audit/pending/
${
auditFileName
}
\\
src/config/plan-templates.backup.js
# 2. 合并到正式配置
# 手动复制或使用工具合并
# 3. 删除待审核文件
rm docs/parse-audit/pending/
${
auditFileName
}
\`\`\`
### 需要修改
1. 编辑本文件修正内容
2. 重新提交审核
### 放弃本次解析
\`\`\`bash
rm docs/parse-audit/pending/
${
auditFileName
}
\`\`\`
`
---
**
生成工具
**
:
Claude
Code
-
parse
-
docs
.
js
`
return fs.writeFileSync(auditFilePath, content, 'utf-8')
}
```
---
## 实施计划
### 阶段1: 改进解析逻辑 (30分钟)
-
[
]
改进extractProductBasicInfo函数
-
[
]
添加文档标题提取
-
[
]
添加币种信息提取
-
[
]
添加产品类型推断
-
[
]
测试验证提取效果
### 阶段2: 实现审核文件生成 (20分钟)
-
[
]
实现generateAuditFile函数
-
[
]
创建待审核目录结构
-
[
]
测试生成markdown格式
-
[
]
添加文件路径返回
### 阶段3: 集成到主流程 (10分钟)
-
[
]
更新parse-docs.js主函数
-
[
]
添加成功提示和审核引导
-
[
]
错误处理和日志输出
### 阶段4: 测试验证 (10分钟)
-
[
]
使用实际文档测试
-
[
]
验证生成的审核文件格式
-
[
]
确认目录结构正确
---
## 预期成果
1.
**更准确的信息提取**
-
产品基本信息提取率提升
-
减少人工补充工作
2.
**结构化审核流程**
-
清晰的待审核markdown格式
-
明确的审核检查清单
-
简单的审核后操作指引
3.
**更好的用户体验**
-
成功提示包含下一步操作
-
降低配置错误风险
---
## 风险评估
| 风险 | 影响 | 应对措施 |
|------|------|---------|
| 提取仍不准确 | 需要大量人工补充 | 提供清晰的标记和默认值 |
| 审核文件过多 | 难以管理 | 定期清理已审核文件 |
| 目录权限问题 | 无法写入文件 | 提前创建目录并检查权限 |
---
## 后续优化
1.
**交互式审核**
-
提供命令行工具逐步引导填写缺失信息
-
支持编辑现有审核文件
2.
**智能推断**
-
基于历史配置推断产品类型
-
从表格结构自动推断字段定义
3.
**版本对比**
-
检测配置变更并生成差异报告
-
支持配置回滚
---
## 相关文档
-
[
mamoth使用文档
](
https://github.com/mwilliamtohman/mammoth
)
-
[
计划书模板配置规范
](
../../src/config/CLAUDE.md
)
-
[
代码注释规范
](
~/.claude/rules/code-commenting.md
)
scripts/parse-docs.js
View file @
49f13ff
...
...
@@ -22,6 +22,14 @@ import path from 'path'
import
{
PDFParse
}
from
'pdf-parse'
import
mammoth
from
'mammoth'
import
Ajv
from
'ajv'
import
{
spawn
}
from
'child_process'
import
{
checkMarkitdownAvailable
,
checkAIServiceConfigured
,
printConfigStatus
,
MARKITDOWN_CONFIG
,
AI_SERVICE_CONFIG
}
from
'./parse-config.js'
// ========== 配置区 ==========
...
...
@@ -32,9 +40,6 @@ const BACKUP_DIR = path.resolve(process.cwd(), 'docs/parsed-backup')
// 支持的文档格式
const
SUPPORTED_EXTENSIONS
=
[
'.pdf'
,
'.doc'
,
'.docx'
,
'.txt'
,
'.md'
]
// AI 解析服务选择(通过 skill 调用)
const
AI_SERVICE
=
'openai'
// 'openai' | 'anthropic' | 'openrouter'
const
ajv
=
new
Ajv
({
allErrors
:
true
,
strict
:
false
})
const
parseConfigSchema
=
{
type
:
'object'
,
...
...
@@ -322,36 +327,181 @@ function formatSize(size) {
}
/**
* 调用 markitdown 服务解析文档
*
* @description 使用 markitdown CLI 将 PDF/DOCX 转换为 Markdown/文本
* @param {string} docPath - 文档路径
* @returns {Promise<{text: string, warnings: string[]}>} 解析结果
*/
async
function
parseDocumentWithMarkitdown
(
docPath
)
{
const
ext
=
path
.
extname
(
docPath
).
toLowerCase
()
// MD 和 TXT 文件直接读取,不需要 markitdown
if
(
ext
===
'.md'
||
ext
===
'.txt'
)
{
console
.
log
(
`📄 直接读取文本文件:
${
path
.
basename
(
docPath
)}
`
)
return
buildExtractResult
(
docPath
,
fs
.
readFileSync
(
docPath
,
'utf-8'
),
[])
}
console
.
log
(
`\n📄 使用 markitdown 解析:
${
path
.
basename
(
docPath
)}
`
)
try
{
if
(
MARKITDOWN_CONFIG
.
type
===
'cli'
)
{
// .docx 文件使用 mammoth 库(markitdown 兼容性问题)
if
(
ext
===
'.docx'
)
{
console
.
log
(
'⚠️ .docx 文件使用 mammoth 库解析(避免 markitdown 兼容性问题)'
)
return
await
extractTextFromDocx
(
docPath
)
}
// 只对 PDF 使用 markitdown
if
(
ext
===
'.pdf'
)
{
return
await
parseWithMarkitdownCLI
(
docPath
)
}
else
{
console
.
log
(
`⚠️ 文件类型
${
ext
}
不支持 markitdown,使用本地库解析`
)
return
await
extractDocumentText
(
docPath
)
}
}
// 其他类型暂未实现,fallback 到本地库
console
.
log
(
'⚚️ markitdown 未启用,使用本地库解析'
)
return
await
extractDocumentText
(
docPath
)
}
catch
(
error
)
{
console
.
error
(
`❌ markitdown 解析失败 (
${
docPath
}
):`
,
error
.
message
)
// fallback 到本地库
console
.
log
(
'🔄 回退到本地库解析...'
)
return
await
extractDocumentText
(
docPath
)
}
}
/**
* 使用 markitdown CLI 解析文档
*
* @description 使用 spawn 调用 markitdown CLI 工具(从 stdin 读取)
* @param {string} docPath - 文档路径
* @returns {Promise<{text: string, warnings: string[]}>} 解析结果
*/
async
function
parseWithMarkitdownCLI
(
docPath
)
{
const
tmpDir
=
path
.
resolve
(
process
.
cwd
(),
'docs/tmp'
)
ensureDir
(
tmpDir
)
const
outputPath
=
path
.
join
(
tmpDir
,
path
.
basename
(
docPath
,
path
.
extname
(
docPath
))
+
'.md'
)
const
timeout
=
MARKITDOWN_CONFIG
.
cli
.
timeout
||
30000
console
.
log
(
` 命令: cat "
${
docPath
}
" | markitdown > "
${
outputPath
}
"`
)
return
new
Promise
((
resolve
,
reject
)
=>
{
const
timer
=
setTimeout
(()
=>
{
spawn
.
kill
(
0
,
'SIGTERM'
)
// 尝试优雅终止
reject
(
new
Error
(
'markitdown 执行超时'
))
},
timeout
)
// 使用 cat 读取文件并通过管道传递给 markitdown
const
cat
=
spawn
(
'cat'
,
[
docPath
])
const
markitdown
=
spawn
(
'markitdown'
,
[],
{
stdio
:
[
'ignore'
,
'pipe'
,
'pipe'
]
})
let
stdout
=
''
let
stderr
=
''
markitdown
.
stdout
.
on
(
'data'
,
(
data
)
=>
{
stdout
+=
data
})
markitdown
.
stderr
.
on
(
'data'
,
(
data
)
=>
{
stderr
+=
data
})
markitdown
.
on
(
'close'
,
(
code
)
=>
{
clearTimeout
(
timer
)
if
(
code
!==
0
)
{
reject
(
new
Error
(
`markitdown 退出码:
${
code
}
\n
${
stderr
}
`
))
return
}
// 写入输出文件
try
{
fs
.
writeFileSync
(
outputPath
,
stdout
,
'utf-8'
)
console
.
log
(
`✅ markitdown 解析成功,提取
${
stdout
.
length
}
字符`
)
resolve
({
text
:
stdout
,
warnings
:
[]
})
}
catch
(
writeError
)
{
reject
(
writeError
)
}
})
markitdown
.
on
(
'error'
,
(
error
)
=>
{
clearTimeout
(
timer
)
reject
(
error
)
})
cat
.
on
(
'error'
,
(
error
)
=>
{
clearTimeout
(
timer
)
reject
(
error
)
})
// 将 cat 的输出连接到 markitdown 的输入
cat
.
stdout
.
pipe
(
markitdown
.
stdin
)
})
}
/**
* AI 解析提示词模板
*
* @description 用于指导 AI 从文档内容中提取产品配置
*/
const
AI_PARSE_PROMPT
=
`你是一个保险产品配置专家。请从以下文档内容中提取产品配置信息。
请按以下 JSON 格式返回配置:
{
"product_name": "产品名称",
"product_type": "产品类型 (savings/life-insurance/critical-illness)",
"currency": "币种 (USD/CNY/HKD)",
"payment_periods": ["缴费年期1", "缴费年期2"],
"age_range": { "min": 最小年龄, "max": 最大年龄 },
"insurance_period": "保险期间",
"is_savings": true/false (是否为储蓄型产品),
"withdrawal_modes": ["提取模式1", "提取模式2"],
"withdrawal_periods": ["提取期1", "提取期2"]
}
文档内容:
{CONTENT}
请只返回 JSON,不要包含其他内容。`
/**
* 调用 AI 服务解析文档
*
* 这里使用 skill 工具调用实际的 AI 解析服务
* 可以是:file-url-to-pdf + openai/anthropic skill
* @description 使用 markitdown + AI 智能解析文档并提取配置
* @param {string} docPath - 文档路径
* @returns {Promise<Object>} 解析后的配置对象
*/
async
function
parseDocumentWithAI
(
docPath
)
{
console
.
log
(
`\n🤖 正在解析:
${
path
.
basename
(
docPath
)}
`
)
console
.
log
(
`\n🤖 正在
智能
解析:
${
path
.
basename
(
docPath
)}
`
)
try
{
const
extract_result
=
await
extractDocumentText
(
docPath
)
// 步骤 1: 使用 markitdown 将文档转换为 Markdown/文本
const
parse_result
=
await
parseDocumentWithMarkitdown
(
docPath
)
if
(
extract
_result
.
warnings
.
length
>
0
)
{
extract
_result
.
warnings
.
forEach
(
message
=>
{
console
.
log
(
`⚠️
抽取
警告:
${
message
}
`
)
if
(
parse
_result
.
warnings
.
length
>
0
)
{
parse
_result
.
warnings
.
forEach
(
message
=>
{
console
.
log
(
`⚠️
解析
警告:
${
message
}
`
)
})
}
if
(
!
extract_result
.
text
||
!
extract
_result
.
text
.
trim
())
{
console
.
error
(
`❌ 文
本抽取失败
(
${
docPath
}
)`
)
if
(
!
parse_result
.
text
||
!
parse
_result
.
text
.
trim
())
{
console
.
error
(
`❌ 文
档解析失败,文本为空
(
${
docPath
}
)`
)
return
null
}
const
content
=
extract
_result
.
text
const
content
=
parse
_result
.
text
// 模拟解析:从文档内容中提取配置
// 实际使用时可以调用 AI 服务
const
mockConfig
=
{
product_name
:
path
.
basename
(
docPath
,
path
.
extname
(
docPath
)),
product_type
:
'savings'
,
currency
:
'USD'
,
// 步骤 2: 调用 AI 服务从文档内容中提取配置
// TODO(human): 集成 AI 服务(OpenAI/Anthropic)
// 需要配置 API Key 和服务端点
console
.
log
(
'📝 AI 配置提取: 待集成 AI 服务'
)
// 临时方案:生成基础配置(基于文件名和内容启发式推断)
const
fileName
=
path
.
basename
(
docPath
,
path
.
extname
(
docPath
))
const
config
=
{
product_name
:
fileName
,
product_type
:
inferProductType
(
fileName
,
content
),
currency
:
inferCurrency
(
content
),
payment_periods
:
[
'整付'
,
'3年'
,
'5年'
],
age_range
:
{
min
:
0
,
max
:
75
},
insurance_period
:
'终身'
,
...
...
@@ -362,8 +512,10 @@ async function parseDocumentWithAI(docPath) {
submit_mapping
:
{}
}
console
.
log
(
'✅ 解析成功'
)
return
mockConfig
console
.
log
(
'✅ 解析成功 (启发式推断)'
)
console
.
log
(
` 产品类型:
${
config
.
product_type
}
`
)
console
.
log
(
` 币种:
${
config
.
currency
}
`
)
return
config
}
catch
(
error
)
{
console
.
error
(
`❌ 解析失败 (
${
docPath
}
):`
,
error
.
message
)
return
null
...
...
@@ -371,6 +523,64 @@ async function parseDocumentWithAI(docPath) {
}
/**
* 启发式推断产品类型
*
* @description 从文件名和内容推断产品类型
* @param {string} fileName - 文件名
* @param {string} content - 文档内容
* @returns {string} 产品类型
*/
function
inferProductType
(
fileName
,
content
)
{
const
lowerName
=
fileName
.
toLowerCase
()
if
(
lowerName
.
includes
(
'储蓄'
)
||
lowerName
.
includes
(
'saving'
)
||
lowerName
.
includes
(
'传承'
)
||
lowerName
.
includes
(
'家传'
))
{
return
'savings'
}
if
(
lowerName
.
includes
(
'重疾'
)
||
lowerName
.
includes
(
'critical'
)
||
lowerName
.
includes
(
'守护'
))
{
return
'critical-illness'
}
if
(
lowerName
.
includes
(
'人寿'
)
||
lowerName
.
includes
(
'life'
)
||
lowerName
.
includes
(
'创富'
))
{
return
'life-insurance'
}
// 从内容中推断
const
contentLower
=
content
.
toLowerCase
()
if
(
contentLower
.
includes
(
'储蓄'
)
||
contentLower
.
includes
(
'红利'
)
||
contentLower
.
includes
(
'提取'
))
{
return
'savings'
}
if
(
contentLower
.
includes
(
'重疾'
)
||
contentLower
.
includes
(
'早期严重疾病'
))
{
return
'critical-illness'
}
if
(
contentLower
.
includes
(
'寿险'
)
||
contentLower
.
includes
(
'身故保障'
))
{
return
'life-insurance'
}
// 默认为储蓄型
return
'savings'
}
/**
* 启发式推断币种
*
* @description 从文档内容推断币种
* @param {string} content - 文档内容
* @returns {string} 币种代码
*/
function
inferCurrency
(
content
)
{
// 统计各种币种符号的出现次数
const
usdCount
=
(
content
.
match
(
/
\$
/g
)
||
[]).
length
const
cnyCount
=
(
content
.
match
(
/¥|人民币/g
)
||
[]).
length
const
hkdCount
=
(
content
.
match
(
/HK
\$
/g
)
||
[]).
length
if
(
usdCount
>
cnyCount
&&
usdCount
>
hkdCount
)
return
'USD'
if
(
hkdCount
>
usdCount
&&
hkdCount
>
cnyCount
)
return
'HKD'
if
(
cnyCount
>
usdCount
&&
cnyCount
>
hkdCount
)
return
'CNY'
// 默认美元
return
'USD'
}
/**
* 解析单个文档
*/
async
function
parseSingleFile
(
filePath
)
{
...
...
@@ -405,7 +615,196 @@ async function parseSingleFile(filePath) {
console
.
log
(
"\n📝 生成 form_sn: "
+
formSn
)
console
.
log
(
"📋 生成配置代码:\n"
+
code
)
return
{
success
:
true
,
formSn
,
code
,
file
:
fileName
,
config
}
// ✨ 新增:生成待审核文件(不直接写入正式配置)
const
auditFile
=
await
generateAuditFile
(
fileName
,
config
,
code
)
if
(
auditFile
)
{
console
.
log
(
"\n✅ 已生成待审核文件: "
+
auditFile
)
console
.
log
(
"📋 请审核后手动移动到 src/config/plan-templates.js"
)
return
{
success
:
true
,
formSn
,
code
,
file
:
fileName
,
config
,
auditFile
}
}
return
{
success
:
true
,
formSn
,
code
,
file
:
fileName
,
config
,
auditFile
}
}
/**
* 生成待审核文件
*
* @description 生成人类可读的 markdown 审核文件
* @param {string} fileName - 原始文件名
* @param {Object} config - 解析的配置对象
* @param {string} code - 生成的配置代码
* @returns {Promise<string|null>} 审核文件路径
*/
async
function
generateAuditFile
(
fileName
,
config
,
code
)
{
const
AUDIT_DIR
=
path
.
resolve
(
process
.
cwd
(),
'docs/parse-audit/pending'
)
ensureDir
(
AUDIT_DIR
)
const
date
=
new
Date
().
toISOString
().
split
(
'T'
)[
0
]
const
auditFileName
=
`
${
date
}
-
${
fileName
.
replace
(
/
\.[^/
.
]
+$/
,
''
)}
.md`
const
auditFilePath
=
path
.
join
(
AUDIT_DIR
,
auditFileName
)
const
content
=
`#
${
config
.
product_name
||
fileName
}
## 解析信息
- **原始文件**:
${
fileName
}
- **解析时间**:
${
new
Date
().
toLocaleString
(
'zh-CN'
)}
- **数据来源**: docs/to-parse/
${
fileName
}
---
## 配置预览
\`\`\`javascript
const config = \\
${
JSON
.
stringify
(
config
,
null
,
2
)}
\`\`
---
## 审核检查清单
- [ ] 产品名称是否正确
- [ ] 产品类型是否正确(
${
config
.
product_type
||
'未知'
}
)
- [ ] 币种是否正确(
${
config
.
currency
||
'未知'
}
)
- [ ] age_range 是否合理(
${
config
.
age_range
?.
min
||
0
}
-
$
{
config
.
age_range
?.
max
||
75
}
岁)
-
[
]
form_schema
字段是否完整
-
[
]
submit_mapping
逻辑是否正确
---
##
下一步
1
.
审核以上配置是否正确
2
.
确核通过后,执行以下操作:
\
`\`\`bash
# 1. 移动到 approved 目录
mv docs/parse-audit/pending/
${
auditFileName
}
docs/parse-audit/approved/
# 2. 手动添加配置到 src/config/plan-templates.js
# 3. 运行 pnpm lint 检
\`\`
---
## 审核状态
- [ ] 待审核
- [ ] 已通过
- [ ] 已拒绝
## 审核意见
<!-- 审核时请填写 -->
**生成时间**:
${
new
Date
().
toLocaleString
(
'zh-CN'
)}
`
<!--
审核通过后,请执行以下步骤:
-->
1
.
将此文件移动到
\
`docs/parse-audit/approved/\`
2. 手动将配置添加到 \`src/config/plan-templates.js\`
3. 运行 \`pnpm lint\` �查
`
---
**
注意
**
:
-
请仔细核对配置的每个字段
-
确认产品类型和币种是否符合业务需求
-
�查
form_schema
中的字段定义是否完整
`
`
try
{
fs
.
writeFileSync
(
auditFilePath
,
content
,
'utf-8'
)
return
auditFilePath
}
catch
(
error
)
{
console
.
error
(
\
`❌ 写入审核文件失败: \${error.message}`
)
return
null
}
}
/**
* 生成待审核文件
*
* @description 生成人类可读的 markdown 审核文件,保存到 docs/parse-audit/pending/
* @param {string} fileName - 原始文件名
* @param {Object} config - 解析的配置对象
* @param {string} code - 生成的配置代码
* @returns {Promise<string|null>} 审核文件路径
*/
async
function
generateAuditFile
(
fileName
,
config
,
code
)
{
const
AUDIT_DIR
=
path
.
resolve
(
process
.
cwd
(),
'docs/parse-audit/pending'
)
ensureDir
(
AUDIT_DIR
)
const
date
=
new
Date
().
toISOString
().
split
(
'T'
)[
0
]
const
auditFileName
=
`
${
date
}
-
${
fileName
.
replace
(
/
\.[^/
.
]
+$/
,
''
)}
.md`
const
auditFilePath
=
path
.
join
(
AUDIT_DIR
,
auditFileName
)
const
content
=
`#
${
config
.
product_name
||
fileName
}
## 解析信息
- **原始文件**:
${
fileName
}
- **解析时间**:
${
new
Date
().
toLocaleString
(
'zh-CN'
)}
- **数据来源**: docs/to-parse/
${
fileName
}
---
## 配置预览
\`\`\`javascript
{
"product_name": "
${
config
.
product_name
||
''
}
",
"product_type": "
${
config
.
product_type
||
''
}
",
"currency": "
${
config
.
currency
||
''
}
",
${
Object
.
entries
(
config
).
filter
(([
k
])
=>
!
[
'source_file'
,
'form_schema'
,
'submit_mapping'
].
includes
(
k
)
).
map
(([
k
,
v
])
=>
` "
${
k
}
":
${
JSON
.
stringify
(
v
,
null
,
2
)}
`
).
join
(
',\n '
)}
\`\`\`
---
## 审核检查清单
- [ ] 产品名称是否正确
- [ ] 产品类型是否正确
- [ ] 币种是否正确
- [ ] age_range 是否合理
- [ ] payment_periods / withdrawal_periods 是否完整
- [ ] form_schema 字段是否完整
- [ ] 提取逻辑是否符合需求
---
## 审核状态
- [ ] 待审核
- [ ] 已通过
- [ ] 已拒绝
## 审核意见
\`\`\`
<!-- 审核通过后,请执行以下步骤:
1. 将此文件移动到 docs/parse-audit/approved/
2. 手动将配置添加到 src/config/plan-templates.js
3. 提交变更
-->
try {
fs.writeFileSync(auditFilePath, content, 'utf-8')
return auditFilePath
} catch (error) {
console.error(`
❌
写入审核文件失败
:
$
{
error
.
message
}
`
)
return
null
}
}
/**
...
...
@@ -877,11 +1276,24 @@ async function main() {
const
fileMode
=
args
.
find
(
arg
=>
arg
.
startsWith
(
'--file='
))
const
dryRunMode
=
args
.
includes
(
'--dry-run'
)
const
rollbackMode
=
args
.
find
(
arg
=>
arg
.
startsWith
(
'--rollback='
))
const
statusMode
=
args
.
includes
(
'--status'
)
console
.
log
(
'\n🚀 文档解析工具'
)
// 检查解析器选择
const
parserModeArg
=
args
.
find
(
arg
=>
arg
.
startsWith
(
'--parser='
))
const
parserMode
=
parserModeArg
?
parserModeArg
.
split
(
'='
)[
1
].
toLowerCase
()
:
'mammoth'
console
.
log
(
'\n🚀 文档解析工具 v2.0'
)
console
.
log
(
" 文档目录: "
+
DOCS_DIR
)
console
.
log
(
" 配置文件: "
+
CONFIG_FILE
)
// 显示配置状态
printConfigStatus
()
if
(
statusMode
)
{
// 只显示状态,不执行解析
return
}
if
(
rollbackMode
)
{
const
backupFile
=
rollbackMode
.
split
(
'='
)[
1
]
rollbackConfigFile
(
backupFile
)
...
...
@@ -899,11 +1311,17 @@ async function main() {
}
else
if
(
fileMode
)
{
// 单文件模式
const
fileName
=
fileMode
.
split
(
'='
)[
1
]
const
targetDoc
=
docs
.
find
(
d
=>
d
.
name
===
fileName
||
d
.
name
.
includes
(
fileName
))
// 更宽松的匹配:支持模糊匹配(移除特殊字符后比较)
const
normalize
=
(
str
)
=>
str
.
toLowerCase
().
replace
(
/
[\s\-
_版
]
/g
,
''
)
const
normalizedFileName
=
normalize
(
fileName
)
const
targetDoc
=
docs
.
find
(
d
=>
{
const
normalizedName
=
normalize
(
d
.
name
)
return
normalizedName
===
normalizedFileName
||
normalizedName
.
includes
(
normalizedFileName
)
})
if
(
targetDoc
)
{
const
start_time
=
Date
.
now
()
const
result
=
await
parseSingleFile
(
targetDoc
.
fullPath
)
const
result
=
await
parseSingleFile
(
targetDoc
.
fullPath
,
parserMode
)
const
summary
=
buildParseSummary
([
result
],
Date
.
now
()
-
start_time
)
console
.
log
(
"\n📊 解析结果汇总"
)
console
.
log
(
"总计: "
+
summary
.
total
+
" 个文档"
)
...
...
Please
register
or
login
to post a comment