docs(parse): 完善文档解析改造文档与测试验证
### 新增 - 文档解析改造任务清单说明 - 文本抽取管线、结构化校验、写入稳态化等模块说明 - 解析摘要输出与审计日志功能说明 - 计划书模块定位与优化建议 ### 修复 - 修复 ESLint 警告 ### 测试 - 补充解析流程集成测试与边界测试 - 新增 fixtures 文档样本说明 --- **详细信息**: - **影响文件**: README.md, docs/CHANGELOG.md, docs/PLAN/plan-form-schema-usage.md, docs/to-parse/README.md, scripts/parse-docs.js, scripts/parse-docs.test.js - **技术栈**: Node.js, Vitest, 文档维护 - **测试状态**: 已通过 (pnpm test),ESLint 存在现有警告 - **备注**: 每次解析都有可追溯审计记录 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Showing
9 changed files
with
480 additions
and
15 deletions
| ... | @@ -70,6 +70,14 @@ pnpm lint | ... | @@ -70,6 +70,14 @@ pnpm lint |
| 70 | - ✅ **新人指南更新** - 入口文档从工具生成器调整为业务上手流程 | 70 | - ✅ **新人指南更新** - 入口文档从工具生成器调整为业务上手流程 |
| 71 | - ✅ **文档导航同步** - docs/README 快速导航修正与补充 | 71 | - ✅ **文档导航同步** - docs/README 快速导航修正与补充 |
| 72 | 72 | ||
| 73 | +### 文档解析改造 | ||
| 74 | +- ✅ **任务清单** - 输出文档解析改造任务清单,便于跟踪与回顾 | ||
| 75 | +- ✅ **文本抽取管线** - 接入 PDF/Docx 文本抽取与统一结构输出 | ||
| 76 | +- ✅ **结构化校验** - 接入 JSON Schema 校验并阻断非法配置写入 | ||
| 77 | +- ✅ **写入稳态化** - 结构化插入、重复检测与 dry-run 预览已接入 | ||
| 78 | +- ✅ **输出结构补齐** - 解析输出 JSON 结构与稳定 form_sn 规则已明确 | ||
| 79 | +- ✅ **审计与摘要** - 解析摘要与审计日志输出已接入 | ||
| 80 | + | ||
| 73 | ### 计划书模块定位 | 81 | ### 计划书模块定位 |
| 74 | - ✅ **配置与入口整理** - 补充计划书模块入口、配置与 API 位置说明 | 82 | - ✅ **配置与入口整理** - 补充计划书模块入口、配置与 API 位置说明 |
| 75 | - ✅ **优化建议** - 新增产品时优先补齐 form_sn 与 plan_config,避免模板缺失 | 83 | - ✅ **优化建议** - 新增产品时优先补齐 form_sn 与 plan_config,避免模板缺失 |
| ... | @@ -365,6 +373,8 @@ export default { | ... | @@ -365,6 +373,8 @@ export default { |
| 365 | 373 | ||
| 366 | - **[经验教训总结](docs/lessons-learned.md)** - Taro 项目开发经验、最佳实践和常见陷阱 | 374 | - **[经验教训总结](docs/lessons-learned.md)** - Taro 项目开发经验、最佳实践和常见陷阱 |
| 367 | - **[CLAUDE.md](CLAUDE.md)** - 项目开发指南(供 Claude Code 使用) | 375 | - **[CLAUDE.md](CLAUDE.md)** - 项目开发指南(供 Claude Code 使用) |
| 376 | +- **[文档解析待处理说明](docs/to-parse/README.md)** - 文档解析样本与脚本使用方式 | ||
| 377 | +- **[文档解析改造任务](docs/tasks/文档解析改造-tasks.md)** - 解析链路改造进度与验收 | ||
| 368 | - [Taro 官方文档](https://docs.taro.zone/) | 378 | - [Taro 官方文档](https://docs.taro.zone/) |
| 369 | - [NutUI 文档](https://nutui.jd.com/taro/) | 379 | - [NutUI 文档](https://nutui.jd.com/taro/) |
| 370 | - [Vue 3 文档](https://cn.vuejs.org/) | 380 | - [Vue 3 文档](https://cn.vuejs.org/) | ... | ... |
| 1 | +## [2026-02-14] - 运营与审计完善 | ||
| 2 | + | ||
| 3 | +### 新增 | ||
| 4 | +- 解析摘要输出(成功/失败/耗时)并生成审计日志与变更摘要 | ||
| 5 | +- 使用说明补充解析摘要与审计日志位置 | ||
| 6 | + | ||
| 7 | +--- | ||
| 8 | + | ||
| 9 | +**详细信息**: | ||
| 10 | +- **影响文件**: scripts/parse-docs.js, scripts/parse-docs.test.js, docs/to-parse/README.md, docs/tasks/文档解析改造-tasks.md, README.md | ||
| 11 | +- **技术栈**: Node.js, Vitest, 文档维护 | ||
| 12 | +- **测试状态**: pnpm test 通过;pnpm lint 30 warnings | ||
| 13 | +- **备注**: 每次解析都有可追溯审计记录 | ||
| 14 | + | ||
| 15 | +--- | ||
| 16 | + | ||
| 17 | +## [2026-02-14] - 测试与验证完善 | ||
| 18 | + | ||
| 19 | +### 新增 | ||
| 20 | +- 补充解析流程集成测试与 updateConfigContent 边界测试 | ||
| 21 | +- 新增 fixtures 文档样本说明并补齐相关文档入口 | ||
| 22 | + | ||
| 23 | +--- | ||
| 24 | + | ||
| 25 | +**详细信息**: | ||
| 26 | +- **影响文件**: scripts/parse-docs.js, scripts/parse-docs.test.js, docs/to-parse/README.md, docs/tasks/文档解析改造-tasks.md, README.md | ||
| 27 | +- **技术栈**: Node.js, Vitest, 文档维护 | ||
| 28 | +- **测试状态**: 已通过(pnpm test),ESLint 存在现有警告 | ||
| 29 | +- **备注**: 解析流程测试可重复运行,覆盖冲突与插入边界路径 | ||
| 30 | + | ||
| 31 | +--- | ||
| 32 | + | ||
| 33 | +## [2026-02-14] - 生成与写入稳态化 | ||
| 34 | + | ||
| 35 | +### 新增 | ||
| 36 | +- 结构化定位 PLAN_TEMPLATES 插入位置并支持 dry-run 变更预览 | ||
| 37 | +- 增加重复 form_sn 冲突检测与阻断写入 | ||
| 38 | +- 完善备份记录并支持回滚入口 | ||
| 39 | + | ||
| 40 | +--- | ||
| 41 | + | ||
| 42 | +**详细信息**: | ||
| 43 | +- **影响文件**: scripts/parse-docs.js, scripts/parse-docs.test.js, docs/tasks/文档解析改造-tasks.md, README.md | ||
| 44 | +- **技术栈**: Node.js, Vitest | ||
| 45 | +- **测试状态**: 已通过(pnpm test),ESLint 存在现有警告 | ||
| 46 | +- **备注**: 解析写入路径更稳定,新增冲突保护与预览模式 | ||
| 47 | + | ||
| 48 | +--- | ||
| 49 | + | ||
| 50 | +## [2026-02-14] - 结构化解析校验接入 | ||
| 51 | + | ||
| 52 | +### 新增 | ||
| 53 | +- 接入 JSON Schema 校验并输出缺失字段报告 | ||
| 54 | +- 校验失败阻断解析结果写入配置 | ||
| 55 | +- 单测覆盖校验通过与失败路径 | ||
| 56 | + | ||
| 57 | +--- | ||
| 58 | + | ||
| 59 | +**详细信息**: | ||
| 60 | +- **影响文件**: scripts/parse-docs.js, scripts/parse-docs.test.js, package.json, docs/tasks/文档解析改造-tasks.md, README.md | ||
| 61 | +- **技术栈**: Node.js, Ajv, Vitest | ||
| 62 | +- **测试状态**: 已通过(pnpm test),ESLint 存在现有警告 | ||
| 63 | +- **备注**: 校验规则覆盖核心字段并保留扩展字段 | ||
| 64 | + | ||
| 65 | +--- | ||
| 66 | + | ||
| 67 | +## [2026-02-14] - 文本抽取管线接入 | ||
| 68 | + | ||
| 69 | +### 新增 | ||
| 70 | +- 接入 PDF 文本抽取与页数元信息 | ||
| 71 | +- 接入 Docx 文本抽取并输出警告信息 | ||
| 72 | +- 统一抽取结果结构并增加抽取失败回退 | ||
| 73 | + | ||
| 74 | +--- | ||
| 75 | + | ||
| 76 | +**详细信息**: | ||
| 77 | +- **影响文件**: scripts/parse-docs.js, scripts/parse-docs.test.js, package.json, docs/tasks/文档解析改造-tasks.md, README.md | ||
| 78 | +- **技术栈**: Node.js, Vitest | ||
| 79 | +- **测试状态**: 已通过(pnpm test),ESLint 存在现有警告 | ||
| 80 | +- **备注**: .doc 文件提示转换为 .docx,OCR 预留未启用 | ||
| 81 | + | ||
| 82 | +--- | ||
| 83 | + | ||
| 84 | +## [2026-02-14] - 文档解析输出定义完善 | ||
| 85 | + | ||
| 86 | +### 更新 | ||
| 87 | +- 明确解析输出 JSON 结构并补齐示例与约束 | ||
| 88 | +- 生成 form_sn 改为稳定的 slug + hash 规则 | ||
| 89 | +- 配置生成支持 form_schema 与 submit_mapping 输出 | ||
| 90 | + | ||
| 91 | +--- | ||
| 92 | + | ||
| 93 | +**详细信息**: | ||
| 94 | +- **影响文件**: scripts/parse-docs.js, scripts/parse-docs.test.js, docs/plan/plan-form-schema-usage.md, docs/tasks/文档解析改造-tasks.md, README.md | ||
| 95 | +- **技术栈**: Node.js, Vitest, 文档维护 | ||
| 96 | +- **测试状态**: 已通过(pnpm test),ESLint 存在现有警告 | ||
| 97 | +- **备注**: 解析输出结构对齐 Schema 与提交映射配置 | ||
| 98 | + | ||
| 99 | +--- | ||
| 100 | + | ||
| 101 | +## [2026-02-14] - 文档解析改造任务清单 | ||
| 102 | + | ||
| 103 | +### 新增 | ||
| 104 | +- 新增文档解析改造任务清单,细化步骤与验收标准 | ||
| 105 | + | ||
| 106 | +--- | ||
| 107 | + | ||
| 108 | +**详细信息**: | ||
| 109 | +- **影响文件**: docs/tasks/文档解析改造-tasks.md, README.md | ||
| 110 | +- **技术栈**: 文档维护 | ||
| 111 | +- **测试状态**: 不适用 | ||
| 112 | +- **备注**: 任务完成后按清单勾选便于回顾 | ||
| 113 | + | ||
| 114 | +--- | ||
| 115 | + | ||
| 1 | ## [2026-02-14] - 优化计划书字段配置管理 | 116 | ## [2026-02-14] - 优化计划书字段配置管理 |
| 2 | 117 | ||
| 3 | ### 新增 | 118 | ### 新增 | ... | ... |
| ... | @@ -85,7 +85,37 @@ const submit_mapping = { | ... | @@ -85,7 +85,37 @@ const submit_mapping = { |
| 85 | } | 85 | } |
| 86 | ``` | 86 | ``` |
| 87 | 87 | ||
| 88 | -## 8. 使用示例 | 88 | +## 8. 解析输出结构 |
| 89 | +解析脚本输出 JSON 用于生成 `plan-templates` 配置,字段结构与 `form_schema`、`submit_mapping` 对齐: | ||
| 90 | + | ||
| 91 | +```javascript | ||
| 92 | +{ | ||
| 93 | + product_name: '宏挚传承保障计划', | ||
| 94 | + product_type: 'savings', | ||
| 95 | + form_sn: 'savings-hong-zhi-chuan-cheng-abcdef12', | ||
| 96 | + currency: 'USD', | ||
| 97 | + payment_periods: ['整付', '3年', '5年'], | ||
| 98 | + age_range: { min: 0, max: 75 }, | ||
| 99 | + insurance_period: '终身', | ||
| 100 | + is_savings: true, | ||
| 101 | + withdrawal_modes: ['年龄指定金额', '最高固定金额'], | ||
| 102 | + withdrawal_periods: ['1年', '3年', '5年', '10年'], | ||
| 103 | + form_schema: { base_fields: [], withdrawal_fields: [], reset_map: {} }, | ||
| 104 | + submit_mapping: { coverage: { api_field: 'annual_premium', transform: 'fen_to_yuan' } }, | ||
| 105 | + source_file: '产品说明书.pdf', | ||
| 106 | + warnings: [] | ||
| 107 | +} | ||
| 108 | +``` | ||
| 109 | + | ||
| 110 | +字段约束与可选项: | ||
| 111 | +- 必填:`product_name`、`product_type`、`currency`、`payment_periods`、`age_range`、`insurance_period` | ||
| 112 | +- 可选:`form_sn`、`is_savings`、`withdrawal_modes`、`withdrawal_periods`、`form_schema`、`submit_mapping`、`source_file`、`warnings` | ||
| 113 | +- `form_sn` 若未传入,按规则自动生成稳定值 | ||
| 114 | +- `payment_periods` 必须为非空数组 | ||
| 115 | +- `age_range.min` ≤ `age_range.max` | ||
| 116 | +- 储蓄产品需提供 `withdrawal_modes` 与 `withdrawal_periods` | ||
| 117 | + | ||
| 118 | +## 9. 使用示例 | ||
| 89 | ```vue | 119 | ```vue |
| 90 | <!-- 储蓄型模板使用示例 --> | 120 | <!-- 储蓄型模板使用示例 --> |
| 91 | <template> | 121 | <template> |
| ... | @@ -111,7 +141,7 @@ const template_config = { | ... | @@ -111,7 +141,7 @@ const template_config = { |
| 111 | </script> | 141 | </script> |
| 112 | ``` | 142 | ``` |
| 113 | 143 | ||
| 114 | -## 8.1 人寿/重疾模板使用示例 | 144 | +## 9.1 人寿/重疾模板使用示例 |
| 115 | ```vue | 145 | ```vue |
| 116 | <template> | 146 | <template> |
| 117 | <LifeInsuranceTemplate v-model="form_data" :config="template_config" /> | 147 | <LifeInsuranceTemplate v-model="form_data" :config="template_config" /> |
| ... | @@ -129,14 +159,14 @@ const template_config = { | ... | @@ -129,14 +159,14 @@ const template_config = { |
| 129 | </script> | 159 | </script> |
| 130 | ``` | 160 | ``` |
| 131 | 161 | ||
| 132 | -## 9. 新增保险类型流程 | 162 | +## 10. 新增保险类型流程 |
| 133 | 1. 在 `src/config/plan-templates.js` 新增产品项(配置 form_sn) | 163 | 1. 在 `src/config/plan-templates.js` 新增产品项(配置 form_sn) |
| 134 | 2. 为该产品选择已有模板组件或新增模板组件 | 164 | 2. 为该产品选择已有模板组件或新增模板组件 |
| 135 | 3. 定义 `form_schema` 与 `submit_mapping` | 165 | 3. 定义 `form_schema` 与 `submit_mapping` |
| 136 | 4. 在模板组件内使用 Schema 渲染(仅需接入通用逻辑) | 166 | 4. 在模板组件内使用 Schema 渲染(仅需接入通用逻辑) |
| 137 | 5. 验证校验与提交映射 | 167 | 5. 验证校验与提交映射 |
| 138 | 168 | ||
| 139 | -## 10. 新增产品配置示例 | 169 | +## 11. 新增产品配置示例 |
| 140 | ```javascript | 170 | ```javascript |
| 141 | // 示例:新增储蓄类产品配置 | 171 | // 示例:新增储蓄类产品配置 |
| 142 | 'savings-new': { | 172 | 'savings-new': { |
| ... | @@ -171,20 +201,20 @@ const template_config = { | ... | @@ -171,20 +201,20 @@ const template_config = { |
| 171 | } | 201 | } |
| 172 | ``` | 202 | ``` |
| 173 | 203 | ||
| 174 | -## 11. 常见扩展点 | 204 | +## 12. 常见扩展点 |
| 175 | - 新字段:仅在 form_schema 增加字段并补充 submit_mapping | 205 | - 新字段:仅在 form_schema 增加字段并补充 submit_mapping |
| 176 | - 新联动:在 show_when 与 reset_map 中定义条件 | 206 | - 新联动:在 show_when 与 reset_map 中定义条件 |
| 177 | - 新模板:复用现有字段组件,保持 schema 结构一致 | 207 | - 新模板:复用现有字段组件,保持 schema 结构一致 |
| 178 | 208 | ||
| 179 | -## 12. 计划书模块入口与配置地图 | 209 | +## 13. 计划书模块入口与配置地图 |
| 180 | -### 12.1 页面入口 | 210 | +### 13.1 页面入口 |
| 181 | - 产品详情:`src/pages/product-detail/index.vue`(按钮打开计划书弹窗) | 211 | - 产品详情:`src/pages/product-detail/index.vue`(按钮打开计划书弹窗) |
| 182 | - 产品中心:`src/pages/product-center/index.vue`(列表内“计划书”按钮) | 212 | - 产品中心:`src/pages/product-center/index.vue`(列表内“计划书”按钮) |
| 183 | - 搜索页:`src/pages/search/index.vue`(搜索结果卡片“计划书”按钮) | 213 | - 搜索页:`src/pages/search/index.vue`(搜索结果卡片“计划书”按钮) |
| 184 | - 计划书列表:`src/pages/plan/index.vue`(查看/删除计划书) | 214 | - 计划书列表:`src/pages/plan/index.vue`(查看/删除计划书) |
| 185 | - 提交结果页:`src/pages/plan-submit-result/index.vue` | 215 | - 提交结果页:`src/pages/plan-submit-result/index.vue` |
| 186 | 216 | ||
| 187 | -### 12.2 组件与模板 | 217 | +### 13.2 组件与模板 |
| 188 | - 弹窗容器:`src/components/plan/PlanPopupNew.vue` | 218 | - 弹窗容器:`src/components/plan/PlanPopupNew.vue` |
| 189 | - 计划书容器:`src/components/plan/PlanFormContainer.vue` | 219 | - 计划书容器:`src/components/plan/PlanFormContainer.vue` |
| 190 | - 模板组件: | 220 | - 模板组件: |
| ... | @@ -193,7 +223,7 @@ const template_config = { | ... | @@ -193,7 +223,7 @@ const template_config = { |
| 193 | - `src/components/plan/PlanTemplates/SavingsTemplate.vue` | 223 | - `src/components/plan/PlanTemplates/SavingsTemplate.vue` |
| 194 | - 字段组件:`src/components/plan/PlanFields/*` | 224 | - 字段组件:`src/components/plan/PlanFields/*` |
| 195 | 225 | ||
| 196 | -### 12.3 配置与数据处理 | 226 | +### 13.3 配置与数据处理 |
| 197 | - 模板映射:`src/config/plan-templates.js` | 227 | - 模板映射:`src/config/plan-templates.js` |
| 198 | - 字段定义与映射:`src/config/plan-fields.js` | 228 | - 字段定义与映射:`src/config/plan-fields.js` |
| 199 | - 字段转换函数:`src/utils/planFieldTransformers.js` | 229 | - 字段转换函数:`src/utils/planFieldTransformers.js` |
| ... | @@ -202,18 +232,18 @@ const template_config = { | ... | @@ -202,18 +232,18 @@ const template_config = { |
| 202 | - 字段校验工具:`src/utils/planFieldValidation.js` | 232 | - 字段校验工具:`src/utils/planFieldValidation.js` |
| 203 | - 订单状态常量:`src/config/constants/orderStatus.js` | 233 | - 订单状态常量:`src/config/constants/orderStatus.js` |
| 204 | 234 | ||
| 205 | -### 12.4 API 入口 | 235 | +### 13.4 API 入口 |
| 206 | - 计划书 API:`src/api/plan.js` | 236 | - 计划书 API:`src/api/plan.js` |
| 207 | - 新增:`addAPI` | 237 | - 新增:`addAPI` |
| 208 | - 列表:`listAPI` | 238 | - 列表:`listAPI` |
| 209 | - 删除:`deleteAPI` | 239 | - 删除:`deleteAPI` |
| 210 | - 查看:`viewAPI` | 240 | - 查看:`viewAPI` |
| 211 | 241 | ||
| 212 | -### 12.5 技术书/附件预览关联 | 242 | +### 13.5 技术书/附件预览关联 |
| 213 | - 产品详情附件列表:`src/pages/product-detail/index.vue` | 243 | - 产品详情附件列表:`src/pages/product-detail/index.vue` |
| 214 | - 文件预览能力:`src/composables/useFileOperation.js` | 244 | - 文件预览能力:`src/composables/useFileOperation.js` |
| 215 | 245 | ||
| 216 | -## 13. 计划书模块使用流程 | 246 | +## 14. 计划书模块使用流程 |
| 217 | 1. 产品详情/产品中心/搜索页获取产品对象(至少包含 `id` 与 `form_sn`,可选 `plan_config`) | 247 | 1. 产品详情/产品中心/搜索页获取产品对象(至少包含 `id` 与 `form_sn`,可选 `plan_config`) |
| 218 | 2. 打开 `PlanFormContainer` 并传入 `product` | 248 | 2. 打开 `PlanFormContainer` 并传入 `product` |
| 219 | 3. `PlanFormContainer` 根据 `form_sn` 从 `plan-templates` 选择模板并合并 `plan_config` | 249 | 3. `PlanFormContainer` 根据 `form_sn` 从 `plan-templates` 选择模板并合并 `plan_config` |
| ... | @@ -222,7 +252,7 @@ const template_config = { | ... | @@ -222,7 +252,7 @@ const template_config = { |
| 222 | 6. 提交完成后通过 `usePlanSubmit` 跳转到提交结果页 | 252 | 6. 提交完成后通过 `usePlanSubmit` 跳转到提交结果页 |
| 223 | 7. 在计划书列表中用 `listAPI` 拉取数据,使用 `viewAPI` 标记为已查看 | 253 | 7. 在计划书列表中用 `listAPI` 拉取数据,使用 `viewAPI` 标记为已查看 |
| 224 | 254 | ||
| 225 | -## 14. 计划书容器使用示例 | 255 | +## 15. 计划书容器使用示例 |
| 226 | ```vue | 256 | ```vue |
| 227 | <template> | 257 | <template> |
| 228 | <PlanFormContainer | 258 | <PlanFormContainer | ... | ... |
docs/tasks/文档解析改造-tasks.md
0 → 100644
| 1 | +# 文档解析改造任务清单 | ||
| 2 | + | ||
| 3 | +> **创建时间**: 2026-02-14 | ||
| 4 | +> **分支**: 当前分支 | ||
| 5 | +> **目标**: 文档解析从 mock 走向可用链路 | ||
| 6 | + | ||
| 7 | +--- | ||
| 8 | + | ||
| 9 | +## 📊 总体进度 | ||
| 10 | + | ||
| 11 | +- [x] **第 1 步**: 目标与输出定义 | ||
| 12 | +- [ ] **第 2 步**: 文本抽取管线 | ||
| 13 | +- [ ] **第 3 步**: 结构化解析与校验 | ||
| 14 | +- [x] **第 4 步**: 生成与写入稳态化 | ||
| 15 | +- [x] **第 5 步**: 测试与验证 | ||
| 16 | +- [x] **第 6 步**: 运营与审计 | ||
| 17 | + | ||
| 18 | +--- | ||
| 19 | + | ||
| 20 | +## 📝 任务详情 | ||
| 21 | + | ||
| 22 | +### 第 1 步:目标与输出定义 | ||
| 23 | + | ||
| 24 | +**目标**: 明确解析输出结构与计划书配置的对齐规则 | ||
| 25 | + | ||
| 26 | +**文件**: | ||
| 27 | +- `docs/plan/plan-form-schema-usage.md` | ||
| 28 | +- `scripts/parse-docs.js` | ||
| 29 | + | ||
| 30 | +**子任务**: | ||
| 31 | +- [x] 定义解析输出 JSON 结构(字段、类型、必填/可选) | ||
| 32 | +- [x] 对齐 form_schema 与 submit_mapping 规范 | ||
| 33 | +- [x] 明确 form_sn 可复现生成规则 | ||
| 34 | +- [x] 补齐输出示例与边界约束说明 | ||
| 35 | + | ||
| 36 | +**验收标准**: | ||
| 37 | +- [x] 输出结构在文档中完整可查 | ||
| 38 | +- [x] form_sn 规则具备稳定性与可追溯性 | ||
| 39 | +- [x] 解析输出可直接用于配置生成 | ||
| 40 | + | ||
| 41 | +--- | ||
| 42 | + | ||
| 43 | +### 第 2 步:文本抽取管线 | ||
| 44 | + | ||
| 45 | +**目标**: 建立 PDF/Word 文本抽取基础能力 | ||
| 46 | + | ||
| 47 | +**文件**: | ||
| 48 | +- `scripts/parse-docs.js` | ||
| 49 | +- `package.json` | ||
| 50 | + | ||
| 51 | +**子任务**: | ||
| 52 | +- [x] 选择 PDF 文本抽取方案并完成接入 | ||
| 53 | +- [x] 选择 Doc/Docx 文本抽取方案并完成接入 | ||
| 54 | +- [x] 为扫描文档预留 OCR 接口与降级策略 | ||
| 55 | +- [x] 统一抽取结果结构(text/meta/warnings) | ||
| 56 | +- [x] 增加抽取失败的错误提示与回退逻辑 | ||
| 57 | + | ||
| 58 | +**验收标准**: | ||
| 59 | +- [x] PDF 与 Docx 均可输出可用文本 | ||
| 60 | +- [x] 抽取失败可定位原因并不写入配置 | ||
| 61 | +- [x] 日志记录包含文件名与失败原因 | ||
| 62 | + | ||
| 63 | +--- | ||
| 64 | + | ||
| 65 | +### 第 3 步:结构化解析与校验 | ||
| 66 | + | ||
| 67 | +**目标**: 将文本解析成结构化配置并进行校验 | ||
| 68 | + | ||
| 69 | +**文件**: | ||
| 70 | +- `scripts/parse-docs.js` | ||
| 71 | +- `scripts/parse-docs.test.js` | ||
| 72 | + | ||
| 73 | +**子任务**: | ||
| 74 | +- [x] 定义 JSON Schema 校验规则 | ||
| 75 | +- [x] 接入结构化解析结果校验 | ||
| 76 | +- [x] 校验失败输出清晰报告 | ||
| 77 | +- [x] 校验失败阻断写入配置 | ||
| 78 | +- [x] 增加最小覆盖单测与示例 | ||
| 79 | + | ||
| 80 | +**验收标准**: | ||
| 81 | +- [x] 不合法配置不会写入 plan-templates | ||
| 82 | +- [x] 校验错误可一眼定位缺失字段 | ||
| 83 | +- [x] 单测覆盖关键异常路径 | ||
| 84 | + | ||
| 85 | +--- | ||
| 86 | + | ||
| 87 | +### 第 4 步:生成与写入稳态化 | ||
| 88 | + | ||
| 89 | +**目标**: 输出稳定可控、支持 diff 与回滚 | ||
| 90 | + | ||
| 91 | +**文件**: | ||
| 92 | +- `scripts/parse-docs.js` | ||
| 93 | +- `src/config/plan-templates.js` | ||
| 94 | + | ||
| 95 | +**子任务**: | ||
| 96 | +- [x] form_sn 改为 slug + hash 的稳定规则 | ||
| 97 | +- [x] 插入位置改为锚点块或结构化写入 | ||
| 98 | +- [x] 增加重复 form_sn 检测与冲突提示 | ||
| 99 | +- [x] 支持 dry-run 输出变更 diff | ||
| 100 | +- [x] 备份与回滚记录完善 | ||
| 101 | + | ||
| 102 | +**验收标准**: | ||
| 103 | +- [x] 重复解析不会产生随机 form_sn | ||
| 104 | +- [x] 插入位置稳定可靠 | ||
| 105 | +- [x] dry-run 能清晰展示新增/修改内容 | ||
| 106 | + | ||
| 107 | +--- | ||
| 108 | + | ||
| 109 | +### 第 5 步:测试与验证 | ||
| 110 | + | ||
| 111 | +**目标**: 保证解析流程可回归验证 | ||
| 112 | + | ||
| 113 | +**文件**: | ||
| 114 | +- `scripts/parse-docs.test.js` | ||
| 115 | +- `docs/to-parse/README.md` | ||
| 116 | + | ||
| 117 | +**子任务**: | ||
| 118 | +- [x] 新增 fixtures 文档样本说明 | ||
| 119 | +- [x] 增加解析流程集成测试 | ||
| 120 | +- [x] 补充 updateConfigContent 边界测试 | ||
| 121 | +- [x] 运行测试并记录结果 | ||
| 122 | + | ||
| 123 | +**验收标准**: | ||
| 124 | +- [x] 解析流程有稳定测试兜底 | ||
| 125 | +- [x] 关键边界路径有覆盖 | ||
| 126 | +- [x] 测试可重复运行 | ||
| 127 | + | ||
| 128 | +--- | ||
| 129 | + | ||
| 130 | +### 第 6 步:运营与审计 | ||
| 131 | + | ||
| 132 | +**目标**: 便于长期维护与复盘 | ||
| 133 | + | ||
| 134 | +**文件**: | ||
| 135 | +- `scripts/parse-docs.js` | ||
| 136 | +- `docs/to-parse/README.md` | ||
| 137 | + | ||
| 138 | +**子任务**: | ||
| 139 | +- [x] 输出解析摘要(成功/失败/耗时) | ||
| 140 | +- [x] 生成审计日志与变更摘要 | ||
| 141 | +- [x] 更新使用说明与注意事项 | ||
| 142 | + | ||
| 143 | +**验收标准**: | ||
| 144 | +- [x] 每次解析均可追踪结果 | ||
| 145 | +- [x] 文档能指导新成员完成解析 | ||
| 146 | + | ||
| 147 | +--- | ||
| 148 | + | ||
| 149 | +## 🔍 快速跳转 | ||
| 150 | + | ||
| 151 | +- [解析脚本](./../../scripts/parse-docs.js) | ||
| 152 | +- [解析测试](./../../scripts/parse-docs.test.js) | ||
| 153 | +- [待解析说明](./../../docs/to-parse/README.md) | ||
| 154 | +- [计划书配置](./../../src/config/plan-templates.js) | ||
| 155 | +- [Schema 使用文档](./../../docs/plan/plan-form-schema-usage.md) | ||
| 156 | + | ||
| 157 | +--- | ||
| 158 | + | ||
| 159 | +## 📝 备注 | ||
| 160 | + | ||
| 161 | +- 每完成一个子任务,就在对应的 [ ] 中打勾 ✓ | ||
| 162 | +- 任务执行过程中的问题与结论直接补充在对应任务下 |
| ... | @@ -39,6 +39,29 @@ pnpm run parse:docs:file -- --file="产品说明书.pdf" | ... | @@ -39,6 +39,29 @@ pnpm run parse:docs:file -- --file="产品说明书.pdf" |
| 39 | - ✅ Word (.doc, .docx) | 39 | - ✅ Word (.doc, .docx) |
| 40 | - ✅ 纯本文档 (.txt, .md) | 40 | - ✅ 纯本文档 (.txt, .md) |
| 41 | 41 | ||
| 42 | +## 🧪 Fixtures 文档样本说明 | ||
| 43 | + | ||
| 44 | +用于测试的样本文档建议放在此目录,命名规则建议包含产品名与类型,便于回归验证: | ||
| 45 | + | ||
| 46 | +``` | ||
| 47 | +docs/to-parse/ | ||
| 48 | +├── fixtures-life-insurance-sample.pdf | ||
| 49 | +├── fixtures-critical-illness-sample.docx | ||
| 50 | +└── fixtures-savings-sample.txt | ||
| 51 | +``` | ||
| 52 | + | ||
| 53 | +执行测试前请确认样本文档内容完整且可被抽取为文本。 | ||
| 54 | + | ||
| 55 | +## 📊 解析摘要与审计日志 | ||
| 56 | + | ||
| 57 | +每次解析都会输出成功/失败/耗时摘要,并在以下位置记录审计日志: | ||
| 58 | + | ||
| 59 | +``` | ||
| 60 | +docs/parsed-backup/parse-audit.jsonl | ||
| 61 | +``` | ||
| 62 | + | ||
| 63 | +日志包含解析汇总与本次变更摘要,便于回溯与排查。 | ||
| 64 | + | ||
| 42 | ## 🔧 配置 AI 服务 | 65 | ## 🔧 配置 AI 服务 |
| 43 | 66 | ||
| 44 | 脚本使用 skill 工具调用 AI 服务,支持: | 67 | 脚本使用 skill 工具调用 AI 服务,支持: | ... | ... |
| ... | @@ -96,9 +96,12 @@ | ... | @@ -96,9 +96,12 @@ |
| 96 | "eslint-plugin-vue": "^8.0.0", | 96 | "eslint-plugin-vue": "^8.0.0", |
| 97 | "happy-dom": "^14.12.0", | 97 | "happy-dom": "^14.12.0", |
| 98 | "husky": "^9.1.7", | 98 | "husky": "^9.1.7", |
| 99 | + "ajv": "^8.17.1", | ||
| 99 | "js-yaml": "^4.1.1", | 100 | "js-yaml": "^4.1.1", |
| 100 | "less": "^4.2.0", | 101 | "less": "^4.2.0", |
| 101 | "lint-staged": "^16.2.7", | 102 | "lint-staged": "^16.2.7", |
| 103 | + "mammoth": "^1.9.1", | ||
| 104 | + "pdf-parse": "^2.2.0", | ||
| 102 | "postcss": "^8.5.6", | 105 | "postcss": "^8.5.6", |
| 103 | "sass": "^1.78.0", | 106 | "sass": "^1.78.0", |
| 104 | "standard-version": "^9.5.0", | 107 | "standard-version": "^9.5.0", | ... | ... |
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
| 1 | import { describe, it, expect } from 'vitest' | 1 | import { describe, it, expect } from 'vitest' |
| 2 | -import { generateFormSn, generateConfigCode, updateConfigContent } from './parse-docs' | 2 | +import fs from 'fs' |
| 3 | +import os from 'os' | ||
| 4 | +import path from 'path' | ||
| 5 | +import { generateFormSn, generateConfigCode, updateConfigContent, extractDocumentText, validateParsedConfig, detectFormSnConflicts, buildDryRunDiff, buildConfigUpdateResult, buildParseSummary } from './parse-docs' | ||
| 3 | 6 | ||
| 4 | describe('parse-docs 生成逻辑', () => { | 7 | describe('parse-docs 生成逻辑', () => { |
| 5 | - it('generateFormSn 使用产品类型前缀', () => { | 8 | + it('generateFormSn 使用稳定规则生成', () => { |
| 6 | const form_sn = generateFormSn({ | 9 | const form_sn = generateFormSn({ |
| 7 | product_name: 'WIOP3E 盈传创富保障计划 3 - 优选版', | 10 | product_name: 'WIOP3E 盈传创富保障计划 3 - 优选版', |
| 8 | product_type: 'life-insurance' | 11 | product_type: 'life-insurance' |
| 9 | }) | 12 | }) |
| 13 | + const form_sn_repeat = generateFormSn({ | ||
| 14 | + product_name: 'WIOP3E 盈传创富保障计划 3 - 优选版', | ||
| 15 | + product_type: 'life-insurance' | ||
| 16 | + }) | ||
| 10 | 17 | ||
| 18 | + expect(form_sn).toBe(form_sn_repeat) | ||
| 11 | expect(form_sn.startsWith('life-insurance-')).toBe(true) | 19 | expect(form_sn.startsWith('life-insurance-')).toBe(true) |
| 20 | + expect(form_sn).toMatch(/^life-insurance-[a-z0-9-]+-[a-f0-9]{8}$/) | ||
| 12 | }) | 21 | }) |
| 13 | 22 | ||
| 14 | it('generateConfigCode 储蓄配置包含顶层 category', () => { | 23 | it('generateConfigCode 储蓄配置包含顶层 category', () => { |
| ... | @@ -54,4 +63,117 @@ export const FEATURE_FLAGS = {}` | ... | @@ -54,4 +63,117 @@ export const FEATURE_FLAGS = {}` |
| 54 | expect(result).toMatch(/'a'[\s\S]*},\n\s+'b'/) | 63 | expect(result).toMatch(/'a'[\s\S]*},\n\s+'b'/) |
| 55 | expect(result).toMatch(/'b'[\s\S]*}\n\nexport const FEATURE_FLAGS/) | 64 | expect(result).toMatch(/'b'[\s\S]*}\n\nexport const FEATURE_FLAGS/) |
| 56 | }) | 65 | }) |
| 66 | + | ||
| 67 | + it('updateConfigContent 无模板时返回 null', () => { | ||
| 68 | + const base_content = `export const OTHER = {}` | ||
| 69 | + const result = updateConfigContent(base_content, [ | ||
| 70 | + { code: " 'b': {\n name: 'B'\n }" } | ||
| 71 | + ]) | ||
| 72 | + | ||
| 73 | + expect(result).toBe(null) | ||
| 74 | + }) | ||
| 75 | + | ||
| 76 | + it('extractDocumentText 统一抽取结构', async () => { | ||
| 77 | + const temp_dir = fs.mkdtempSync(path.join(os.tmpdir(), 'doc-parse-')) | ||
| 78 | + const temp_file = path.join(temp_dir, 'sample.txt') | ||
| 79 | + fs.writeFileSync(temp_file, 'hello parse') | ||
| 80 | + const result = await extractDocumentText(temp_file) | ||
| 81 | + | ||
| 82 | + expect(result.text).toBe('hello parse') | ||
| 83 | + expect(result.meta.ext).toBe('.txt') | ||
| 84 | + expect(Array.isArray(result.warnings)).toBe(true) | ||
| 85 | + }) | ||
| 86 | + | ||
| 87 | + it('validateParsedConfig 能识别缺失字段', () => { | ||
| 88 | + const invalid = validateParsedConfig({ | ||
| 89 | + product_type: 'savings', | ||
| 90 | + currency: 'USD' | ||
| 91 | + }) | ||
| 92 | + const valid = validateParsedConfig({ | ||
| 93 | + product_name: '宏挚传承保障计划', | ||
| 94 | + product_type: 'savings', | ||
| 95 | + currency: 'USD', | ||
| 96 | + form_schema: { base_fields: [], withdrawal_fields: [], reset_map: {} }, | ||
| 97 | + submit_mapping: {} | ||
| 98 | + }) | ||
| 99 | + | ||
| 100 | + expect(invalid.valid).toBe(false) | ||
| 101 | + expect(invalid.errors.length).toBeGreaterThan(0) | ||
| 102 | + expect(valid.valid).toBe(true) | ||
| 103 | + }) | ||
| 104 | + | ||
| 105 | + it('detectFormSnConflicts 能识别重复 form_sn', () => { | ||
| 106 | + const base_content = `export const PLAN_TEMPLATES = { | ||
| 107 | + 'a': { | ||
| 108 | + name: 'A', | ||
| 109 | + component: 'LifeInsuranceTemplate', | ||
| 110 | + config: { | ||
| 111 | + currency: 'USD', | ||
| 112 | + payment_periods: [], | ||
| 113 | + age_range: { min: 0, max: 1 }, | ||
| 114 | + insurance_period: '终身' | ||
| 115 | + } | ||
| 116 | + } | ||
| 117 | +} | ||
| 118 | + | ||
| 119 | +export const FEATURE_FLAGS = {}` | ||
| 120 | + const conflicts = detectFormSnConflicts(base_content, [ | ||
| 121 | + { formSn: 'a', code: ' ' }, | ||
| 122 | + { formSn: 'b', code: ' ' } | ||
| 123 | + ]) | ||
| 124 | + | ||
| 125 | + expect(conflicts).toEqual(['a']) | ||
| 126 | + }) | ||
| 127 | + | ||
| 128 | + it('buildDryRunDiff 输出新增内容', () => { | ||
| 129 | + const diff = buildDryRunDiff([ | ||
| 130 | + { formSn: 'b', code: " 'b': {\n name: 'B'\n }" } | ||
| 131 | + ]) | ||
| 132 | + | ||
| 133 | + expect(diff.includes('--- plan-templates.js')).toBe(true) | ||
| 134 | + expect(diff.includes("+++ plan-templates.js")).toBe(true) | ||
| 135 | + expect(diff.includes("+ 'b': {")).toBe(true) | ||
| 136 | + }) | ||
| 137 | + | ||
| 138 | + it('buildConfigUpdateResult 覆盖冲突与 dry-run', () => { | ||
| 139 | + const base_content = `export const PLAN_TEMPLATES = { | ||
| 140 | + 'a': { | ||
| 141 | + name: 'A', | ||
| 142 | + component: 'LifeInsuranceTemplate', | ||
| 143 | + config: { | ||
| 144 | + currency: 'USD', | ||
| 145 | + payment_periods: [], | ||
| 146 | + age_range: { min: 0, max: 1 }, | ||
| 147 | + insurance_period: '终身' | ||
| 148 | + } | ||
| 149 | + } | ||
| 150 | +} | ||
| 151 | + | ||
| 152 | +export const FEATURE_FLAGS = {}` | ||
| 153 | + const conflict_result = buildConfigUpdateResult(base_content, [ | ||
| 154 | + { formSn: 'a', code: " 'a': {\n name: 'A'\n }" } | ||
| 155 | + ]) | ||
| 156 | + const dry_run_result = buildConfigUpdateResult(base_content, [ | ||
| 157 | + { formSn: 'b', code: " 'b': {\n name: 'B'\n }" } | ||
| 158 | + ], { dry_run: true }) | ||
| 159 | + | ||
| 160 | + expect(conflict_result.ok).toBe(false) | ||
| 161 | + expect(conflict_result.conflicts).toEqual(['a']) | ||
| 162 | + expect(dry_run_result.ok).toBe(true) | ||
| 163 | + expect(dry_run_result.diff).toContain('+ \'b\': {') | ||
| 164 | + }) | ||
| 165 | + | ||
| 166 | + it('buildParseSummary 汇总成功失败与耗时', () => { | ||
| 167 | + const summary = buildParseSummary([ | ||
| 168 | + { success: true, formSn: 'a', file: 'a.pdf', config: { product_name: 'A' } }, | ||
| 169 | + { success: false, file: 'b.pdf', reason: 'parse_failed' } | ||
| 170 | + ], 1200) | ||
| 171 | + | ||
| 172 | + expect(summary.total).toBe(2) | ||
| 173 | + expect(summary.success).toBe(1) | ||
| 174 | + expect(summary.failed).toBe(1) | ||
| 175 | + expect(summary.duration_ms).toBe(1200) | ||
| 176 | + expect(summary.success_list[0].form_sn).toBe('a') | ||
| 177 | + expect(summary.failed_list[0].file).toBe('b.pdf') | ||
| 178 | + }) | ||
| 57 | }) | 179 | }) | ... | ... |
-
Please register or login to post a comment