# 验收测试报告 **项目**: Proposa AI - `bid_proposal` 包 **测试日期**: 2026-07-07 **测试人员**: 测试工程师(test) --- ## 测试概要 | 测试项 | 状态 | 说明 | |--------|------|------| | 测试1: 模块导入测试 | ✅ 通过 | 所有模块可正确导入 | | 测试2: bid_reader 功能测试 | ✅ 通过 | PDF读取正常: 76页, 36个表格, 64990字符 | | 测试3: proposal_writer 生成测试 | ⚠️ 有警告 | DOCX成功生成(38KB),但表格解析有边界错误 | | 测试4: 代码质量检查 | ⚠️ 有发现 | 代码质量良好,发现少量改进点 | | 测试5: Pipeline dry-run测试 | ✅ 通过 | 流程编排逻辑正常 | --- ## 详细测试结果 ### 测试1: 模块导入测试 所有模块均可正确导入,包括: - `bid_proposal` - `bid_proposal.bid_reader` - `bid_proposal.config` - `bid_proposal.models` - `bid_proposal.pipeline` - `bid_proposal.proposal_generator` - `bid_proposal.proposal_writer` - `bid_proposal.requirement_analyzer` - `bid_proposal.utils` **结论**: ✅ 通过 --- ### 测试2: bid_reader 功能测试 | 指标 | 结果 | |------|------| | 输入文件 | `data/松江区机关事务管理局物业管理服务招标文件.pdf` | | 文件大小 | 3,033,606 bytes (约2.9MB) | | 页数 | 76 | | 表格数 | 36 | | 文本总字符数 | 64,990 | **结论**: ✅ 通过 --- ### 测试3: proposal_writer 生成测试 | 指标 | 结果 | |------|------| | 生成文件 | `data/test_proposal_output.docx` | | 文件大小 | 38,332 bytes (38KB) | | 生成耗时 | < 1秒 | | 错误 | 无 | **发现的问题**: 1. **表格分隔行检测Bug** ⚠️: Markdown表格的隔行 `|---------|---------|` 被 `stripped.startswith("|---")` 误判为表格结束标记,因为 `|---------` 的前4个字符是 `|` `-` `-` `-`,恰好匹配 `|---`。这导致 `_write_table` 被提前调用(仅传入表头行),触发 "表格数据不足,跳过表格写入" 警告。 **影响范围**: 如果内容中某个 Markdown 表格后面紧跟其他非表格内容,该表格会被错误地截断,只有表头被写入 DOCX。 **建议修复**: 方案1 (推荐): 在 `_write_markdown_content` 中用正则替换 `stripped.startswith("|---")` 条件为更精确的匹配: ```python # 替换第353行附近的条件 if stripped.startswith("|") and not re.match(r'^\|[\s\-:]+\|?$', stripped): ``` 方案2: 将 `not stripped.startswith("|---")` 改为 `not re.match(r'^\|[-]{3,}', stripped)`,只匹配连续的三个以上短横线。 2. **表格数据校验过于严格**: `_parse_markdown_table` 中 `len(data) < 2` 的判断使得单行表格(仅表头)被过滤。对于某些仅含表头无数据的场景,可以考虑放宽限制。 **结论**: ⚠️ 有警告 — 功能正常,但表格解析存在边界Bug,建议修复。 --- ### 测试4: 代码质量检查 #### 4.1 模块文档字符串 | 文件 | 行数 | 代码行 | 模块Docstring | 函数数 | 类数 | |------|------|--------|---------------|--------|------| | `__init__.py` | 6 | 4 | ✅ | 0 | 0 | | `__main__.py` | 126 | 102 | ✅ | 2 | 0 | | `bid_reader.py` | 128 | 91 | ✅ | 3 | 1 | | `config.py` | 56 | 38 | ✅ | 3 | 1 | | `models.py` | 133 | 106 | ✅ | 12 | 6 | | `pipeline.py` | 127 | 100 | ✅ | 1 | 0 | | `proposal_generator.py` | 424 | 362 | ✅ | 8 | 1 | | `proposal_writer.py` | 549 | 414 | ✅ | 15 | 0 | | `requirement_analyzer.py` | 303 | 249 | ✅ | 6 | 2 | | `utils.py` | 105 | 73 | ✅ | 3 | 0 | **结论**: ✅ 所有文件均有模块级文档字符串。 #### 4.2 异常处理分析 | 文件 | try/except | raise | 分析 | |------|-----------|-------|------| | `bid_reader.py` | 4/3 | 4 | **良好** — 自定义 `BidReadError` 异常,表格提取有 graceful degradation | | `config.py` | 0/0 | 0 | N/A — 纯数据类 + 环境读取,无需异常处理 | | `models.py` | 0/0 | 0 | N/A — 纯数据类,无需异常处理 | | `pipeline.py` | 4/4 | 6 | **良好** — 每一步有 try/except,用 RuntimeError 包装 | | `proposal_generator.py` | 6/6 | 2 | **良好** — 有 fallback 机制和重试 | | `proposal_writer.py` | 0/0 | 2 | ⚠️ `write_proposal_docx` 声明会抛出 IOError,但未捕获 `doc.save()` 的异常 | | `requirement_analyzer.py` | 3/3 | 5 | **良好** — 有重试逻辑和分块容错 | | `__main__.py` | 1/4 | 0 | **良好** — 顶层异常按类型分级处理 | | `utils.py` | 0/0 | 0 | ⚠️ `table_to_markdown` 和 `chunk_text` 缺少输入校验 | **发现的问题**: 1. **`proposal_writer.py` — `doc.save()` 未保护**: 第530行 `doc.save(output_path)` 在磁盘空间不足或路径不可写时会抛出 OSError/IOError,该异常未被捕获或包装为更明确的错误信息。 2. **`utils.py` — `table_to_markdown` 健壮性不足**: 函数依赖 `hasattr` 检查,但如果 `cells` 内容格式不符预期(如非嵌套列表),会抛出 AttributeError。 #### 4.3 日志覆盖分析 | 文件 | logging.getLogger | logger.xxx 调用数 | 分析 | |------|------------------|-------------------|------| | `bid_reader.py` | 是 | 6 | 充分覆盖关键流程节点 | | `pipeline.py` | 是 | 10 | 充分覆盖每一步 | | `proposal_generator.py` | 是 | 13 | 充分覆盖 | | `proposal_writer.py` | 是 | 3 | 偏少:仅入口和出口有日志,表格写入失败有日志 | | `requirement_analyzer.py` | 是 | 10 | 充分覆盖,含重试日志 | | `__main__.py` | 是 | 14 | 充分覆盖 | | `__init__.py` | 否 | 0 | N/A — 初始化文件 | | `config.py` | 是 | 1 | 较少但可接受 | | `models.py` | 否 | 0 | N/A — 数据类,无需日志 | | `utils.py` | 否 | 0 | ⚠️ `chunk_text` 没有日志 | **发现的问题**: 1. **`utils.py` 缺少日志**: 工具函数模块没有定义 logger,`chunk_text` 和 `table_to_markdown` 的关键路径无日志输出,调试时难以追踪。 2. **`proposal_writer.py` 日志偏少**: 在 `_write_markdown_content` 中处理各个 Markdown 元素时缺少 DEBUG 级别日志。 #### 4.4 敏感信息检查 | 检查项 | 结果 | |--------|------| | 代码中硬编码 API Key | ✅ 未发现 | | 代码中硬编码密码/令牌 | ✅ 未发现 | | `api_key` 参数传递 | ✅ 通过函数参数和环境变量传递,无硬编码 | | `.env` 或配置文件泄露 | N/A — 无此类文件 | **结论**: ✅ 无敏感信息泄露风险。 #### 4.5 代码质量综合评分 | 维度 | 评分 | 说明 | |------|------|------| | 代码结构 | 4/5 | 模块职责清晰,符合单一职责原则 | | 注释/docstring | 5/5 | 所有模块、类、函数均有详细 docstring | | 异常处理 | 3.5/5 | 主要模块良好,`proposal_writer` 和 `utils.py` 有待加强 | | 日志覆盖 | 3.5/5 | 主要流程充分,工具函数偏少 | | 命名规范 | 5/5 | 符合 Python 命名规范(snake_case, PascalCase) | | 类型注解 | 4/5 | 主要函数有类型注解,部分内部函数缺少 | **结论**: ⚠️ 有发现 — 代码质量总体良好,存在少量需要改进的点。 --- ### 测试5: Pipeline dry-run 测试 | 检查项 | 结果 | |--------|------| | `run_pipeline` 函数签名 | ✅ 正确 | | 输入PDF文件存在性校验 | ✅ `FileNotFoundError` 在文件不存在时抛出的逻辑正确 | | 默认输出路径计算 | ✅ 输出路径为 `data/松江区机关事务管理局物业管理服务招标文件_投标文件.docx` | | 模块导入 | ✅ 所有依赖模块可正确导入 | | 流程步骤逻辑 | ✅ 参数校验 → 文件读取 → 分析 → 生成 → DOCX输出,顺序正确 | **结论**: ✅ 通过 --- ## 综合评估 ### 总体评价 `bid_proposal` 包整体质量良好,模块结构清晰,文档完善。各模块之间的数据模型对齐,接口兼容。 ### 评分 | 维度 | 等级 | 说明 | |------|------|------| | 功能完整性 | ⭐⭐⭐⭐☆ | 核心功能完整,从PDF读取到DOCX生成流程通顺 | | 代码质量 | ⭐⭐⭐⭐☆ | 规范良好,少量改进点 | | 健壮性 | ⭐⭐⭐☆☆ | 主流程有异常处理,但边界情况和工具函数偏弱 | | 可维护性 | ⭐⭐⭐⭐⭐ | 模块划分清晰,文档注释充分 | | 兼容性 | ⭐⭐⭐⭐☆ | 数据结构统一,接口对齐 | ### 关键问题(建议高优先级修复) 1. **表格隔行检测Bug** (proposal_writer.py:353): `stripped.startswith("|---")` 误匹配 `|---------`,导致表格在含有隔行时被截断。 ### 次要改进建议 1. **`proposal_writer.py`** `doc.save()` 应加 try/except,捕获 IOError 并提供更有意义的错误信息。 2. **`utils.py`** 添加 logger 定义,在关键路径增加 DEBUG 日志,便于调试。 3. **添加单元测试**: 项目目前没有测试目录(`tests/`),建议为关键函数(`_parse_markdown_table`、`chunk_text`、`_extract_text`)添加 pytest 单元测试。 4. **`utils.py`** `table_to_markdown` 函数增加对异常输入(空对象、异常嵌套结构)的处理。 5. **`pyproject.toml`** 中的依赖列表不完整,缺少 `python-docx`、`pdf_table_to_docx` 等运行时依赖。 --- ## 测试数据文件 - 测试输入PDF: `data/松江区机关事务管理局物业管理服务招标文件.pdf` (3.0 MB) - 测试输出DOCX: `data/test_proposal_output.docx` (38 KB) - 测试依据PDF: `data/松江区机关事务管理局物业管理服务项目投标文件.pdf` (1.2 MB) --- *报告生成于 2026-07-07,由 `proposa-ai` 项目的验收测试脚本自动生成。*