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