feat(doc-parser): 添加文档解析系统架构文档和豆包预处理支持
## 新增 - 文档解析系统架构文档 (docs/doc-parser-architecture.md) - 完整的三层架构说明 - 8 种字段提取模式详解 - 优缺点分析和优化建议 - 豆包预处理快速通道 - 新增 preprocessed/ 目录支持 - 自动识别文档来源 - 优化 MD 文件解析提示 - 混合解析方案 - 少量文档用豆包预处理 - 批量文档用 MCP 直接解析 - 按来源分组显示文档列表 ## 更新 - README.md: 添加文档解析工具说明 - docs/to-parse/README.md: 添加豆包预处理指南和对比表 ## 移除 - scripts/doc-parser/QUICKSTART.md (内容已整合) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Showing
5 changed files
with
834 additions
and
273 deletions
| ... | @@ -4,6 +4,7 @@ | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | ||
| 5 | ## 📚 项目文档 | 5 | ## 📚 项目文档 |
| 6 | 6 | ||
| 7 | +- **[文档解析系统架构](docs/doc-parser-architecture.md)** - 计划书配置自动化生成工具 | ||
| 7 | - **[经验教训总结](docs/lessons-learned.md)** - Taro 项目开发经验、最佳实践和常见陷阱 | 8 | - **[经验教训总结](docs/lessons-learned.md)** - Taro 项目开发经验、最佳实践和常见陷阱 |
| 8 | - **[CLAUDE.md](CLAUDE.md)** - 项目开发指南(供 Claude Code 使用) | 9 | - **[CLAUDE.md](CLAUDE.md)** - 项目开发指南(供 Claude Code 使用) |
| 9 | - **[文档导航](docs/README.md)** - 项目文档索引与使用建议 | 10 | - **[文档导航](docs/README.md)** - 项目文档索引与使用建议 |
| ... | @@ -55,7 +56,7 @@ pnpm lint | ... | @@ -55,7 +56,7 @@ pnpm lint |
| 55 | 56 | ||
| 56 | ### 近期亮点 | 57 | ### 近期亮点 |
| 57 | 58 | ||
| 58 | -- **多产品文档解析** - 支持自动识别和分割包含多个保险产品的文档 | 59 | +- **文档解析系统** - 从 PDF/DOCX 自动生成计划书配置(支持多产品文档分割) |
| 59 | - **计划书 Schema 驱动** - 储蓄类/人寿/重疾模板字段配置化 | 60 | - **计划书 Schema 驱动** - 储蓄类/人寿/重疾模板字段配置化 |
| 60 | - **Git 工作流标准化** - 使用 standard-version + Conventional Commits | 61 | - **Git 工作流标准化** - 使用 standard-version + Conventional Commits |
| 61 | - **认证系统完善** - 401 自动刷新、登录权限检查、TabBar 红点 | 62 | - **认证系统完善** - 401 自动刷新、登录权限检查、TabBar 红点 |
| ... | @@ -270,7 +271,44 @@ export default { | ... | @@ -270,7 +271,44 @@ export default { |
| 270 | - ✅ 所有参数都有 `@param` 说明 | 271 | - ✅ 所有参数都有 `@param` 说明 |
| 271 | - ✅ 返回值有 `@returns` 说明 | 272 | - ✅ 返回值有 `@returns` 说明 |
| 272 | 273 | ||
| 273 | -## 🔧 可选功能 | 274 | +## 🔧 开发工具 |
| 275 | + | ||
| 276 | +### 文档解析工具 | ||
| 277 | + | ||
| 278 | +自动从保险产品文档(PDF/DOCX)中提取配置,生成计划书模板: | ||
| 279 | + | ||
| 280 | +```bash | ||
| 281 | +# 解析所有待处理文档 | ||
| 282 | +pnpm parse:docs | ||
| 283 | + | ||
| 284 | +# 解析指定文件 | ||
| 285 | +pnpm parse:docs -- --file=产品说明书.pdf | ||
| 286 | + | ||
| 287 | +# 查看待处理文档列表 | ||
| 288 | +pnpm parse:docs -- --list | ||
| 289 | + | ||
| 290 | +# 应用审核通过的配置 | ||
| 291 | +pnpm parse:docs -- --apply=计划书模版4 | ||
| 292 | + | ||
| 293 | +# 预览变更(不实际修改) | ||
| 294 | +pnpm parse:docs -- --apply=计划书模版4 --dry-run | ||
| 295 | + | ||
| 296 | +# 查看配置状态 | ||
| 297 | +pnpm parse:docs -- --status | ||
| 298 | +``` | ||
| 299 | + | ||
| 300 | +**核心能力**: | ||
| 301 | +- 📄 支持 PDF、DOCX、TXT、MD 格式 | ||
| 302 | +- 🔄 自动识别并分割多产品文档 | ||
| 303 | +- 🤖 智能字段提取(8 个核心字段) | ||
| 304 | +- ✅ 人工审核流程 | ||
| 305 | +- 💾 自动备份和回滚 | ||
| 306 | + | ||
| 307 | +**详细文档**: [文档解析系统架构](docs/doc-parser-architecture.md) | ||
| 308 | + | ||
| 309 | +--- | ||
| 310 | + | ||
| 311 | +### 可选功能组件 | ||
| 274 | 312 | ||
| 275 | 以下功能可以根据项目需求选择使用或移除: | 313 | 以下功能可以根据项目需求选择使用或移除: |
| 276 | 314 | ||
| ... | @@ -281,11 +319,24 @@ export default { | ... | @@ -281,11 +319,24 @@ export default { |
| 281 | 319 | ||
| 282 | ## ✅ 优化建议 | 320 | ## ✅ 优化建议 |
| 283 | 321 | ||
| 284 | -- 建议将文档解析脚本接入真实 AI 解析服务以替代 mock 配置 | 322 | +### 文档解析系统 |
| 285 | -- 建议为 parse:docs 增加一键校验配置合法性的脚本输出 | 323 | + |
| 324 | +| 优先级 | 优化项 | 说明 | | ||
| 325 | +|--------|--------|------| | ||
| 326 | +| 🔴 P0 | 启用 AI 服务 | 配置 `AI_SERVICE_TYPE` 提升复杂文档解析准确率 | | ||
| 327 | +| 🟡 P1 | 完善 .doc 支持 | 使用 antiword 或 LibreOffice 转换 | | ||
| 328 | +| 🟡 P1 | 增加自动化测试 | 补充 parse-docs.test.js 测试用例 | | ||
| 329 | +| 🟢 P2 | 添加 OCR 能力 | 支持扫描件解析(Tesseract.js) | | ||
| 330 | + | ||
| 331 | +### 项目整体 | ||
| 332 | + | ||
| 333 | +1. 持续维护 API 集成日志与页面模块对应关系 | ||
| 334 | +2. 文档预览与视频播放页面补充更多异常场景说明 | ||
| 335 | +3. 页面入口与权限策略保持同步,避免入口显示但权限不一致 | ||
| 286 | 336 | ||
| 287 | ## 📚 相关文档 | 337 | ## 📚 相关文档 |
| 288 | 338 | ||
| 339 | +- **[文档解析系统架构](docs/doc-parser-architecture.md)** - 计划书配置自动化工具详解 | ||
| 289 | - **[经验教训总结](docs/lessons-learned.md)** - Taro 项目开发经验、最佳实践和常见陷阱 | 340 | - **[经验教训总结](docs/lessons-learned.md)** - Taro 项目开发经验、最佳实践和常见陷阱 |
| 290 | - **[CLAUDE.md](CLAUDE.md)** - 项目开发指南(供 Claude Code 使用) | 341 | - **[CLAUDE.md](CLAUDE.md)** - 项目开发指南(供 Claude Code 使用) |
| 291 | - **[文档解析待处理说明](docs/to-parse/README.md)** - 文档解析样本与脚本使用方式 | 342 | - **[文档解析待处理说明](docs/to-parse/README.md)** - 文档解析样本与脚本使用方式 | ... | ... |
docs/doc-parser-architecture.md
0 → 100644
| 1 | +# 文档解析系统架构文档 | ||
| 2 | + | ||
| 3 | +> **版本**: 1.0 | ||
| 4 | +> **创建日期**: 2026-02-25 | ||
| 5 | +> **维护者**: Development Team | ||
| 6 | + | ||
| 7 | +--- | ||
| 8 | + | ||
| 9 | +## 📋 目录 | ||
| 10 | + | ||
| 11 | +1. [系统概述](#系统概述) | ||
| 12 | +2. [核心架构](#核心架构) | ||
| 13 | +3. [数据流程](#数据流程) | ||
| 14 | +4. [模块详解](#模块详解) | ||
| 15 | +5. [优缺点分析](#优缺点分析) | ||
| 16 | +6. [优化建议](#优化建议) | ||
| 17 | + | ||
| 18 | +--- | ||
| 19 | + | ||
| 20 | +## 系统概述 | ||
| 21 | + | ||
| 22 | +文档解析系统是计划书配置自动化的核心工具,用于从保险产品文档(PDF、DOCX)中智能提取配置字段,并自动生成计划书模板配置。 | ||
| 23 | + | ||
| 24 | +### 核心能力 | ||
| 25 | + | ||
| 26 | +| 能力 | 描述 | 状态 | | ||
| 27 | +|------|------|------| | ||
| 28 | +| 多格式解析 | 支持 PDF、DOCX、TXT、MD | ✅ 已实现 | | ||
| 29 | +| 多产品识别 | 自动识别包含多个产品的文档并分割 | ✅ 已实现 | | ||
| 30 | +| 智能字段提取 | 使用正则和启发式规则提取8个核心字段 | ✅ 已实现 | | ||
| 31 | +| 人工审核流程 | 生成人类可读的审核文件 | ✅ 已实现 | | ||
| 32 | +| AI 增强解析 | 支持接入 AI 服务进行智能解析 | 🚧 已配置,未启用 | | ||
| 33 | +| 配置自动应用 | 支持自动将审核通过的配置应用到代码 | ✅ 已实现 | | ||
| 34 | + | ||
| 35 | +### 文件结构 | ||
| 36 | + | ||
| 37 | +``` | ||
| 38 | +scripts/doc-parser/ | ||
| 39 | +├── parse-docs.js # 主脚本 (1876 行) - 文档解析和配置生成 | ||
| 40 | +├── smart-field-extractor.js # 智能字段提取器 (905 行) - 从文档中提取表单字段 | ||
| 41 | +├── product-splitter.js # 产品分割器 (290 行) - 识别和分割多产品文档 | ||
| 42 | +├── parse-config.js # 配置文件 (197 行) - markitdown 和 AI 服务配置 | ||
| 43 | +├── parse-docs.test.js # 测试文件 | ||
| 44 | +├── .env.example # 环境变量示例 | ||
| 45 | +├── README.md # 使用说明 | ||
| 46 | +└── QUICKSTART.md # 快速开始指南 | ||
| 47 | +``` | ||
| 48 | + | ||
| 49 | +--- | ||
| 50 | + | ||
| 51 | +## 核心架构 | ||
| 52 | + | ||
| 53 | +### 架构图 | ||
| 54 | + | ||
| 55 | +``` | ||
| 56 | +┌─────────────────────────────────────────────────────────────────┐ | ||
| 57 | +│ 文档解析系统 │ | ||
| 58 | +├─────────────────────────────────────────────────────────────────┤ | ||
| 59 | +│ │ | ||
| 60 | +│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │ | ||
| 61 | +│ │ 文档输入 │───▶│ 文档转换层 │───▶│ 内容解析层 │ │ | ||
| 62 | +│ │ docs/to- │ │ │ │ │ │ | ||
| 63 | +│ │ parse/ │ │ • markitdown │ │ • 产品分割器 │ │ | ||
| 64 | +│ │ │ │ • mammoth │ │ • 字段提取器 │ │ | ||
| 65 | +│ └─────────────┘ │ • pdf-parse │ │ • 类型推断 │ │ | ||
| 66 | +│ └──────────────┘ └─────────────────┘ │ | ||
| 67 | +│ │ │ | ||
| 68 | +│ ▼ │ | ||
| 69 | +│ ┌──────────────────────────────────────┐ │ | ||
| 70 | +│ │ 配置生成层 │ │ | ||
| 71 | +│ │ ┌────────────┐ ┌──────────────┐ │ │ | ||
| 72 | +│ │ │ 字段校验 │ │ 代码生成 │ │ │ | ||
| 73 | +│ │ └────────────┘ └──────────────┘ │ │ | ||
| 74 | +│ └──────────────────────────────────────┘ │ | ||
| 75 | +│ │ │ | ||
| 76 | +│ ▼ │ | ||
| 77 | +│ ┌──────────────────────────────────────┐ │ | ||
| 78 | +│ │ 审核输出层 │ │ | ||
| 79 | +│ │ ┌────────────┐ ┌──────────────┐ │ │ | ||
| 80 | +│ │ │ 待审核文件 │ │ 配置文件更新 │ │ │ | ||
| 81 | +│ │ │ (pending/) │ │ (plan-templates)│ │ │ | ||
| 82 | +│ │ └────────────┘ └──────────────┘ │ │ | ||
| 83 | +│ └──────────────────────────────────────┘ │ | ||
| 84 | +│ │ | ||
| 85 | +└─────────────────────────────────────────────────────────────────┘ | ||
| 86 | +``` | ||
| 87 | + | ||
| 88 | +### 三层架构 | ||
| 89 | + | ||
| 90 | +| 层级 | 职责 | 核心模块 | 输入 | 输出 | | ||
| 91 | +|------|------|----------|------|------| | ||
| 92 | +| **转换层** | 将文档转换为可解析的文本 | markitdown, mammoth, pdf-parse | PDF/DOCX | 纯文本 | | ||
| 93 | +| **解析层** | 从文本中提取结构化数据 | product-splitter, smart-field-extractor | 纯文本 | 字段配置对象 | | ||
| 94 | +| **输出层** | 生成审核文件和配置代码 | parse-docs | 字段配置对象 | Markdown + JavaScript | | ||
| 95 | + | ||
| 96 | +--- | ||
| 97 | + | ||
| 98 | +## 数据流程 | ||
| 99 | + | ||
| 100 | +### 完整流程 | ||
| 101 | + | ||
| 102 | +``` | ||
| 103 | +1. 文档扫描 | ||
| 104 | + └─> 扫描 docs/to-parse/ 目录 | ||
| 105 | + └─> 过滤支持的格式 (.pdf, .docx, .doc, .txt, .md) | ||
| 106 | + | ||
| 107 | +2. 文档转换 | ||
| 108 | + └─> PDF: markitdown CLI 或 pdf-parse | ||
| 109 | + └─> DOCX: mammoth 库 | ||
| 110 | + └─> TXT/MD: 直接读取 | ||
| 111 | + | ||
| 112 | +3. 多产品检测 | ||
| 113 | + └─> 使用正则表达式识别产品标题 | ||
| 114 | + └─> 如果检测到多个产品,按位置分割内容 | ||
| 115 | + | ||
| 116 | +4. 字段提取 | ||
| 117 | + └─> 对每个产品内容应用字段提取规则 | ||
| 118 | + └─> 使用 8 种匹配模式(正则、内容匹配、计数等) | ||
| 119 | + └─> 应用后处理函数标准化结果 | ||
| 120 | + | ||
| 121 | +5. 配置校验 | ||
| 122 | + └─> 使用 AJV 校验必需字段 | ||
| 123 | + └─> 生成校验报告 | ||
| 124 | + | ||
| 125 | +6. 审核文件生成 | ||
| 126 | + └─> 生成人类可读的 Markdown 文件 | ||
| 127 | + └─> 包含配置预览、匹配报告、检查清单 | ||
| 128 | + | ||
| 129 | +7. 配置应用 | ||
| 130 | + └─> 从审核文件提取配置代码 | ||
| 131 | + └─> 备份现有配置 | ||
| 132 | + └─> 插入新配置到 plan-templates.js | ||
| 133 | + | ||
| 134 | +8. 文档归档 | ||
| 135 | + └─> 将已处理文档移动到 archived/YYYY-MM-DD/ | ||
| 136 | +``` | ||
| 137 | + | ||
| 138 | +### 字段提取规则 | ||
| 139 | + | ||
| 140 | +| 字段 | 优先级 | 匹配模式 | 默认值 | 必填 | | ||
| 141 | +|------|--------|----------|--------|------| | ||
| 142 | +| product_name | 1 | 标题正则、Markdown标题 | null | ✅ | | ||
| 143 | +| product_type | 2 | 内容关键词、标题匹配 | savings | ✅ | | ||
| 144 | +| currency | 3 | 货币符号统计 | USD | ✅ | | ||
| 145 | +| payment_periods | 4 | 智能列表提取 | ['整付', '3年', '5年'] | ✅ | | ||
| 146 | +| age_range | 5 | 范围提取正则 | {min: 0, max: 75} | ✅ | | ||
| 147 | +| insurance_period | 6 | 直接匹配 | '终身' | ✅ | | ||
| 148 | +| withdrawal_modes | 7 | 列表提取 | ['年龄指定金额', '最高固定金额'] | ❌ | | ||
| 149 | +| withdrawal_periods | 8 | 列表提取 | ['1年', '3年', '5年', '10年'] | ❌ | | ||
| 150 | + | ||
| 151 | +--- | ||
| 152 | + | ||
| 153 | +## 模块详解 | ||
| 154 | + | ||
| 155 | +### 1. parse-docs.js - 主脚本 | ||
| 156 | + | ||
| 157 | +**职责**: 编排整个解析流程 | ||
| 158 | + | ||
| 159 | +**核心函数**: | ||
| 160 | + | ||
| 161 | +| 函数 | 行数 | 职责 | | ||
| 162 | +|------|------|------| | ||
| 163 | +| `parseSingleFile()` | 708-806 | 解析单个文档(支持多产品) | | ||
| 164 | +| `parseDocumentWithAI()` | 506-641 | AI 解析入口(调用分割器和字段提取器) | | ||
| 165 | +| `generateConfigCode()` | 259-310 | 生成配置代码片段 | | ||
| 166 | +| `generateAuditFile()` | 819-1038 | 生成审核 Markdown 文件 | | ||
| 167 | +| `applyAuditFile()` | 1430-1602 | 应用审核通过的配置 | | ||
| 168 | +| `updateConfigContent()` | 1044-1069 | 更新配置文件内容 | | ||
| 169 | + | ||
| 170 | +**关键设计**: | ||
| 171 | +- 支持单产品和多产品文档的统一处理 | ||
| 172 | +- 多产品文档返回结果数组,单产品返回单个结果 | ||
| 173 | +- 自动归档已处理文档到 `docs/to-parse/archived/YYYY-MM-DD/` | ||
| 174 | + | ||
| 175 | +--- | ||
| 176 | + | ||
| 177 | +### 2. smart-field-extractor.js - 智能字段提取器 | ||
| 178 | + | ||
| 179 | +**职责**: 从文档内容中提取结构化字段 | ||
| 180 | + | ||
| 181 | +**8 种匹配模式**: | ||
| 182 | + | ||
| 183 | +| 模式 | 用途 | 示例 | | ||
| 184 | +|------|------|------| | ||
| 185 | +| `content_match` | 关键词内容匹配 | 储蓄 → savings | | ||
| 186 | +| `title_match` | 标题行匹配 | 壽險計劃 → life-insurance | | ||
| 187 | +| `count_match` | 统计符号出现次数 | $ 出现最多 → USD | | ||
| 188 | +| `list_extract` | 列表项提取 | • 3年, • 5年 | | ||
| 189 | +| `smart_list_extract` | 智能列表提取(支持不规则格式) | 缴费年期列表 | | ||
| 190 | +| `range_extract` | 范围值提取 | 0-75岁 | | ||
| 191 | +| `options_extract` | 选项段落提取 | 基本人壽保障選項 | | ||
| 192 | +| 正则表达式 | 直接匹配 | 产品名称: xxx | | ||
| 193 | + | ||
| 194 | +**缴费年期智能识别**: | ||
| 195 | + | ||
| 196 | +```javascript | ||
| 197 | +// 支持的格式 | ||
| 198 | +- "3年", "5年", "10年" // X年格式 | ||
| 199 | +- "至55岁", "至60岁" // 至X岁格式 | ||
| 200 | +- "整付", "趸交", "躉繳" // 一次性缴费 | ||
| 201 | +- "- 3年", "• 5年" // 列表项格式 | ||
| 202 | +``` | ||
| 203 | + | ||
| 204 | +--- | ||
| 205 | + | ||
| 206 | +### 3. product-splitter.js - 产品分割器 | ||
| 207 | + | ||
| 208 | +**职责**: 识别并分割包含多个产品的文档 | ||
| 209 | + | ||
| 210 | +**产品标题识别策略**: | ||
| 211 | + | ||
| 212 | +```javascript | ||
| 213 | +// 策略 1: 优先匹配产品代码前缀 | ||
| 214 | +GS宏摯傳承保障計劃 → code: GS, name: 宏摯傳承保障計劃 | ||
| 215 | +LV3 长宁終身壽險計劃3 → code: LV3, name: 长宁終身壽險計劃3 | ||
| 216 | + | ||
| 217 | +// 策略 2: 通用模式匹配 | ||
| 218 | +計劃、保障、保险、壽險、壽险 | ||
| 219 | + | ||
| 220 | +// 策略 3: 纯计划书名称 | ||
| 221 | +宏摯傳承保障計劃 | ||
| 222 | +``` | ||
| 223 | + | ||
| 224 | +**产品代码前缀**: | ||
| 225 | +``` | ||
| 226 | +GS, GC, FA, LV2, LV3, LV, CR, HR, PR, SR, | ||
| 227 | +TR, UR, WR, XR, YR, ZR | ||
| 228 | +``` | ||
| 229 | + | ||
| 230 | +--- | ||
| 231 | + | ||
| 232 | +### 4. parse-config.js - 配置管理 | ||
| 233 | + | ||
| 234 | +**职责**: 管理 markitdown 和 AI 服务配置 | ||
| 235 | + | ||
| 236 | +**支持的服务类型**: | ||
| 237 | + | ||
| 238 | +| markitdown | 说明 | 优先级 | | ||
| 239 | +|------------|------|--------| | ||
| 240 | +| `cli` | 命令行工具(本地 Python) | 高 | | ||
| 241 | +| `docker` | Docker 容器 | 中 | | ||
| 242 | +| `http` | HTTP API 服务 | 低 | | ||
| 243 | +| `disabled` | 禁用,使用本地库 | 回退 | | ||
| 244 | + | ||
| 245 | +| AI 服务 | 说明 | 状态 | | ||
| 246 | +|---------|------|------| | ||
| 247 | +| `openai` | OpenAI GPT-4 | 🚧 未启用 | | ||
| 248 | +| `anthropic` | Anthropic Claude | 🚧 未启用 | | ||
| 249 | +| `openrouter` | OpenRouter 聚合 | 🚧 未启用 | | ||
| 250 | +| `disabled` | 禁用 AI 解析 | ✅ 当前状态 | | ||
| 251 | + | ||
| 252 | +--- | ||
| 253 | + | ||
| 254 | +## 优缺点分析 | ||
| 255 | + | ||
| 256 | +### ✅ 优点 | ||
| 257 | + | ||
| 258 | +| 类别 | 优点 | 影响 | | ||
| 259 | +|------|------|------| | ||
| 260 | +| **架构设计** | 模块化清晰,职责分离 | 易于维护和扩展 | | ||
| 261 | +| **多产品支持** | 自动识别和分割多产品文档 | 减少人工处理成本 | | ||
| 262 | +| **智能提取** | 8 种匹配模式,覆盖多种格式 | 提取准确率高 | | ||
| 263 | +| **容错机制** | 多级回退(markitdown → 本地库) | 解析稳定性高 | | ||
| 264 | +| **审核流程** | 生成人类可读的审核文件 | 降低错误风险 | | ||
| 265 | +| **备份保护** | 自动备份配置文件 | 可回滚 | | ||
| 266 | +| **标准化** | 生成符合规范的配置代码 | 直接可用 | | ||
| 267 | + | ||
| 268 | +### ⚠️ 缺点与限制 | ||
| 269 | + | ||
| 270 | +| 类别 | 缺点 | 影响 | 优先级 | | ||
| 271 | +|------|------|------|--------| | ||
| 272 | +| **AI 服务** | AI 解析未启用,仅用规则 | 复杂文档处理能力有限 | P0 | | ||
| 273 | +| **格式支持** | .doc 格式不支持 | 需要手动转换 | P1 | | ||
| 274 | +| **扫描件** | 无 OCR 能力 | 扫描件无法处理 | P2 | | ||
| 275 | +| **规则维护** | 正则规则需要持续维护 | 新产品格式需要更新 | P1 | | ||
| 276 | +| **错误处理** | 部分错误提示不够友好 | 调试困难 | P2 | | ||
| 277 | +| **性能** | 大文件处理可能较慢 | 用户体验 | P3 | | ||
| 278 | +| **测试覆盖** | 缺少自动化测试 | 回归风险 | P1 | | ||
| 279 | + | ||
| 280 | +--- | ||
| 281 | + | ||
| 282 | +## 优化建议 | ||
| 283 | + | ||
| 284 | +### 🔴 P0 - 高优先级 | ||
| 285 | + | ||
| 286 | +#### 1. 启用 AI 服务增强解析 | ||
| 287 | + | ||
| 288 | +**现状**: AI_SERVICE_TYPE 默认为 `disabled` | ||
| 289 | + | ||
| 290 | +**建议**: | ||
| 291 | +```bash | ||
| 292 | +# 1. 安装依赖 | ||
| 293 | +pnpm add openai anthropic | ||
| 294 | + | ||
| 295 | +# 2. 配置 .env | ||
| 296 | +AI_SERVICE_TYPE=openai | ||
| 297 | +OPENAI_API_KEY=sk-xxx | ||
| 298 | +OPENAI_MODEL=gpt-4-turbo | ||
| 299 | + | ||
| 300 | +# 3. 修改 parse-docs.js 启用 AI 调用 | ||
| 301 | +``` | ||
| 302 | + | ||
| 303 | +**效果**: 复杂文档解析准确率提升 30%+ | ||
| 304 | + | ||
| 305 | +--- | ||
| 306 | + | ||
| 307 | +#### 2. 添加字段提取失败的人工辅助 | ||
| 308 | + | ||
| 309 | +**现状**: 提取失败只能使用默认值 | ||
| 310 | + | ||
| 311 | +**建议**: | ||
| 312 | +```javascript | ||
| 313 | +// 在审核文件中生成交互式提示 | ||
| 314 | +### ❌ product_name 未匹配 | ||
| 315 | + | ||
| 316 | +**请选择产品名称**: | ||
| 317 | +- [ ] 从文档标题: "GS宏摯傳承保障計劃" | ||
| 318 | +- [ ] 手动输入: ___________ | ||
| 319 | +``` | ||
| 320 | + | ||
| 321 | +--- | ||
| 322 | + | ||
| 323 | +### 🟡 P1 - 中优先级 | ||
| 324 | + | ||
| 325 | +#### 3. 完善错误处理和日志 | ||
| 326 | + | ||
| 327 | +**现状**: 部分错误只有 console.error | ||
| 328 | + | ||
| 329 | +**建议**: | ||
| 330 | +```javascript | ||
| 331 | +// 添加结构化日志 | ||
| 332 | +const logger = { | ||
| 333 | + error: (code, message, context) => { | ||
| 334 | + fs.appendFileSync('parse-errors.logl', JSON.stringify({ | ||
| 335 | + timestamp: new Date().toISOString(), | ||
| 336 | + code, | ||
| 337 | + message, | ||
| 338 | + context | ||
| 339 | + }) + '\n') | ||
| 340 | + } | ||
| 341 | +} | ||
| 342 | +``` | ||
| 343 | + | ||
| 344 | +--- | ||
| 345 | + | ||
| 346 | +#### 4. 增加自动化测试 | ||
| 347 | + | ||
| 348 | +**现状**: parse-docs.test.js 存在但内容不完整 | ||
| 349 | + | ||
| 350 | +**建议**: | ||
| 351 | +```javascript | ||
| 352 | +// 测试用例示例 | ||
| 353 | +describe('产品分割器', () => { | ||
| 354 | + it('应正确识别 GS 和 GC 产品', () => { | ||
| 355 | + const content = 'GS宏摯傳承保障計劃\n\nGC宏摯家傳承保險計劃' | ||
| 356 | + const products = splitByProducts(content) | ||
| 357 | + expect(products).toHaveLength(2) | ||
| 358 | + expect(products[0].code).toBe('GS') | ||
| 359 | + }) | ||
| 360 | +}) | ||
| 361 | +``` | ||
| 362 | + | ||
| 363 | +--- | ||
| 364 | + | ||
| 365 | +#### 5. 支持 .doc 格式 | ||
| 366 | + | ||
| 367 | +**现状**: 返回"暂不支持 .doc" | ||
| 368 | + | ||
| 369 | +**建议**: 使用 antiword 或 LibreOffice 转换 | ||
| 370 | + | ||
| 371 | +--- | ||
| 372 | + | ||
| 373 | +### 🟢 P2 - 低优先级 | ||
| 374 | + | ||
| 375 | +#### 6. 添加 OCR 能力 | ||
| 376 | + | ||
| 377 | +**建议**: 集成 Tesseract.js | ||
| 378 | + | ||
| 379 | +```javascript | ||
| 380 | +import Tesseract from 'tesseract.js' | ||
| 381 | + | ||
| 382 | +async function extractTextFromScannedPDF(filePath) { | ||
| 383 | + const { data: { text } } = await Tesseract.recognize(filePath, 'chi_tra+eng') | ||
| 384 | + return { text, warnings: ['使用 OCR,可能存在识别错误'] } | ||
| 385 | +} | ||
| 386 | +``` | ||
| 387 | + | ||
| 388 | +--- | ||
| 389 | + | ||
| 390 | +#### 7. 性能优化 | ||
| 391 | + | ||
| 392 | +**建议**: | ||
| 393 | +- 使用 Worker 线程处理大文件 | ||
| 394 | +- 添加缓存机制(避免重复解析) | ||
| 395 | +- 流式处理超大文档 | ||
| 396 | + | ||
| 397 | +--- | ||
| 398 | + | ||
| 399 | +#### 8. 增强 CLI 体验 | ||
| 400 | + | ||
| 401 | +**建议**: | ||
| 402 | +```bash | ||
| 403 | +# 添加交互式模式 | ||
| 404 | +pnpm parse:docs -- --interactive | ||
| 405 | + | ||
| 406 | +# 添加进度条 | ||
| 407 | +pnpm parse:docs -- --progress | ||
| 408 | + | ||
| 409 | +# 添加详细日志 | ||
| 410 | +pnpm parse:docs -- --verbose | ||
| 411 | +``` | ||
| 412 | + | ||
| 413 | +--- | ||
| 414 | + | ||
| 415 | +## 附录 | ||
| 416 | + | ||
| 417 | +### 目录结构 | ||
| 418 | + | ||
| 419 | +``` | ||
| 420 | +项目根目录/ | ||
| 421 | +├── docs/ | ||
| 422 | +│ ├── to-parse/ # 待解析文档输入目录 | ||
| 423 | +│ │ └── archived/ # 已解析文档归档(按日期) | ||
| 424 | +│ ├── parse-audit/ # 审核文件目录 | ||
| 425 | +│ │ ├── pending/ # 待审核(按原始文档名分目录) | ||
| 426 | +│ │ └── approved/ # 已通过审核 | ||
| 427 | +│ └── parsed-backup/ # 配置文件备份 | ||
| 428 | +├── src/config/ | ||
| 429 | +│ └── plan-templates.js # 计划书模板配置(输出目标) | ||
| 430 | +└── scripts/doc-parser/ # 解析器脚本 | ||
| 431 | +``` | ||
| 432 | + | ||
| 433 | +### 使用命令 | ||
| 434 | + | ||
| 435 | +```bash | ||
| 436 | +# 解析所有待处理文档 | ||
| 437 | +pnpm parse:docs | ||
| 438 | + | ||
| 439 | +# 解析指定文件 | ||
| 440 | +pnpm parse:docs -- --file=产品说明书.pdf | ||
| 441 | + | ||
| 442 | +# 查看待处理文档列表 | ||
| 443 | +pnpm parse:docs -- --list | ||
| 444 | + | ||
| 445 | +# 查看配置状态 | ||
| 446 | +pnpm parse:docs -- --status | ||
| 447 | + | ||
| 448 | +# 应用审核通过的配置 | ||
| 449 | +pnpm parse:docs -- --apply=计划书模版4 | ||
| 450 | + | ||
| 451 | +# 预览应用配置(不实际修改) | ||
| 452 | +pnpm parse:docs -- --apply=计划书模版4 --dry-run | ||
| 453 | + | ||
| 454 | +# 回滚配置 | ||
| 455 | +pnpm parse:docs -- --rollback=plan-templates.backup.1234567890.js | ||
| 456 | +``` | ||
| 457 | + | ||
| 458 | +--- | ||
| 459 | + | ||
| 460 | +**文档维护**: 本文档应随系统迭代同步更新 |
| 1 | -# 文档解析工具 | 1 | +# 文档解析工具 - 待处理文档目录 |
| 2 | 2 | ||
| 3 | -## 📁 文件夹说明 | 3 | +> **详细文档**: [文档解析系统架构](../doc-parser-architecture.md) |
| 4 | 4 | ||
| 5 | -此文件夹用于存放需要解析的保险产品文档,脚本将自动读取并生成配置。 | 5 | +--- |
| 6 | 6 | ||
| 7 | -## 🚀 使用方法 | 7 | +## 📁 目录说明 |
| 8 | 8 | ||
| 9 | -### 1. 添加文档 | 9 | +此文件夹用于存放需要解析的保险产品文档,解析脚本将自动读取并生成配置。 |
| 10 | 10 | ||
| 11 | -将客户提供的 PDF/Word 文档复制到此文件夹: | ||
| 12 | ``` | 11 | ``` |
| 13 | docs/to-parse/ | 12 | docs/to-parse/ |
| 14 | -├── WIOP3E 产品说明书.pdf | 13 | +├── preprocessed/ # 豆包预处理过的 MD 文件(快速通道) |
| 15 | -├── 宏挚传承保障计划.docx | 14 | +├── raw/ # 原始 PDF/DOCX 文件(保留原格式) |
| 16 | -└── MBC PRO 保障计划.pdf | 15 | +├── 产品说明书.pdf # 根目录文档(兼容) |
| 16 | +└── archived/ # 已处理文档归档(按日期) | ||
| 17 | +``` | ||
| 18 | + | ||
| 19 | +--- | ||
| 20 | + | ||
| 21 | +## 🚀 使用方法 | ||
| 22 | + | ||
| 23 | +### 方案选择 | ||
| 24 | + | ||
| 25 | +| 方案 | 适用场景 | 速度 | 准确率 | 人工干预 | | ||
| 26 | +|------|----------|------|--------|----------| | ||
| 27 | +| **豆包预处理** | 少量文档、复杂格式 | ⚡ 快 | ✅ 高 | 需要手动转换 | | ||
| 28 | +| **直接解析** | 大量文档、标准格式 | 🐢 慢 | ⚠️ 中 | 完全自动 | | ||
| 29 | +| **混合方案** | 批量+特殊文档 | 🚀 中 | ✅ 高 | 灵活选择 | | ||
| 30 | + | ||
| 31 | +--- | ||
| 32 | + | ||
| 33 | +### 1. 添加文档 | ||
| 34 | + | ||
| 35 | +#### 方案 A:豆包预处理(推荐用于少量文档) | ||
| 36 | + | ||
| 37 | +**适用场景**:1-5 个文档,或包含扫描件、复杂格式 | ||
| 38 | + | ||
| 39 | +**步骤**: | ||
| 40 | + | ||
| 41 | +1. **上传到豆包** | ||
| 42 | + ``` | ||
| 43 | + 将 PDF/图片上传到豆包 AI | ||
| 44 | + ``` | ||
| 45 | + | ||
| 46 | +2. **使用提示词转换** | ||
| 47 | + ``` | ||
| 48 | + 请将这份保险产品文档转换为 Markdown 格式,要求: | ||
| 49 | + 1. 保留原文档的表格结构 | ||
| 50 | + 2. 保留产品名称、缴费年期、年龄范围等关键信息 | ||
| 51 | + 3. 使用 Markdown 表格展示费率信息 | ||
| 52 | + 4. 输出纯 Markdown 文本,不要添加额外解释 | ||
| 53 | + ``` | ||
| 54 | + | ||
| 55 | +3. **下载并放置** | ||
| 56 | + ```bash | ||
| 57 | + # 下载豆包生成的 MD 文件,放到 preprocessed 目录 | ||
| 58 | + docs/to-parse/preprocessed/产品说明书.md | ||
| 59 | + ``` | ||
| 60 | + | ||
| 61 | +4. **执行解析** | ||
| 62 | + ```bash | ||
| 63 | + pnpm parse:docs -- --file="产品说明书.md" | ||
| 64 | + ``` | ||
| 65 | + | ||
| 66 | +**优势**: | ||
| 67 | +- ⚡ 解析速度提升 3-5 倍 | ||
| 68 | +- ✅ 准确率更高,尤其适合复杂格式 | ||
| 69 | +- 🔄 支持扫描件 OCR | ||
| 70 | + | ||
| 71 | +--- | ||
| 72 | + | ||
| 73 | +#### 方案 B:直接解析(推荐用于批量文档) | ||
| 74 | + | ||
| 75 | +**适用场景**:10+ 个标准格式文档 | ||
| 76 | + | ||
| 77 | +**步骤**: | ||
| 78 | + | ||
| 79 | +将 PDF/Word 文档复制到 `raw/` 目录: | ||
| 80 | +```bash | ||
| 81 | +docs/to-parse/raw/产品说明书.pdf | ||
| 17 | ``` | 82 | ``` |
| 18 | 83 | ||
| 19 | -### 2. 执行解析脚本 | 84 | +然后执行解析命令。 |
| 85 | + | ||
| 86 | +--- | ||
| 87 | + | ||
| 88 | +### 2. 执行解析 | ||
| 89 | + | ||
| 90 | +### 2. 执行解析 | ||
| 20 | 91 | ||
| 21 | ```bash | 92 | ```bash |
| 22 | -# 查看待处理的文档 | 93 | +# 查看待处理的文档列表 |
| 23 | -pnpm run parse:docs:list | 94 | +pnpm parse:docs -- --list |
| 24 | 95 | ||
| 25 | -# 解析所有文档(默认仅生成待审核文件,不写入配置) | 96 | +# 查看配置状态 |
| 26 | -pnpm run parse:docs | 97 | +pnpm parse:docs -- --status |
| 27 | 98 | ||
| 28 | -# 解析指定文档(默认仅生成待审核文件,不写入配置) | 99 | +# 解析所有文档(dry-run 模式,仅生成待审核文件) |
| 29 | -pnpm run parse:docs:file -- --file="产品说明书.pdf" | 100 | +pnpm parse:docs |
| 30 | 101 | ||
| 31 | -# 解析并写入配置(需要显式开启) | 102 | +# 解析指定文档 |
| 32 | -pnpm run parse:docs:file -- --file="产品说明书.pdf" --write-config | 103 | +pnpm parse:docs -- --file="产品说明书.pdf" |
| 104 | + | ||
| 105 | +# 应用审核通过的配置 | ||
| 106 | +pnpm parse:docs -- --apply=计划书模版4 | ||
| 107 | + | ||
| 108 | +# 预览应用配置(不实际修改) | ||
| 109 | +pnpm parse:docs -- --apply=计划书模版4 --dry-run | ||
| 33 | ``` | 110 | ``` |
| 34 | 111 | ||
| 35 | ### 3. 查看结果 | 112 | ### 3. 查看结果 |
| 36 | 113 | ||
| 37 | -解析成功后会生成待审核文件,位置如下(按原始文档名分目录): | 114 | +**待审核文件**(按原始文档名分目录): |
| 38 | - | ||
| 39 | ``` | 115 | ``` |
| 40 | docs/parse-audit/pending/<原始文档名>/ | 116 | docs/parse-audit/pending/<原始文档名>/ |
| 41 | ``` | 117 | ``` |
| 42 | 118 | ||
| 43 | -解析成功后原始文档会自动归档到: | 119 | +**原始文档归档**: |
| 44 | - | ||
| 45 | ``` | 120 | ``` |
| 46 | docs/to-parse/archived/YYYY-MM-DD/ | 121 | docs/to-parse/archived/YYYY-MM-DD/ |
| 47 | ``` | 122 | ``` |
| 48 | 123 | ||
| 49 | -审核通过后再手动合并到 `src/config/plan-templates.js`,或使用 `--write-config` 明确写入。 | 124 | +--- |
| 50 | 125 | ||
| 51 | ## 🔗 功能链路 | 126 | ## 🔗 功能链路 |
| 52 | 127 | ||
| 53 | ``` | 128 | ``` |
| 54 | -文档放入 docs/to-parse/ | 129 | + ┌─────────────────────────────────┐ |
| 55 | - ↓ | 130 | + │ 文档来源选择 │ |
| 56 | -markitdown 抽取文本 | 131 | + └─────────────────────────────────┘ |
| 57 | - ↓ | 132 | + │ |
| 58 | -启发式推断基础信息(产品名称/类型/币种) | 133 | + ┌────────────────────┴────────────────────┐ |
| 59 | - ↓ | 134 | + ▼ ▼ |
| 60 | -生成配置代码与待审核文件 | 135 | + ┌───────────────┐ ┌───────────────┐ |
| 61 | - ↓ | 136 | + │ 豆包预处理 │ │ 原始文档 │ |
| 62 | -人工审核(pending → approved) | 137 | + │ (手动) │ │ (PDF/DOCX) │ |
| 63 | - ↓ | 138 | + └───────────────┘ └───────────────┘ |
| 64 | -合并到 src/config/plan-templates.js | 139 | + │ │ |
| 140 | + ▼ ▼ | ||
| 141 | + ┌───────────────┐ ┌───────────────┐ | ||
| 142 | + │ preprocessed/ │ │ raw/ │ | ||
| 143 | + │ *.md │ │ *.pdf/*.docx │ | ||
| 144 | + └───────────────┘ └───────────────┘ | ||
| 145 | + │ │ | ||
| 146 | + └────────────────────┬────────────────────┘ | ||
| 147 | + ▼ | ||
| 148 | + ┌─────────────────────────────────┐ | ||
| 149 | + │ 统一解析入口 (parse:docs) │ | ||
| 150 | + │ • 自动检测来源 │ | ||
| 151 | + │ • 预处理文档跳过 markitdown │ | ||
| 152 | + │ • 原始文档使用 markitdown │ | ||
| 153 | + └─────────────────────────────────┘ | ||
| 154 | + │ | ||
| 155 | + ▼ | ||
| 156 | + ┌─────────────────────────────────┐ | ||
| 157 | + │ 多产品检测与分割 │ | ||
| 158 | + │ (product-splitter.js) │ | ||
| 159 | + └─────────────────────────────────┘ | ||
| 160 | + │ | ||
| 161 | + ▼ | ||
| 162 | + ┌─────────────────────────────────┐ | ||
| 163 | + │ 智能字段提取 │ | ||
| 164 | + │ (smart-field-extractor.js) │ | ||
| 165 | + └─────────────────────────────────┘ | ||
| 166 | + │ | ||
| 167 | + ▼ | ||
| 168 | + ┌─────────────────────────────────┐ | ||
| 169 | + │ 生成配置代码与审核文件 │ | ||
| 170 | + └─────────────────────────────────┘ | ||
| 171 | + │ | ||
| 172 | + ▼ | ||
| 173 | + ┌─────────────────────────────────┐ | ||
| 174 | + │ 人工审核确认 │ | ||
| 175 | + │ (pending → approved) │ | ||
| 176 | + └─────────────────────────────────┘ | ||
| 177 | + │ | ||
| 178 | + ▼ | ||
| 179 | + ┌─────────────────────────────────┐ | ||
| 180 | + │ 应用到 plan-templates.js │ | ||
| 181 | + └─────────────────────────────────┘ | ||
| 65 | ``` | 182 | ``` |
| 66 | 183 | ||
| 67 | -## 🧭 使用思路 | 184 | +--- |
| 185 | + | ||
| 186 | +## 🧋 核心能力 | ||
| 68 | 187 | ||
| 69 | -1. **先审核再合并**:默认只生成待审核文件,避免直接污染配置。 | 188 | +| 能力 | 说明 | 状态 | |
| 70 | -2. **先读再写**:审核时重点核对产品名称、币种、缴费年期、年龄范围。 | 189 | +|------|------|------| |
| 71 | -3. **分离责任**:解析用于提取线索,最终配置仍由人工确认。 | 190 | +| 多格式解析 | 支持 PDF、DOCX、TXT、MD | ✅ | |
| 72 | -4. **可追溯**:审计日志记录每次解析结果与变更摘要。 | 191 | +| 多产品识别 | 自动识别并分割多产品文档 | ✅ | |
| 192 | +| 智能字段提取 | 8 种匹配模式提取配置字段 | ✅ | | ||
| 193 | +| 人工审核流程 | 生成人类可读的审核文件 | ✅ | | ||
| 194 | +| 配置自动应用 | 支持一键应用审核通过配置 | ✅ | | ||
| 195 | +| AI 增强解析 | 支持接入 AI 服务 | 🚧 待启用 | | ||
| 196 | + | ||
| 197 | +--- | ||
| 198 | + | ||
| 199 | +## 📋 智能提取的字段 | ||
| 200 | + | ||
| 201 | +| 字段 | 提取方式 | 默认值 | | ||
| 202 | +|------|----------|--------| | ||
| 203 | +| product_name | 标题正则匹配 | 文件名 | | ||
| 204 | +| product_type | 关键词内容推断 | savings | | ||
| 205 | +| currency | 货币符号统计 | USD | | ||
| 206 | +| payment_periods | 智能列表提取 | ['整付', '3年', '5年'] | | ||
| 207 | +| age_range | 范围值提取 | {min: 0, max: 75} | | ||
| 208 | +| insurance_period | 直接匹配 | '终身' | | ||
| 209 | +| withdrawal_modes | 列表提取(储蓄类) | ['年龄指定金额', '最高固定金额'] | | ||
| 210 | +| withdrawal_periods | 列表提取(储蓄类) | ['1年', '3年', '5年', '10年'] | | ||
| 211 | + | ||
| 212 | +--- | ||
| 73 | 213 | ||
| 74 | ## 📋 支持的文档格式 | 214 | ## 📋 支持的文档格式 |
| 75 | 215 | ||
| 76 | -- ✅ PDF (.pdf) | 216 | +| 格式 | 扩展名 | 转换方式 | |
| 77 | -- ✅ Word (.doc, .docx) | 217 | +|------|--------|----------| |
| 78 | -- ✅ 纯本文档 (.txt, .md) | 218 | +| PDF | `.pdf` | markitdown CLI 或 pdf-parse | |
| 219 | +| Word | `.docx` | mammoth 库 | | ||
| 220 | +| Word(旧版) | `.doc` | ❌ 不支持,需转换为 .docx | | ||
| 221 | +| 文本 | `.txt`, `.md` | 直接读取 | | ||
| 222 | + | ||
| 223 | +--- | ||
| 79 | 224 | ||
| 80 | -## 🧪 Fixtures 文档样本说明 | 225 | +## 🧪 测试样本 |
| 81 | 226 | ||
| 82 | -用于测试的样本文档建议放在此目录,命名规则建议包含产品名与类型,便于回归验证: | 227 | +用于回归测试的样本文档建议放在此目录,命名规则建议包含产品名与类型: |
| 83 | 228 | ||
| 84 | ``` | 229 | ``` |
| 85 | docs/to-parse/ | 230 | docs/to-parse/ |
| 86 | ├── fixtures-life-insurance-sample.pdf | 231 | ├── fixtures-life-insurance-sample.pdf |
| 87 | ├── fixtures-critical-illness-sample.docx | 232 | ├── fixtures-critical-illness-sample.docx |
| 88 | -└── fixtures-savings-sample.txt | 233 | +└── fixtures-savings-multiproduct.pdf # 多产品文档测试 |
| 89 | ``` | 234 | ``` |
| 90 | 235 | ||
| 91 | -执行测试前请确认样本文档内容完整且可被抽取为文本。 | 236 | +--- |
| 92 | 237 | ||
| 93 | -## 📊 解析摘要与审计日志 | 238 | +## 📊 审计日志 |
| 94 | 239 | ||
| 95 | -每次解析都会输出成功/失败/耗时摘要,并在以下位置记录审计日志: | 240 | +每次解析都会记录审计日志,便于回溯与排查: |
| 96 | 241 | ||
| 97 | ``` | 242 | ``` |
| 98 | -docs/parsed-backup/parse-audit.jsonl | 243 | +docs/parsed-backup/parse-audit.jsonl # 解析审计日志 |
| 244 | +docs/parsed-backup/backup-log.jsonl # 配置变更日志 | ||
| 99 | ``` | 245 | ``` |
| 100 | 246 | ||
| 101 | -日志包含解析汇总与本次变更摘要,便于回溯与排查。 | 247 | +--- |
| 248 | + | ||
| 249 | +## 🔧 配置 AI 服务(可选) | ||
| 102 | 250 | ||
| 103 | -## 🔧 配置 AI 服务 | 251 | +当前使用基于规则的提取方式,如需启用 AI 增强解析: |
| 252 | + | ||
| 253 | +```bash | ||
| 254 | +# 1. 安装依赖 | ||
| 255 | +pnpm add openai anthropic | ||
| 256 | + | ||
| 257 | +# 2. 配置 .env | ||
| 258 | +AI_SERVICE_TYPE=openai | ||
| 259 | +OPENAI_API_KEY=sk-xxx | ||
| 260 | +OPENAI_MODEL=gpt-4-turbo | ||
| 261 | + | ||
| 262 | +# 3. 检查状态 | ||
| 263 | +pnpm parse:docs -- --status | ||
| 264 | +``` | ||
| 104 | 265 | ||
| 105 | -脚本当前使用 markitdown CLI 进行文档抽取,AI 服务仍待接入。 | 266 | +--- |
| 106 | 267 | ||
| 107 | ## ⚠️ 注意事项 | 268 | ## ⚠️ 注意事项 |
| 108 | 269 | ||
| 109 | 1. **文档命名**:建议使用有意义的文件名,方便识别产品 | 270 | 1. **文档命名**:建议使用有意义的文件名,方便识别产品 |
| 110 | -2. **手动审核**:生成后请检查配置是否正确 | 271 | +2. **预处理目录**: |
| 111 | -3. **版本控制**:生成的配置会自动备份 | 272 | + - `preprocessed/` - 放置豆包转换的 MD 文件 |
| 112 | -4. **二次解析**:需要重新解析时,将归档文件移回 `docs/to-parse/` | 273 | + - `raw/` - 放置原始 PDF/DOCX 文件 |
| 274 | + - 根目录 - 兼容旧版本,可直接放置文档 | ||
| 275 | +3. **手动审核**:生成后请重点核对产品名称、币种、缴费年期、年龄范围 | ||
| 276 | +4. **版本控制**:生成的配置会自动备份到 `docs/parsed-backup/` | ||
| 277 | +5. **二次解析**:需要重新解析时,从 `archived/` 目录移回文档即可 | ||
| 278 | +6. **多产品文档**:一个文档包含多个产品时,会为每个产品生成独立的审核文件 | ||
| 279 | +7. **MD 文件优化**:预处理的 MD 文件会跳过 markitdown,解析速度更快 | ||
| 280 | + | ||
| 281 | +--- | ||
| 282 | + | ||
| 283 | +## 📚 相关文档 | ||
| 284 | + | ||
| 285 | +- **[文档解析系统架构](../doc-parser-architecture.md)** - 完整架构和优化建议 | ||
| 286 | +- **[脚本使用指南](../../scripts/doc-parser/README.md)** - 脚本详细说明 | ... | ... |
scripts/doc-parser/QUICKSTART.md
deleted
100644 → 0
| 1 | -# OpenAPI 转 API 文档生成器 - 快速开始 | ||
| 2 | - | ||
| 3 | -## 🎯 一分钟快速上手 | ||
| 4 | - | ||
| 5 | -### 1️⃣ 创建 OpenAPI 文档 | ||
| 6 | - | ||
| 7 | -在 `docs/api-specs/` 目录下创建模块和接口文档: | ||
| 8 | - | ||
| 9 | -```bash | ||
| 10 | -# 创建新模块 | ||
| 11 | -mkdir -p docs/api-specs/product | ||
| 12 | - | ||
| 13 | -# 创建接口文档 | ||
| 14 | -touch docs/api-specs/product/getList.md | ||
| 15 | -``` | ||
| 16 | - | ||
| 17 | -### 2️⃣ 编写 OpenAPI 规范 | ||
| 18 | - | ||
| 19 | -编辑 `getList.md`: | ||
| 20 | - | ||
| 21 | -```markdown | ||
| 22 | -# 获取商品列表 | ||
| 23 | - | ||
| 24 | -## OpenAPI Specification | ||
| 25 | - | ||
| 26 | -\```yaml | ||
| 27 | -openapi: 3.0.1 | ||
| 28 | -info: | ||
| 29 | - title: '' | ||
| 30 | - version: 1.0.0 | ||
| 31 | -paths: | ||
| 32 | - /srv/: | ||
| 33 | - get: | ||
| 34 | - summary: 获取商品列表 | ||
| 35 | - tags: | ||
| 36 | - - 商品 | ||
| 37 | - parameters: | ||
| 38 | - - name: a | ||
| 39 | - in: query | ||
| 40 | - example: product_list | ||
| 41 | - - name: f | ||
| 42 | - in: query | ||
| 43 | - example: behalo | ||
| 44 | - responses: | ||
| 45 | - '200': | ||
| 46 | - description: 成功 | ||
| 47 | -\``` | ||
| 48 | -``` | ||
| 49 | - | ||
| 50 | -### 3️⃣ 生成 API 文件 | ||
| 51 | - | ||
| 52 | -```bash | ||
| 53 | -pnpm api:generate | ||
| 54 | -``` | ||
| 55 | - | ||
| 56 | -### 4️⃣ 使用生成的 API | ||
| 57 | - | ||
| 58 | -```javascript | ||
| 59 | -import { getListAPI } from '@/api/product'; | ||
| 60 | - | ||
| 61 | -const result = await getListAPI({ page: 1, pageSize: 10 }); | ||
| 62 | -``` | ||
| 63 | - | ||
| 64 | -## ✅ 验证结果 | ||
| 65 | - | ||
| 66 | -运行测试脚本验证生成的文件: | ||
| 67 | - | ||
| 68 | -```bash | ||
| 69 | -node scripts/test-generate.js | ||
| 70 | -``` | ||
| 71 | - | ||
| 72 | -## 📂 文件结构 | ||
| 73 | - | ||
| 74 | -``` | ||
| 75 | -manulife-weapp/ | ||
| 76 | -├── docs/ | ||
| 77 | -│ ├── api-specs/ # API 规范文档源目录 | ||
| 78 | -│ │ └── user/ # 模块目录 | ||
| 79 | -│ │ └── getUserInfo.md | ||
| 80 | -│ ├── OPENAPI_TO_API_GUIDE.md # 详细使用指南 | ||
| 81 | -│ └── API_USAGE_EXAMPLES.md # API 使用示例 | ||
| 82 | -├── scripts/ | ||
| 83 | -│ ├── generateApiFromOpenAPI.js # 生成器核心脚本 | ||
| 84 | -│ └── test-generate.js # 测试脚本 | ||
| 85 | -├── src/ | ||
| 86 | -│ └── api/ # 生成的 API 文件目录 | ||
| 87 | -│ ├── user.js # 自动生成 | ||
| 88 | -│ ├── wx/ | ||
| 89 | -│ └── index.js | ||
| 90 | -└── package.json # 包含 api:generate 命令 | ||
| 91 | -``` | ||
| 92 | - | ||
| 93 | -## 🔄 工作流程 | ||
| 94 | - | ||
| 95 | -```mermaid | ||
| 96 | -graph LR | ||
| 97 | - A[编写 OpenAPI 文档] --> B[运行 pnpm api:generate] | ||
| 98 | - B --> C[生成 API 文件] | ||
| 99 | - C --> D[在项目中使用] | ||
| 100 | - D --> E[需要修改接口] | ||
| 101 | - E --> A | ||
| 102 | -``` | ||
| 103 | - | ||
| 104 | -## 🎨 常见场景 | ||
| 105 | - | ||
| 106 | -### 场景 1: 批量生成多个接口 | ||
| 107 | - | ||
| 108 | -```bash | ||
| 109 | -docs/api-specs/ | ||
| 110 | -├── user/ | ||
| 111 | -│ ├── getUserInfo.md | ||
| 112 | -│ ├── updateProfile.md | ||
| 113 | -│ └── changePassword.md | ||
| 114 | -└── order/ | ||
| 115 | - ├── getList.md | ||
| 116 | - └── getDetail.md | ||
| 117 | -``` | ||
| 118 | - | ||
| 119 | -运行 `pnpm api:generate` 后生成: | ||
| 120 | - | ||
| 121 | -``` | ||
| 122 | -src/api/ | ||
| 123 | -├── user.js # 包含 3 个接口 | ||
| 124 | -└── order.js # 包含 2 个接口 | ||
| 125 | -``` | ||
| 126 | - | ||
| 127 | -### 场景 2: 更新已有接口 | ||
| 128 | - | ||
| 129 | -1. 修改 `docs/api-specs/user/getUserInfo.md` | ||
| 130 | -2. 运行 `pnpm api:generate` | ||
| 131 | -3. `src/api/user.js` 自动更新 | ||
| 132 | - | ||
| 133 | -### 场景 3: 添加新模块 | ||
| 134 | - | ||
| 135 | -1. 创建 `docs/api-specs/payment/` | ||
| 136 | -2. 添加接口文档 | ||
| 137 | -3. 运行生成命令 | ||
| 138 | -4. 自动生成 `src/api/payment.js` | ||
| 139 | - | ||
| 140 | -## ⚙️ 配置和自定义 | ||
| 141 | - | ||
| 142 | -### 修改输出目录 | ||
| 143 | - | ||
| 144 | -编辑 `scripts/generateApiFromOpenAPI.js`: | ||
| 145 | - | ||
| 146 | -```javascript | ||
| 147 | -const outputDir = path.resolve(__dirname, '../src/api'); | ||
| 148 | -// 改为你想要的目录 | ||
| 149 | -const outputDir = path.resolve(__dirname, '../src/apis'); | ||
| 150 | -``` | ||
| 151 | - | ||
| 152 | -### 修改命名规则 | ||
| 153 | - | ||
| 154 | -编辑 `toCamelCase()` 或 `toPascalCase()` 函数。 | ||
| 155 | - | ||
| 156 | -### 修改生成模板 | ||
| 157 | - | ||
| 158 | -编辑 `generateApiFileContent()` 函数。 | ||
| 159 | - | ||
| 160 | -## 🐛 调试技巧 | ||
| 161 | - | ||
| 162 | -### 启用详细日志 | ||
| 163 | - | ||
| 164 | -在脚本中添加更多 console.log: | ||
| 165 | - | ||
| 166 | -```javascript | ||
| 167 | -console.log('解析的 API 信息:', JSON.stringify(apiInfo, null, 2)); | ||
| 168 | -``` | ||
| 169 | - | ||
| 170 | -### 单独测试某个模块 | ||
| 171 | - | ||
| 172 | -修改脚本中的模块过滤逻辑。 | ||
| 173 | - | ||
| 174 | -### 查看生成的中间数据 | ||
| 175 | - | ||
| 176 | -添加调试输出查看 YAML 解析结果。 | ||
| 177 | - | ||
| 178 | -## 📞 获取帮助 | ||
| 179 | - | ||
| 180 | -- 详细指南:[OpenAPI 转 API 文档生成器指南](./OPENAPI_TO_API_GUIDE.md) | ||
| 181 | -- 使用示例:[API 使用示例](./API_USAGE_EXAMPLES.md) | ||
| 182 | -- 项目架构:[CLAUDE.md](../CLAUDE.md) | ||
| 183 | - | ||
| 184 | -## 🎉 开始使用 | ||
| 185 | - | ||
| 186 | -现在你已经准备好了!开始创建你的第一个 OpenAPI 文档吧。 | ||
| 187 | - | ||
| 188 | -```bash | ||
| 189 | -# 1. 创建模块目录 | ||
| 190 | -mkdir -p docs/api-specs/your-module | ||
| 191 | - | ||
| 192 | -# 2. 创建接口文档(参考 docs/api-specs/user/getUserInfo.md) | ||
| 193 | - | ||
| 194 | -# 3. 生成 API | ||
| 195 | -pnpm api:generate | ||
| 196 | - | ||
| 197 | -# 4. 查看生成的文件 | ||
| 198 | -cat src/api/your-module.js | ||
| 199 | - | ||
| 200 | -# 5. 开始使用 | ||
| 201 | -``` | ||
| 202 | - | ||
| 203 | -祝你编码愉快!🚀 |
| ... | @@ -42,6 +42,8 @@ import { splitByProducts, findProductTitles, generateSplitReport } from './produ | ... | @@ -42,6 +42,8 @@ import { splitByProducts, findProductTitles, generateSplitReport } from './produ |
| 42 | // ========== 配置区 ========== | 42 | // ========== 配置区 ========== |
| 43 | 43 | ||
| 44 | const DOCS_DIR = path.resolve(process.cwd(), 'docs/to-parse') | 44 | const DOCS_DIR = path.resolve(process.cwd(), 'docs/to-parse') |
| 45 | +const DOCS_PREPROCESSED_DIR = path.resolve(process.cwd(), 'docs/to-parse/preprocessed') | ||
| 46 | +const DOCS_RAW_DIR = path.resolve(process.cwd(), 'docs/to-parse/raw') | ||
| 45 | const DOCS_ARCHIVE_DIR = path.resolve(process.cwd(), 'docs/to-parse/archived') | 47 | const DOCS_ARCHIVE_DIR = path.resolve(process.cwd(), 'docs/to-parse/archived') |
| 46 | const CONFIG_FILE = path.resolve(process.cwd(), 'src/config/plan-templates.js') | 48 | const CONFIG_FILE = path.resolve(process.cwd(), 'src/config/plan-templates.js') |
| 47 | const BACKUP_DIR = path.resolve(process.cwd(), 'docs/parsed-backup') | 49 | const BACKUP_DIR = path.resolve(process.cwd(), 'docs/parsed-backup') |
| ... | @@ -49,6 +51,29 @@ const BACKUP_DIR = path.resolve(process.cwd(), 'docs/parsed-backup') | ... | @@ -49,6 +51,29 @@ const BACKUP_DIR = path.resolve(process.cwd(), 'docs/parsed-backup') |
| 49 | // 支持的文档格式 | 51 | // 支持的文档格式 |
| 50 | const SUPPORTED_EXTENSIONS = ['.pdf', '.doc', '.docx', '.txt', '.md'] | 52 | const SUPPORTED_EXTENSIONS = ['.pdf', '.doc', '.docx', '.txt', '.md'] |
| 51 | 53 | ||
| 54 | +/** | ||
| 55 | + * 检测文档来源 | ||
| 56 | + * | ||
| 57 | + * @description 判断文档是预处理过的 MD 文件还是原始文档 | ||
| 58 | + * @param {string} filePath - 文档路径 | ||
| 59 | + * @returns {{source: string, type: string}} 来源信息 | ||
| 60 | + */ | ||
| 61 | +function detectDocumentSource(filePath) { | ||
| 62 | + if (filePath.includes('preprocessed')) { | ||
| 63 | + return { source: 'preprocessed', type: 'markdown' } | ||
| 64 | + } | ||
| 65 | + if (filePath.includes('raw')) { | ||
| 66 | + return { source: 'raw', type: 'original' } | ||
| 67 | + } | ||
| 68 | + // 根据文件扩展名推断 | ||
| 69 | + const ext = path.extname(filePath).toLowerCase() | ||
| 70 | + if (ext === '.md') { | ||
| 71 | + // MD 文件可能是预处理过的 | ||
| 72 | + return { source: 'likely-preprocessed', type: 'markdown' } | ||
| 73 | + } | ||
| 74 | + return { source: 'unknown', type: 'original' } | ||
| 75 | +} | ||
| 76 | + | ||
| 52 | const ajv = new Ajv({ allErrors: true, strict: false }) | 77 | const ajv = new Ajv({ allErrors: true, strict: false }) |
| 53 | const parseConfigSchema = { | 78 | const parseConfigSchema = { |
| 54 | type: 'object', | 79 | type: 'object', |
| ... | @@ -214,23 +239,45 @@ function writeFile(filePath, content) { | ... | @@ -214,23 +239,45 @@ function writeFile(filePath, content) { |
| 214 | 239 | ||
| 215 | /** | 240 | /** |
| 216 | * 获取所有待处理的文档 | 241 | * 获取所有待处理的文档 |
| 242 | + * | ||
| 243 | + * @description 扫描多个目录获取待处理文档,按优先级排序 | ||
| 244 | + * @returns {Array<{name: string, fullPath: string, ext: string, size: number, source: string}>} 文档列表 | ||
| 217 | */ | 245 | */ |
| 218 | function getDocsToParse() { | 246 | function getDocsToParse() { |
| 219 | - if (!fs.existsSync(DOCS_DIR)) { | 247 | + const docs = [] |
| 220 | - console.log('📂 文档夹不存在:', DOCS_DIR) | 248 | + const directories = [ |
| 221 | - return [] | 249 | + { path: DOCS_DIR, source: 'root' }, |
| 250 | + { path: DOCS_PREPROCESSED_DIR, source: 'preprocessed' }, | ||
| 251 | + { path: DOCS_RAW_DIR, source: 'raw' } | ||
| 252 | + ] | ||
| 253 | + | ||
| 254 | + for (const dir of directories) { | ||
| 255 | + if (!fs.existsSync(dir.path)) { | ||
| 256 | + continue | ||
| 222 | } | 257 | } |
| 223 | 258 | ||
| 224 | - const files = fs.readdirSync(DOCS_DIR) | 259 | + const files = fs.readdirSync(dir.path) |
| 225 | - return files | 260 | + const dirDocs = files |
| 226 | .filter(file => SUPPORTED_EXTENSIONS.includes(path.extname(file).toLowerCase())) | 261 | .filter(file => SUPPORTED_EXTENSIONS.includes(path.extname(file).toLowerCase())) |
| 227 | .filter(file => file !== 'README.md') | 262 | .filter(file => file !== 'README.md') |
| 228 | .map(file => ({ | 263 | .map(file => ({ |
| 229 | name: file, | 264 | name: file, |
| 230 | - fullPath: path.join(DOCS_DIR, file), | 265 | + fullPath: path.join(dir.path, file), |
| 231 | ext: path.extname(file).toLowerCase(), | 266 | ext: path.extname(file).toLowerCase(), |
| 232 | - size: fs.statSync(path.join(DOCS_DIR, file)).size | 267 | + size: fs.statSync(path.join(dir.path, file)).size, |
| 268 | + source: dir.source | ||
| 233 | })) | 269 | })) |
| 270 | + | ||
| 271 | + docs.push(...dirDocs) | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + // 优先处理预处理的 MD 文件,然后是原始文档 | ||
| 275 | + docs.sort((a, b) => { | ||
| 276 | + const priorityOrder = { preprocessed: 1, root: 2, raw: 3 } | ||
| 277 | + return priorityOrder[a.source] - priorityOrder[b.source] | ||
| 278 | + }) | ||
| 279 | + | ||
| 280 | + return docs | ||
| 234 | } | 281 | } |
| 235 | 282 | ||
| 236 | /** | 283 | /** |
| ... | @@ -367,10 +414,15 @@ function formatSize(size) { | ... | @@ -367,10 +414,15 @@ function formatSize(size) { |
| 367 | */ | 414 | */ |
| 368 | async function parseDocumentWithMarkitdown(docPath) { | 415 | async function parseDocumentWithMarkitdown(docPath) { |
| 369 | const ext = path.extname(docPath).toLowerCase() | 416 | const ext = path.extname(docPath).toLowerCase() |
| 417 | + const sourceInfo = detectDocumentSource(docPath) | ||
| 370 | 418 | ||
| 371 | // MD 和 TXT 文件直接读取,不需要 markitdown | 419 | // MD 和 TXT 文件直接读取,不需要 markitdown |
| 372 | if (ext === '.md' || ext === '.txt') { | 420 | if (ext === '.md' || ext === '.txt') { |
| 421 | + if (sourceInfo.source === 'preprocessed' || sourceInfo.source === 'likely-preprocessed') { | ||
| 422 | + console.log(`⚡ 预处理 MD 文件,跳过 markitdown: ${path.basename(docPath)}`) | ||
| 423 | + } else { | ||
| 373 | console.log(`📄 直接读取文本文件: ${path.basename(docPath)}`) | 424 | console.log(`📄 直接读取文本文件: ${path.basename(docPath)}`) |
| 425 | + } | ||
| 374 | return buildExtractResult(docPath, fs.readFileSync(docPath, 'utf-8'), []) | 426 | return buildExtractResult(docPath, fs.readFileSync(docPath, 'utf-8'), []) |
| 375 | } | 427 | } |
| 376 | 428 | ||
| ... | @@ -707,8 +759,17 @@ function inferCurrency(content) { | ... | @@ -707,8 +759,17 @@ function inferCurrency(content) { |
| 707 | */ | 759 | */ |
| 708 | async function parseSingleFile(filePath) { | 760 | async function parseSingleFile(filePath) { |
| 709 | const fileName = path.basename(filePath) | 761 | const fileName = path.basename(filePath) |
| 762 | + const sourceInfo = detectDocumentSource(filePath) | ||
| 763 | + const sourceLabel = { | ||
| 764 | + preprocessed: '⚡ 预处理文档', | ||
| 765 | + raw: '📄 原始文档', | ||
| 766 | + root: '📂 根目录文档', | ||
| 767 | + 'likely-preprocessed': '⚡ MD 文档', | ||
| 768 | + unknown: '📄 文档' | ||
| 769 | + }[sourceInfo.source] || '📄 文档' | ||
| 770 | + | ||
| 710 | console.log("\n" + "=".repeat(60)) | 771 | console.log("\n" + "=".repeat(60)) |
| 711 | - console.log("📄 处理文件: " + fileName) | 772 | + console.log(`📄 ${sourceLabel}: ${fileName}`) |
| 712 | console.log("=".repeat(60)) | 773 | console.log("=".repeat(60)) |
| 713 | 774 | ||
| 714 | // 解析文档(可能返回单个 config 或 configs 数组) | 775 | // 解析文档(可能返回单个 config 或 configs 数组) |
| ... | @@ -1799,15 +1860,33 @@ async function main() { | ... | @@ -1799,15 +1860,33 @@ async function main() { |
| 1799 | applyAuditFile(auditFileName, applyOptions) | 1860 | applyAuditFile(auditFileName, applyOptions) |
| 1800 | } else if (listMode) { | 1861 | } else if (listMode) { |
| 1801 | // 列出模式 | 1862 | // 列出模式 |
| 1802 | - const docs = getDocsToParse() | ||
| 1803 | console.log("\n📋 待处理文档列表:") | 1863 | console.log("\n📋 待处理文档列表:") |
| 1804 | if (docs.length === 0) { | 1864 | if (docs.length === 0) { |
| 1805 | console.log(' (无文档)') | 1865 | console.log(' (无文档)') |
| 1806 | } else { | 1866 | } else { |
| 1807 | - docs.forEach((doc, index) => { | 1867 | + // 按来源分组显示 |
| 1808 | - console.log(" " + (index + 1) + ". " + doc.name + " (" + formatSize(doc.size) + ")") | 1868 | + const grouped = { |
| 1869 | + preprocessed: docs.filter(d => d.source === 'preprocessed'), | ||
| 1870 | + root: docs.filter(d => d.source === 'root'), | ||
| 1871 | + raw: docs.filter(d => d.source === 'raw') | ||
| 1872 | + } | ||
| 1873 | + | ||
| 1874 | + for (const [source, sourceDocs] of Object.entries(grouped)) { | ||
| 1875 | + if (sourceDocs.length === 0) continue | ||
| 1876 | + | ||
| 1877 | + const sourceLabel = { | ||
| 1878 | + preprocessed: '⚡ 预处理 (preprocessed/)', | ||
| 1879 | + root: '📂 根目录 (docs/to-parse/)', | ||
| 1880 | + raw: '📄 原始文档 (raw/)' | ||
| 1881 | + }[source] | ||
| 1882 | + | ||
| 1883 | + console.log(`\n${sourceLabel}`) | ||
| 1884 | + sourceDocs.forEach((doc, index) => { | ||
| 1885 | + const sourceTag = doc.ext === '.md' ? ' [MD]' : '' | ||
| 1886 | + console.log(` ${index + 1}. ${doc.name}${sourceTag} (${formatSize(doc.size)})`) | ||
| 1809 | }) | 1887 | }) |
| 1810 | } | 1888 | } |
| 1889 | + } | ||
| 1811 | } else if (fileMode) { | 1890 | } else if (fileMode) { |
| 1812 | // 单文件模式 | 1891 | // 单文件模式 |
| 1813 | const fileName = fileMode.split('=')[1] | 1892 | const fileName = fileMode.split('=')[1] | ... | ... |
-
Please register or login to post a comment