test_step2.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. """
  2. Step 2 测试:招标文件深度分析
  3. 读取文档 → 构建 ProjectData → 调用 analyze_tender → 打印分析结果。
  4. 用法: uv run python scripts/test_step2.py
  5. """
  6. import logging
  7. import os
  8. import pickle
  9. import sys
  10. from _bootstrap import PROJECT_ROOT
  11. # ---------- 测试数据(环境变量可覆盖) ----------
  12. TENDER_FILE = os.environ.get(
  13. "PROPOSA_TENDER_FILE",
  14. "test_data/171-上海群众艺术馆/上海市群众艺术馆物业管理服务采购项目招标文件.pdf",
  15. )
  16. PROCUREMENT_FILE = os.environ.get(
  17. "PROPOSA_PROCUREMENT_FILE",
  18. "test_data/171-上海群众艺术馆/采购需求.docx",
  19. )
  20. CLARIFICATION_FILE = os.environ.get("PROPOSA_CLARIFICATION_FILE", "")
  21. COMPANY_INFO_DIR = os.environ.get(
  22. "PROPOSA_COMPANY_INFO_DIR",
  23. "src/templates/通用标书生成资料",
  24. )
  25. REFERENCE_BID = os.environ.get(
  26. "PROPOSA_REFERENCE_BID",
  27. "test_data/171-上海群众艺术馆/参考投标文件/物业管理费项目投标文件.docx",
  28. )
  29. OUTPUT_DIR = os.environ.get(
  30. "PROPOSA_STEP2_OUTPUT_DIR",
  31. os.environ.get("PROPOSA_WORK_DIR", "output/171-上海群众艺术馆"),
  32. )
  33. STEP1_ITEMS_FILE = os.environ.get(
  34. "PROPOSA_STEP1_ITEMS_FILE",
  35. os.path.join(OUTPUT_DIR, "step1_extracted_items.pkl"),
  36. )
  37. STEP2_INFO_FILE = os.environ.get(
  38. "PROPOSA_STEP2_INFO_FILE",
  39. os.path.join(OUTPUT_DIR, "step2_info.pkl"),
  40. )
  41. # ---------- 日志 ----------
  42. logging.basicConfig(
  43. level=logging.INFO,
  44. format="%(asctime)s [%(levelname)s] %(message)s",
  45. datefmt="%H:%M:%S",
  46. stream=sys.stderr,
  47. )
  48. def _load_reference_bid(path, project_data, output_dir):
  49. """载入参考投书正文及表格,供 Step3/Step4/Step5 复用。"""
  50. if not path or not os.path.isfile(path):
  51. return 0
  52. from doc_reader.reader import read_docx_paragraph_texts_et
  53. from models import ParsedDocument
  54. from step1_parsing.table_extractor import extract_reference_tables_with_llm
  55. reference_content = "\n".join(read_docx_paragraph_texts_et(path))
  56. if reference_content:
  57. project_data.reference_bids.append(ParsedDocument(
  58. file_path=path,
  59. file_name=os.path.basename(path),
  60. content=reference_content,
  61. doc_type="reference_bid",
  62. ))
  63. print(
  64. f"参考投标书: {os.path.basename(path)} "
  65. f"({len(reference_content):,} 字符)"
  66. )
  67. reference_table_dir = os.path.join(output_dir, "内容提取_参考投书")
  68. project_data.reference_tables = extract_reference_tables_with_llm(
  69. path, output_dir=reference_table_dir
  70. )
  71. print(
  72. f"参考投书表格(含LLM增强): {len(project_data.reference_tables)} 张 "
  73. f"-> {reference_table_dir}"
  74. )
  75. return len(project_data.reference_tables)
  76. # ---------- 1. 读取文档 ----------
  77. print("=" * 60)
  78. print("Step 2: 加载文档并构建 ProjectData")
  79. print("=" * 60)
  80. from doc_reader import read_file
  81. from models import ProjectData, ParsedDocument
  82. pd = ProjectData(project_id="test", project_name="测试项目")
  83. # ---------- 0. 复用 Step1 已提取的表/函/表格 ----------
  84. if not os.path.isfile(STEP1_ITEMS_FILE):
  85. raise FileNotFoundError(
  86. f"Step1 提取结果不存在: {STEP1_ITEMS_FILE}\n"
  87. "请先运行 uv run python scripts/test_step1.py。"
  88. )
  89. with open(STEP1_ITEMS_FILE, "rb") as file:
  90. pd.extracted_items = pickle.load(file)
  91. print(f"复用 Step1 表/函/表格: {len(pd.extracted_items)} 项")
  92. # 读招标文件
  93. if os.path.isfile(TENDER_FILE):
  94. content = read_file(TENDER_FILE)
  95. if content:
  96. pd.tender_docs.append(ParsedDocument(
  97. file_path=TENDER_FILE,
  98. file_name=os.path.basename(TENDER_FILE),
  99. content=content,
  100. doc_type="tender",
  101. ))
  102. print(f"招标文件: {os.path.basename(TENDER_FILE)} ({len(content):,} 字符)")
  103. # 读采购需求
  104. if PROCUREMENT_FILE and os.path.isfile(PROCUREMENT_FILE):
  105. content = read_file(PROCUREMENT_FILE)
  106. if content:
  107. pd.procurement_docs.append(ParsedDocument(
  108. file_path=PROCUREMENT_FILE,
  109. file_name=os.path.basename(PROCUREMENT_FILE),
  110. content=content,
  111. doc_type="procurement",
  112. ))
  113. print(f"采购需求: {os.path.basename(PROCUREMENT_FILE)} ({len(content):,} 字符)")
  114. # 读澄清公告
  115. if CLARIFICATION_FILE and os.path.isfile(CLARIFICATION_FILE):
  116. content = read_file(CLARIFICATION_FILE)
  117. if content:
  118. pd.clarification_docs.append(ParsedDocument(
  119. file_path=CLARIFICATION_FILE,
  120. file_name=os.path.basename(CLARIFICATION_FILE),
  121. content=content,
  122. doc_type="clarification",
  123. ))
  124. print(f"澄清公告: {os.path.basename(CLARIFICATION_FILE)} ({len(content):,} 字符)")
  125. # 读通用资料
  126. if os.path.isdir(COMPANY_INFO_DIR):
  127. count = 0
  128. for root, dirs, files in os.walk(COMPANY_INFO_DIR):
  129. for fn in files:
  130. if fn.endswith(".md"):
  131. fp = os.path.join(root, fn)
  132. content = read_file(fp)
  133. if content:
  134. pd.general_materials.append(ParsedDocument(
  135. file_path=fp,
  136. file_name=fn,
  137. content=content,
  138. doc_type="general_info",
  139. ))
  140. count += 1
  141. print(f"通用资料: {count} 个文件")
  142. # 参考投书在 Step2 载入后保存到 step2_info.pkl,Step3/Step4/Step5 不再重复读取。
  143. _load_reference_bid(REFERENCE_BID, pd, OUTPUT_DIR)
  144. # ---------- 2. 深度分析 ----------
  145. print()
  146. print("=" * 60)
  147. print("Step 2: 深度分析招标文件")
  148. print("=" * 60)
  149. os.environ.setdefault("BID_LLM_CACHE_DIR", os.path.join(OUTPUT_DIR, ".llm_cache"))
  150. from step2_analysis import analyze_tender
  151. analysis = analyze_tender(pd, output_dir=OUTPUT_DIR)
  152. print(f"Step2 分析结果已落盘: {STEP2_INFO_FILE}")
  153. # ---------- 3. 打印结果 ----------
  154. print()
  155. print("=== 分析结果 ===")
  156. print(f"项目名称: {analysis.project_name}")
  157. print(f"采购人: {analysis.buyer_name}")
  158. print(f"包件数: {len(analysis.packages)}")
  159. print(f"服务需求: {analysis.service_requirements[:120]}...")
  160. print()
  161. print(f"评分细则 ({len(analysis.scoring_criteria)} 项):")
  162. for c in analysis.scoring_criteria:
  163. print(f" [{c.id}] {c.name} | {c.category} | {c.max_score}分")
  164. if c.description:
  165. print(f" {c.description[:100]}")
  166. print()
  167. print(f"废标项 ({len(analysis.rejection_items)} 项):")
  168. for r in analysis.rejection_items:
  169. print(f" [{r.id}] {r.description[:80]}...")
  170. print()
  171. print(f"响应清单 ({len(analysis.response_requirements)} 项):")
  172. for r in analysis.response_requirements:
  173. print(f" [{r.id}] [{r.category}] {r.name}")
  174. print()
  175. print(f"文档模板 ({len(analysis.templates)} 个):")
  176. for t in analysis.templates:
  177. print(f" - {t.name}")
  178. print()
  179. print("Step 2 测试完成 [OK]")